Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/picking/custom_hit_data.rs
30632 views
1
//! Demonstrates a custom picking backend with custom hit data.
2
//!
3
//! The example contains pickable 3D meshes. When a mesh is hovered, a custom
4
//! picking backend performs a ray cast against the mesh and retrieves the
5
//! triangle that was hit. The triangle vertices are stored in a custom struct
6
//! (`TriangleHitInfo`) that implements `HitDataExtra`, and saved into `HitData`
7
//! structs. This information is not available by default in `HitData` and thus
8
//! requires its `extra` field. A follow-up system reads the hit data and draws
9
//! an outline around the hovered triangle using gizmos.
10
11
use bevy::{
12
color::palettes::css::*,
13
picking::{
14
backend::{ray::RayMap, HitData, PointerHits},
15
mesh_picking::{
16
ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastVisibility},
17
MeshPickingSettings,
18
},
19
prelude::*,
20
PickingSettings, PickingSystems,
21
},
22
prelude::*,
23
};
24
25
fn main() {
26
App::new()
27
.add_plugins((DefaultPlugins, MeshPickingPlugin))
28
.insert_resource(MeshPickingSettings {
29
require_markers: true,
30
..default()
31
})
32
.insert_resource(PickingSettings {
33
is_window_picking_enabled: false,
34
..default()
35
})
36
.init_resource::<HoveredTriangles>()
37
.add_systems(Startup, (setup_gizmos, setup_scene))
38
.add_systems(
39
PreUpdate,
40
(
41
custom_backend_system.in_set(PickingSystems::Backend),
42
cache_hovered_triangles.after(PickingSystems::Backend),
43
),
44
)
45
.add_systems(Update, draw_hit_gizmos)
46
.run();
47
}
48
49
/// The custom hit data used by our picking backend. All structs that implement
50
/// `Send + Sync + fmt::Debug + 'static` automatically implement `HitDataExtra`
51
/// and can be used as extra data in `HitData`.
52
#[derive(Debug)]
53
struct TriangleHitInfo {
54
triangle_vertices: Option<[Vec3; 3]>,
55
}
56
57
#[derive(Resource, Default)]
58
struct HoveredTriangles(Vec<TriangleOverlay>);
59
60
struct TriangleOverlay {
61
position: Vec3,
62
normal: Vec3,
63
vertices: [Vec3; 3],
64
}
65
66
fn setup_scene(
67
mut commands: Commands,
68
mut meshes: ResMut<Assets<Mesh>>,
69
mut materials: ResMut<Assets<StandardMaterial>>,
70
) {
71
let shapes: [(Mesh, Color); 3] = [
72
(Cuboid::default().into(), RED.into()),
73
(Sphere::default().mesh().ico(2).unwrap(), GREEN.into()),
74
(Cylinder::default().into(), BLUE.into()),
75
];
76
77
for (i, (mesh, color)) in shapes.iter().enumerate() {
78
let x = i as f32 * 1.5 - 1.5;
79
let material = materials.add(StandardMaterial::from_color(*color));
80
81
commands.spawn((
82
Mesh3d(meshes.add(mesh.clone())),
83
MeshMaterial3d(material),
84
Transform::from_xyz(x, 0.5, 0.0),
85
Pickable::default(),
86
));
87
}
88
89
commands.spawn((
90
Mesh3d(meshes.add(Plane3d::default().mesh().size(30.0, 30.0))),
91
MeshMaterial3d(materials.add(Color::from(DARK_GRAY))),
92
Pickable::IGNORE,
93
));
94
95
commands.spawn((PointLight::default(), Transform::from_xyz(0.0, 8.0, 4.0)));
96
97
commands.spawn((
98
Camera3d::default(),
99
Transform::from_xyz(0.0, 2.5, 6.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
100
));
101
}
102
103
fn setup_gizmos(mut config_store: ResMut<GizmoConfigStore>) {
104
let (config, _) = config_store.config_mut::<DefaultGizmoConfigGroup>();
105
config.depth_bias = -1.0;
106
config.line.width = 3.0;
107
}
108
109
fn custom_backend_system(
110
ray_map: Res<RayMap>,
111
cameras: Query<&Camera>,
112
pickables: Query<&Pickable>,
113
mut ray_cast: MeshRayCast,
114
mut pointer_hits: MessageWriter<PointerHits>,
115
) {
116
for (&ray_id, &ray) in ray_map.iter() {
117
let Ok(camera) = cameras.get(ray_id.camera) else {
118
continue;
119
};
120
121
let settings = MeshRayCastSettings {
122
visibility: RayCastVisibility::VisibleInView,
123
filter: &|e| pickables.get(e).is_ok_and(|p| p.is_hoverable),
124
early_exit_test: &|entity_hit| {
125
pickables
126
.get(entity_hit)
127
.is_ok_and(|p| p.should_block_lower)
128
},
129
};
130
131
let picks: Vec<(Entity, HitData)> = ray_cast
132
.cast_ray(ray, &settings)
133
.iter()
134
.map(|(entity, hit)| {
135
let extra = TriangleHitInfo {
136
triangle_vertices: hit.triangle,
137
};
138
139
let hit_data = HitData::new_with_extra(
140
ray_id.camera,
141
hit.distance,
142
Some(hit.point),
143
Some(hit.normal),
144
extra,
145
);
146
147
(*entity, hit_data)
148
})
149
.collect();
150
151
if !picks.is_empty() {
152
pointer_hits.write(PointerHits::new(ray_id.pointer, picks, camera.order as f32));
153
}
154
}
155
}
156
157
fn cache_hovered_triangles(
158
mut pointer_hits: MessageReader<PointerHits>,
159
mut hovered_triangles: ResMut<HoveredTriangles>,
160
) {
161
hovered_triangles.0.clear();
162
163
for hits in pointer_hits.read() {
164
for (_, hit) in &hits.picks {
165
let (Some(position), Some(normal)) = (hit.position, hit.normal) else {
166
continue;
167
};
168
169
let Some(info) = hit.extra_as::<TriangleHitInfo>() else {
170
continue;
171
};
172
let Some(vertices) = info.triangle_vertices else {
173
continue;
174
};
175
176
hovered_triangles.0.push(TriangleOverlay {
177
position,
178
normal,
179
vertices,
180
});
181
}
182
}
183
}
184
185
fn draw_hit_gizmos(hovered_triangles: Res<HoveredTriangles>, mut gizmos: Gizmos) {
186
for triangle in &hovered_triangles.0 {
187
gizmos.arrow(
188
triangle.position,
189
triangle.position + triangle.normal.normalize() * 0.5,
190
WHITE,
191
);
192
193
let vertices = triangle.vertices;
194
let center = (vertices[0] + vertices[1] + vertices[2]) / 3.0;
195
let offset = triangle.normal.normalize_or_zero() * 0.025;
196
197
// The outline is made bigger and offset a bit to prevent being covered
198
// by the mesh
199
let outline = vertices.map(|vertex| center + (vertex - center) * 1.05 + offset);
200
201
gizmos.line(outline[0], outline[1], WHITE);
202
gizmos.line(outline[1], outline[2], WHITE);
203
gizmos.line(outline[2], outline[0], WHITE);
204
}
205
}
206
207