Skip to content

Commit a3d406d

Browse files
authored
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.
1 parent bd4258b commit a3d406d

File tree

2 files changed

+63
-20
lines changed

2 files changed

+63
-20
lines changed

benches/benches/bevy_picking/ray_mesh_intersection.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ fn bench(c: &mut Criterion) {
155155
&mesh.positions,
156156
Some(&mesh.normals),
157157
Some(&mesh.indices),
158+
None,
158159
backface_culling,
159160
);
160161

crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec3, Vec3A};
2-
use bevy_mesh::{Indices, Mesh, PrimitiveTopology};
1+
use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec2, Vec3, Vec3A};
2+
use bevy_mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
33
use bevy_reflect::Reflect;
44

55
use super::Backfaces;
@@ -18,6 +18,8 @@ pub struct RayMeshHit {
1818
pub distance: f32,
1919
/// The vertices of the triangle that was hit.
2020
pub triangle: Option<[Vec3; 3]>,
21+
/// UV coordinate of the hit, if the mesh has UV attributes.
22+
pub uv: Option<Vec2>,
2123
/// The index of the triangle that was hit.
2224
pub triangle_index: Option<usize>,
2325
}
@@ -26,6 +28,10 @@ pub struct RayMeshHit {
2628
#[derive(Default, Debug)]
2729
pub struct RayTriangleHit {
2830
pub distance: f32,
31+
/// Note this uses the convention from the Moller-Trumbore algorithm:
32+
/// P = (1 - u - v)A + uB + vC
33+
/// This is different from the more common convention of
34+
/// P = uA + vB + (1 - u - v)C
2935
pub barycentric_coords: (f32, f32),
3036
}
3137

@@ -34,7 +40,7 @@ pub(super) fn ray_intersection_over_mesh(
3440
mesh: &Mesh,
3541
transform: &Mat4,
3642
ray: Ray3d,
37-
culling: Backfaces,
43+
cull: Backfaces,
3844
) -> Option<RayMeshHit> {
3945
if mesh.primitive_topology() != PrimitiveTopology::TriangleList {
4046
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(
4753
.attribute(Mesh::ATTRIBUTE_NORMAL)
4854
.and_then(|normal_values| normal_values.as_float3());
4955

56+
let uvs = mesh
57+
.attribute(Mesh::ATTRIBUTE_UV_0)
58+
.and_then(|uvs| match uvs {
59+
VertexAttributeValues::Float32x2(uvs) => Some(uvs.as_slice()),
60+
_ => None,
61+
});
62+
5063
match mesh.indices() {
5164
Some(Indices::U16(indices)) => {
52-
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling)
65+
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), uvs, cull)
5366
}
5467
Some(Indices::U32(indices)) => {
55-
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling)
68+
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), uvs, cull)
5669
}
57-
None => ray_mesh_intersection::<usize>(ray, transform, positions, normals, None, culling),
70+
None => ray_mesh_intersection::<usize>(ray, transform, positions, normals, None, uvs, cull),
5871
}
5972
}
6073

