picking + entity names

This commit is contained in:
Jonas H
2026-03-05 15:05:19 +01:00
parent 5e5c6a47e7
commit bab54b6f21
9 changed files with 400 additions and 50 deletions

View File

@@ -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
}
}
/// Raymesh triangle intersection in local space, returning world-space distance.
///
/// Transforms the ray into local mesh space, tests every triangle with
/// MöllerTrumbore, 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
}