Skip to main content

bevy_gizmos/
frustum.rs

1//! Module for the drawing of [`Frustum`]s.
2//!
3//! Camera entities are spawned with a [`Frustum`] component,
4//! which describes a camera's field of vision. With this module,
5//! a camera's frustum can be drawn with gizmos. This is useful
6//! for debugging what a camera can see and what entities in the scene
7//! will be subject to the camera's frustum culling, especially when
8//! combined with drawing [`Aabb`](bevy_camera::primitives::Aabb)
9//! [`gizmos`](crate::aabb).
10//!
11//! There are two ways to enable gizmo drawing of a [`Frustum`]:
12//! 1) The [`ShowFrustumGizmo`] component can be added to individual camera
13//!    entities.
14//! ```rust
15//! # use bevy_gizmos::frustum::ShowFrustumGizmo;
16//! # use bevy_camera::Camera2d;
17//! # use bevy_ecs::system::Commands;
18//! fn setup(mut commands: Commands) {
19//!     commands.spawn((Camera2d, ShowFrustumGizmo::default()));
20//! }
21//! ```
22//! 2) Setting the [`FrustumGizmoConfigGroup`] configuration's `draw_all`
23//!    field to `true` will draw every frustum. Note that this will include
24//!    drawing `bevy_light` `SpotLight` [`Frustum`]s.
25//! ```rust
26//! # use bevy_ecs::prelude::*;
27//! # use bevy_gizmos::prelude::*;
28//! fn turn_on_frustum_gizmos(mut config: ResMut<GizmoConfigStore>) {
29//!    config.config_mut::<FrustumGizmoConfigGroup>().1.draw_all = true;
30//! }
31//! ```
32
33use bevy_app::{Plugin, PostUpdate};
34use bevy_camera::{primitives::Frustum, visibility::VisibilitySystems};
35use bevy_color::Color;
36use bevy_ecs::{
37    component::Component,
38    entity::Entity,
39    query::Without,
40    reflect::ReflectComponent,
41    schedule::{IntoScheduleConfigs, SystemSet},
42    system::{Query, Res},
43};
44use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectFromReflect};
45
46use crate::{
47    color_from_entity,
48    config::{GizmoConfigGroup, GizmoConfigStore},
49    gizmos::Gizmos,
50    AppGizmoBuilder,
51};
52
53/// A [`Plugin`] that provides visualization of [`Frustum`]s for debugging.
54pub struct FrustumGizmoPlugin;
55
56/// Frustum Gizmo system set. This exists in [`PostUpdate`].
57#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
58pub struct FrustumGizmoSystems;
59
60impl Plugin for FrustumGizmoPlugin {
61    fn build(&self, app: &mut bevy_app::App) {
62        app.init_gizmo_group::<FrustumGizmoConfigGroup>()
63            .add_systems(
64                PostUpdate,
65                (
66                    draw_frustum_gizmos,
67                    draw_all_frustum_gizmos.run_if(|config: Res<GizmoConfigStore>| {
68                        config.config::<FrustumGizmoConfigGroup>().1.draw_all
69                    }),
70                )
71                    .in_set(FrustumGizmoSystems)
72                    .after(VisibilitySystems::UpdateFrusta),
73            );
74    }
75}
76
77/// The [`GizmoConfigGroup`] used for debug visualizations of [`Frustum`] components on entities
78#[derive(Clone, Default, Reflect, GizmoConfigGroup)]
79#[reflect(Clone, Default)]
80pub struct FrustumGizmoConfigGroup {
81    /// Draws all frusta in the scene when set to `true`.
82    ///
83    /// To draw a specific entity's frustum, you can add the [`ShowFrustumGizmo`] component.
84    ///
85    /// Defaults to `false`.
86    pub draw_all: bool,
87    /// The default color for frustum gizmos.
88    ///
89    /// A random color is chosen per frustum if `None`.
90    ///
91    /// Defaults to `None`.
92    pub default_color: Option<Color>,
93}
94
95/// Add this [`Component`] to an entity to draw its [`Frustum`] component.
96#[derive(Component, Reflect, Default, Debug)]
97#[reflect(Component, FromReflect, Default)]
98pub struct ShowFrustumGizmo {
99    /// The color of the frustum.
100    ///
101    /// The default color from the [`FrustumGizmoConfigGroup`] resource is used if `None`,
102    pub color: Option<Color>,
103}
104
105fn draw_frustum_gizmos(
106    query: Query<(Entity, &Frustum, &ShowFrustumGizmo)>,
107    mut gizmos: Gizmos<FrustumGizmoConfigGroup>,
108) {
109    for (entity, &frustum, gizmo) in &query {
110        let color = gizmo
111            .color
112            .or(gizmos.config_ext.default_color)
113            .unwrap_or_else(|| color_from_entity(entity));
114
115        frustum_inner(&frustum, color, &mut gizmos);
116    }
117}
118
119fn draw_all_frustum_gizmos(
120    query: Query<(Entity, &Frustum), Without<ShowFrustumGizmo>>,
121    mut gizmos: Gizmos<FrustumGizmoConfigGroup>,
122) {
123    for (entity, &frustum) in &query {
124        let color = gizmos
125            .config_ext
126            .default_color
127            .unwrap_or_else(|| color_from_entity(entity));
128
129        frustum_inner(&frustum, color, &mut gizmos);
130    }
131}
132
133fn frustum_inner(frustum: &Frustum, color: Color, gizmos: &mut Gizmos<FrustumGizmoConfigGroup>) {
134    let Some([tln, trn, brn, bln, tlf, trf, brf, blf]) = frustum.corners() else {
135        return;
136    };
137
138    gizmos.linestrip(
139        [
140            tln, trn, brn, bln, // Near
141            tln, tlf, // Top Left Near to Far
142            trf, brf, blf, tlf, // Far
143        ],
144        color,
145    );
146    gizmos.line(trn, trf, color); // Top Right Near to Far
147    gizmos.line(brn, brf, color); // Bottom Right Near to Far
148    gizmos.line(bln, blf, color); // Bottom Left Near to Far
149}