251 lines
6.4 KiB
Rust
251 lines
6.4 KiB
Rust
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<f32>
|
||
{
|
||
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<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
|
||
}
|