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