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}