From 34f3f80f21713500dab46395851576f16b55f628 Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:23:43 +1100 Subject: [PATCH 01/11] remove raw indexing in mesh picking --- .../mesh_picking/ray_cast/intersections.rs | 210 ++++++++++++++++-- 1 file changed, 193 insertions(+), 17 deletions(-) diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index d4ec97e1f3549..cf727ae98690d 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -94,17 +94,20 @@ pub fn ray_mesh_intersection + Clone + Copy>( let triangle_index = Some(a); let tri_vertex_positions = &[ - Vec3::from(positions[a]), - Vec3::from(positions[b]), - Vec3::from(positions[c]), + Vec3::from(*positions.get(a)?), + Vec3::from(*positions.get(b)?), + Vec3::from(*positions.get(c)?), ]; - let tri_normals = vertex_normals.map(|normals| { - [ - Vec3::from(normals[a]), - Vec3::from(normals[b]), - Vec3::from(normals[c]), - ] - }); + let tri_normals = vertex_normals + .map(|normals| -> Result<[Vec3; 3], ()> { + Ok([ + Vec3::from(*normals.get(a).ok_or(())?), + Vec3::from(*normals.get(b).ok_or(())?), + Vec3::from(*normals.get(c).ok_or(())?), + ]) + }) + .transpose() + .ok()?; let Some(hit) = triangle_intersection( tri_vertex_positions, @@ -141,13 +144,15 @@ pub fn ray_mesh_intersection + Clone + Copy>( }; let triangle_index = Some(i); let tri_vertex_positions = &[Vec3::from(a), Vec3::from(b), Vec3::from(c)]; - let tri_normals = vertex_normals.map(|normals| { - [ - Vec3::from(normals[i]), - Vec3::from(normals[i + 1]), - Vec3::from(normals[i + 2]), - ] - }); + let tri_normals = vertex_normals + .map(|normals| -> Result<[Vec3; 3], ()> { + let c = Vec3::from(*normals.get(i + 2).ok_or(())?); + let b = Vec3::from(*normals.get(i + 1).ok_or(())?); + let a = Vec3::from(*normals.get(i).ok_or(())?); + Ok([a, b, c]) + }) + .transpose() + .ok()?; let Some(hit) = triangle_intersection( tri_vertex_positions, @@ -313,6 +318,7 @@ pub fn ray_aabb_intersection_3d(ray: Ray3d, aabb: &Aabb3d, model_to_world: &Mat4 #[cfg(test)] mod tests { use bevy_math::Vec3; + use bevy_transform::components::GlobalTransform; use super::*; @@ -336,4 +342,174 @@ mod tests { let result = ray_triangle_intersection(&ray, &triangle, Backfaces::Cull); assert!(result.is_none()); } + + #[test] + fn ray_mesh_intersection_simple() { + let ray = Ray3d::new(Vec3::ZERO, Dir3::X); + let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let positions = &[V0, V1, V2]; + let vertex_normals = None; + let indices: Option<&[u16]> = None; + let backface_culling = Backfaces::Cull; + + let result = ray_mesh_intersection( + ray, + &mesh_transform, + positions, + vertex_normals, + indices, + backface_culling, + ); + + assert!(result.is_some()); + } + + #[test] + fn ray_mesh_intersection_indices() { + let ray = Ray3d::new(Vec3::ZERO, Dir3::X); + let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let positions = &[V0, V1, V2]; + let vertex_normals = None; + let indices: Option<&[u16]> = Some(&[0, 1, 2]); + let backface_culling = Backfaces::Cull; + + let result = ray_mesh_intersection( + ray, + &mesh_transform, + positions, + vertex_normals, + indices, + backface_culling, + ); + + assert!(result.is_some()); + } + + #[test] + fn ray_mesh_intersection_indices_vertex_normals() { + let ray = Ray3d::new(Vec3::ZERO, Dir3::X); + let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let positions = &[V0, V1, V2]; + let vertex_normals: Option<&[[f32; 3]]> = + Some(&[[-1., 0., 0.], [-1., 0., 0.], [-1., 0., 0.]]); + let indices: Option<&[u16]> = Some(&[0, 1, 2]); + let backface_culling = Backfaces::Cull; + + let result = ray_mesh_intersection( + ray, + &mesh_transform, + positions, + vertex_normals, + indices, + backface_culling, + ); + + assert!(result.is_some()); + } + + #[test] + fn ray_mesh_intersection_vertex_normals() { + let ray = Ray3d::new(Vec3::ZERO, Dir3::X); + let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let positions = &[V0, V1, V2]; + let vertex_normals: Option<&[[f32; 3]]> = + Some(&[[-1., 0., 0.], [-1., 0., 0.], [-1., 0., 0.]]); + let indices: Option<&[u16]> = None; + let backface_culling = Backfaces::Cull; + + let result = ray_mesh_intersection( + ray, + &mesh_transform, + positions, + vertex_normals, + indices, + backface_culling, + ); + + assert!(result.is_some()); + } + + #[test] + fn ray_mesh_intersection_missing_vertex_normals() { + let ray = Ray3d::new(Vec3::ZERO, Dir3::X); + let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let positions = &[V0, V1, V2]; + let vertex_normals: Option<&[[f32; 3]]> = Some(&[]); + let indices: Option<&[u16]> = None; + let backface_culling = Backfaces::Cull; + + let result = ray_mesh_intersection( + ray, + &mesh_transform, + positions, + vertex_normals, + indices, + backface_culling, + ); + + assert!(result.is_none()); + } + + #[test] + fn ray_mesh_intersection_incides_missing_vertex_normals() { + let ray = Ray3d::new(Vec3::ZERO, Dir3::X); + let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let positions = &[V0, V1, V2]; + let vertex_normals: Option<&[[f32; 3]]> = Some(&[]); + let indices: Option<&[u16]> = Some(&[0, 1, 2]); + let backface_culling = Backfaces::Cull; + + let result = ray_mesh_intersection( + ray, + &mesh_transform, + positions, + vertex_normals, + indices, + backface_culling, + ); + + assert!(result.is_none()); + } + + #[test] + fn ray_mesh_intersection_not_enough_indices() { + let ray = Ray3d::new(Vec3::ZERO, Dir3::X); + let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let positions = &[V0, V1, V2]; + let vertex_normals = None; + let indices: Option<&[u16]> = Some(&[0]); + let backface_culling = Backfaces::Cull; + + let result = ray_mesh_intersection( + ray, + &mesh_transform, + positions, + vertex_normals, + indices, + backface_culling, + ); + + assert!(result.is_none()); + } + + #[test] + fn ray_mesh_intersection_bad_indices() { + let ray = Ray3d::new(Vec3::ZERO, Dir3::X); + let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); + let positions = &[V0, V1, V2]; + let vertex_normals = None; + let indices: Option<&[u16]> = Some(&[0, 1, 3]); + let backface_culling = Backfaces::Cull; + + let result = ray_mesh_intersection( + ray, + &mesh_transform, + positions, + vertex_normals, + indices, + backface_culling, + ); + + assert!(result.is_none()); + } } From f39f7ccbb01a5fefb037c8b3c608ebe1b244bd9f Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:39:33 +1100 Subject: [PATCH 02/11] spelling --- crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index cf727ae98690d..becd070c9f288 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -451,7 +451,7 @@ mod tests { } #[test] - fn ray_mesh_intersection_incides_missing_vertex_normals() { + fn ray_mesh_intersection_indices_missing_vertex_normals() { let ray = Ray3d::new(Vec3::ZERO, Dir3::X); let mesh_transform = GlobalTransform::IDENTITY.compute_matrix(); let positions = &[V0, V1, V2]; From 276feb4d7a2acb9f0f98ee1468beb627af63a35e Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:18:53 +1100 Subject: [PATCH 03/11] simplify result nesting --- .../mesh_picking/ray_cast/intersections.rs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index becd070c9f288..abe8b8751cabd 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -98,16 +98,14 @@ pub fn ray_mesh_intersection + Clone + Copy>( Vec3::from(*positions.get(b)?), Vec3::from(*positions.get(c)?), ]; - let tri_normals = vertex_normals - .map(|normals| -> Result<[Vec3; 3], ()> { - Ok([ - Vec3::from(*normals.get(a).ok_or(())?), - Vec3::from(*normals.get(b).ok_or(())?), - Vec3::from(*normals.get(c).ok_or(())?), - ]) - }) - .transpose() - .ok()?; + let tri_normals = match vertex_normals { + Some(normals) => Some([ + Vec3::from(*normals.get(a)?), + Vec3::from(*normals.get(b)?), + Vec3::from(*normals.get(c)?), + ]), + None => None, + }; let Some(hit) = triangle_intersection( tri_vertex_positions, @@ -144,15 +142,15 @@ pub fn ray_mesh_intersection + Clone + Copy>( }; let triangle_index = Some(i); let tri_vertex_positions = &[Vec3::from(a), Vec3::from(b), Vec3::from(c)]; - let tri_normals = vertex_normals - .map(|normals| -> Result<[Vec3; 3], ()> { - let c = Vec3::from(*normals.get(i + 2).ok_or(())?); - let b = Vec3::from(*normals.get(i + 1).ok_or(())?); - let a = Vec3::from(*normals.get(i).ok_or(())?); - Ok([a, b, c]) - }) - .transpose() - .ok()?; + let tri_normals = match vertex_normals { + Some(normals) => { + let c = Vec3::from(*normals.get(i + 2)?); + let b = Vec3::from(*normals.get(i + 1)?); + let a = Vec3::from(*normals.get(i)?); + Some([a, b, c]) + } + None => None, + }; let Some(hit) = triangle_intersection( tri_vertex_positions, From 39952c72e80cabc1890d0f56ad7a65b958ba48b1 Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:41:23 +1100 Subject: [PATCH 04/11] continue picking through missing vertex normals, add bench --- .../bevy_picking/ray_mesh_intersection.rs | 28 ++++++++++++++++++- .../mesh_picking/ray_cast/intersections.rs | 28 ++++++++----------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/benches/benches/bevy_picking/ray_mesh_intersection.rs b/benches/benches/bevy_picking/ray_mesh_intersection.rs index f451b790612c3..e42fefe092e34 100644 --- a/benches/benches/bevy_picking/ray_mesh_intersection.rs +++ b/benches/benches/bevy_picking/ray_mesh_intersection.rs @@ -111,10 +111,36 @@ fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { } } +fn ray_mesh_intersection_no_indices(c: &mut Criterion) { + let mut group = c.benchmark_group("ray_mesh_intersection_no_indices"); + group.warm_up_time(std::time::Duration::from_millis(500)); + + for vertices_per_side in [10_u32, 100, 1000] { + group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { + let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + let mesh = mesh_creation(vertices_per_side); + let indices: Option<&[u32]> = None; + + b.iter(|| { + black_box(ray_cast::ray_mesh_intersection( + ray, + &mesh_to_world, + &mesh.positions, + Some(&mesh.normals), + indices, + ray_cast::Backfaces::Cull, + )); + }); + }); + } +} + criterion_group!( benches, ray_mesh_intersection, ray_mesh_intersection_no_cull, - ray_mesh_intersection_no_intersection + ray_mesh_intersection_no_intersection, + ray_mesh_intersection_no_indices, ); criterion_main!(benches); diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index abe8b8751cabd..aefd69c9124f6 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -98,14 +98,13 @@ pub fn ray_mesh_intersection + Clone + Copy>( Vec3::from(*positions.get(b)?), Vec3::from(*positions.get(c)?), ]; - let tri_normals = match vertex_normals { - Some(normals) => Some([ + let tri_normals = vertex_normals.and_then(|normals| { + Some([ Vec3::from(*normals.get(a)?), Vec3::from(*normals.get(b)?), Vec3::from(*normals.get(c)?), - ]), - None => None, - }; + ]) + }); let Some(hit) = triangle_intersection( tri_vertex_positions, @@ -142,15 +141,12 @@ pub fn ray_mesh_intersection + Clone + Copy>( }; let triangle_index = Some(i); let tri_vertex_positions = &[Vec3::from(a), Vec3::from(b), Vec3::from(c)]; - let tri_normals = match vertex_normals { - Some(normals) => { - let c = Vec3::from(*normals.get(i + 2)?); - let b = Vec3::from(*normals.get(i + 1)?); - let a = Vec3::from(*normals.get(i)?); - Some([a, b, c]) - } - None => None, - }; + let tri_normals = vertex_normals.and_then(|normals| { + let Some([a, b, c]) = normals.get(i..(i + 2)) else { + return None; + }; + Some([Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]) + }); let Some(hit) = triangle_intersection( tri_vertex_positions, @@ -445,7 +441,7 @@ mod tests { backface_culling, ); - assert!(result.is_none()); + assert!(result.is_some()); } #[test] @@ -466,7 +462,7 @@ mod tests { backface_culling, ); - assert!(result.is_none()); + assert!(result.is_some()); } #[test] From 932aa8303c71967dd818a9aac39dd8f7f7ac4eee Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:06:33 +1100 Subject: [PATCH 05/11] rearrange mesh intersection --- .../bevy_picking/ray_mesh_intersection.rs | 150 ++++++------ .../mesh_picking/ray_cast/intersections.rs | 223 +++++++++++++----- 2 files changed, 236 insertions(+), 137 deletions(-) diff --git a/benches/benches/bevy_picking/ray_mesh_intersection.rs b/benches/benches/bevy_picking/ray_mesh_intersection.rs index e42fefe092e34..319300cefceab 100644 --- a/benches/benches/bevy_picking/ray_mesh_intersection.rs +++ b/benches/benches/bevy_picking/ray_mesh_intersection.rs @@ -63,84 +63,84 @@ fn ray_mesh_intersection(c: &mut Criterion) { } } -fn ray_mesh_intersection_no_cull(c: &mut Criterion) { - let mut group = c.benchmark_group("ray_mesh_intersection_no_cull"); - group.warm_up_time(std::time::Duration::from_millis(500)); - - for vertices_per_side in [10_u32, 100, 1000] { - group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { - let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); - let mesh_to_world = Mat4::IDENTITY; - let mesh = mesh_creation(vertices_per_side); - - b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( - ray, - &mesh_to_world, - &mesh.positions, - Some(&mesh.normals), - Some(&mesh.indices), - ray_cast::Backfaces::Include, - )); - }); - }); - } -} - -fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { - let mut group = c.benchmark_group("ray_mesh_intersection_no_intersection"); - group.warm_up_time(std::time::Duration::from_millis(500)); - - for vertices_per_side in [10_u32, 100, 1000] { - group.bench_function(format!("{}_vertices", (vertices_per_side).pow(2)), |b| { - let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::X); - let mesh_to_world = Mat4::IDENTITY; - let mesh = mesh_creation(vertices_per_side); - - b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( - ray, - &mesh_to_world, - &mesh.positions, - Some(&mesh.normals), - Some(&mesh.indices), - ray_cast::Backfaces::Cull, - )); - }); - }); - } -} - -fn ray_mesh_intersection_no_indices(c: &mut Criterion) { - let mut group = c.benchmark_group("ray_mesh_intersection_no_indices"); - group.warm_up_time(std::time::Duration::from_millis(500)); - - for vertices_per_side in [10_u32, 100, 1000] { - group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { - let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); - let mesh_to_world = Mat4::IDENTITY; - let mesh = mesh_creation(vertices_per_side); - let indices: Option<&[u32]> = None; - - b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( - ray, - &mesh_to_world, - &mesh.positions, - Some(&mesh.normals), - indices, - ray_cast::Backfaces::Cull, - )); - }); - }); - } -} +// fn ray_mesh_intersection_no_cull(c: &mut Criterion) { +// let mut group = c.benchmark_group("ray_mesh_intersection_no_cull"); +// group.warm_up_time(std::time::Duration::from_millis(500)); + +// for vertices_per_side in [10_u32, 100, 1000] { +// group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { +// let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); +// let mesh_to_world = Mat4::IDENTITY; +// let mesh = mesh_creation(vertices_per_side); + +// b.iter(|| { +// black_box(ray_cast::ray_mesh_intersection( +// ray, +// &mesh_to_world, +// &mesh.positions, +// Some(&mesh.normals), +// Some(&mesh.indices), +// ray_cast::Backfaces::Include, +// )); +// }); +// }); +// } +// } + +// fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { +// let mut group = c.benchmark_group("ray_mesh_intersection_no_intersection"); +// group.warm_up_time(std::time::Duration::from_millis(500)); + +// for vertices_per_side in [10_u32, 100, 1000] { +// group.bench_function(format!("{}_vertices", (vertices_per_side).pow(2)), |b| { +// let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::X); +// let mesh_to_world = Mat4::IDENTITY; +// let mesh = mesh_creation(vertices_per_side); + +// b.iter(|| { +// black_box(ray_cast::ray_mesh_intersection( +// ray, +// &mesh_to_world, +// &mesh.positions, +// Some(&mesh.normals), +// Some(&mesh.indices), +// ray_cast::Backfaces::Cull, +// )); +// }); +// }); +// } +// } + +// fn ray_mesh_intersection_no_indices(c: &mut Criterion) { +// let mut group = c.benchmark_group("ray_mesh_intersection_no_indices"); +// group.warm_up_time(std::time::Duration::from_millis(500)); + +// for vertices_per_side in [10_u32, 100, 1000] { +// group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { +// let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); +// let mesh_to_world = Mat4::IDENTITY; +// let mesh = mesh_creation(vertices_per_side); +// let indices: Option<&[u32]> = None; + +// b.iter(|| { +// black_box(ray_cast::ray_mesh_intersection( +// ray, +// &mesh_to_world, +// &mesh.positions, +// Some(&mesh.normals), +// indices, +// ray_cast::Backfaces::Cull, +// )); +// }); +// }); +// } +// } criterion_group!( benches, ray_mesh_intersection, - ray_mesh_intersection_no_cull, - ray_mesh_intersection_no_intersection, - ray_mesh_intersection_no_indices, + // ray_mesh_intersection_no_cull, + // ray_mesh_intersection_no_intersection, + // ray_mesh_intersection_no_indices, ); criterion_main!(benches); diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index aefd69c9124f6..a7d333f1e24bd 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -69,6 +69,7 @@ pub fn ray_mesh_intersection + Clone + Copy>( // The ray cast can hit the same mesh many times, so we need to track which hit is // closest to the camera, and record that. let mut closest_hit_distance = f32::MAX; + let mut closest_tri_hit = None; let mut closest_hit = None; let world_to_mesh = mesh_transform.inverse(); @@ -85,55 +86,141 @@ pub fn ray_mesh_intersection + Clone + Copy>( return None; } - for triangle in indices.chunks_exact(3) { - let [a, b, c] = [ - triangle[0].try_into().ok()?, - triangle[1].try_into().ok()?, - triangle[2].try_into().ok()?, - ]; - - let triangle_index = Some(a); - let tri_vertex_positions = &[ - Vec3::from(*positions.get(a)?), - Vec3::from(*positions.get(b)?), - Vec3::from(*positions.get(c)?), - ]; + indices + .chunks_exact(3) + .enumerate() + .for_each(|(tri_idx, triangle)| { + let [Ok(a), Ok(b), Ok(c)] = [ + triangle[0].try_into(), + triangle[1].try_into(), + triangle[2].try_into(), + ] else { + return; + }; + + let [Some(c), Some(b), Some(a)] = + [positions.get(c), positions.get(b), positions.get(a)] + else { + return; + }; + + let tri_vertices = [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]; + + if let Some(hit) = ray_triangle_intersection(&ray, &tri_vertices, backface_culling) + { + if hit.distance > 0. && hit.distance < closest_hit_distance { + closest_hit_distance = hit.distance; + closest_tri_hit = Some((tri_idx, hit)); + } + } + }); + + if let Some((tri_idx, hit)) = closest_tri_hit { + let triangle = indices.get((tri_idx * 3)..(tri_idx * 3 + 3))?; + + let [Ok(a), Ok(b), Ok(c)] = [ + triangle[0].try_into(), + triangle[1].try_into(), + triangle[2].try_into(), + ] else { + return None; + }; + + let tri_vertices = match [positions.get(c), positions.get(b), positions.get(a)] { + [Some(c), Some(b), Some(a)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], + _ => return None, + }; + let tri_normals = vertex_normals.and_then(|normals| { - Some([ - Vec3::from(*normals.get(a)?), - Vec3::from(*normals.get(b)?), - Vec3::from(*normals.get(c)?), - ]) + let [Some(c), Some(b), Some(a)] = [normals.get(c), normals.get(b), normals.get(a)] + else { + return None; + }; + Some([Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]) }); - let Some(hit) = triangle_intersection( - tri_vertex_positions, - tri_normals.as_ref(), - closest_hit_distance, - &mesh_space_ray, - backface_culling, - ) else { - continue; + let point = ray.get_point(hit.distance); + let u = hit.barycentric_coords.0; + let v = hit.barycentric_coords.1; + let w = 1.0 - u - v; + let barycentric = Vec3::new(u, v, w); + + let normal = if let Some(normals) = tri_normals { + normals[1] * u + normals[2] * v + normals[0] * w + } else { + (tri_vertices[1] - tri_vertices[0]) + .cross(tri_vertices[2] - tri_vertices[0]) + .normalize() }; - closest_hit = Some(RayMeshHit { - point: mesh_transform.transform_point3(hit.point), - normal: mesh_transform.transform_vector3(hit.normal), - barycentric_coords: hit.barycentric_coords, - distance: mesh_transform - .transform_vector3(mesh_space_ray.direction * hit.distance) - .length(), - triangle: hit.triangle.map(|tri| { - [ - mesh_transform.transform_point3(tri[0]), - mesh_transform.transform_point3(tri[1]), - mesh_transform.transform_point3(tri[2]), - ] - }), - triangle_index, + return Some(RayMeshHit { + point, + normal, + barycentric_coords: barycentric, + distance: hit.distance, + triangle: Some(tri_vertices), + triangle_index: Some(tri_idx), }); - closest_hit_distance = hit.distance; } + + // for triangle in indices.chunks_exact(3) { + // let [a, b, c] = [ + // triangle[0].try_into().ok()?, + // triangle[1].try_into().ok()?, + // triangle[2].try_into().ok()?, + // ]; + + // let triangle_index = Some(a); + // let tri_vertex_positions = &[ + // Vec3::from(*positions.get(a)?), + // Vec3::from(*positions.get(b)?), + // Vec3::from(*positions.get(c)?), + // ]; + + // let Some(hit) = triangle_intersection( + // tri_vertex_positions, + // None, + // closest_hit_distance, + // &mesh_space_ray, + // backface_culling, + // ) else { + // continue; + // }; + + // let tri_normals = vertex_normals.and_then(|normals| { + // Some([ + // Vec3::from(*normals.get(a)?), + // Vec3::from(*normals.get(b)?), + // Vec3::from(*normals.get(c)?), + // ]) + // }); + + // let hit_normal = if let Some(normals) = tri_normals { + // normals[1] * hit.barycentric_coords.x + // + normals[2] * hit.barycentric_coords.y + // + normals[0] * hit.barycentric_coords.z + // } else { + // (tri_vertex_positions[1] - tri_vertex_positions[0]) + // .cross(tri_vertex_positions[2] - tri_vertex_positions[0]) + // .normalize() + // }; + + // closest_hit = Some(RayMeshHit { + // point: mesh_transform.transform_point3(hit.point), + // normal: mesh_transform.transform_vector3(hit_normal), + // barycentric_coords: hit.barycentric_coords, + // distance: mesh_transform + // .transform_vector3(mesh_space_ray.direction * hit.distance) + // .length(), + // triangle: Some([ + // mesh_transform.transform_point3(tri_vertex_positions[0]), + // mesh_transform.transform_point3(tri_vertex_positions[1]), + // mesh_transform.transform_point3(tri_vertex_positions[2]), + // ]), + // triangle_index, + // }); + // closest_hit_distance = hit.distance; + // } } else { for (i, triangle) in positions.chunks_exact(3).enumerate() { let &[a, b, c] = triangle else { @@ -158,20 +245,22 @@ pub fn ray_mesh_intersection + Clone + Copy>( continue; }; + let hit_normal = (tri_vertex_positions[1] - tri_vertex_positions[0]) + .cross(tri_vertex_positions[2] - tri_vertex_positions[0]) + .normalize(); + closest_hit = Some(RayMeshHit { point: mesh_transform.transform_point3(hit.point), - normal: mesh_transform.transform_vector3(hit.normal), + normal: mesh_transform.transform_vector3(hit_normal), barycentric_coords: hit.barycentric_coords, distance: mesh_transform .transform_vector3(mesh_space_ray.direction * hit.distance) .length(), - triangle: hit.triangle.map(|tri| { - [ - mesh_transform.transform_point3(tri[0]), - mesh_transform.transform_point3(tri[1]), - mesh_transform.transform_point3(tri[2]), - ] - }), + triangle: Some([ + mesh_transform.transform_point3(tri_vertex_positions[0]), + mesh_transform.transform_point3(tri_vertex_positions[1]), + mesh_transform.transform_point3(tri_vertex_positions[2]), + ]), triangle_index, }); closest_hit_distance = hit.distance; @@ -181,13 +270,24 @@ pub fn ray_mesh_intersection + Clone + Copy>( closest_hit } +/// Hit data for an intersection between a ray and a mesh. +#[derive(Debug, Clone, Reflect)] +pub struct PartialRayMeshHit { + /// The point of intersection in world space. + pub point: Vec3, + /// The barycentric coordinates of the intersection. + pub barycentric_coords: Vec3, + /// The distance from the ray origin to the intersection point. + pub distance: f32, +} + fn triangle_intersection( tri_vertices: &[Vec3; 3], tri_normals: Option<&[Vec3; 3]>, max_distance: f32, ray: &Ray3d, backface_culling: Backfaces, -) -> Option { +) -> Option { let hit = ray_triangle_intersection(ray, tri_vertices, backface_culling)?; if hit.distance < 0.0 || hit.distance > max_distance { @@ -200,21 +300,20 @@ fn triangle_intersection( let w = 1.0 - u - v; let barycentric = Vec3::new(u, v, w); - let normal = if let Some(normals) = tri_normals { - normals[1] * u + normals[2] * v + normals[0] * w - } else { - (tri_vertices[1] - tri_vertices[0]) - .cross(tri_vertices[2] - tri_vertices[0]) - .normalize() - }; + // let normal = if let Some(normals) = tri_normals { + // normals[1] * u + normals[2] * v + normals[0] * w + // } else { + // (tri_vertices[1] - tri_vertices[0]) + // .cross(tri_vertices[2] - tri_vertices[0]) + // .normalize() + // }; - Some(RayMeshHit { + Some(PartialRayMeshHit { point, - normal, barycentric_coords: barycentric, distance: hit.distance, - triangle: Some(*tri_vertices), - triangle_index: None, + // triangle: Some(*tri_vertices), + // triangle_index: None, }) } From 20b8b21e50b20f44a4c205ed83b2e6e23da2b807 Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:32:24 +1100 Subject: [PATCH 06/11] cleanup, inline ray_triangle_intersection --- .../bevy_picking/ray_mesh_intersection.rs | 237 +++++++++------ .../mesh_picking/ray_cast/intersections.rs | 271 +++++------------- 2 files changed, 216 insertions(+), 292 deletions(-) diff --git a/benches/benches/bevy_picking/ray_mesh_intersection.rs b/benches/benches/bevy_picking/ray_mesh_intersection.rs index 319300cefceab..05e71e9771da0 100644 --- a/benches/benches/bevy_picking/ray_mesh_intersection.rs +++ b/benches/benches/bevy_picking/ray_mesh_intersection.rs @@ -1,6 +1,6 @@ use bevy_math::{Dir3, Mat4, Ray3d, Vec3}; use bevy_picking::mesh_picking::ray_cast; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; fn ptoxznorm(p: u32, size: u32) -> (f32, f32) { let ij = (p / (size), p % (size)); @@ -44,103 +44,156 @@ fn ray_mesh_intersection(c: &mut Criterion) { group.warm_up_time(std::time::Duration::from_millis(500)); for vertices_per_side in [10_u32, 100, 1000] { - group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { - let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); - let mesh_to_world = Mat4::IDENTITY; - let mesh = mesh_creation(vertices_per_side); - - b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( - ray, - &mesh_to_world, - &mesh.positions, - Some(&mesh.normals), - Some(&mesh.indices), - ray_cast::Backfaces::Cull, - )); - }); - }); + let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + let mesh = mesh_creation(vertices_per_side); + let tri_count = (mesh.indices.len() / 3) as u64; + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions, {} indices)", + underscore_separate_number(tri_count), + underscore_separate_number(mesh.positions.len()), + underscore_separate_number(mesh.indices.len() / 3) + ), + &(ray, mesh_to_world, mesh), + |b, (ray, mesh_to_world, mesh)| { + b.iter(|| { + black_box(ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + &mesh.positions, + Some(&mesh.normals), + Some(&mesh.indices), + ray_cast::Backfaces::Cull, + )); + }); + }, + ); } } -// fn ray_mesh_intersection_no_cull(c: &mut Criterion) { -// let mut group = c.benchmark_group("ray_mesh_intersection_no_cull"); -// group.warm_up_time(std::time::Duration::from_millis(500)); - -// for vertices_per_side in [10_u32, 100, 1000] { -// group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { -// let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); -// let mesh_to_world = Mat4::IDENTITY; -// let mesh = mesh_creation(vertices_per_side); - -// b.iter(|| { -// black_box(ray_cast::ray_mesh_intersection( -// ray, -// &mesh_to_world, -// &mesh.positions, -// Some(&mesh.normals), -// Some(&mesh.indices), -// ray_cast::Backfaces::Include, -// )); -// }); -// }); -// } -// } - -// fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { -// let mut group = c.benchmark_group("ray_mesh_intersection_no_intersection"); -// group.warm_up_time(std::time::Duration::from_millis(500)); - -// for vertices_per_side in [10_u32, 100, 1000] { -// group.bench_function(format!("{}_vertices", (vertices_per_side).pow(2)), |b| { -// let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::X); -// let mesh_to_world = Mat4::IDENTITY; -// let mesh = mesh_creation(vertices_per_side); - -// b.iter(|| { -// black_box(ray_cast::ray_mesh_intersection( -// ray, -// &mesh_to_world, -// &mesh.positions, -// Some(&mesh.normals), -// Some(&mesh.indices), -// ray_cast::Backfaces::Cull, -// )); -// }); -// }); -// } -// } - -// fn ray_mesh_intersection_no_indices(c: &mut Criterion) { -// let mut group = c.benchmark_group("ray_mesh_intersection_no_indices"); -// group.warm_up_time(std::time::Duration::from_millis(500)); - -// for vertices_per_side in [10_u32, 100, 1000] { -// group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { -// let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); -// let mesh_to_world = Mat4::IDENTITY; -// let mesh = mesh_creation(vertices_per_side); -// let indices: Option<&[u32]> = None; - -// b.iter(|| { -// black_box(ray_cast::ray_mesh_intersection( -// ray, -// &mesh_to_world, -// &mesh.positions, -// Some(&mesh.normals), -// indices, -// ray_cast::Backfaces::Cull, -// )); -// }); -// }); -// } -// } +fn ray_mesh_intersection_no_cull(c: &mut Criterion) { + let mut group = c.benchmark_group("ray_mesh_intersection_no_cull"); + group.warm_up_time(std::time::Duration::from_millis(500)); + + for vertices_per_side in [10_u32, 100, 1000] { + let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + let mesh = mesh_creation(vertices_per_side); + let tri_count = (mesh.indices.len() / 3) as u64; + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions, {} indices)", + underscore_separate_number(tri_count), + underscore_separate_number(mesh.positions.len()), + underscore_separate_number(mesh.indices.len() / 3) + ), + &(ray, mesh_to_world, mesh), + |b, (ray, mesh_to_world, mesh)| { + b.iter(|| { + black_box(ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + &mesh.positions, + Some(&mesh.normals), + Some(&mesh.indices), + ray_cast::Backfaces::Include, + )); + }); + }, + ); + } +} + +fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { + let mut group = c.benchmark_group("ray_mesh_intersection_no_intersection"); + group.warm_up_time(std::time::Duration::from_millis(500)); + + for vertices_per_side in [10_u32, 100, 1000] { + let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + let mesh = mesh_creation(vertices_per_side); + let tri_count = (mesh.indices.len() / 3) as u64; + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions, {} indices)", + underscore_separate_number(tri_count), + underscore_separate_number(mesh.positions.len()), + underscore_separate_number(mesh.indices.len() / 3) + ), + &(ray, mesh_to_world, mesh), + |b, (ray, mesh_to_world, mesh)| { + b.iter(|| { + black_box(ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + &mesh.positions, + Some(&mesh.normals), + Some(&mesh.indices), + ray_cast::Backfaces::Cull, + )); + }); + }, + ); + } +} + +fn ray_mesh_intersection_no_indices(c: &mut Criterion) { + let mut group = c.benchmark_group("ray_mesh_intersection_no_indices"); + group.warm_up_time(std::time::Duration::from_millis(500)); + + for vertices_per_side in [10_u32, 100, 1000] { + let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + let mesh = mesh_creation(vertices_per_side); + let tri_count = mesh.positions.len() as u64; + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions)", + underscore_separate_number(tri_count), + underscore_separate_number(mesh.positions.len()), + ), + &(ray, mesh_to_world, mesh), + |b, (ray, mesh_to_world, mesh)| { + b.iter(|| { + black_box(ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + &mesh.positions, + Some(&mesh.normals), + Option::<&[u32]>::None, + ray_cast::Backfaces::Cull, + )); + }); + }, + ); + } +} + +fn underscore_separate_number(n: impl ToString) -> String { + n.to_string() + .as_bytes() + .rchunks(3) + .rev() + .map(std::str::from_utf8) + .collect::, _>>() + .unwrap() + .join("_") +} criterion_group!( benches, ray_mesh_intersection, - // ray_mesh_intersection_no_cull, - // ray_mesh_intersection_no_intersection, - // ray_mesh_intersection_no_indices, + ray_mesh_intersection_no_cull, + ray_mesh_intersection_no_intersection, + ray_mesh_intersection_no_indices, ); criterion_main!(benches); diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index a7d333f1e24bd..4633e5616f376 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -69,12 +69,11 @@ pub fn ray_mesh_intersection + Clone + Copy>( // The ray cast can hit the same mesh many times, so we need to track which hit is // closest to the camera, and record that. let mut closest_hit_distance = f32::MAX; - let mut closest_tri_hit = None; let mut closest_hit = None; let world_to_mesh = mesh_transform.inverse(); - let mesh_space_ray = Ray3d::new( + let ray = Ray3d::new( world_to_mesh.transform_point3(ray.origin), Dir3::new(world_to_mesh.transform_vector3(*ray.direction)).ok()?, ); @@ -98,226 +97,98 @@ pub fn ray_mesh_intersection + Clone + Copy>( return; }; - let [Some(c), Some(b), Some(a)] = - [positions.get(c), positions.get(b), positions.get(a)] - else { - return; + let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] { + [Some(c), Some(b), Some(a)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], + _ => return, }; - let tri_vertices = [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]; - if let Some(hit) = ray_triangle_intersection(&ray, &tri_vertices, backface_culling) { if hit.distance > 0. && hit.distance < closest_hit_distance { closest_hit_distance = hit.distance; - closest_tri_hit = Some((tri_idx, hit)); + closest_hit = Some((tri_idx, hit)); } } }); + } else { + positions + .chunks_exact(3) + .enumerate() + .for_each(|(tri_idx, triangle)| { + let tri_vertices = [ + Vec3::from(triangle[0]), + Vec3::from(triangle[1]), + Vec3::from(triangle[2]), + ]; - if let Some((tri_idx, hit)) = closest_tri_hit { - let triangle = indices.get((tri_idx * 3)..(tri_idx * 3 + 3))?; - - let [Ok(a), Ok(b), Ok(c)] = [ - triangle[0].try_into(), - triangle[1].try_into(), - triangle[2].try_into(), - ] else { - return None; - }; - - let tri_vertices = match [positions.get(c), positions.get(b), positions.get(a)] { - [Some(c), Some(b), Some(a)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], - _ => return None, - }; - - let tri_normals = vertex_normals.and_then(|normals| { - let [Some(c), Some(b), Some(a)] = [normals.get(c), normals.get(b), normals.get(a)] - else { - return None; - }; - Some([Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]) + if let Some(hit) = ray_triangle_intersection(&ray, &tri_vertices, backface_culling) + { + if hit.distance > 0. && hit.distance < closest_hit_distance { + closest_hit_distance = hit.distance; + closest_hit = Some((tri_idx, hit)); + } + } }); + } - let point = ray.get_point(hit.distance); - let u = hit.barycentric_coords.0; - let v = hit.barycentric_coords.1; - let w = 1.0 - u - v; - let barycentric = Vec3::new(u, v, w); - - let normal = if let Some(normals) = tri_normals { - normals[1] * u + normals[2] * v + normals[0] * w - } else { - (tri_vertices[1] - tri_vertices[0]) - .cross(tri_vertices[2] - tri_vertices[0]) - .normalize() - }; - - return Some(RayMeshHit { - point, - normal, - barycentric_coords: barycentric, - distance: hit.distance, - triangle: Some(tri_vertices), - triangle_index: Some(tri_idx), - }); - } + closest_hit.and_then(|(tri_idx, hit)| { + let [a, b, c] = match indices { + Some(indices) => { + let triangle = indices.get((tri_idx * 3)..(tri_idx * 3 + 3))?; - // for triangle in indices.chunks_exact(3) { - // let [a, b, c] = [ - // triangle[0].try_into().ok()?, - // triangle[1].try_into().ok()?, - // triangle[2].try_into().ok()?, - // ]; - - // let triangle_index = Some(a); - // let tri_vertex_positions = &[ - // Vec3::from(*positions.get(a)?), - // Vec3::from(*positions.get(b)?), - // Vec3::from(*positions.get(c)?), - // ]; - - // let Some(hit) = triangle_intersection( - // tri_vertex_positions, - // None, - // closest_hit_distance, - // &mesh_space_ray, - // backface_culling, - // ) else { - // continue; - // }; - - // let tri_normals = vertex_normals.and_then(|normals| { - // Some([ - // Vec3::from(*normals.get(a)?), - // Vec3::from(*normals.get(b)?), - // Vec3::from(*normals.get(c)?), - // ]) - // }); - - // let hit_normal = if let Some(normals) = tri_normals { - // normals[1] * hit.barycentric_coords.x - // + normals[2] * hit.barycentric_coords.y - // + normals[0] * hit.barycentric_coords.z - // } else { - // (tri_vertex_positions[1] - tri_vertex_positions[0]) - // .cross(tri_vertex_positions[2] - tri_vertex_positions[0]) - // .normalize() - // }; - - // closest_hit = Some(RayMeshHit { - // point: mesh_transform.transform_point3(hit.point), - // normal: mesh_transform.transform_vector3(hit_normal), - // barycentric_coords: hit.barycentric_coords, - // distance: mesh_transform - // .transform_vector3(mesh_space_ray.direction * hit.distance) - // .length(), - // triangle: Some([ - // mesh_transform.transform_point3(tri_vertex_positions[0]), - // mesh_transform.transform_point3(tri_vertex_positions[1]), - // mesh_transform.transform_point3(tri_vertex_positions[2]), - // ]), - // triangle_index, - // }); - // closest_hit_distance = hit.distance; - // } - } else { - for (i, triangle) in positions.chunks_exact(3).enumerate() { - let &[a, b, c] = triangle else { - continue; - }; - let triangle_index = Some(i); - let tri_vertex_positions = &[Vec3::from(a), Vec3::from(b), Vec3::from(c)]; - let tri_normals = vertex_normals.and_then(|normals| { - let Some([a, b, c]) = normals.get(i..(i + 2)) else { + let [Ok(a), Ok(b), Ok(c)] = [ + triangle[0].try_into(), + triangle[1].try_into(), + triangle[2].try_into(), + ] else { return None; }; - Some([Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]) - }); - - let Some(hit) = triangle_intersection( - tri_vertex_positions, - tri_normals.as_ref(), - closest_hit_distance, - &mesh_space_ray, - backface_culling, - ) else { - continue; - }; - let hit_normal = (tri_vertex_positions[1] - tri_vertex_positions[0]) - .cross(tri_vertex_positions[2] - tri_vertex_positions[0]) - .normalize(); - - closest_hit = Some(RayMeshHit { - point: mesh_transform.transform_point3(hit.point), - normal: mesh_transform.transform_vector3(hit_normal), - barycentric_coords: hit.barycentric_coords, - distance: mesh_transform - .transform_vector3(mesh_space_ray.direction * hit.distance) - .length(), - triangle: Some([ - mesh_transform.transform_point3(tri_vertex_positions[0]), - mesh_transform.transform_point3(tri_vertex_positions[1]), - mesh_transform.transform_point3(tri_vertex_positions[2]), - ]), - triangle_index, - }); - closest_hit_distance = hit.distance; - } - } - - closest_hit -} - -/// Hit data for an intersection between a ray and a mesh. -#[derive(Debug, Clone, Reflect)] -pub struct PartialRayMeshHit { - /// The point of intersection in world space. - pub point: Vec3, - /// The barycentric coordinates of the intersection. - pub barycentric_coords: Vec3, - /// The distance from the ray origin to the intersection point. - pub distance: f32, -} + [a, b, c] + } + None => [tri_idx * 3, tri_idx * 3 + 1, tri_idx * 3 + 2], + }; -fn triangle_intersection( - tri_vertices: &[Vec3; 3], - tri_normals: Option<&[Vec3; 3]>, - max_distance: f32, - ray: &Ray3d, - backface_culling: Backfaces, -) -> Option { - let hit = ray_triangle_intersection(ray, tri_vertices, backface_culling)?; + let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] { + [Some(c), Some(b), Some(a)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], + _ => return None, + }; - if hit.distance < 0.0 || hit.distance > max_distance { - return None; - }; - - let point = ray.get_point(hit.distance); - let u = hit.barycentric_coords.0; - let v = hit.barycentric_coords.1; - let w = 1.0 - u - v; - let barycentric = Vec3::new(u, v, w); - - // let normal = if let Some(normals) = tri_normals { - // normals[1] * u + normals[2] * v + normals[0] * w - // } else { - // (tri_vertices[1] - tri_vertices[0]) - // .cross(tri_vertices[2] - tri_vertices[0]) - // .normalize() - // }; - - Some(PartialRayMeshHit { - point, - barycentric_coords: barycentric, - distance: hit.distance, - // triangle: Some(*tri_vertices), - // triangle_index: None, + let tri_normals = vertex_normals.and_then(|normals| { + let [Some(a), Some(b), Some(c)] = [normals.get(a), normals.get(b), normals.get(c)] + else { + return None; + }; + Some([Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]) + }); + + let point = ray.get_point(hit.distance); + let u = hit.barycentric_coords.0; + let v = hit.barycentric_coords.1; + let w = 1.0 - u - v; + let barycentric = Vec3::new(u, v, w); + + let normal = if let Some(normals) = tri_normals { + normals[1] * u + normals[2] * v + normals[0] * w + } else { + (tri_vertices[1] - tri_vertices[0]) + .cross(tri_vertices[2] - tri_vertices[0]) + .normalize() + }; + + Some(RayMeshHit { + point, + normal, + barycentric_coords: barycentric, + distance: hit.distance, + triangle: Some(tri_vertices), + triangle_index: Some(a), + }) }) } /// Takes a ray and triangle and computes the intersection. +#[inline] fn ray_triangle_intersection( ray: &Ray3d, triangle: &[Vec3; 3], From 64881135a7868980a5cd5d67d300eda299c29a2e Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:49:08 +1100 Subject: [PATCH 07/11] fix mesh hit values, correctly unpack position indices, distance check --- .../mesh_picking/ray_cast/intersections.rs | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index 4633e5616f376..f08689320162f 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -85,31 +85,27 @@ pub fn ray_mesh_intersection + Clone + Copy>( return None; } - indices - .chunks_exact(3) - .enumerate() - .for_each(|(tri_idx, triangle)| { - let [Ok(a), Ok(b), Ok(c)] = [ - triangle[0].try_into(), - triangle[1].try_into(), - triangle[2].try_into(), - ] else { - return; - }; + indices.chunks_exact(3).for_each(|triangle| { + let [Ok(a), Ok(b), Ok(c)] = [ + triangle[0].try_into(), + triangle[1].try_into(), + triangle[2].try_into(), + ] else { + return; + }; - let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] { - [Some(c), Some(b), Some(a)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], - _ => return, - }; + let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] { + [Some(a), Some(b), Some(c)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], + _ => return, + }; - if let Some(hit) = ray_triangle_intersection(&ray, &tri_vertices, backface_culling) - { - if hit.distance > 0. && hit.distance < closest_hit_distance { - closest_hit_distance = hit.distance; - closest_hit = Some((tri_idx, hit)); - } + if let Some(hit) = ray_triangle_intersection(&ray, &tri_vertices, backface_culling) { + if hit.distance >= 0. && hit.distance < closest_hit_distance { + closest_hit_distance = hit.distance; + closest_hit = Some((a, hit)); } - }); + } + }); } else { positions .chunks_exact(3) @@ -123,7 +119,7 @@ pub fn ray_mesh_intersection + Clone + Copy>( if let Some(hit) = ray_triangle_intersection(&ray, &tri_vertices, backface_culling) { - if hit.distance > 0. && hit.distance < closest_hit_distance { + if hit.distance >= 0. && hit.distance < closest_hit_distance { closest_hit_distance = hit.distance; closest_hit = Some((tri_idx, hit)); } @@ -150,7 +146,7 @@ pub fn ray_mesh_intersection + Clone + Copy>( }; let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] { - [Some(c), Some(b), Some(a)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], + [Some(a), Some(b), Some(c)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], _ => return None, }; @@ -177,11 +173,13 @@ pub fn ray_mesh_intersection + Clone + Copy>( }; Some(RayMeshHit { - point, - normal, + point: mesh_transform.transform_point3(point), + normal: mesh_transform.transform_vector3(normal), barycentric_coords: barycentric, - distance: hit.distance, - triangle: Some(tri_vertices), + distance: mesh_transform + .transform_vector3(ray.direction * hit.distance) + .length(), + triangle: Some(tri_vertices.map(|v| mesh_transform.transform_point3(v))), triangle_index: Some(a), }) }) From fc3f737270c214c53d12b542abfa265855817217 Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:59:07 +1100 Subject: [PATCH 08/11] change for_each to fold --- .../mesh_picking/ray_cast/intersections.rs | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index f08689320162f..317310f48c48b 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -66,11 +66,6 @@ pub fn ray_mesh_intersection + Clone + Copy>( indices: Option<&[I]>, backface_culling: Backfaces, ) -> Option { - // The ray cast can hit the same mesh many times, so we need to track which hit is - // closest to the camera, and record that. - let mut closest_hit_distance = f32::MAX; - let mut closest_hit = None; - let world_to_mesh = mesh_transform.inverse(); let ray = Ray3d::new( @@ -78,54 +73,66 @@ pub fn ray_mesh_intersection + Clone + Copy>( Dir3::new(world_to_mesh.transform_vector3(*ray.direction)).ok()?, ); - if let Some(indices) = indices { + let closest_hit = if let Some(indices) = indices { // The index list must be a multiple of three. If not, the mesh is malformed and the raycast // result might be nonsensical. if indices.len() % 3 != 0 { return None; } - indices.chunks_exact(3).for_each(|triangle| { - let [Ok(a), Ok(b), Ok(c)] = [ - triangle[0].try_into(), - triangle[1].try_into(), - triangle[2].try_into(), - ] else { - return; - }; - - let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] { - [Some(a), Some(b), Some(c)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)], - _ => return, - }; - - if let Some(hit) = ray_triangle_intersection(&ray, &tri_vertices, backface_culling) { - if hit.distance >= 0. && hit.distance < closest_hit_distance { - closest_hit_distance = hit.distance; - closest_hit = Some((a, hit)); - } - } - }); + indices + .chunks_exact(3) + .fold( + (f32::MAX, None), + |(closest_distance, closest_hit), triangle| { + let [Ok(a), Ok(b), Ok(c)] = [ + triangle[0].try_into(), + triangle[1].try_into(), + triangle[2].try_into(), + ] else { + return (closest_distance, closest_hit); + }; + + let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] + { + [Some(a), Some(b), Some(c)] => { + [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)] + } + _ => return (closest_distance, closest_hit), + }; + + match ray_triangle_intersection(&ray, &tri_vertices, backface_culling) { + Some(hit) if hit.distance >= 0. && hit.distance < closest_distance => { + (hit.distance, Some((a, hit))) + } + _ => (closest_distance, closest_hit), + } + }, + ) + .1 } else { positions .chunks_exact(3) .enumerate() - .for_each(|(tri_idx, triangle)| { - let tri_vertices = [ - Vec3::from(triangle[0]), - Vec3::from(triangle[1]), - Vec3::from(triangle[2]), - ]; - - if let Some(hit) = ray_triangle_intersection(&ray, &tri_vertices, backface_culling) - { - if hit.distance >= 0. && hit.distance < closest_hit_distance { - closest_hit_distance = hit.distance; - closest_hit = Some((tri_idx, hit)); + .fold( + (f32::MAX, None), + |(closest_distance, closest_hit), (tri_idx, triangle)| { + let tri_vertices = [ + Vec3::from(triangle[0]), + Vec3::from(triangle[1]), + Vec3::from(triangle[2]), + ]; + + match ray_triangle_intersection(&ray, &tri_vertices, backface_culling) { + Some(hit) if hit.distance >= 0. && hit.distance < closest_distance => { + (hit.distance, Some((tri_idx, hit))) + } + _ => (closest_distance, closest_hit), } - } - }); - } + }, + ) + .1 + }; closest_hit.and_then(|(tri_idx, hit)| { let [a, b, c] = match indices { From 3dcb2cb33b7a71526a067ca03bf7469680a7bf46 Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Fri, 8 Nov 2024 16:43:46 +1100 Subject: [PATCH 09/11] add more ray_mesh_intersection benchmarks --- .../bevy_picking/ray_mesh_intersection.rs | 144 +++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/benches/benches/bevy_picking/ray_mesh_intersection.rs b/benches/benches/bevy_picking/ray_mesh_intersection.rs index 05e71e9771da0..15897f82ea9e3 100644 --- a/benches/benches/bevy_picking/ray_mesh_intersection.rs +++ b/benches/benches/bevy_picking/ray_mesh_intersection.rs @@ -1,5 +1,6 @@ -use bevy_math::{Dir3, Mat4, Ray3d, Vec3}; +use bevy_math::{Dir3, Mat4, Ray3d, Vec2, Vec3}; use bevy_picking::mesh_picking::ray_cast; +use bevy_render::mesh::{Indices, Mesh, MeshBuilder, PlaneMeshBuilder, VertexAttributeValues}; use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; fn ptoxznorm(p: u32, size: u32) -> (f32, f32) { @@ -178,6 +179,145 @@ fn ray_mesh_intersection_no_indices(c: &mut Criterion) { } } +fn ray_mesh_intersection_single_plane(c: &mut Criterion) { + let mut group = c.benchmark_group("ray_mesh_intersection_single_plane"); + group.warm_up_time(std::time::Duration::from_millis(500)); + + for subdivisions in [10_u32, 100, 1000] { + let ray = Ray3d::new(Vec3::new(0.01, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + let mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE) + .subdivisions(subdivisions) + .build(); + + let positions = mesh + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() + .as_float3() + .unwrap(); + + let normals = mesh + .attribute(Mesh::ATTRIBUTE_NORMAL) + .unwrap() + .as_float3() + .unwrap(); + + let indices = mesh.indices(); + + let tri_count = positions.len() as u64; + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions, {} indices)", + underscore_separate_number(tri_count), + underscore_separate_number(positions.len()), + underscore_separate_number(indices.unwrap().len() / 3) + ), + &(ray, mesh_to_world, positions, normals, indices), + |b, (ray, mesh_to_world, positions, normals, indices)| { + b.iter(|| { + black_box(ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + positions, + Some(normals), + match indices { + Some(Indices::U32(indices)) => Some(indices), + _ => None, + }, + ray_cast::Backfaces::Cull, + )); + }); + }, + ); + } +} + +fn ray_mesh_intersection_overlapping_planes(c: &mut Criterion) { + for (bench_group_name, pos_mul) in [ + ( + "ray_mesh_intersection_overlapping_planes_back_to_front", + 1usize, + ), + ( + "ray_mesh_intersection_overlapping_planes_front_to_back", + 0usize, + ), + ] { + let mut group = c.benchmark_group(bench_group_name); + group.warm_up_time(std::time::Duration::from_millis(500)); + + for planes in [10_u32, 1000, 100_000] { + let ray = Ray3d::new(Vec3::new(0.1, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + + let mut mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE).build(); + + let copy_mesh = mesh.clone(); + + for _ in 0..(planes - 1) { + mesh.merge(©_mesh); + } + + if let VertexAttributeValues::Float32x3(positions) = + mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap() + { + let pos_count = positions.len(); + // place each subsequent plane closer to the ray origin + positions.iter_mut().enumerate().for_each(|(i, pos)| { + pos[1] -= (pos_count * pos_mul - i) as f32; + }); + } else { + panic!("No positions"); + } + + let positions = mesh + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() + .as_float3() + .unwrap(); + + let normals = mesh + .attribute(Mesh::ATTRIBUTE_NORMAL) + .unwrap() + .as_float3() + .unwrap(); + + let indices = mesh.indices(); + + let tri_count = positions.len() as u64; + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions, {} indices)", + underscore_separate_number(tri_count), + underscore_separate_number(positions.len()), + underscore_separate_number(indices.unwrap().len() / 3) + ), + &(ray, mesh_to_world, positions, normals, indices), + |b, (ray, mesh_to_world, positions, normals, indices)| { + b.iter(|| { + black_box(ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + positions, + Some(normals), + match indices { + Some(Indices::U32(indices)) => Some(indices), + _ => None, + }, + ray_cast::Backfaces::Cull, + )); + }); + }, + ); + } + } +} + +// changes numbers from 1000000 to 1_000_000 fn underscore_separate_number(n: impl ToString) -> String { n.to_string() .as_bytes() @@ -195,5 +335,7 @@ criterion_group!( ray_mesh_intersection_no_cull, ray_mesh_intersection_no_intersection, ray_mesh_intersection_no_indices, + ray_mesh_intersection_single_plane, + ray_mesh_intersection_overlapping_planes, ); criterion_main!(benches); From 7a98739766c1db4da40a6e11567b914f2cced60d Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Fri, 8 Nov 2024 18:31:29 +1100 Subject: [PATCH 10/11] regine ray_mesh benchmarks --- .../bevy_picking/ray_mesh_intersection.rs | 148 ++++++++---------- 1 file changed, 63 insertions(+), 85 deletions(-) diff --git a/benches/benches/bevy_picking/ray_mesh_intersection.rs b/benches/benches/bevy_picking/ray_mesh_intersection.rs index 15897f82ea9e3..5e2fecc7bbba8 100644 --- a/benches/benches/bevy_picking/ray_mesh_intersection.rs +++ b/benches/benches/bevy_picking/ray_mesh_intersection.rs @@ -56,7 +56,7 @@ fn ray_mesh_intersection(c: &mut Criterion) { "{} triangles ({} positions, {} indices)", underscore_separate_number(tri_count), underscore_separate_number(mesh.positions.len()), - underscore_separate_number(mesh.indices.len() / 3) + underscore_separate_number(mesh.indices.len()) ), &(ray, mesh_to_world, mesh), |b, (ray, mesh_to_world, mesh)| { @@ -91,7 +91,7 @@ fn ray_mesh_intersection_no_cull(c: &mut Criterion) { "{} triangles ({} positions, {} indices)", underscore_separate_number(tri_count), underscore_separate_number(mesh.positions.len()), - underscore_separate_number(mesh.indices.len() / 3) + underscore_separate_number(mesh.indices.len()) ), &(ray, mesh_to_world, mesh), |b, (ray, mesh_to_world, mesh)| { @@ -126,7 +126,7 @@ fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { "{} triangles ({} positions, {} indices)", underscore_separate_number(tri_count), underscore_separate_number(mesh.positions.len()), - underscore_separate_number(mesh.indices.len() / 3) + underscore_separate_number(mesh.indices.len()) ), &(ray, mesh_to_world, mesh), |b, (ray, mesh_to_world, mesh)| { @@ -145,92 +145,69 @@ fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { } } -fn ray_mesh_intersection_no_indices(c: &mut Criterion) { - let mut group = c.benchmark_group("ray_mesh_intersection_no_indices"); - group.warm_up_time(std::time::Duration::from_millis(500)); - - for vertices_per_side in [10_u32, 100, 1000] { - let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); - let mesh_to_world = Mat4::IDENTITY; - let mesh = mesh_creation(vertices_per_side); - let tri_count = mesh.positions.len() as u64; - - group.throughput(Throughput::Elements(tri_count)); - group.bench_with_input( - format!( - "{} triangles ({} positions)", - underscore_separate_number(tri_count), - underscore_separate_number(mesh.positions.len()), - ), - &(ray, mesh_to_world, mesh), - |b, (ray, mesh_to_world, mesh)| { - b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( - *ray, - mesh_to_world, - &mesh.positions, - Some(&mesh.normals), - Option::<&[u32]>::None, - ray_cast::Backfaces::Cull, - )); - }); - }, - ); - } -} - fn ray_mesh_intersection_single_plane(c: &mut Criterion) { - let mut group = c.benchmark_group("ray_mesh_intersection_single_plane"); - group.warm_up_time(std::time::Duration::from_millis(500)); + for (benchmark_group_name, use_indices) in [ + ("ray_mesh_intersection_single_plane_indices", true), + ("ray_mesh_intersection_single_plane_no_indices", false), + ] { + let mut group = c.benchmark_group(benchmark_group_name); + group.warm_up_time(std::time::Duration::from_millis(500)); - for subdivisions in [10_u32, 100, 1000] { - let ray = Ray3d::new(Vec3::new(0.01, 1.0, 0.0), Dir3::NEG_Y); - let mesh_to_world = Mat4::IDENTITY; - let mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE) - .subdivisions(subdivisions) - .build(); + for subdivisions in [10_u32, 100, 1000] { + let ray = Ray3d::new(Vec3::new(0.01, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + let mut mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE) + .subdivisions(subdivisions) + .build(); - let positions = mesh - .attribute(Mesh::ATTRIBUTE_POSITION) - .unwrap() - .as_float3() - .unwrap(); + if !use_indices { + mesh.duplicate_vertices(); + } - let normals = mesh - .attribute(Mesh::ATTRIBUTE_NORMAL) - .unwrap() - .as_float3() - .unwrap(); + let positions = mesh + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() + .as_float3() + .unwrap(); - let indices = mesh.indices(); + let normals = mesh + .attribute(Mesh::ATTRIBUTE_NORMAL) + .unwrap() + .as_float3() + .unwrap(); - let tri_count = positions.len() as u64; + let indices = mesh.indices(); - group.throughput(Throughput::Elements(tri_count)); - group.bench_with_input( - format!( - "{} triangles ({} positions, {} indices)", - underscore_separate_number(tri_count), - underscore_separate_number(positions.len()), - underscore_separate_number(indices.unwrap().len() / 3) - ), - &(ray, mesh_to_world, positions, normals, indices), - |b, (ray, mesh_to_world, positions, normals, indices)| { - b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( - *ray, - mesh_to_world, - positions, - Some(normals), - match indices { - Some(Indices::U32(indices)) => Some(indices), - _ => None, - }, - ray_cast::Backfaces::Cull, - )); - }); - }, - ); + let tri_count = indices + .map(|i| i.len() as u64 / 3) + .unwrap_or(positions.len() as u64 / 3); + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions, {} indices)", + underscore_separate_number(tri_count), + underscore_separate_number(positions.len()), + underscore_separate_number(indices.map(|i| i.len()).unwrap_or_default()) + ), + &(ray, mesh_to_world, positions, normals, indices), + |b, (ray, mesh_to_world, positions, normals, indices)| { + b.iter(|| { + black_box(ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + positions, + Some(normals), + match indices { + Some(Indices::U32(indices)) => Some(indices), + _ => None, + }, + ray_cast::Backfaces::Cull, + )); + }); + }, + ); + } } } @@ -286,7 +263,9 @@ fn ray_mesh_intersection_overlapping_planes(c: &mut Criterion) { let indices = mesh.indices(); - let tri_count = positions.len() as u64; + let tri_count = indices + .map(|i| i.len() as u64 / 3) + .unwrap_or(positions.len() as u64 / 3); group.throughput(Throughput::Elements(tri_count)); group.bench_with_input( @@ -294,7 +273,7 @@ fn ray_mesh_intersection_overlapping_planes(c: &mut Criterion) { "{} triangles ({} positions, {} indices)", underscore_separate_number(tri_count), underscore_separate_number(positions.len()), - underscore_separate_number(indices.unwrap().len() / 3) + underscore_separate_number(indices.unwrap().len()) ), &(ray, mesh_to_world, positions, normals, indices), |b, (ray, mesh_to_world, positions, normals, indices)| { @@ -334,7 +313,6 @@ criterion_group!( ray_mesh_intersection, ray_mesh_intersection_no_cull, ray_mesh_intersection_no_intersection, - ray_mesh_intersection_no_indices, ray_mesh_intersection_single_plane, ray_mesh_intersection_overlapping_planes, ); From e3940a0d37ddd2926f4d623f9da9a36040dd0bcc Mon Sep 17 00:00:00 2001 From: Trent <2771466+tbillington@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:06:05 +1100 Subject: [PATCH 11/11] integrate picking benchmark overhaul changes from #17033 --- .../bevy_picking/ray_mesh_intersection.rs | 640 +++++++++++++----- 1 file changed, 454 insertions(+), 186 deletions(-) diff --git a/benches/benches/bevy_picking/ray_mesh_intersection.rs b/benches/benches/bevy_picking/ray_mesh_intersection.rs index 76f206dbbc93a..211f3cd5e4f63 100644 --- a/benches/benches/bevy_picking/ray_mesh_intersection.rs +++ b/benches/benches/bevy_picking/ray_mesh_intersection.rs @@ -1,32 +1,57 @@ +use core::hint::black_box; +use std::time::Duration; + +use benches::bench; use bevy_math::{Dir3, Mat4, Ray3d, Vec2, Vec3}; -use bevy_picking::mesh_picking::ray_cast; +use bevy_picking::mesh_picking::ray_cast::{self, Backfaces}; use bevy_render::mesh::{Indices, Mesh, MeshBuilder, PlaneMeshBuilder, VertexAttributeValues}; -use criterion::{black_box, criterion_group, Criterion, Throughput}; +use criterion::{ + criterion_group, measurement::WallTime, AxisScale, BenchmarkGroup, BenchmarkId, Criterion, + PlotConfiguration, Throughput, +}; -fn ptoxznorm(p: u32, size: u32) -> (f32, f32) { - let ij = (p / (size), p % (size)); - (ij.0 as f32 / size as f32, ij.1 as f32 / size as f32) -} +criterion_group!(benches, bench); +/// A mesh that can be passed to [`ray_cast::ray_mesh_intersection()`]. struct SimpleMesh { positions: Vec<[f32; 3]>, normals: Vec<[f32; 3]>, indices: Vec, } -fn mesh_creation(vertices_per_side: u32) -> SimpleMesh { +/// Selects a point within a normal square. +/// +/// `p` is an index within `0..vertices_per_side.pow(2)`. The returned value is a coordinate where +/// both `x` and `z` are within `0..1`. +fn p_to_xz_norm(p: u32, vertices_per_side: u32) -> (f32, f32) { + let x = (p / vertices_per_side) as f32; + let z = (p % vertices_per_side) as f32; + + let vertices_per_side = vertices_per_side as f32; + + // Scale `x` and `z` to be between 0 and 1. + (x / vertices_per_side, z / vertices_per_side) +} + +fn create_mesh(vertices_per_side: u32) -> SimpleMesh { let mut positions = Vec::new(); let mut normals = Vec::new(); + let mut indices = Vec::new(); + for p in 0..vertices_per_side.pow(2) { - let xz = ptoxznorm(p, vertices_per_side); - positions.push([xz.0 - 0.5, 0.0, xz.1 - 0.5]); + let (x, z) = p_to_xz_norm(p, vertices_per_side); + + // Push a new vertice to the mesh. We translate all vertices so the final square is + // centered at (0, 0), instead of (0.5, 0.5). + positions.push([x - 0.5, 0.0, z - 0.5]); + + // All vertices have the same normal. normals.push([0.0, 1.0, 0.0]); - } - let mut indices = vec![]; - for p in 0..vertices_per_side.pow(2) { - if p % (vertices_per_side) != vertices_per_side - 1 - && p / (vertices_per_side) != vertices_per_side - 1 + // Extend the indices for for all vertices except for the final row and column, since + // indices are "between" points. + if p % vertices_per_side != vertices_per_side - 1 + && p / vertices_per_side != vertices_per_side - 1 { indices.extend_from_slice(&[p, p + 1, p + vertices_per_side]); indices.extend_from_slice(&[p + vertices_per_side, p + 1, p + vertices_per_side + 1]); @@ -40,14 +65,11 @@ fn mesh_creation(vertices_per_side: u32) -> SimpleMesh { } } -fn ray_mesh_intersection(c: &mut Criterion) { - let mut group = c.benchmark_group("ray_mesh_intersection"); - group.warm_up_time(std::time::Duration::from_millis(500)); - +fn cull_intersect(mut group: BenchmarkGroup<'_, WallTime>, should_intersect: bool) { for vertices_per_side in [10_u32, 100, 1000] { let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); let mesh_to_world = Mat4::IDENTITY; - let mesh = mesh_creation(vertices_per_side); + let mesh = create_mesh(vertices_per_side); let tri_count = (mesh.indices.len() / 3) as u64; group.throughput(Throughput::Elements(tri_count)); @@ -61,28 +83,30 @@ fn ray_mesh_intersection(c: &mut Criterion) { &(ray, mesh_to_world, mesh), |b, (ray, mesh_to_world, mesh)| { b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( + let intersection = ray_cast::ray_mesh_intersection( *ray, mesh_to_world, &mesh.positions, Some(&mesh.normals), Some(&mesh.indices), - ray_cast::Backfaces::Cull, - )); + Backfaces::Cull, + ); + + #[cfg(test)] + assert_eq!(intersection.is_some(), should_intersect); + + intersection }); }, ); } } -fn ray_mesh_intersection_no_cull(c: &mut Criterion) { - let mut group = c.benchmark_group("ray_mesh_intersection_no_cull"); - group.warm_up_time(std::time::Duration::from_millis(500)); - +fn no_cull_intersect(mut group: BenchmarkGroup<'_, WallTime>, should_intersect: bool) { for vertices_per_side in [10_u32, 100, 1000] { let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); let mesh_to_world = Mat4::IDENTITY; - let mesh = mesh_creation(vertices_per_side); + let mesh = create_mesh(vertices_per_side); let tri_count = (mesh.indices.len() / 3) as u64; group.throughput(Throughput::Elements(tri_count)); @@ -96,28 +120,30 @@ fn ray_mesh_intersection_no_cull(c: &mut Criterion) { &(ray, mesh_to_world, mesh), |b, (ray, mesh_to_world, mesh)| { b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( + let intersection = ray_cast::ray_mesh_intersection( *ray, mesh_to_world, &mesh.positions, Some(&mesh.normals), Some(&mesh.indices), - ray_cast::Backfaces::Include, - )); + Backfaces::Include, + ); + + #[cfg(test)] + assert_eq!(intersection.is_some(), should_intersect); + + intersection }); }, ); } } -fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { - let mut group = c.benchmark_group("ray_mesh_intersection_no_intersection"); - group.warm_up_time(std::time::Duration::from_millis(500)); - +fn cull_no_intersect(mut group: BenchmarkGroup<'_, WallTime>, should_intersect: bool) { for vertices_per_side in [10_u32, 100, 1000] { - let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); + let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::X); let mesh_to_world = Mat4::IDENTITY; - let mesh = mesh_creation(vertices_per_side); + let mesh = create_mesh(vertices_per_side); let tri_count = (mesh.indices.len() / 3) as u64; group.throughput(Throughput::Elements(tri_count)); @@ -131,172 +157,263 @@ fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { &(ray, mesh_to_world, mesh), |b, (ray, mesh_to_world, mesh)| { b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( + let intersection = ray_cast::ray_mesh_intersection( *ray, mesh_to_world, &mesh.positions, Some(&mesh.normals), Some(&mesh.indices), - ray_cast::Backfaces::Cull, - )); + Backfaces::Cull, + ); + + #[cfg(test)] + assert_eq!(intersection.is_some(), should_intersect); + + intersection }); }, ); } } -fn ray_mesh_intersection_single_plane(c: &mut Criterion) { - for (benchmark_group_name, use_indices) in [ - ("ray_mesh_intersection_single_plane_indices", true), - ("ray_mesh_intersection_single_plane_no_indices", false), - ] { - let mut group = c.benchmark_group(benchmark_group_name); - group.warm_up_time(std::time::Duration::from_millis(500)); - - for subdivisions in [10_u32, 100, 1000] { - let ray = Ray3d::new(Vec3::new(0.01, 1.0, 0.0), Dir3::NEG_Y); - let mesh_to_world = Mat4::IDENTITY; - let mut mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE) - .subdivisions(subdivisions) - .build(); - - if !use_indices { - mesh.duplicate_vertices(); - } - - let positions = mesh - .attribute(Mesh::ATTRIBUTE_POSITION) - .unwrap() - .as_float3() - .unwrap(); - - let normals = mesh - .attribute(Mesh::ATTRIBUTE_NORMAL) - .unwrap() - .as_float3() - .unwrap(); - - let indices = mesh.indices(); - - let tri_count = indices - .map(|i| i.len() as u64 / 3) - .unwrap_or(positions.len() as u64 / 3); - - group.throughput(Throughput::Elements(tri_count)); - group.bench_with_input( - format!( - "{} triangles ({} positions, {} indices)", - underscore_separate_number(tri_count), - underscore_separate_number(positions.len()), - underscore_separate_number(indices.map(|i| i.len()).unwrap_or_default()) - ), - &(ray, mesh_to_world, positions, normals, indices), - |b, (ray, mesh_to_world, positions, normals, indices)| { - b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( - *ray, - mesh_to_world, - positions, - Some(normals), - match indices { - Some(Indices::U32(indices)) => Some(indices), - _ => None, - }, - ray_cast::Backfaces::Cull, - )); - }); - }, - ); +enum IntersectIndices { + Include, + Skip, +} + +fn single_plane( + mut group: BenchmarkGroup<'_, WallTime>, + should_intersect: bool, + use_indices: IntersectIndices, +) { + for subdivisions in [10_u32, 100, 1000] { + let ray = Ray3d::new(Vec3::new(0.01, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; + let mut mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE) + .subdivisions(subdivisions) + .build(); + + if matches!(use_indices, IntersectIndices::Skip) { + // duplicate_mesh consumes the indices + mesh.duplicate_vertices(); } + + let positions = mesh + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() + .as_float3() + .unwrap(); + + let normals = mesh + .attribute(Mesh::ATTRIBUTE_NORMAL) + .unwrap() + .as_float3() + .unwrap(); + + let indices = mesh.indices(); + + let tri_count = indices + .map(|i| i.len() as u64 / 3) + .unwrap_or(positions.len() as u64 / 3); + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions, {} indices)", + underscore_separate_number(tri_count), + underscore_separate_number(positions.len()), + underscore_separate_number(indices.map(Indices::len).unwrap_or_default()) + ), + &(ray, mesh_to_world, positions, normals, indices), + |b, (ray, mesh_to_world, positions, normals, indices)| { + b.iter(|| { + let intersection = ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + positions, + Some(normals), + match indices { + Some(Indices::U32(indices)) => Some(indices), + _ => None, + }, + Backfaces::Cull, + ); + + #[cfg(test)] + assert_eq!(intersection.is_some(), should_intersect); + + intersection + }); + }, + ); } } -fn ray_mesh_intersection_overlapping_planes(c: &mut Criterion) { - for (bench_group_name, pos_mul) in [ - ( - "ray_mesh_intersection_overlapping_planes_back_to_front", - 1usize, - ), - ( - "ray_mesh_intersection_overlapping_planes_front_to_back", - 0usize, - ), - ] { - let mut group = c.benchmark_group(bench_group_name); - group.warm_up_time(std::time::Duration::from_millis(500)); - - for planes in [10_u32, 1000, 100_000] { - let ray = Ray3d::new(Vec3::new(0.1, 1.0, 0.0), Dir3::NEG_Y); - let mesh_to_world = Mat4::IDENTITY; - - let mut mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE).build(); - - let copy_mesh = mesh.clone(); - - for _ in 0..(planes - 1) { - mesh.merge(©_mesh); - } - - if let VertexAttributeValues::Float32x3(positions) = - mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap() - { - let pos_count = positions.len(); - // place each subsequent plane closer to the ray origin - positions.iter_mut().enumerate().for_each(|(i, pos)| { - pos[1] -= (pos_count * pos_mul - i) as f32; - }); - } else { - panic!("No positions"); - } +enum OverlappingPlaneOrdering { + BackToFront, + FrontToBack, +} + +fn overlapping_planes( + mut group: BenchmarkGroup<'_, WallTime>, + should_intersect: bool, + plane_ordering: OverlappingPlaneOrdering, +) { + let pos_mul = match plane_ordering { + OverlappingPlaneOrdering::BackToFront => 1.0, + OverlappingPlaneOrdering::FrontToBack => -1.0, + }; + + for planes in [10_u32, 1000, 100_000] { + let ray = Ray3d::new(Vec3::new(0.1, 1.0, 0.0), Dir3::NEG_Y); + let mesh_to_world = Mat4::IDENTITY; - let positions = mesh - .attribute(Mesh::ATTRIBUTE_POSITION) - .unwrap() - .as_float3() - .unwrap(); + // let mut mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE).build(); - let normals = mesh - .attribute(Mesh::ATTRIBUTE_NORMAL) - .unwrap() - .as_float3() - .unwrap(); + // let copy_mesh = mesh.clone(); - let indices = mesh.indices(); + // Generate a mesh of many planes with subsequent plane being further from ray origin + // or closer depending on plane_ordering + let mesh = (0..planes) + .map(|i| { + let mut plane_mesh = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE).build(); - let tri_count = indices - .map(|i| i.len() as u64 / 3) - .unwrap_or(positions.len() as u64 / 3); + if let VertexAttributeValues::Float32x3(positions) = + plane_mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap() + { + positions.iter_mut().for_each(|vert| { + let plane_count = planes as f32; + let plane_height = -plane_count + pos_mul * i as f32; - group.throughput(Throughput::Elements(tri_count)); - group.bench_with_input( - format!( - "{} triangles ({} positions, {} indices)", - underscore_separate_number(tri_count), - underscore_separate_number(positions.len()), - underscore_separate_number(indices.unwrap().len()) - ), - &(ray, mesh_to_world, positions, normals, indices), - |b, (ray, mesh_to_world, positions, normals, indices)| { - b.iter(|| { - black_box(ray_cast::ray_mesh_intersection( - *ray, - mesh_to_world, - positions, - Some(normals), - match indices { - Some(Indices::U32(indices)) => Some(indices), - _ => None, - }, - ray_cast::Backfaces::Cull, - )); + vert[1] = plane_height; }); - }, - ); - } + + // positions.iter_mut().enumerate().for_each(|(i, pos)| { + // pos[1] -= (planes as usize + planes as usize * pos_mul - i / 4) as f32; + // }); + } else { + panic!("No positions"); + } + + plane_mesh + }) + .reduce(|mut acc, next_mesh| { + acc.merge(&next_mesh); + acc + }) + .unwrap(); + + // for i in 1..(planes) { + // let mut next_plane = PlaneMeshBuilder::new(Dir3::Y, Vec2::ONE).build(); + + // if let VertexAttributeValues::Float32x3(positions) = + // next_plane.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap() + // { + // positions.iter_mut().for_each(|vert| { + // let plane_count = planes as f32; + // let plane_height = -plane_count + pos_mul * i as f32; + + // vert[1] = plane_height; + // }); + + // // positions.iter_mut().enumerate().for_each(|(i, pos)| { + // // pos[1] -= (planes as usize + planes as usize * pos_mul - i / 4) as f32; + // // }); + // } else { + // panic!("No positions"); + // } + + // mesh.merge(&next_plane); + // } + + // for _ in 0..(planes - 1) { + // mesh.merge(©_mesh); + // } + + // if let VertexAttributeValues::Float32x3(positions) = + // mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap() + // { + // println!("{}", positions.len()); + // // place each subsequent plane closer to the ray origin + + // const CHUNK_SIZE: usize = 4; // the number of vertices of a plane with 0 subdivisions + // positions + // .chunks_exact_mut(CHUNK_SIZE) + // .enumerate() + // // .take(1) + // .for_each(|(i, vertices)| { + // let plane_count = planes as f32; + // let plane_height = -plane_count + pos_mul * i as f32; + // // println!("{i} {plane_height}"); + + // // println!("{:?}", vertices); + + // for v in vertices.iter_mut() { + // v[1] = 0.1; + // } + + // // println!("{:?}", vertices); + // }); + + // // positions.iter_mut().enumerate().for_each(|(i, pos)| { + // // pos[1] -= (planes as usize + planes as usize * pos_mul - i / 4) as f32; + // // }); + // } else { + // panic!("No positions"); + // } + + let positions = mesh + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() + .as_float3() + .unwrap(); + + let normals = mesh + .attribute(Mesh::ATTRIBUTE_NORMAL) + .unwrap() + .as_float3() + .unwrap(); + + let indices = mesh.indices(); + + let tri_count = indices + .map(|i| i.len() as u64 / 3) + .unwrap_or(positions.len() as u64 / 3); + + group.throughput(Throughput::Elements(tri_count)); + group.bench_with_input( + format!( + "{} triangles ({} positions, {} indices)", + underscore_separate_number(tri_count), + underscore_separate_number(positions.len()), + underscore_separate_number(indices.unwrap().len()) + ), + &(ray, mesh_to_world, positions, normals, indices), + |b, (ray, mesh_to_world, positions, normals, indices)| { + b.iter(|| { + let intersection = ray_cast::ray_mesh_intersection( + *ray, + mesh_to_world, + positions, + Some(normals), + match indices { + Some(Indices::U32(indices)) => Some(indices), + _ => None, + }, + Backfaces::Cull, + ); + + #[cfg(test)] + assert_eq!(intersection.is_some(), should_intersect); + + intersection + }); + }, + ); } } -// changes numbers from 1000000 to 1_000_000 +// formats numbers with separators, eg 1_000_000 fn underscore_separate_number(n: impl ToString) -> String { n.to_string() .as_bytes() @@ -308,11 +425,162 @@ fn underscore_separate_number(n: impl ToString) -> String { .join("_") } -criterion_group!( - benches, - ray_mesh_intersection, - ray_mesh_intersection_no_cull, - ray_mesh_intersection_no_intersection, - ray_mesh_intersection_single_plane, - ray_mesh_intersection_overlapping_planes, -); +// criterion_group!( +// benches, +// ray_mesh_intersection, +// ray_mesh_intersection_no_cull, +// ray_mesh_intersection_no_intersection, +// ray_mesh_intersection_single_plane, +// ray_mesh_intersection_overlapping_planes, +// ); + +/// An enum that represents the configuration for all variations of the ray mesh intersection +/// benchmarks. +enum Benchmarks { + /// The ray intersects the mesh, and culling is enabled. + CullHit, + + /// The ray intersects the mesh, and culling is disabled. + NoCullHit, + + /// The ray does not intersect the mesh, and culling is enabled. + CullMiss, +} + +impl Benchmarks { + const WARM_UP_TIME: Duration = Duration::from_millis(500); + const VERTICES_PER_SIDE: [u32; 3] = [10, 100, 1000]; + + /// Returns an iterator over every variant in this enum. + fn iter() -> impl Iterator { + [Self::CullHit, Self::NoCullHit, Self::CullMiss].into_iter() + } + + /// Returns the benchmark group name. + fn name(&self) -> &'static str { + match *self { + Self::CullHit => bench!("cull_intersect"), + Self::NoCullHit => bench!("no_cull_intersect"), + Self::CullMiss => bench!("cull_no_intersect"), + } + } + + fn ray(&self) -> Ray3d { + Ray3d::new( + Vec3::new(0.0, 1.0, 0.0), + match *self { + Self::CullHit | Self::NoCullHit => Dir3::NEG_Y, + // `NoIntersection` should not hit the mesh, so it goes an orthogonal direction. + Self::CullMiss => Dir3::X, + }, + ) + } + + fn mesh_to_world(&self) -> Mat4 { + Mat4::IDENTITY + } + + fn backface_culling(&self) -> Backfaces { + match *self { + Self::CullHit | Self::CullMiss => Backfaces::Cull, + Self::NoCullHit => Backfaces::Include, + } + } + + /// Returns whether the ray should intersect with the mesh. + #[cfg(test)] + fn should_intersect(&self) -> bool { + match *self { + Self::CullHit | Self::NoCullHit => true, + Self::CullMiss => false, + } + } +} + +/// A benchmark that times [`ray_cast::ray_mesh_intersection()`]. +/// +/// There are multiple different scenarios that are tracked, which are described by the +/// [`Benchmarks`] enum. Each scenario has its own benchmark group, where individual benchmarks +/// track a ray intersecting a square mesh of an increasing amount of vertices. +fn bench_orig(c: &mut Criterion) { + for benchmark in Benchmarks::iter() { + let mut group = c.benchmark_group(benchmark.name()); + + group + .warm_up_time(Benchmarks::WARM_UP_TIME) + // Make the scale logarithmic, to match `VERTICES_PER_SIDE`. + .plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); + + for vertices_per_side in Benchmarks::VERTICES_PER_SIDE { + group.bench_with_input( + BenchmarkId::from_parameter(format!("{}_vertices", vertices_per_side.pow(2))), + &vertices_per_side, + |b, &vertices_per_side| { + let ray = black_box(benchmark.ray()); + let mesh_to_world = black_box(benchmark.mesh_to_world()); + let mesh = black_box(create_mesh(vertices_per_side)); + let backface_culling = black_box(benchmark.backface_culling()); + + b.iter(|| { + let intersected = ray_cast::ray_mesh_intersection( + ray, + &mesh_to_world, + &mesh.positions, + Some(&mesh.normals), + Some(&mesh.indices), + backface_culling, + ); + + #[cfg(test)] + assert_eq!(intersected.is_some(), benchmark.should_intersect()); + + intersected + }); + }, + ); + } + } +} + +macro_rules! bench_group { + ($c: expr, $name:literal) => {{ + let mut group = $c.benchmark_group(bench!($name)); + + group + .warm_up_time(Benchmarks::WARM_UP_TIME) + // Make the scale logarithmic, to match `VERTICES_PER_SIDE`. + .plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); + + group + }}; +} + +fn bench(c: &mut Criterion) { + cull_intersect(bench_group!(c, "cull_intersect"), true); + + no_cull_intersect(bench_group!(c, "no_cull_intersect"), true); + + cull_no_intersect(bench_group!(c, "cull_no_intersect"), false); + + single_plane( + bench_group!(c, "single_plane_indices"), + true, + IntersectIndices::Include, + ); + single_plane( + bench_group!(c, "single_plane_no_indices"), + true, + IntersectIndices::Skip, + ); + + overlapping_planes( + bench_group!(c, "overlapping_planes_back_to_front"), + true, + OverlappingPlaneOrdering::BackToFront, + ); + overlapping_planes( + bench_group!(c, "overlapping_planes_front_to_back"), + true, + OverlappingPlaneOrdering::FrontToBack, + ); +}