picking + entity names
This commit is contained in:
245
src/picking.rs
245
src/picking.rs
@@ -1,7 +1,10 @@
|
||||
use crate::camera::Camera;
|
||||
use crate::mesh::Mesh;
|
||||
use glam::{Mat4, Vec3, Vec4};
|
||||
|
||||
use crate::components::TreeInstancesComponent;
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::mesh::Mesh;
|
||||
use crate::world::World;
|
||||
|
||||
pub struct Ray
|
||||
{
|
||||
pub origin: Vec3,
|
||||
@@ -15,7 +18,8 @@ impl Ray
|
||||
screen_y: f32,
|
||||
screen_width: u32,
|
||||
screen_height: u32,
|
||||
camera: &Camera,
|
||||
view: &Mat4,
|
||||
projection: &Mat4,
|
||||
) -> Self
|
||||
{
|
||||
let ndc_x = (2.0 * screen_x) / screen_width as f32 - 1.0;
|
||||
@@ -23,10 +27,8 @@ impl Ray
|
||||
|
||||
let clip_coords = Vec4::new(ndc_x, ndc_y, -1.0, 1.0);
|
||||
|
||||
let view_matrix = camera.view_matrix();
|
||||
let projection_matrix = camera.projection_matrix();
|
||||
let inv_projection = projection_matrix.inverse();
|
||||
let inv_view = view_matrix.inverse();
|
||||
let inv_projection = projection.inverse();
|
||||
let inv_view = view.inverse();
|
||||
|
||||
let eye_coords = inv_projection * clip_coords;
|
||||
let eye_coords = Vec4::new(eye_coords.x, eye_coords.y, -1.0, 0.0);
|
||||
@@ -34,54 +36,215 @@ impl Ray
|
||||
let world_coords = inv_view * eye_coords;
|
||||
let direction = Vec3::new(world_coords.x, world_coords.y, world_coords.z).normalize();
|
||||
|
||||
Ray {
|
||||
origin: camera.position,
|
||||
direction,
|
||||
}
|
||||
let origin = inv_view.col(3).truncate();
|
||||
|
||||
Ray { origin, direction }
|
||||
}
|
||||
|
||||
pub fn intersects_mesh(&self, mesh: &Mesh, transform: &Mat4) -> Option<f32>
|
||||
pub fn intersects_aabb(&self, min: Vec3, max: Vec3) -> Option<f32>
|
||||
{
|
||||
let inv_transform = transform.inverse();
|
||||
let local_origin = inv_transform.transform_point3(self.origin);
|
||||
let local_direction = inv_transform.transform_vector3(self.direction).normalize();
|
||||
let inv_dir = Vec3::new(
|
||||
1.0 / self.direction.x,
|
||||
1.0 / self.direction.y,
|
||||
1.0 / self.direction.z,
|
||||
);
|
||||
|
||||
let mut closest_distance = f32::MAX;
|
||||
let mut hit = false;
|
||||
let t1 = (min - self.origin) * inv_dir;
|
||||
let t2 = (max - self.origin) * inv_dir;
|
||||
|
||||
for triangle_idx in (0..mesh.num_indices).step_by(3)
|
||||
let tmin = t1.min(t2);
|
||||
let tmax = t1.max(t2);
|
||||
|
||||
let tenter = tmin.x.max(tmin.y).max(tmin.z);
|
||||
let texit = tmax.x.min(tmax.y).min(tmax.z);
|
||||
|
||||
if tenter <= texit && texit >= 0.0
|
||||
{
|
||||
let distance =
|
||||
self.intersects_triangle_local(local_origin, local_direction, mesh, triangle_idx);
|
||||
|
||||
if let Some(d) = distance
|
||||
{
|
||||
if d < closest_distance
|
||||
{
|
||||
closest_distance = d;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hit
|
||||
{
|
||||
Some(closest_distance)
|
||||
Some(tenter.max(0.0))
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn intersects_triangle_local(
|
||||
&self,
|
||||
local_origin: Vec3,
|
||||
local_direction: Vec3,
|
||||
_mesh: &Mesh,
|
||||
_triangle_idx: u32,
|
||||
) -> Option<f32>
|
||||
fn transform_aabb(aabb_min: Vec3, aabb_max: Vec3, model: &Mat4) -> (Vec3, Vec3)
|
||||
{
|
||||
let corners = [
|
||||
Vec3::new(aabb_min.x, aabb_min.y, aabb_min.z),
|
||||
Vec3::new(aabb_max.x, aabb_min.y, aabb_min.z),
|
||||
Vec3::new(aabb_min.x, aabb_max.y, aabb_min.z),
|
||||
Vec3::new(aabb_max.x, aabb_max.y, aabb_min.z),
|
||||
Vec3::new(aabb_min.x, aabb_min.y, aabb_max.z),
|
||||
Vec3::new(aabb_max.x, aabb_min.y, aabb_max.z),
|
||||
Vec3::new(aabb_min.x, aabb_max.y, aabb_max.z),
|
||||
Vec3::new(aabb_max.x, aabb_max.y, aabb_max.z),
|
||||
];
|
||||
|
||||
let mut world_min = Vec3::splat(f32::MAX);
|
||||
let mut world_max = Vec3::splat(f32::MIN);
|
||||
for corner in &corners
|
||||
{
|
||||
let world_corner = model.transform_point3(*corner);
|
||||
world_min = world_min.min(world_corner);
|
||||
world_max = world_max.max(world_corner);
|
||||
}
|
||||
(world_min, world_max)
|
||||
}
|
||||
|
||||
fn intersect_triangle(origin: Vec3, dir: Vec3, v0: Vec3, v1: Vec3, v2: Vec3) -> Option<f32>
|
||||
{
|
||||
let edge1 = v1 - v0;
|
||||
let edge2 = v2 - v0;
|
||||
let h = dir.cross(edge2);
|
||||
let a = edge1.dot(h);
|
||||
if a.abs() < 1e-7
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let f = 1.0 / a;
|
||||
let s = origin - v0;
|
||||
let u = f * s.dot(h);
|
||||
if !(0.0..=1.0).contains(&u)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let q = s.cross(edge1);
|
||||
let v = f * dir.dot(q);
|
||||
if v < 0.0 || u + v > 1.0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let t = f * edge2.dot(q);
|
||||
if t > 1e-7
|
||||
{
|
||||
Some(t)
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Ray–mesh triangle intersection in local space, returning world-space distance.
|
||||
///
|
||||
/// Transforms the ray into local mesh space, tests every triangle with
|
||||
/// Möller–Trumbore, then maps the closest local hit back to a world-space
|
||||
/// distance so results can be compared across entities.
|
||||
fn intersect_mesh_triangles(ray: &Ray, mesh: &Mesh, model: &Mat4) -> Option<f32>
|
||||
{
|
||||
if mesh.cpu_indices.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let inv_model = model.inverse();
|
||||
let local_origin = inv_model.transform_point3(ray.origin);
|
||||
let local_dir = inv_model.transform_vector3(ray.direction);
|
||||
|
||||
let mut closest_t = f32::MAX;
|
||||
let mut hit = false;
|
||||
|
||||
for tri in mesh.cpu_indices.chunks_exact(3)
|
||||
{
|
||||
let v0 = Vec3::from(mesh.cpu_positions[tri[0] as usize]);
|
||||
let v1 = Vec3::from(mesh.cpu_positions[tri[1] as usize]);
|
||||
let v2 = Vec3::from(mesh.cpu_positions[tri[2] as usize]);
|
||||
|
||||
if let Some(t) = intersect_triangle(local_origin, local_dir, v0, v1, v2)
|
||||
{
|
||||
if t < closest_t
|
||||
{
|
||||
closest_t = t;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hit
|
||||
{
|
||||
let local_hit = local_origin + local_dir * closest_t;
|
||||
let world_hit = model.transform_point3(local_hit);
|
||||
Some((world_hit - ray.origin).length())
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_instances(
|
||||
ray: &Ray,
|
||||
mesh: &Mesh,
|
||||
tree_instances: &TreeInstancesComponent,
|
||||
) -> Option<f32>
|
||||
{
|
||||
let mut closest: Option<f32> = None;
|
||||
|
||||
for instance in &tree_instances.instances
|
||||
{
|
||||
let instance_model = Mat4::from_scale_rotation_translation(
|
||||
instance.scale,
|
||||
instance.rotation,
|
||||
instance.position,
|
||||
);
|
||||
let (world_min, world_max) = transform_aabb(mesh.aabb_min, mesh.aabb_max, &instance_model);
|
||||
|
||||
if let Some(d) = ray.intersects_aabb(world_min, world_max)
|
||||
{
|
||||
if closest.map_or(true, |c| d < c)
|
||||
{
|
||||
closest = Some(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closest
|
||||
}
|
||||
|
||||
pub fn pick_entity(ray: &Ray, world: &World) -> Option<EntityHandle>
|
||||
{
|
||||
let mut closest_entity = None;
|
||||
let mut closest_distance = f32::MAX;
|
||||
|
||||
for (&entity, mesh_component) in &world.meshes.components
|
||||
{
|
||||
let transform = match world.transforms.get(entity)
|
||||
{
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let model = transform.to_matrix();
|
||||
|
||||
let distance = if let Some(tree_instances) = world.tree_instances.get(entity)
|
||||
{
|
||||
intersect_instances(ray, &mesh_component.mesh, tree_instances)
|
||||
}
|
||||
else
|
||||
{
|
||||
let (world_min, world_max) = transform_aabb(
|
||||
mesh_component.mesh.aabb_min,
|
||||
mesh_component.mesh.aabb_max,
|
||||
&model,
|
||||
);
|
||||
if ray.intersects_aabb(world_min, world_max).is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
intersect_mesh_triangles(ray, &mesh_component.mesh, &model)
|
||||
};
|
||||
|
||||
if let Some(d) = distance
|
||||
{
|
||||
if d < closest_distance
|
||||
{
|
||||
closest_distance = d;
|
||||
closest_entity = Some(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closest_entity
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user