1use 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
25fn 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#[derive(Debug)]
53struct TriangleHitInfo {
54 triangle_vertices: Option<[Vec3; 3]>,
55}
56
57#[derive(Resource, Default)]
58struct HoveredTriangles(Vec<TriangleOverlay>);
59
60struct TriangleOverlay {
61 position: Vec3,
62 normal: Vec3,
63 vertices: [Vec3; 3],
64}
65
66fn 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
103fn 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
109fn 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
157fn 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
185fn 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 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}