Skip to content

Commit 54b323e

Browse files
authored
Mesh picking fixes (bevyengine#16110)
# Objective - Mesh picking is noisy when a non triangle list is used - Mesh picking runs even when users don't need it - Resolve bevyengine#16065 ## Solution - Don't add the mesh picking plugin by default - Remove error spam
1 parent a644ac7 commit 54b323e

File tree

5 files changed

+126
-157
lines changed

5 files changed

+126
-157
lines changed

benches/benches/bevy_picking/ray_mesh_intersection.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bevy_math::{Dir3, Mat4, Ray3d, Vec3};
2-
use bevy_picking::{mesh_picking::ray_cast, prelude::*};
2+
use bevy_picking::mesh_picking::ray_cast;
33
use criterion::{black_box, criterion_group, criterion_main, Criterion};
44

55
fn ptoxznorm(p: u32, size: u32) -> (f32, f32) {

crates/bevy_picking/src/lib.rs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -277,24 +277,10 @@ pub struct DefaultPickingPlugins;
277277

278278
impl PluginGroup for DefaultPickingPlugins {
279279
fn build(self) -> PluginGroupBuilder {
280-
#[cfg_attr(
281-
not(feature = "bevy_mesh"),
282-
expect(
283-
unused_mut,
284-
reason = "Group is not mutated when `bevy_mesh` is not enabled."
285-
)
286-
)]
287-
let mut group = PluginGroupBuilder::start::<Self>()
280+
PluginGroupBuilder::start::<Self>()
288281
.add(input::PointerInputPlugin::default())
289282
.add(PickingPlugin::default())
290-
.add(InteractionPlugin);
291-
292-
#[cfg(feature = "bevy_mesh")]
293-
{
294-
group = group.add(mesh_picking::MeshPickingPlugin);
295-
};
296-
297-
group
283+
.add(InteractionPlugin)
298284
}
299285
}
300286

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

Lines changed: 63 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec3, Vec3A};
22
use bevy_reflect::Reflect;
3-
use bevy_render::mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
4-
use bevy_utils::tracing::{error, warn};
3+
use bevy_render::mesh::{Indices, Mesh, PrimitiveTopology};
54

65
use super::Backfaces;
76

