diff --git a/examples/select.rs b/examples/select.rs index 6e3a93d..dc95e85 100644 --- a/examples/select.rs +++ b/examples/select.rs @@ -1,8 +1,9 @@ -#![feature(iter_next_chunk)] +#![feature(iter_array_chunks)] /// Example to illustrate selecting objects in 3d space /// use bevy::{ + math::Vec3A, prelude::*, render::mesh::MeshVertexAttribute, render::render_resource::{Extent3d, TextureDimension, TextureFormat}, @@ -40,32 +41,25 @@ fn startup( }); commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::default().into()), + mesh: meshes.add(shape::Cube::default().into()), material: debug_material.clone(), - transform: Transform::from_xyz(1.0, 1.0, 1.0), + transform: Transform::from_xyz(0.0, 0.0, 0.0), ..default() }); - // commands.spawn(PbrBundle { - // mesh: meshes.add(shape::Cube::default().into()), - // material: debug_material.clone(), - // transform: Transform::from_xyz(0.0, 0.0, 0.0), - // ..default() - // }); - - // commands.spawn(PbrBundle { - // mesh: meshes.add(shape::Torus::default().into()), - // material: debug_material.clone(), - // transform: Transform::from_xyz(3.0, 0.0, 0.0), - // ..default() - // }); - - // commands.spawn(PbrBundle { - // mesh: meshes.add(shape::Icosphere::default().try_into().unwrap()), - // material: debug_material.clone(), - // transform: Transform::from_xyz(0.0, 3.0, 0.0), - // ..default() - // }); + commands.spawn(PbrBundle { + mesh: meshes.add(shape::Torus::default().into()), + material: debug_material.clone(), + transform: Transform::from_xyz(3.0, 0.0, 0.0), + ..default() + }); + + commands.spawn(PbrBundle { + mesh: meshes.add(shape::Icosphere::default().try_into().unwrap()), + material: debug_material.clone(), + transform: Transform::from_xyz(0.0, 3.0, 0.0), + ..default() + }); } /// Creates a colorful test pattern @@ -117,26 +111,60 @@ fn select( .for_each(|(entity, mesh, gt)| { let hit = intersects(&ray, >, mesh); if hit.is_some() { - info!("We got a hit at {:?}", entity); + info!("We got a hit on {:?} at {:?}", entity, hit); } }); } }); } }); - panic!("Lol"); } +#[derive(Debug)] +struct Triangle { + v0: Vec3, + v1: Vec3, + v2: Vec3, +} + +impl Triangle { + fn normal(&self) -> Vec3 { + (self.edge_a()).cross(self.edge_b()) + } + + fn edge_a(&self) -> Vec3 { + self.v1 - self.v0 + } + + fn edge_b(&self) -> Vec3 { + self.v2 - self.v0 + } + + fn edge0(&self) -> Vec3 { + self.v1 - self.v0 + } + + fn edge1(&self) -> Vec3 { + self.v2 - self.v1 + } + + fn edge2(&self) -> Vec3 { + self.v0 - self.v2 + } +} + +/// Heavily synthesized by these two things: +/// * 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 fn intersects(ray: &Ray, gt: &GlobalTransform, mesh: &Mesh) -> Option { - info!("Ray: {:#?}", ray); 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) => { - let mut itr = idxs.iter(); - while let Ok(points) = itr - .next_chunk::<3>() + idxs.iter() + .array_chunks::<3>() + // Convert arrays to vec3 .map(|[x, y, z]| { [ Vec3::from_array(vals[x]), @@ -144,6 +172,7 @@ fn intersects(ray: &Ray, gt: &GlobalTransform, mesh: &Mesh) -> Option { Vec3::from_array(vals[z]), ] }) + // Transform each point by the global transform .map(|[a, b, c]| { [ gt.transform_point(a), @@ -151,17 +180,40 @@ fn intersects(ray: &Ray, gt: &GlobalTransform, mesh: &Mesh) -> Option { gt.transform_point(c), ] }) - { - info!("Does ray intersect with points: {:?}", points); - // OK so I have a ray with an origin and direction - // And I have a triangle made of 3 points - // How do I test if ray intersects with tri??? - } + // 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()) { + // 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(d) + } else { + None + } + }) + .min_by(|a, b| a.total_cmp(b)) } - _ => warn!("Unrecognized indexes"), + _ => None, } + } else { + None } + } else { + None } - - None }