Path: blob/main/examples/usage/debug_frustum_culling.rs
30632 views
//! This example demonstrates how to debug and visualize frustum culling,1//! a process (and Bevy [`system`](bevy::camera::visibility::check_visibility)) that determines2//! which entities are visible and should be rendered within3//! a camera's view. If an entity's [`Aabb`](bevy::camera::primitives::Aabb)4//! (Axis-Aligned Bounding Box) does not intersect with a camera's5//! [`Frustum`](bevy::camera::primitives::Frustum), that entity is said to be "culled"6//! from the camera's view frustum.7//!8//! To debug and visualize frustum culling, this example uses Aabb and Frustum gizmos provided9//! by bevy's [`Gizmo`] library. [`Aabb gizmos`](bevy::gizmos::aabb) are used to visualize the10//! [`Aabb`](bevy::camera::primitives::Aabb) of entities.11//! [`Frustum gizmos`](bevy::gizmos::frustum) are used to visualize the12//! [`Frustum`](bevy::camera::primitives::Frustum) of a camera.13//! Both can be used together to visualize which entities have been culled14//! from a given camera's view, which entities are visible, and when15//! that change happens during an entity's Aabb interaction with a camera's Frustum.16//!17//! This example contains a scene with a camera `MyCamera` that has its18//! [`Frustum`](bevy::camera::primitives::Frustum) gizmo visible.19//! A collection of `MyShape`s, with their individual20//! [`Aabb`](bevy::camera::primitives::Aabb) gizmos visible, periodically move in and21//! out of the camera's frustum. The [`Aabb`](bevy::camera::primitives::Aabb)22//! gizmos are colored red when they have been culled from `MyCamera`'s view.23//! The gizmos change color to green when the shape is considered visible by the24//! camera and would be extracted for rendering.25//!26//! A second active camera, controllable via the [`FreeCameraPlugin`], is used to observe the scene.27//! This second camera's view occupies most of the window. `MyCamera`'s view is visible in the28//! bottom right ninth of the screen.2930use bevy::{31camera::{32visibility::{VisibilitySystems, VisibleEntities},33Viewport,34},35camera_controller::free_camera::{FreeCamera, FreeCameraPlugin, FreeCameraState},36gizmos::aabb::ShowAabbGizmo,37input::common_conditions::input_just_pressed,38prelude::*,39};40use std::f32::consts::PI;4142fn main() {43App::new()44.add_plugins((45DefaultPlugins.set(WindowPlugin {46primary_window: Some(Window {47resizable: false,48..default()49}),50..default()51}),52FreeCameraPlugin,53))54.add_systems(Startup, setup)55.add_systems(56Update,57(58move_shapes,59move_free_camera_to_my_camera.run_if(input_just_pressed(KeyCode::Digit1)),60move_free_camera_to_original_position.run_if(input_just_pressed(KeyCode::Digit2)),61),62)63.add_systems(64// Frustum culling happens in PostUpdate.65// Our system will update the color of aabb's upon reading66// the results of frustum culling after CheckVisibility runs.67PostUpdate,68update_shape_aabb_colors.after(VisibilitySystems::CheckVisibility),69)70.run();71}7273/// A marker component for the ring some shapes will rotate on74#[derive(Component)]75struct ShapeRing;7677/// A marker component for our shapes so they can be queried separately from the planes.78/// The `ShowAabbGizmo` component will be automatically added to `MyShape` to make their Aabbs79/// visible.80#[derive(Component, Default)]81#[require(ShowAabbGizmo)]82struct MyShape;8384/// A marker component for the shape behind the wall.85#[derive(Component)]86#[require(MyShape)]87struct WallShape;8889/// A marker component for the camera that is being debugged90/// The `ShowFrustumGizmo` component will be automatically added to `MyCamera` to make91/// its view frustum visible.92#[derive(Component)]93#[require(ShowFrustumGizmo)]94struct MyCamera;9596const SHAPE_RING_RADIUS: f32 = 10.0;97const WALL_SHAPE_TIMER_DURATION_SECS: f32 = 8.0;98const FREE_CAMERA_START_TRANSFORM: Transform = Transform::from_xyz(-20., 10., 22.);99const FREE_CAMERA_START_TARGET: Vec3 = Vec3::new(7., 1.5, 0.);100101fn setup(102mut commands: Commands,103windows: Query<&Window>,104mut config_store: ResMut<GizmoConfigStore>,105mut meshes: ResMut<Assets<Mesh>>,106mut materials: ResMut<Assets<StandardMaterial>>,107) -> Result {108let window = windows.single()?;109// The camera that the user controls to observe the scene.110let free_camera = commands111.spawn((112Camera3d::default(),113FREE_CAMERA_START_TRANSFORM.looking_at(FREE_CAMERA_START_TARGET, Vec3::Y),114FreeCamera::default(),115))116.id();117118// The camera that we want to debug frustum culling for. This will be rendered119// as a picture-in-picture in the lower right ninth of the screen.120let my_camera = commands121.spawn((122Camera3d::default(),123Transform::from_xyz(0., 1.5, 0.).looking_at(Vec3::new(1.0, 1.5, 0.), Vec3::Y),124Camera {125order: 1,126// The camera-to-debug's view will be in the lower right ninth of the screen.127viewport: Some(Viewport {128physical_position: window.physical_size() * 2 / 3,129physical_size: window.physical_size() / 3,130..default()131}),132// Do not write the free camera's view rendering back into the P-I-P133msaa_writeback: MsaaWriteback::Off,134..default()135},136MyCamera,137))138.id();139140// Instructions placed on top of the free_camera view141commands.spawn((142UiTargetCamera(free_camera),143Node {144width: percent(100),145height: percent(100),146..default()147},148children![(149Text::new(150"This example utilizes free camera controls i.e. move with WASD and mouse grab to change orientation.\n\151Press '1' to move the free camera to where MyCamera is, matching its view frustum.\n\152Press '2' to move the free camera to its initial position in the example.",153),154Node {155position_type: PositionType::Absolute,156top: px(12),157left: px(12),158..default()159},160)]161));162// Label for the picture-in-picture view of MyCamera163commands.spawn((164UiTargetCamera(my_camera),165Node {166width: percent(100),167height: percent(100),168..default()169},170children![(171Text::new("View of MyCamera"),172Node {173position_type: PositionType::Absolute,174bottom: px(12),175right: px(100),176..default()177},178)],179));180181// Green Floor Plane182commands.spawn((183Mesh3d(184meshes.add(185Plane3d::default()186.mesh()187.size(SHAPE_RING_RADIUS * 4., SHAPE_RING_RADIUS * 4.),188),189),190MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),191));192// Blue Wall Plane193commands.spawn((194Mesh3d(meshes.add(Plane3d::default().mesh().size(5., 5.))),195MeshMaterial3d(materials.add(Color::srgb(0.3, 0.3, 0.5))),196Transform::from_xyz(20., 2.5, 10.).with_rotation(Quat::from_rotation_z(PI / 2.)),197));198// Light199commands.spawn((200PointLight {201shadow_maps_enabled: true,202..default()203},204Transform::from_xyz(0.0, 10.0, 0.0),205));206207// Configure all AABB's to have a default color of red208let (_, aabb_gizmo_config) = config_store.config_mut::<AabbGizmoConfigGroup>();209aabb_gizmo_config.default_color = Some(Color::LinearRgba(LinearRgba::RED));210211// Configure the shapes on the ring that will have their AABB's drawn and updated212let white_matl = materials.add(Color::WHITE);213let shapes = [214meshes.add(Cuboid {215half_size: Vec3::new(2., 0.5, 1.),216}),217meshes.add(Tetrahedron {218vertices: [219Vec3::new(3., 4., 3.),220Vec3::new(-0.5, 4., -0.5),221Vec3::new(-0.5, -0.5, 3.),222Vec3::new(3., -0.5, -0.5),223],224}),225meshes.add(Cylinder {226radius: 0.1,227half_height: 1.5,228}),229meshes.add(Cuboid {230half_size: Vec3::new(1., 0.1, 2.),231}),232meshes.add(Sphere::default().mesh().ico(5).unwrap()),233];234let shapes_len = shapes.len() as f32;235let mut shape_ring = commands.spawn((Transform::default(), Visibility::default(), ShapeRing));236for (i, shape) in shapes.into_iter().enumerate() {237// Space the shapes out evenly along the ring238let shape_angle = i as f32 * 2. * PI / shapes_len;239let (s, c) = ops::sin_cos(shape_angle);240let (x, z) = (SHAPE_RING_RADIUS * c, SHAPE_RING_RADIUS * s);241shape_ring.with_child((242Mesh3d(shape),243MeshMaterial3d(white_matl.clone()),244Transform::from_xyz(x, 1.5, z).with_rotation(Quat::from_rotation_x(-PI / 4.)),245MyShape,246));247}248249// Configure the shape that peeks out of the wall plane250let wall_shape = meshes.add(Torus::default());251commands.spawn((252Mesh3d(wall_shape),253MeshMaterial3d(white_matl.clone()),254Transform::from_xyz(25., 1.5, 12.5).with_rotation(Quat::from_rotation_x(-PI / 4.)),255WallShape,256));257258Ok(())259}260261// A system that:262// - rotates shapes in place263// - moves the ring shapes in a circle around MyCamera264// - moves the wall shape up and down265fn move_shapes(266time: Res<Time>,267mut timer: Local<Timer>,268mut ring_query: Query<&mut Transform, (With<ShapeRing>, Without<MyShape>)>,269mut shape_query: Query<(&mut Transform, Has<WallShape>), (With<MyShape>, Without<ShapeRing>)>,270) -> Result {271// Initialize the wall shape's movement timer on the first run.272if timer.duration().is_zero() {273*timer = Timer::from_seconds(WALL_SHAPE_TIMER_DURATION_SECS, TimerMode::Repeating);274}275timer.tick(time.delta());276let dt = time.delta_secs();277278// Rotate the shapes themselves on their own axis279for (mut transform, has_wall_shape) in &mut shape_query {280transform.rotate_y(dt / 2.);281if has_wall_shape {282// the wall shape moves up for 4 seconds and then down for 4 seconds.283// it oscillates between y = 1.5 and 15.0284transform.translation.y = if timer.elapsed_secs() < WALL_SHAPE_TIMER_DURATION_SECS / 2.0285{2861.5 + 15.0 * timer.elapsed_secs() / (WALL_SHAPE_TIMER_DURATION_SECS / 2.0)287} else {2881.5 + 15.0 * (WALL_SHAPE_TIMER_DURATION_SECS - timer.elapsed_secs())289/ (WALL_SHAPE_TIMER_DURATION_SECS / 2.0)290}291}292}293294// Rotate the ring295let transform = &mut ring_query.single_mut()?;296transform.rotate_y(dt / 3.);297298Ok(())299}300301// A system that changes the color of the [`AabbGizmo`](bevy::gizmos::Aabb)302// if they are considered visible by the camera.303fn update_shape_aabb_colors(304view_query: Query<&VisibleEntities, With<MyCamera>>,305mut gizmo_query: Query<&mut ShowAabbGizmo, With<MyShape>>,306) -> Result {307// Reset the color to use the config's default color308for mut shape_gizmo in &mut gizmo_query {309shape_gizmo.color = None;310}311312// Query for the shape entities visible for this camera313// Update the gizmo on any such shape entity to be green314let visible_entities = view_query.single()?;315for entity in visible_entities.entities.values().flatten() {316if let Ok(mut shape_gizmo) = gizmo_query.get_mut(*entity) {317shape_gizmo.color = Some(Color::LinearRgba(LinearRgba::GREEN));318}319}320Ok(())321}322323// A system that moves the free camera to `MyCamera`, matching its view frustum.324// From here, the camera orientation can be moved to more easily see the transition of325// entities' visibilities with respect to `MyCamera` by looking at the frustum edges.326fn move_free_camera_to_my_camera(327view_query: Query<&Transform, With<MyCamera>>,328free_camera_query: Query<329(&mut Transform, &mut FreeCameraState),330(With<Camera3d>, Without<MyCamera>),331>,332) -> Result {333let my_camera_transform = view_query.single()?;334move_free_camera(*my_camera_transform, free_camera_query)335}336337// A system that moves the free camera back to its starting position in the example.338fn move_free_camera_to_original_position(339free_camera_query: Query<340(&mut Transform, &mut FreeCameraState),341(With<Camera3d>, Without<MyCamera>),342>,343) -> Result {344move_free_camera(345FREE_CAMERA_START_TRANSFORM.looking_at(FREE_CAMERA_START_TARGET, Vec3::Y),346free_camera_query,347)348}349350fn move_free_camera(351new_transform: Transform,352mut free_camera_query: Query<353(&mut Transform, &mut FreeCameraState),354(With<Camera3d>, Without<MyCamera>),355>,356) -> Result {357let (mut transform, mut state) = free_camera_query.single_mut()?;358*transform = new_transform;359360// Update the yaw and pitch so that free camera orientation is updated correctly upon mouse grab361let (yaw, pitch, _roll) = transform.rotation.to_euler(EulerRot::YXZ);362state.yaw = yaw;363state.pitch = pitch;364365Ok(())366}367368369