@@ -17,7 +16,7 @@ pub struct RayMeshHit {
1716
/// The distance from the ray origin to the intersection point.
1817
pub distance: f32,
1918
/// The vertices of the triangle that was hit.
20-
pub triangle: Option<[Vec3A; 3]>,
19+
pub triangle: Option<[Vec3; 3]>,
2120
/// The index of the triangle that was hit.
2221
pub triangle_index: Option<usize>,
2322
}
@@ -32,84 +31,41 @@ pub struct RayTriangleHit {
3231
/// Casts a ray on a mesh, and returns the intersection.
3332
pub(super) fn ray_intersection_over_mesh(
3433
mesh: &Mesh,
35-
mesh_transform: &Mat4,
34+
transform: &Mat4,
3635
ray: Ray3d,
37-
backface_culling: Backfaces,
36+
culling: Backfaces,
3837
) -> Option<RayMeshHit> {
3938
if mesh.primitive_topology() != PrimitiveTopology::TriangleList {
40-
error!(
41-
"Invalid intersection check: `TriangleList` is the only supported `PrimitiveTopology`"
42-
);
43-
return None;
39+
return None; // ray_mesh_intersection assumes vertices are laid out in a triangle list
4440
}
41+
// Vertex positions are required
42+
let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?.as_float3()?;
4543

46-
// Get the vertex positions and normals from the mesh.
47-
let vertex_positions: &Vec<[f32; 3]> = match mesh.attribute(Mesh::ATTRIBUTE_POSITION) {
48-
None => {
49-
error!("Mesh does not contain vertex positions");
50-
return None;
44+
// Normals are optional
45+
let normals = mesh
46+
.attribute(Mesh::ATTRIBUTE_NORMAL)
47+
.and_then(|normal_values| normal_values.as_float3());
48+
49+
match mesh.indices() {
50+
Some(Indices::U16(indices)) => {
51+
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling)
5152
}
52-
Some(vertex_values) => match &vertex_values {
53-
VertexAttributeValues::Float32x3(positions) => positions,
54-
_ => {
55-
error!("Unexpected types in {:?}", Mesh::ATTRIBUTE_POSITION);
56-
return None;
57-
}
58-
},
59-
};
60-
let vertex_normals: Option<&[[f32; 3]]> =
61-
if let Some(normal_values) = mesh.attribute(Mesh::ATTRIBUTE_NORMAL) {
62-
match &normal_values {
63-
VertexAttributeValues::Float32x3(normals) => Some(normals),
64-
_ => None,
65-
}
66-
} else {
67-
None
68-
};
69-
70-
if let Some(indices) = &mesh.indices() {
71-
match indices {
72-
Indices::U16(vertex_indices) => ray_mesh_intersection(
73-
ray,
74-
mesh_transform,
75-
vertex_positions,
76-
vertex_normals,
77-
Some(vertex_indices),
78-
backface_culling,
79-
),
80-
Indices::U32(vertex_indices) => ray_mesh_intersection(
81-
ray,
82-
mesh_transform,
83-
vertex_positions,
84-
vertex_normals,
85-
Some(vertex_indices),
86-
backface_culling,
87-
),
53+
Some(Indices::U32(indices)) => {
54+
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling)
8855
}
89-
} else {
90-
ray_mesh_intersection(
91-
ray,
92-
mesh_transform,
93-
vertex_positions,
94-
vertex_normals,
95-
None::<&[usize]>,
96-
backface_culling,
97-
)
56+
None => ray_mesh_intersection::<usize>(ray, transform, positions, normals, None, culling),
9857
}
9958
}
10059

10160
/// Checks if a ray intersects a mesh, and returns the nearest intersection if one exists.
102-
pub fn ray_mesh_intersection<Index: Clone + Copy>(
61+
pub fn ray_mesh_intersection<I: TryInto<usize> + Clone + Copy>(
10362
ray: Ray3d,
10463
mesh_transform: &Mat4,
105-
vertex_positions: &[[f32; 3]],
64+
positions: &[[f32; 3]],
10665
vertex_normals: Option<&[[f32; 3]]>,
107-
indices: Option<&[Index]>,
66+
indices: Option<&[I]>,
10867
backface_culling: Backfaces,
109-
) -> Option<RayMeshHit>
110-
where
111-
usize: TryFrom<Index>,
112-
{
68+
) -> Option<RayMeshHit> {
11369
// The ray cast can hit the same mesh many times, so we need to track which hit is
11470
// closest to the camera, and record that.
11571
let mut closest_hit_distance = f32::MAX;
@@ -123,38 +79,36 @@ where
12379
);
12480

12581
if let Some(indices) = indices {
126-
// Make sure this chunk has 3 vertices to avoid a panic.
82+
// The index list must be a multiple of three. If not, the mesh is malformed and the raycast
83+
// result might be nonsensical.
12784
if indices.len() % 3 != 0 {
128-
warn!("Index list not a multiple of 3");
12985
return None;
13086
}
13187

132-
// Now that we're in the vector of vertex indices, we want to look at the vertex
133-
// positions for each triangle, so we'll take indices in chunks of three, where each
134-
// chunk of three indices are references to the three vertices of a triangle.
135-
for index_chunk in indices.chunks_exact(3) {
136-
let [index1, index2, index3] = [
137-
usize::try_from(index_chunk[0]).ok()?,
138-
usize::try_from(index_chunk[1]).ok()?,
139-
usize::try_from(index_chunk[2]).ok()?,
88+
for triangle in indices.chunks_exact(3) {
89+
let [a, b, c] = [
90+
triangle[0].try_into().ok()?,
91+
triangle[1].try_into().ok()?,
92+
triangle[2].try_into().ok()?,
14093
];
141-
let triangle_index = Some(index1);
142-
let tri_vertex_positions = [
143-
Vec3A::from(vertex_positions[index1]),
144-
Vec3A::from(vertex_positions[index2]),
145-
Vec3A::from(vertex_positions[index3]),
94+
95+
let triangle_index = Some(a);
96+
let tri_vertex_positions = &[
97+
Vec3::from(positions[a]),
98+
Vec3::from(positions[b]),
99+
Vec3::from(positions[c]),
146100
];
147101
let tri_normals = vertex_normals.map(|normals| {
148102
[
149-
Vec3A::from(normals[index1]),
150-
Vec3A::from(normals[index2]),
151-
Vec3A::from(normals[index3]),
103+
Vec3::from(normals[a]),
104+
Vec3::from(normals[b]),
105+
Vec3::from(normals[c]),
152106
]
153107
});
154108

155109
let Some(hit) = triangle_intersection(
156110
tri_vertex_positions,
157-
tri_normals,
111+
tri_normals.as_ref(),
158112
closest_hit_distance,
159113
&mesh_space_ray,
160114
backface_culling,
@@ -171,33 +125,33 @@ where
171125
.length(),
172126
triangle: hit.triangle.map(|tri| {
173127
[
174-
mesh_transform.transform_point3a(tri[0]),
175-
mesh_transform.transform_point3a(tri[1]),
176-
mesh_transform.transform_point3a(tri[2]),
128+
mesh_transform.transform_point3(tri[0]),
129+
mesh_transform.transform_point3(tri[1]),
130+
mesh_transform.transform_point3(tri[2]),
177131
]
178132
}),
179133
triangle_index,
180134
});
181135
closest_hit_distance = hit.distance;
182136
}
183137
} else {
184-
for (i, chunk) in vertex_positions.chunks_exact(3).enumerate() {
185-
let &[a, b, c] = chunk else {
138+
for (i, triangle) in positions.chunks_exact(3).enumerate() {
139+
let &[a, b, c] = triangle else {
186140
continue;
187141
};
188142
let triangle_index = Some(i);
189-
let tri_vertex_positions = [Vec3A::from(a), Vec3A::from(b), Vec3A::from(c)];
143+
let tri_vertex_positions = &[Vec3::from(a), Vec3::from(b), Vec3::from(c)];
190144
let tri_normals = vertex_normals.map(|normals| {
191145
[
192-
Vec3A::from(normals[i]),
193-
Vec3A::from(normals[i + 1]),
194-
Vec3A::from(normals[i + 2]),
146+
Vec3::from(normals[i]),
147+
Vec3::from(normals[i + 1]),
148+
Vec3::from(normals[i + 2]),
195149
]
196150
});
197151

198152
let Some(hit) = triangle_intersection(
199153
tri_vertex_positions,
200-
tri_normals,
154+
tri_normals.as_ref(),
201155
closest_hit_distance,
202156
&mesh_space_ray,
203157
backface_culling,
@@ -214,9 +168,9 @@ where
214168
.length(),
215169
triangle: hit.triangle.map(|tri| {
216170
[
217-
mesh_transform.transform_point3a(tri[0]),
218-
mesh_transform.transform_point3a(tri[1]),
219-
mesh_transform.transform_point3a(tri[2]),
171+
mesh_transform.transform_point3(tri[0]),
172+
mesh_transform.transform_point3(tri[1]),
173+
mesh_transform.transform_point3(tri[2]),
220174
]
221175
}),
222176
triangle_index,
@@ -228,15 +182,14 @@ where
228182
closest_hit
229183
}
230184

231-
#[inline(always)]
232185
fn triangle_intersection(
233-
tri_vertices: [Vec3A; 3],
234-
tri_normals: Option<[Vec3A; 3]>,
186+
tri_vertices: &[Vec3; 3],
187+
tri_normals: Option<&[Vec3; 3]>,
235188
max_distance: f32,
236189
ray: &Ray3d,
237190
backface_culling: Backfaces,
238191
) -> Option<RayMeshHit> {
239-
let hit = ray_triangle_intersection(ray, &tri_vertices, backface_culling)?;
192+
let hit = ray_triangle_intersection(ray, tri_vertices, backface_culling)?;
240193

241194
if hit.distance < 0.0 || hit.distance > max_distance {
242195
return None;
@@ -258,25 +211,24 @@ fn triangle_intersection(
258211

259212
Some(RayMeshHit {
260213
point,
261-
normal: normal.into(),
214+
normal,
262215
barycentric_coords: barycentric,
263216
distance: hit.distance,
264-
triangle: Some(tri_vertices),
217+
triangle: Some(*tri_vertices),
265218
triangle_index: None,
266219
})
267220
}
268221

269222
/// Takes a ray and triangle and computes the intersection.
270-
#[inline(always)]
271223
fn ray_triangle_intersection(
272224
ray: &Ray3d,
273-
triangle: &[Vec3A; 3],
225+
triangle: &[Vec3; 3],
274226
backface_culling: Backfaces,
275227
) -> Option<RayTriangleHit> {
276228
// Source: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection
277-
let vector_v0_to_v1: Vec3A = triangle[1] - triangle[0];
278-
let vector_v0_to_v2: Vec3A = triangle[2] - triangle[0];
279-
let p_vec: Vec3A = (Vec3A::from(*ray.direction)).cross(vector_v0_to_v2);
229+
let vector_v0_to_v1: Vec3 = triangle[1] - triangle[0];
230+
let vector_v0_to_v2: Vec3 = triangle[2] - triangle[0];
231+
let p_vec: Vec3 = ray.direction.cross(vector_v0_to_v2);
280232
let determinant: f32 = vector_v0_to_v1.dot(p_vec);
281233

282234
match backface_culling {
@@ -298,14 +250,14 @@ fn ray_triangle_intersection(
298250

299251
let determinant_inverse = 1.0 / determinant;
300252

301-
let t_vec = Vec3A::from(ray.origin) - triangle[0];
253+
let t_vec = ray.origin - triangle[0];
302254
let u = t_vec.dot(p_vec) * determinant_inverse;
303255
if !(0.0..=1.0).contains(&u) {
304256
return None;
305257
}
306258

307259
let q_vec = t_vec.cross(vector_v0_to_v1);
308-
let v = Vec3A::from(*ray.direction).dot(q_vec) * determinant_inverse;
260+
let v = (*ray.direction).dot(q_vec) * determinant_inverse;
309261
if v < 0.0 || u + v > 1.0 {
310262
return None;
311263
}

examples/picking/mesh_picking.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
//! A simple 3D scene to demonstrate mesh picking.
22
//!
3-
//! By default, all meshes are pickable. Picking can be disabled for individual entities
4-
//! by adding [`PickingBehavior::IGNORE`].
3+
//! [`bevy::picking::backend`] provides an API for adding picking hit tests to any entity. To get
4+
//! started with picking 3d meshes, the [`MeshPickingPlugin`] is provided as a simple starting
5+
//! point, especially useful for debugging. For your game, you may want to use a 3d picking backend
6+
//! provided by your physics engine, or a picking shader, depending on your specific use case.
57
//!
6-
//! If you want mesh picking to be entirely opt-in, you can set [`MeshPickingSettings::require_markers`]
7-
//! to `true` and add a [`RayCastPickable`] component to the desired camera and target entities.
8+
//! [`bevy::picking`] allows you to compose backends together to make any entity on screen pickable
9+
//! with pointers, regardless of how that entity is rendered. For example, `bevy_ui` and
10+
//! `bevy_sprite` provide their own picking backends that can be enabled at the same time as this
11+
//! mesh picking backend. This makes it painless to deal with cases like the UI or sprites blocking
12+
//! meshes underneath them, or vice versa.
13+
//!
14+
//! If you want to build more complex interactions than afforded by the provided pointer events, you
15+
//! may want to use [`MeshRayCast`] or a full physics engine with raycasting capabilities.
16+
//!
17+
//! By default, the mesh picking plugin will raycast against all entities, which is especially
18+
//! useful for debugging. If you want mesh picking to be opt-in, you can set
19+
//! [`MeshPickingSettings::require_markers`] to `true` and add a [`RayCastPickable`] component to
20+
//! the desired camera and target entities.
821
922
use std::f32::consts::PI;
1023

@@ -19,7 +32,12 @@ use bevy::{
1932

2033
fn main() {
2134
App::new()
22-
.add_plugins(DefaultPlugins)
35+
.add_plugins((
36+
DefaultPlugins,
37+
// The mesh picking plugin is not enabled by default, because raycasting against all
38+
// meshes has a performance cost.
39+
MeshPickingPlugin,
40+
))
2341
.init_resource::<SceneMaterials>()
2442
.add_systems(Startup, setup)
2543
.add_systems(Update, (on_mesh_hover, rotate))

0 commit comments

Comments
 (0)