diff --git a/src/hit3d.rs b/src/hit3d.rs index f881869..9da618a 100644 --- a/src/hit3d.rs +++ b/src/hit3d.rs @@ -1,3 +1,5 @@ +use bevy::{math::Vec3A, render::primitives::Aabb}; + use crate::prelude::*; /// Hit data for 3d objects in Global (not Local) space and the distance from the Camera @@ -49,64 +51,112 @@ impl Triangle { } } +pub(crate) fn intersects_aabb_3d(ray: &Ray3d, aabb: &Aabb, gt: &GlobalTransform) -> Option { + let world_to_model = gt.compute_matrix().inverse(); + let ray_dir: Vec3A = world_to_model.transform_vector3(*ray.direction).into(); + let ray_origin: Vec3A = world_to_model.transform_point3(ray.origin).into(); + + let t0 = (aabb.min() - ray_origin) / ray_dir; + let t1 = (aabb.max() - ray_origin) / ray_dir; + let t_min = t0.min(t1); + let t_max = t0.max(t1); + + let mut hit_near = t_min.x; + let mut hit_far = t_max.x; + + if hit_near > t_max.y || t_min.y > hit_far { + return None; + } + + if t_min.y > hit_near { + hit_near = t_min.y; + } + if t_max.y < hit_far { + hit_far = t_max.y; + } + + if (hit_near > t_max.z) || (t_min.z > hit_far) { + return None; + } + + if t_min.z > hit_near { + hit_near = t_min.z; + } + if t_max.z < hit_far { + hit_far = t_max.z; + } + Some(Hit3d { distance: (hit_near + hit_far) / 2.0 }) +} + /// Heavily synthesized from these two resources: /// * Textbook: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/ray-triangle-intersection-geometric-solution.html /// * Example: https://github.com/aevyrie/bevy_mod_raycast/blob/435d8ef100738797161ac3a9b910ea346a4ed6e6/src/raycast.rs#L43 pub(crate) fn intersects3d(ray: &Ray3d, mesh: &Mesh, gt: &GlobalTransform) -> Option { - let attr = MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3); - if let Some(verts) = mesh.attribute(attr) { - if let Some(idxs) = mesh.indices() { - match verts { - VertexAttributeValues::Float32x3(vals) => { - idxs.iter() - .array_chunks::<3>() - // Convert arrays to vec3 - .map(|[x, y, z]| { - [ - Vec3::from_array(vals[x]), - Vec3::from_array(vals[y]), - Vec3::from_array(vals[z]), - ] - }) - // Transform each point by the global transform - .map(|[a, b, c]| { - [ - gt.transform_point(a), - gt.transform_point(b), - gt.transform_point(c), - ] - }) - // Collect everything into a triangle for easy operations - .map(|[v0, v1, v2]| Triangle { v0, v1, v2 }) - .filter_map(|triangle| { - // Calculate the distance this ray hits the plane normal to the tri - if let Some(d) = - ray.intersect_plane(triangle.v0, triangle.normal_plane()) - { - // Calculate the point on that plane which intersects - let p = ray.get_point(d); - // Inside out test - let hit = { - // Determine if p is w/in edge0 - let c0 = triangle.edge0().cross(p - triangle.v0); - // Determine if p is w/in edge1 - let c1 = triangle.edge1().cross(p - triangle.v1); - // Determine if p is w/in edge2 - let c2 = triangle.edge2().cross(p - triangle.v2); - - // Check all three at once - triangle.normal().dot(c0) > 0.0 - && triangle.normal().dot(c1) > 0.0 - && triangle.normal().dot(c2) > 0.0 - }; - hit.then_some(Hit3d { distance: d }) - } else { - None - } - }) - .min_by(|a, b| a.distance.total_cmp(&b.distance)) + // First do an Aabb intersection test + if let Some(aabb) = mesh.compute_aabb() { + // Do the Aabb test + if let Some(_) = intersects_aabb_3d(ray, &aabb, gt) { + // If it passes that do the real intersection test + let attr = MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3); + if let Some(verts) = mesh.attribute(attr) { + if let Some(idxs) = mesh.indices() { + match verts { + VertexAttributeValues::Float32x3(vals) => { + idxs.iter() + .array_chunks::<3>() + // Convert arrays to vec3 + .map(|[x, y, z]| { + [ + Vec3::from_array(vals[x]), + Vec3::from_array(vals[y]), + Vec3::from_array(vals[z]), + ] + }) + // Transform each point by the global transform + .map(|[a, b, c]| { + [ + gt.transform_point(a), + gt.transform_point(b), + gt.transform_point(c), + ] + }) + // Collect everything into a triangle for easy operations + .map(|[v0, v1, v2]| Triangle { v0, v1, v2 }) + .filter_map(|triangle| { + // Calculate the distance this ray hits the plane normal to the tri + if let Some(d) = + ray.intersect_plane(triangle.v0, triangle.normal_plane()) + { + // Calculate the point on that plane which intersects + let p = ray.get_point(d); + // Inside out test + let hit = { + // Determine if p is w/in edge0 + let c0 = triangle.edge0().cross(p - triangle.v0); + // Determine if p is w/in edge1 + let c1 = triangle.edge1().cross(p - triangle.v1); + // Determine if p is w/in edge2 + let c2 = triangle.edge2().cross(p - triangle.v2); + + // Check all three at once + triangle.normal().dot(c0) > 0.0 + && triangle.normal().dot(c1) > 0.0 + && triangle.normal().dot(c2) > 0.0 + }; + hit.then_some(Hit3d { distance: d }) + } else { + None + } + }) + .min_by(|a, b| a.distance.total_cmp(&b.distance)) + } + _ => None, + } + } else { + None } - _ => None, + } else { + None } } else { None