Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs
9395 views
1
use bevy_math::{bounding::Aabb3d, Affine3A, Dir3, Ray3d, Vec2, Vec3, Vec3A};
2
use bevy_mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
3
use bevy_reflect::Reflect;
4
5
use super::Backfaces;
6
7
/// Hit data for an intersection between a ray and a mesh.
8
#[derive(Debug, Clone, Reflect)]
9
#[reflect(Clone)]
10
pub struct RayMeshHit {
11
/// The point of intersection in world space.
12
pub point: Vec3,
13
/// The normal vector of the triangle at the point of intersection. Not guaranteed to be normalized for scaled meshes.
14
pub normal: Vec3,
15
/// The barycentric coordinates of the intersection.
16
pub barycentric_coords: Vec3,
17
/// The distance from the ray origin to the intersection point.
18
pub distance: f32,
19
/// The vertices of the triangle that was hit.
20
pub triangle: Option<[Vec3; 3]>,
21
/// UV coordinate of the hit, if the mesh has UV attributes.
22
pub uv: Option<Vec2>,
23
/// The index of the triangle that was hit.
24
pub triangle_index: Option<usize>,
25
}
26
27
/// Hit data for an intersection between a ray and a triangle.
28
#[derive(Default, Debug)]
29
pub struct RayTriangleHit {
30
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
35
pub barycentric_coords: (f32, f32),
36
}
37
38
/// Casts a ray on a mesh, and returns the intersection.
39
pub(super) fn ray_intersection_over_mesh(
40
mesh: &Mesh,
41
transform: &Affine3A,
42
ray: Ray3d,
43
cull: Backfaces,
44
) -> Option<RayMeshHit> {
45
if mesh.primitive_topology() != PrimitiveTopology::TriangleList {
46
return None; // ray_mesh_intersection assumes vertices are laid out in a triangle list
47
}
48
// Vertex positions are required
49
let positions = mesh
50
.try_attribute(Mesh::ATTRIBUTE_POSITION)
51
.ok()?
52
.as_float3()?;
53
54
// Normals are optional
55
let normals = mesh
56
.try_attribute(Mesh::ATTRIBUTE_NORMAL)
57
.ok()
58
.and_then(|normal_values| normal_values.as_float3());
59
60
let uvs = mesh
61
.try_attribute(Mesh::ATTRIBUTE_UV_0)
62
.ok()
63
.and_then(|uvs| match uvs {
64
VertexAttributeValues::Float32x2(uvs) => Some(uvs.as_slice()),
65
_ => None,
66
});
67
68
match mesh.try_indices().ok() {
69
Some(Indices::U16(indices)) => {
70
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), uvs, cull)
71
}
72
Some(Indices::U32(indices)) => {
73
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), uvs, cull)
74
}
75
None => ray_mesh_intersection::<u32>(ray, transform, positions, normals, None, uvs, cull),
76
}
77
}
78
79
/// Checks if a ray intersects a mesh, and returns the nearest intersection if one exists.
80
pub fn ray_mesh_intersection<I>(
81
ray: Ray3d,
82
mesh_transform: &Affine3A,
83
positions: &[[f32; 3]],
84
vertex_normals: Option<&[[f32; 3]]>,
85
indices: Option<&[I]>,
86
uvs: Option<&[[f32; 2]]>,
87
backface_culling: Backfaces,
88
) -> Option<RayMeshHit>
89
where
90
I: TryInto<usize> + Clone + Copy,
91
{
92
let world_to_mesh = mesh_transform.inverse();
93
94
let ray = Ray3d::new(
95
world_to_mesh.transform_point3(ray.origin),
96
Dir3::new(world_to_mesh.transform_vector3(*ray.direction)).ok()?,
97
);
98
99
let closest_hit = if let Some(indices) = indices {
100
// The index list must be a multiple of three. If not, the mesh is malformed and the raycast
101
// result might be nonsensical.
102
if indices.len() % 3 != 0 {
103
return None;
104
}
105
106
indices
107
.chunks_exact(3)
108
.enumerate()
109
.fold(
110
(f32::MAX, None),
111
|(closest_distance, closest_hit), (tri_idx, triangle)| {
112
let [Ok(a), Ok(b), Ok(c)] = [
113
triangle[0].try_into(),
114
triangle[1].try_into(),
115
triangle[2].try_into(),
116
] else {
117
return (closest_distance, closest_hit);
118
};
119
120
let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)]
121
{
122
[Some(a), Some(b), Some(c)] => {
123
[Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]
124
}
125
_ => return (closest_distance, closest_hit),
126
};
127
128
match ray_triangle_intersection(&ray, &tri_vertices, backface_culling) {
129
Some(hit) if hit.distance >= 0. && hit.distance < closest_distance => {
130
(hit.distance, Some((tri_idx, hit)))
131
}
132
_ => (closest_distance, closest_hit),
133
}
134
},
135
)
136
.1
137
} else {
138
positions
139
.chunks_exact(3)
140
.enumerate()
141
.fold(
142
(f32::MAX, None),
143
|(closest_distance, closest_hit), (tri_idx, triangle)| {
144
let tri_vertices = [
145
Vec3::from(triangle[0]),
146
Vec3::from(triangle[1]),
147
Vec3::from(triangle[2]),
148
];
149
150
match ray_triangle_intersection(&ray, &tri_vertices, backface_culling) {
151
Some(hit) if hit.distance >= 0. && hit.distance < closest_distance => {
152
(hit.distance, Some((tri_idx, hit)))
153
}
154
_ => (closest_distance, closest_hit),
155
}
156
},
157
)
158
.1
159
};
160
161
closest_hit.and_then(|(tri_idx, hit)| {
162
let [a, b, c] = match indices {
163
Some(indices) => {
164
let [i, j, k] = [tri_idx * 3, tri_idx * 3 + 1, tri_idx * 3 + 2];
165
[
166
indices.get(i).copied()?.try_into().ok()?,
167
indices.get(j).copied()?.try_into().ok()?,
168
indices.get(k).copied()?.try_into().ok()?,
169
]
170
}
171
None => [tri_idx * 3, tri_idx * 3 + 1, tri_idx * 3 + 2],
172
};
173
174
let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] {
175
[Some(a), Some(b), Some(c)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)],
176
_ => return None,
177
};
178
179
let tri_normals = vertex_normals.and_then(|normals| {
180
let [Some(a), Some(b), Some(c)] = [normals.get(a), normals.get(b), normals.get(c)]
181
else {
182
return None;
183
};
184
Some([Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)])
185
});
186
187
let point = ray.get_point(hit.distance);
188
// Note that we need to convert from the Möller-Trumbore convention to the more common
189
// P = uA + vB + (1 - u - v)C convention.
190
let u = hit.barycentric_coords.0;
191
let v = hit.barycentric_coords.1;
192
let w = 1.0 - u - v;
193
let barycentric = Vec3::new(w, u, v);
194
195
let normal = if let Some(normals) = tri_normals {
196
normals[1] * u + normals[2] * v + normals[0] * w
197
} else {
198
(tri_vertices[1] - tri_vertices[0])
199
.cross(tri_vertices[2] - tri_vertices[0])
200
.normalize()
201
};
202
203
let uv = uvs.and_then(|uvs| {
204
let tri_uvs = if let Some(indices) = indices {
205
let i = tri_idx * 3;
206
[
207
uvs[indices[i].try_into().ok()?],
208
uvs[indices[i + 1].try_into().ok()?],
209
uvs[indices[i + 2].try_into().ok()?],
210
]
211
} else {
212
let i = tri_idx * 3;
213
[uvs[i], uvs[i + 1], uvs[i + 2]]
214
};
215
Some(
216
barycentric.x * Vec2::from(tri_uvs[0])
217
+ barycentric.y * Vec2::from(tri_uvs[1])
218
+ barycentric.z * Vec2::from(tri_uvs[2]),
219
)
220
});
221
222
Some(RayMeshHit {
223
point: mesh_transform.transform_point3(point),
224
normal: mesh_transform.transform_vector3(normal),
225
uv,
226
barycentric_coords: barycentric,
227
distance: mesh_transform
228
.transform_vector3(ray.direction * hit.distance)
229
.length(),
230
triangle: Some(tri_vertices.map(|v| mesh_transform.transform_point3(v))),
231
triangle_index: Some(tri_idx),
232
})
233
})
234
}
235
236
/// Takes a ray and triangle and computes the intersection.
237
#[inline]
238
fn ray_triangle_intersection(
239
ray: &Ray3d,
240
triangle: &[Vec3; 3],
241
backface_culling: Backfaces,
242
) -> Option<RayTriangleHit> {
243
// Source: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection
244
let vector_v0_to_v1: Vec3 = triangle[1] - triangle[0];
245
let vector_v0_to_v2: Vec3 = triangle[2] - triangle[0];
246
let p_vec: Vec3 = ray.direction.cross(vector_v0_to_v2);
247
let determinant: f32 = vector_v0_to_v1.dot(p_vec);
248
249
match backface_culling {
250
Backfaces::Cull => {
251
// if the determinant is negative the triangle is back facing
252
// if the determinant is close to 0, the ray misses the triangle
253
// This test checks both cases
254
if determinant < f32::EPSILON {
255
return None;
256
}
257
}
258
Backfaces::Include => {
259
// ray and triangle are parallel if det is close to 0
260
if determinant.abs() < f32::EPSILON {
261
return None;
262
}
263
}
264
}
265
266
let determinant_inverse = 1.0 / determinant;
267
268
let t_vec = ray.origin - triangle[0];
269
let u = t_vec.dot(p_vec) * determinant_inverse;
270
if !(0.0..=1.0).contains(&u) {
271
return None;
272
}
273
274
let q_vec = t_vec.cross(vector_v0_to_v1);
275
let v = (*ray.direction).dot(q_vec) * determinant_inverse;
276
if v < 0.0 || u + v > 1.0 {
277
return None;
278
}
279
280
// The distance between ray origin and intersection is t.
281
let t: f32 = vector_v0_to_v2.dot(q_vec) * determinant_inverse;
282
283
Some(RayTriangleHit {
284
distance: t,
285
barycentric_coords: (u, v),
286
})
287
}
288
289
// TODO: It'd be nice to reuse `RayCast3d::aabb_intersection_at`, but it assumes a normalized ray.
290
// In our case, the ray is transformed to model space, which could involve scaling.
291
/// Checks if the ray intersects with the AABB of a mesh, returning the distance to the point of intersection.
292
/// The distance is zero if the ray starts inside the AABB.
293
pub fn ray_aabb_intersection_3d(
294
ray: Ray3d,
295
aabb: &Aabb3d,
296
model_to_world: &Affine3A,
297
) -> Option<f32> {
298
// Transform the ray to model space
299
let world_to_model = model_to_world.inverse();
300
let ray_direction: Vec3A = world_to_model.transform_vector3a((*ray.direction).into());
301
let ray_direction_recip = ray_direction.recip();
302
let ray_origin: Vec3A = world_to_model.transform_point3a(ray.origin.into());
303
304
// Check if the ray intersects the mesh's AABB. It's useful to work in model space
305
// because we can do an AABB intersection test, instead of an OBB intersection test.
306
307
// NOTE: This is largely copied from `RayCast3d::aabb_intersection_at`.
308
let positive = ray_direction.signum().cmpgt(Vec3A::ZERO);
309
let min = Vec3A::select(positive, aabb.min, aabb.max);
310
let max = Vec3A::select(positive, aabb.max, aabb.min);
311
312
// Calculate the minimum/maximum time for each axis based on how much the direction goes that
313
// way. These values can get arbitrarily large, or even become NaN, which is handled by the
314
// min/max operations below
315
let tmin = (min - ray_origin) * ray_direction_recip;
316
let tmax = (max - ray_origin) * ray_direction_recip;
317
318
// An axis that is not relevant to the ray direction will be NaN. When one of the arguments
319
// to min/max is NaN, the other argument is used.
320
// An axis for which the direction is the wrong way will return an arbitrarily large
321
// negative value.
322
let tmin = tmin.max_element().max(0.0);
323
let tmax = tmax.min_element();
324
325
if tmin <= tmax {
326
Some(tmin)
327
} else {
328
None
329
}
330
}
331
332
#[cfg(test)]
333
mod tests {
334
use bevy_math::Vec3;
335
use bevy_transform::components::GlobalTransform;
336
337
use super::*;
338
339
// Triangle vertices to be used in a left-hand coordinate system
340
const V0: [f32; 3] = [1.0, -1.0, 2.0];
341
const V1: [f32; 3] = [1.0, 2.0, -1.0];
342
const V2: [f32; 3] = [1.0, -1.0, -1.0];
343
344
#[test]
345
fn ray_cast_triangle_mt() {
346
let triangle = [V0.into(), V1.into(), V2.into()];
347
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
348
let result = ray_triangle_intersection(&ray, &triangle, Backfaces::Include);
349
assert!(result.unwrap().distance - 1.0 <= f32::EPSILON);
350
}
351
352
#[test]
353
fn ray_cast_triangle_mt_culling() {
354
let triangle = [V2.into(), V1.into(), V0.into()];
355
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
356
let result = ray_triangle_intersection(&ray, &triangle, Backfaces::Cull);
357
assert!(result.is_none());
358
}
359
360
#[test]
361
fn ray_mesh_intersection_simple() {
362
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
363
let mesh_transform = GlobalTransform::IDENTITY.affine();
364
let positions = &[V0, V1, V2];
365
let vertex_normals = None;
366
let indices: Option<&[u16]> = None;
367
let backface_culling = Backfaces::Cull;
368
369
let result = ray_mesh_intersection(
370
ray,
371
&mesh_transform,
372
positions,
373
vertex_normals,
374
indices,
375
None,
376
backface_culling,
377
);
378
379
assert!(result.is_some());
380
}
381
382
#[test]
383
fn ray_mesh_intersection_indices() {
384
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
385
let mesh_transform = GlobalTransform::IDENTITY.affine();
386
let positions = &[V0, V1, V2];
387
let vertex_normals = None;
388
let indices: Option<&[u16]> = Some(&[0, 1, 2]);
389
let backface_culling = Backfaces::Cull;
390
391
let result = ray_mesh_intersection(
392
ray,
393
&mesh_transform,
394
positions,
395
vertex_normals,
396
indices,
397
None,
398
backface_culling,
399
);
400
401
assert!(result.is_some());
402
}
403
404
#[test]
405
fn ray_mesh_intersection_indices_vertex_normals() {
406
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
407
let mesh_transform = GlobalTransform::IDENTITY.affine();
408
let positions = &[V0, V1, V2];
409
let vertex_normals: Option<&[[f32; 3]]> =
410
Some(&[[-1., 0., 0.], [-1., 0., 0.], [-1., 0., 0.]]);
411
let indices: Option<&[u16]> = Some(&[0, 1, 2]);
412
let backface_culling = Backfaces::Cull;
413
414
let result = ray_mesh_intersection(
415
ray,
416
&mesh_transform,
417
positions,
418
vertex_normals,
419
indices,
420
None,
421
backface_culling,
422
);
423
424
assert!(result.is_some());
425
}
426
427
#[test]
428
fn ray_mesh_intersection_vertex_normals() {
429
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
430
let mesh_transform = GlobalTransform::IDENTITY.affine();
431
let positions = &[V0, V1, V2];
432
let vertex_normals: Option<&[[f32; 3]]> =
433
Some(&[[-1., 0., 0.], [-1., 0., 0.], [-1., 0., 0.]]);
434
let indices: Option<&[u16]> = None;
435
let backface_culling = Backfaces::Cull;
436
437
let result = ray_mesh_intersection(
438
ray,
439
&mesh_transform,
440
positions,
441
vertex_normals,
442
indices,
443
None,
444
backface_culling,
445
);
446
447
assert!(result.is_some());
448
}
449
450
#[test]
451
fn ray_mesh_intersection_missing_vertex_normals() {
452
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
453
let mesh_transform = GlobalTransform::IDENTITY.affine();
454
let positions = &[V0, V1, V2];
455
let vertex_normals: Option<&[[f32; 3]]> = Some(&[]);
456
let indices: Option<&[u16]> = None;
457
let backface_culling = Backfaces::Cull;
458
459
let result = ray_mesh_intersection(
460
ray,
461
&mesh_transform,
462
positions,
463
vertex_normals,
464
indices,
465
None,
466
backface_culling,
467
);
468
469
assert!(result.is_some());
470
}
471
472
#[test]
473
fn ray_mesh_intersection_indices_missing_vertex_normals() {
474
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
475
let mesh_transform = GlobalTransform::IDENTITY.affine();
476
let positions = &[V0, V1, V2];
477
let vertex_normals: Option<&[[f32; 3]]> = Some(&[]);
478
let indices: Option<&[u16]> = Some(&[0, 1, 2]);
479
let backface_culling = Backfaces::Cull;
480
481
let result = ray_mesh_intersection(
482
ray,
483
&mesh_transform,
484
positions,
485
vertex_normals,
486
indices,
487
None,
488
backface_culling,
489
);
490
491
assert!(result.is_some());
492
}
493
494
#[test]
495
fn ray_mesh_intersection_not_enough_indices() {
496
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
497
let mesh_transform = GlobalTransform::IDENTITY.affine();
498
let positions = &[V0, V1, V2];
499
let vertex_normals = None;
500
let indices: Option<&[u16]> = Some(&[0]);
501
let backface_culling = Backfaces::Cull;
502
503
let result = ray_mesh_intersection(
504
ray,
505
&mesh_transform,
506
positions,
507
vertex_normals,
508
indices,
509
None,
510
backface_culling,
511
);
512
513
assert!(result.is_none());
514
}
515
516
#[test]
517
fn ray_mesh_intersection_bad_indices() {
518
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
519
let mesh_transform = GlobalTransform::IDENTITY.affine();
520
let positions = &[V0, V1, V2];
521
let vertex_normals = None;
522
let indices: Option<&[u16]> = Some(&[0, 1, 3]);
523
let backface_culling = Backfaces::Cull;
524
525
let result = ray_mesh_intersection(
526
ray,
527
&mesh_transform,
528
positions,
529
vertex_normals,
530
indices,
531
None,
532
backface_culling,
533
);
534
535
assert!(result.is_none());
536
}
537
}
538
539