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, pub direction: Vec3, } impl Ray { pub fn from_screen_position( screen_x: f32, screen_y: f32, screen_width: u32, screen_height: u32, view: &Mat4, projection: &Mat4, ) -> Self { let ndc_x = (2.0 * screen_x) / screen_width as f32 - 1.0; let ndc_y = 1.0 - (2.0 * screen_y) / screen_height as f32; let clip_coords = Vec4::new(ndc_x, ndc_y, -1.0, 1.0); 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); let world_coords = inv_view * eye_coords; let direction = Vec3::new(world_coords.x, world_coords.y, world_coords.z).normalize(); let origin = inv_view.col(3).truncate(); Ray { origin, direction } } pub fn intersects_aabb(&self, min: Vec3, max: Vec3) -> Option { let inv_dir = Vec3::new( 1.0 / self.direction.x, 1.0 / self.direction.y, 1.0 / self.direction.z, ); let t1 = (min - self.origin) * inv_dir; let t2 = (max - self.origin) * inv_dir; 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 { Some(tenter.max(0.0)) } else { None } } } 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 { 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 { 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 { let mut closest: Option = 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 { 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 }