6174
/// Checks if a ray intersects a mesh, and returns the nearest intersection if one exists.
62-
pub fn ray_mesh_intersection<I: TryInto<usize> + Clone + Copy>(
75+
pub fn ray_mesh_intersection<I>(
6376
ray: Ray3d,
6477
mesh_transform: &Mat4,
6578
positions: &[[f32; 3]],
6679
vertex_normals: Option<&[[f32; 3]]>,
6780
indices: Option<&[I]>,
81+
uvs: Option<&[[f32; 2]]>,
6882
backface_culling: Backfaces,
69-
) -> Option<RayMeshHit> {
83+
) -> Option<RayMeshHit>
84+
where
85+
I: TryInto<usize> + Clone + Copy,
86+
{
7087
let world_to_mesh = mesh_transform.inverse();
7188

7289
let ray = Ray3d::new(
@@ -139,17 +156,12 @@ pub fn ray_mesh_intersection<I: TryInto<usize> + Clone + Copy>(
139156
closest_hit.and_then(|(tri_idx, hit)| {
140157
let [a, b, c] = match indices {
141158
Some(indices) => {
142-
let triangle = indices.get((tri_idx * 3)..(tri_idx * 3 + 3))?;
143-
144-
let [Ok(a), Ok(b), Ok(c)] = [
145-
triangle[0].try_into(),
146-
triangle[1].try_into(),
147-
triangle[2].try_into(),
148-
] else {
149-
return None;
150-
};
151-
152-
[a, b, c]
159+
let [i, j, k] = [tri_idx * 3, tri_idx * 3 + 1, tri_idx * 3 + 2];
160+
[
161+
indices.get(i).copied()?.try_into().ok()?,
162+
indices.get(j).copied()?.try_into().ok()?,
163+
indices.get(k).copied()?.try_into().ok()?,
164+
]
153165
}
154166
None => [tri_idx * 3, tri_idx * 3 + 1, tri_idx * 3 + 2],
155167
};
@@ -168,10 +180,12 @@ pub fn ray_mesh_intersection<I: TryInto<usize> + Clone + Copy>(
168180
});
169181

170182
let point = ray.get_point(hit.distance);
183+
// Note that we need to convert from the Möller-Trumbore convention to the more common
184+
// P = uA + vB + (1 - u - v)C convention.
171185
let u = hit.barycentric_coords.0;
172186
let v = hit.barycentric_coords.1;
173187
let w = 1.0 - u - v;
174-
let barycentric = Vec3::new(u, v, w);
188+
let barycentric = Vec3::new(w, u, v);
175189

176190
let normal = if let Some(normals) = tri_normals {
177191
normals[1] * u + normals[2] * v + normals[0] * w
@@ -181,9 +195,29 @@ pub fn ray_mesh_intersection<I: TryInto<usize> + Clone + Copy>(
181195
.normalize()
182196
};
183197

198+
let uv = uvs.and_then(|uvs| {
199+
let tri_uvs = if let Some(indices) = indices {
200+
let i = tri_idx * 3;
201+
[
202+
uvs[indices[i].try_into().ok()?],
203+
uvs[indices[i + 1].try_into().ok()?],
204+
uvs[indices[i + 2].try_into().ok()?],
205+
]
206+
} else {
207+
let i = tri_idx * 3;
208+
[uvs[i], uvs[i + 1], uvs[i + 2]]
209+
};
210+
Some(
211+
barycentric.x * Vec2::from(tri_uvs[0])
212+
+ barycentric.y * Vec2::from(tri_uvs[1])
213+
+ barycentric.z * Vec2::from(tri_uvs[2]),
214+
)
215+
});
216+
184217
Some(RayMeshHit {
185218
point: mesh_transform.transform_point3(point),
186219
normal: mesh_transform.transform_vector3(normal),
220+
uv,
187221
barycentric_coords: barycentric,
188222
distance: mesh_transform
189223
.transform_vector3(ray.direction * hit.distance)
@@ -329,6 +363,7 @@ mod tests {
329363
positions,
330364
vertex_normals,
331365
indices,
366+
None,
332367
backface_culling,
333368
);
334369

@@ -350,6 +385,7 @@ mod tests {
350385
positions,
351386
vertex_normals,
352387
indices,
388+
None,
353389
backface_culling,
354390
);
355391

@@ -372,6 +408,7 @@ mod tests {
372408
positions,
373409
vertex_normals,
374410
indices,
411+
None,
375412
backface_culling,
376413
);
377414

@@ -394,6 +431,7 @@ mod tests {
394431
positions,
395432
vertex_normals,
396433
indices,
434+
None,
397435
backface_culling,
398436
);
399437

@@ -415,6 +453,7 @@ mod tests {
415453
positions,
416454
vertex_normals,
417455
indices,
456+
None,
418457
backface_culling,
419458
);
420459

@@ -436,6 +475,7 @@ mod tests {
436475
positions,
437476
vertex_normals,
438477
indices,
478+
None,
439479
backface_culling,
440480
);
441481

@@ -457,6 +497,7 @@ mod tests {
457497
positions,
458498
vertex_normals,
459499
indices,
500+
None,
460501
backface_culling,
461502
);
462503

@@ -478,6 +519,7 @@ mod tests {
478519
positions,
479520
vertex_normals,
480521
indices,
522+
None,
481523
backface_culling,
482524
);
483525

0 commit comments

Comments
 (0)