forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdebug_frustum_culling.rs
More file actions
367 lines (341 loc) · 13.3 KB
/
debug_frustum_culling.rs
File metadata and controls
367 lines (341 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
//! This example demonstrates how to debug and visualize frustum culling,
//! a process (and Bevy [`system`](bevy::camera::visibility::check_visibility)) that determines
//! which entities are visible and should be rendered within
//! a camera's view. If an entity's [`Aabb`](bevy::camera::primitives::Aabb)
//! (Axis-Aligned Bounding Box) does not intersect with a camera's
//! [`Frustum`](bevy::camera::primitives::Frustum), that entity is said to be "culled"
//! from the camera's view frustum.
//!
//! To debug and visualize frustum culling, this example uses Aabb and Frustum gizmos provided
//! by bevy's [`Gizmo`] library. [`Aabb gizmos`](bevy::gizmos::aabb) are used to visualize the
//! [`Aabb`](bevy::camera::primitives::Aabb) of entities.
//! [`Frustum gizmos`](bevy::gizmos::frustum) are used to visualize the
//! [`Frustum`](bevy::camera::primitives::Frustum) of a camera.
//! Both can be used together to visualize which entities have been culled
//! from a given camera's view, which entities are visible, and when
//! that change happens during an entity's Aabb interaction with a camera's Frustum.
//!
//! This example contains a scene with a camera [`MyCamera`] that has its
//! [`Frustum`](bevy::camera::primitives::Frustum) gizmo visible.
//! A collection of [`MyShape`]s, with their individual
//! [`Aabb`](bevy::camera::primitives::Aabb) gizmos visible, periodically move in and
//! out of the camera's frustum. The [`Aabb`](bevy::camera::primitives::Aabb)
//! gizmos are colored red when they have been culled from [`MyCamera`]'s view.
//! The gizmos change color to green when the shape is considered visible by the
//! camera and would be extracted for rendering.
//!
//! A second active camera, controllable via the [`FreeCameraPlugin`], is used to observe the scene.
//! This second camera's view occupies most of the window. [`MyCamera`]'s view is visible in the
//! bottom right ninth of the screen.
use bevy::{
camera::{
visibility::{VisibilitySystems, VisibleEntities},
Viewport,
},
camera_controller::free_camera::{FreeCamera, FreeCameraPlugin, FreeCameraState},
gizmos::aabb::ShowAabbGizmo,
input::common_conditions::input_just_pressed,
prelude::*,
};
use std::f32::consts::PI;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resizable: false,
..default()
}),
..default()
}),
FreeCameraPlugin,
))
.add_systems(Startup, setup)
.add_systems(
Update,
(
move_shapes,
move_free_camera_to_my_camera.run_if(input_just_pressed(KeyCode::Digit1)),
move_free_camera_to_original_position.run_if(input_just_pressed(KeyCode::Digit2)),
),
)
.add_systems(
// Frustum culling happens in PostUpdate.
// Our system will update the color of aabb's upon reading
// the results of frustum culling after CheckVisibility runs.
PostUpdate,
update_shape_aabb_colors.after(VisibilitySystems::CheckVisibility),
)
.run();
}
/// A marker component for the ring some shapes will rotate on
#[derive(Component)]
struct ShapeRing;
/// A marker component for our shapes so they can be queried separately from the planes.
/// The `ShowAabbGizmo` component will be automatically added to `MyShape` to make their Aabbs
/// visible.
#[derive(Component, Default)]
#[require(ShowAabbGizmo)]
struct MyShape;
/// A marker component for the shape behind the wall.
#[derive(Component)]
#[require(MyShape)]
struct WallShape;
/// A marker component for the camera that is being debugged
/// The `ShowFrustumGizmo` component will be automatically added to `MyCamera` to make
/// its view frustum visible.
#[derive(Component)]
#[require(ShowFrustumGizmo)]
struct MyCamera;
const SHAPE_RING_RADIUS: f32 = 10.0;
const WALL_SHAPE_TIMER_DURATION_SECS: f32 = 8.0;
const FREE_CAMERA_START_TRANSFORM: Transform = Transform::from_xyz(-20., 10., 22.);
const FREE_CAMERA_START_TARGET: Vec3 = Vec3::new(7., 1.5, 0.);
fn setup(
mut commands: Commands,
windows: Query<&Window>,
mut config_store: ResMut<GizmoConfigStore>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) -> Result {
let window = windows.single()?;
// The camera that the user controls to observe the scene.
let free_camera = commands
.spawn((
Camera3d::default(),
FREE_CAMERA_START_TRANSFORM.looking_at(FREE_CAMERA_START_TARGET, Vec3::Y),
FreeCamera::default(),
))
.id();
// The camera that we want to debug frustum culling for. This will be rendered
// as a picture-in-picture in the lower right ninth of the screen.
let my_camera = commands
.spawn((
Camera3d::default(),
Transform::from_xyz(0., 1.5, 0.).looking_at(Vec3::new(1.0, 1.5, 0.), Vec3::Y),
Camera {
order: 1,
// The camera-to-debug's view will be in the lower right ninth of the screen.
viewport: Some(Viewport {
physical_position: window.physical_size() * 2 / 3,
physical_size: window.physical_size() / 3,
..default()
}),
// Do not write the free camera's view rendering back into the P-I-P
msaa_writeback: MsaaWriteback::Off,
..default()
},
MyCamera,
))
.id();
// Instructions placed on top of the free_camera view
commands.spawn((
UiTargetCamera(free_camera),
Node {
width: percent(100),
height: percent(100),
..default()
},
children![(
Text::new(
"This example utilizes free camera controls i.e. move with WASD and mouse grab to change orientation.\n\
Press '1' to move the free camera to where MyCamera is, matching its view frustum.\n\
Press '2' to move the free camera to its initial position in the example.",
),
Node {
position_type: PositionType::Absolute,
top: px(12),
left: px(12),
..default()
},
)]
));
// Label for the picture-in-picture view of MyCamera
commands.spawn((
UiTargetCamera(my_camera),
Node {
width: percent(100),
height: percent(100),
..default()
},
children![(
Text::new("View of MyCamera"),
Node {
position_type: PositionType::Absolute,
bottom: px(12),
right: px(100),
..default()
},
)],
));
// Green Floor Plane
commands.spawn((
Mesh3d(
meshes.add(
Plane3d::default()
.mesh()
.size(SHAPE_RING_RADIUS * 4., SHAPE_RING_RADIUS * 4.),
),
),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
));
// Blue Wall Plane
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(5., 5.))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.3, 0.5))),
Transform::from_xyz(20., 2.5, 10.).with_rotation(Quat::from_rotation_z(PI / 2.)),
));
// Light
commands.spawn((
PointLight {
shadow_maps_enabled: true,
..default()
},
Transform::from_xyz(0.0, 10.0, 0.0),
));
// Configure all AABB's to have a default color of red
let (_, aabb_gizmo_config) = config_store.config_mut::<AabbGizmoConfigGroup>();
aabb_gizmo_config.default_color = Some(Color::LinearRgba(LinearRgba::RED));
// Configure the shapes on the ring that will have their AABB's drawn and updated
let white_matl = materials.add(Color::WHITE);
let shapes = [
meshes.add(Cuboid {
half_size: Vec3::new(2., 0.5, 1.),
}),
meshes.add(Tetrahedron {
vertices: [
Vec3::new(3., 4., 3.),
Vec3::new(-0.5, 4., -0.5),
Vec3::new(-0.5, -0.5, 3.),
Vec3::new(3., -0.5, -0.5),
],
}),
meshes.add(Cylinder {
radius: 0.1,
half_height: 1.5,
}),
meshes.add(Cuboid {
half_size: Vec3::new(1., 0.1, 2.),
}),
meshes.add(Sphere::default().mesh().ico(5).unwrap()),
];
let shapes_len = shapes.len() as f32;
let mut shape_ring = commands.spawn((Transform::default(), Visibility::default(), ShapeRing));
for (i, shape) in shapes.into_iter().enumerate() {
// Space the shapes out evenly along the ring
let shape_angle = i as f32 * 2. * PI / shapes_len;
let (s, c) = ops::sin_cos(shape_angle);
let (x, z) = (SHAPE_RING_RADIUS * c, SHAPE_RING_RADIUS * s);
shape_ring.with_child((
Mesh3d(shape),
MeshMaterial3d(white_matl.clone()),
Transform::from_xyz(x, 1.5, z).with_rotation(Quat::from_rotation_x(-PI / 4.)),
MyShape,
));
}
// Configure the shape that peeks out of the wall plane
let wall_shape = meshes.add(Torus::default());
commands.spawn((
Mesh3d(wall_shape),
MeshMaterial3d(white_matl.clone()),
Transform::from_xyz(25., 1.5, 12.5).with_rotation(Quat::from_rotation_x(-PI / 4.)),
WallShape,
));
Ok(())
}
// A system that:
// - rotates shapes in place
// - moves the ring shapes in a circle around MyCamera
// - moves the wall shape up and down
fn move_shapes(
time: Res<Time>,
mut timer: Local<Timer>,
mut ring_query: Query<&mut Transform, (With<ShapeRing>, Without<MyShape>)>,
mut shape_query: Query<(&mut Transform, Has<WallShape>), (With<MyShape>, Without<ShapeRing>)>,
) -> Result {
// Initialize the wall shape's movement timer on the first run.
if timer.duration().is_zero() {
*timer = Timer::from_seconds(WALL_SHAPE_TIMER_DURATION_SECS, TimerMode::Repeating);
}
timer.tick(time.delta());
let dt = time.delta_secs();
// Rotate the shapes themselves on their own axis
for (mut transform, has_wall_shape) in &mut shape_query {
transform.rotate_y(dt / 2.);
if has_wall_shape {
// the wall shape moves up for 4 seconds and then down for 4 seconds.
// it oscillates between y = 1.5 and 15.0
transform.translation.y = if timer.elapsed_secs() < WALL_SHAPE_TIMER_DURATION_SECS / 2.0
{
1.5 + 15.0 * timer.elapsed_secs() / (WALL_SHAPE_TIMER_DURATION_SECS / 2.0)
} else {
1.5 + 15.0 * (WALL_SHAPE_TIMER_DURATION_SECS - timer.elapsed_secs())
/ (WALL_SHAPE_TIMER_DURATION_SECS / 2.0)
}
}
}
// Rotate the ring
let transform = &mut ring_query.single_mut()?;
transform.rotate_y(dt / 3.);
Ok(())
}
// A system that changes the color of the [`AabbGizmo`](bevy::gizmos::Aabb)
// if they are considered visible by the camera.
fn update_shape_aabb_colors(
view_query: Query<&VisibleEntities, With<MyCamera>>,
mut gizmo_query: Query<&mut ShowAabbGizmo, With<MyShape>>,
) -> Result {
// Reset the color to use the config's default color
for mut shape_gizmo in &mut gizmo_query {
shape_gizmo.color = None;
}
// Query for the shape entities visible for this camera
// Update the gizmo on any such shape entity to be green
let visible_entities = view_query.single()?;
for entity in visible_entities.entities.values().flatten() {
if let Ok(mut shape_gizmo) = gizmo_query.get_mut(*entity) {
shape_gizmo.color = Some(Color::LinearRgba(LinearRgba::GREEN));
}
}
Ok(())
}
// A system that moves the free camera to `MyCamera`, matching its view frustum.
// From here, the camera orientation can be moved to more easily see the transition of
// entities' visibilities with respect to `MyCamera` by looking at the frustum edges.
fn move_free_camera_to_my_camera(
view_query: Query<&Transform, With<MyCamera>>,
free_camera_query: Query<
(&mut Transform, &mut FreeCameraState),
(With<Camera3d>, Without<MyCamera>),
>,
) -> Result {
let my_camera_transform = view_query.single()?;
move_free_camera(*my_camera_transform, free_camera_query)
}
// A system that moves the free camera back to its starting position in the example.
fn move_free_camera_to_original_position(
free_camera_query: Query<
(&mut Transform, &mut FreeCameraState),
(With<Camera3d>, Without<MyCamera>),
>,
) -> Result {
move_free_camera(
FREE_CAMERA_START_TRANSFORM.looking_at(FREE_CAMERA_START_TARGET, Vec3::Y),
free_camera_query,
)
}
fn move_free_camera(
new_transform: Transform,
mut free_camera_query: Query<
(&mut Transform, &mut FreeCameraState),
(With<Camera3d>, Without<MyCamera>),
>,
) -> Result {
let (mut transform, mut state) = free_camera_query.single_mut()?;
*transform = new_transform;
// Update the yaw and pitch so that free camera orientation is updated correctly upon mouse grab
let (yaw, pitch, _roll) = transform.rotation.to_euler(EulerRot::YXZ);
state.yaw = yaw;
state.pitch = pitch;
Ok(())
}