From a3d406dd497205253e34ace757ab0076d50eec14 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 24 Jun 2025 11:10:59 -0700 Subject: [PATCH] Upstream raycasting UVs (#19791) # Objective - Upstream mesh raycast UV support used in #19199 ## Solution - Compute UVs, debug a bunch of math issues with barycentric coordinates and add docs. ## Testing - Tested in diagetic UI in the linked PR. --- .../bevy_picking/ray_mesh_intersection.rs | 1 + .../mesh_picking/ray_cast/intersections.rs | 82 ++++++++++++++----- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/benches/benches/bevy_picking/ray_mesh_intersection.rs b/benches/benches/bevy_picking/ray_mesh_intersection.rs index 871a6d1062..e9fd0caf9f 100644 --- a/benches/benches/bevy_picking/ray_mesh_intersection.rs +++ b/benches/benches/bevy_picking/ray_mesh_intersection.rs @@ -155,6 +155,7 @@ fn bench(c: &mut Criterion) { &mesh.positions, Some(&mesh.normals), Some(&mesh.indices), + None, backface_culling, ); 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 5ac2d89887..d521fe1213 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -1,5 +1,5 @@ -use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec3, Vec3A}; -use bevy_mesh::{Indices, Mesh, PrimitiveTopology}; +use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec2, Vec3, Vec3A}; +use bevy_mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues}; use bevy_reflect::Reflect; use super::Backfaces; @@ -18,6 +18,8 @@ pub struct RayMeshHit { pub distance: f32, /// The vertices of the triangle that was hit. pub triangle: Option<[Vec3; 3]>, + /// UV coordinate of the hit, if the mesh has UV attributes. + pub uv: Option, /// The index of the triangle that was hit. pub triangle_index: Option, } @@ -26,6 +28,10 @@ pub struct RayMeshHit { #[derive(Default, Debug)] pub struct RayTriangleHit { pub distance: f32, + /// Note this uses the convention from the Moller-Trumbore algorithm: + /// P = (1 - u - v)A + uB + vC + /// This is different from the more common convention of + /// P = uA + vB + (1 - u - v)C pub barycentric_coords: (f32, f32), } @@ -34,7 +40,7 @@ pub(super) fn ray_intersection_over_mesh( mesh: &Mesh, transform: &Mat4, ray: Ray3d, - culling: Backfaces, + cull: Backfaces, ) -> Option { if mesh.primitive_topology() != PrimitiveTopology::TriangleList { return None; // ray_mesh_intersection assumes vertices are laid out in a triangle list @@ -47,26 +53,37 @@ pub(super) fn ray_intersection_over_mesh( .attribute(Mesh::ATTRIBUTE_NORMAL) .and_then(|normal_values| normal_values.as_float3()); + let uvs = mesh + .attribute(Mesh::ATTRIBUTE_UV_0) + .and_then(|uvs| match uvs { + VertexAttributeValues::Float32x2(uvs) => Some(uvs.as_slice()), + _ => None, + }); + match mesh.indices() { Some(Indices::U16(indices)) => { - ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling) + ray_mesh_intersection(ray, transform, positions, normals, Some(indices), uvs, cull) } Some(Indices::U32(indices)) => { - ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling) + ray_mesh_intersection(ray, transform, positions, normals, Some(indices), uvs, cull) } - None => ray_mesh_intersection::(ray, transform, positions, normals, None, culling), + None => ray_mesh_intersection::(ray, transform, positions, normals, None, uvs, cull), } } /// Checks if a ray intersects a mesh, and returns the nearest intersection if one exists. -pub fn ray_mesh_intersection + Clone + Copy>( +pub fn ray_mesh_intersection( ray: Ray3d, mesh_transform: &Mat4, positions: &[[f32; 3]], vertex_normals: Option<&[[f32; 3]]>, indices: Option<&[I]>, + uvs: Option<&[[f32; 2]]>, backface_culling: Backfaces, -) -> Option { +) -> Option +where + I: TryInto + Clone + Copy, +{ let world_to_mesh = mesh_transform.inverse(); let ray = Ray3d::new( @@ -139,17 +156,12 @@ pub fn ray_mesh_intersection + Clone + Copy>( 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))?; - - let [Ok(a), Ok(b), Ok(c)] = [ - triangle[0].try_into(), - triangle[1].try_into(), - triangle[2].try_into(), - ] else { - return None; - }; - - [a, b, c] + let [i, j, k] = [tri_idx * 3, tri_idx * 3 + 1, tri_idx * 3 + 2]; + [ + indices.get(i).copied()?.try_into().ok()?, + indices.get(j).copied()?.try_into().ok()?, + indices.get(k).copied()?.try_into().ok()?, + ] } None => [tri_idx * 3, tri_idx * 3 + 1, tri_idx * 3 + 2], }; @@ -168,10 +180,12 @@ pub fn ray_mesh_intersection + Clone + Copy>( }); let point = ray.get_point(hit.distance); + // Note that we need to convert from the Möller-Trumbore convention to the more common + // P = uA + vB + (1 - u - v)C convention. 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 barycentric = Vec3::new(w, u, v); let normal = if let Some(normals) = tri_normals { normals[1] * u + normals[2] * v + normals[0] * w @@ -181,9 +195,29 @@ pub fn ray_mesh_intersection + Clone + Copy>( .normalize() }; + let uv = uvs.and_then(|uvs| { + let tri_uvs = if let Some(indices) = indices { + let i = tri_idx * 3; + [ + uvs[indices[i].try_into().ok()?], + uvs[indices[i + 1].try_into().ok()?], + uvs[indices[i + 2].try_into().ok()?], + ] + } else { + let i = tri_idx * 3; + [uvs[i], uvs[i + 1], uvs[i + 2]] + }; + Some( + barycentric.x * Vec2::from(tri_uvs[0]) + + barycentric.y * Vec2::from(tri_uvs[1]) + + barycentric.z * Vec2::from(tri_uvs[2]), + ) + }); + Some(RayMeshHit { point: mesh_transform.transform_point3(point), normal: mesh_transform.transform_vector3(normal), + uv, barycentric_coords: barycentric, distance: mesh_transform .transform_vector3(ray.direction * hit.distance) @@ -329,6 +363,7 @@ mod tests { positions, vertex_normals, indices, + None, backface_culling, ); @@ -350,6 +385,7 @@ mod tests { positions, vertex_normals, indices, + None, backface_culling, ); @@ -372,6 +408,7 @@ mod tests { positions, vertex_normals, indices, + None, backface_culling, ); @@ -394,6 +431,7 @@ mod tests { positions, vertex_normals, indices, + None, backface_culling, ); @@ -415,6 +453,7 @@ mod tests { positions, vertex_normals, indices, + None, backface_culling, ); @@ -436,6 +475,7 @@ mod tests { positions, vertex_normals, indices, + None, backface_culling, ); @@ -457,6 +497,7 @@ mod tests { positions, vertex_normals, indices, + None, backface_culling, ); @@ -478,6 +519,7 @@ mod tests { positions, vertex_normals, indices, + None, backface_culling, );