bevy_gizmos/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc(
3    html_logo_url = "https://bevy.org/assets/icon.png",
4    html_favicon_url = "https://bevy.org/assets/icon.png"
5)]
6
7//! This crate adds an immediate mode drawing api to Bevy for visual debugging.
8//!
9//! # Example
10//! ```
11//! # use bevy_gizmos::prelude::*;
12//! # use bevy_math::prelude::*;
13//! # use bevy_color::palettes::basic::GREEN;
14//! fn system(mut gizmos: Gizmos) {
15//!     gizmos.line(Vec3::ZERO, Vec3::X, GREEN);
16//! }
17//! # bevy_ecs::system::assert_is_system(system);
18//! ```
19//!
20//! See the documentation on [Gizmos](crate::gizmos::Gizmos) for more examples.
21
22// Required to make proc macros work in bevy itself.
23extern crate self as bevy_gizmos;
24
25pub mod aabb;
26pub mod arcs;
27pub mod arrows;
28pub mod circles;
29pub mod config;
30pub mod cross;
31pub mod curves;
32pub mod gizmos;
33mod global;
34pub mod grid;
35pub mod primitives;
36pub mod retained;
37pub mod rounded_box;
38
39#[cfg(feature = "bevy_light")]
40pub mod light;
41
42/// The gizmos prelude.
43///
44/// This includes the most common types in this crate, re-exported for your convenience.
45pub mod prelude {
46    #[doc(hidden)]
47    pub use crate::aabb::{AabbGizmoConfigGroup, ShowAabbGizmo};
48
49    #[doc(hidden)]
50    pub use crate::{
51        config::{
52            DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
53            GizmoLineConfig, GizmoLineJoint, GizmoLineStyle,
54        },
55        gizmos::Gizmos,
56        global::gizmo,
57        primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d},
58        retained::Gizmo,
59        AppGizmoBuilder, GizmoAsset,
60    };
61
62    #[doc(hidden)]
63    #[cfg(feature = "bevy_light")]
64    pub use crate::light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo};
65}
66
67use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop};
68use bevy_asset::{Asset, AssetApp, Assets, Handle};
69use bevy_ecs::{
70    resource::Resource,
71    schedule::{IntoScheduleConfigs, SystemSet},
72    system::{Res, ResMut},
73};
74use bevy_reflect::TypePath;
75
76use crate::{config::ErasedGizmoConfigGroup, gizmos::GizmoBuffer};
77
78use bevy_time::Fixed;
79use bevy_utils::TypeIdMap;
80use config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore};
81use core::{any::TypeId, marker::PhantomData, mem};
82use gizmos::{GizmoStorage, Swap};
83#[cfg(feature = "bevy_light")]
84use light::LightGizmoPlugin;
85
86/// A [`Plugin`] that provides an immediate mode drawing api for visual debugging.
87#[derive(Default)]
88pub struct GizmoPlugin;
89
90impl Plugin for GizmoPlugin {
91    fn build(&self, app: &mut App) {
92        app.init_asset::<GizmoAsset>()
93            .init_resource::<GizmoHandles>()
94            // We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist.
95            .init_gizmo_group::<DefaultGizmoConfigGroup>();
96
97        app.add_plugins((aabb::AabbGizmoPlugin, global::GlobalGizmosPlugin));
98
99        #[cfg(feature = "bevy_light")]
100        app.add_plugins(LightGizmoPlugin);
101    }
102}
103
104/// A extension trait adding `App::init_gizmo_group` and `App::insert_gizmo_config`.
105pub trait AppGizmoBuilder {
106    /// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos&lt;Config&gt;](crate::gizmos::Gizmos).
107    ///
108    /// Configurations can be set using the [`GizmoConfigStore`] [`Resource`].
109    fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self;
110
111    /// Insert a [`GizmoConfig`] into a specific [`GizmoConfigGroup`].
112    ///
113    /// This method should be preferred over [`AppGizmoBuilder::init_gizmo_group`] if and only if you need to configure fields upon initialization.
114    fn insert_gizmo_config<Config: GizmoConfigGroup>(
115        &mut self,
116        group: Config,
117        config: GizmoConfig,
118    ) -> &mut Self;
119}
120
121impl AppGizmoBuilder for App {
122    fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self {
123        if self.world().contains_resource::<GizmoStorage<Config, ()>>() {
124            return self;
125        }
126
127        self.world_mut()
128            .get_resource_or_init::<GizmoConfigStore>()
129            .register::<Config>();
130
131        let mut handles = self.world_mut().get_resource_or_init::<GizmoHandles>();
132
133        handles.handles.insert(TypeId::of::<Config>(), None);
134
135        // These handles are safe to mutate in any order
136        self.allow_ambiguous_resource::<GizmoHandles>();
137
138        self.init_resource::<GizmoStorage<Config, ()>>()
139            .init_resource::<GizmoStorage<Config, Fixed>>()
140            .init_resource::<GizmoStorage<Config, Swap<Fixed>>>()
141            .add_systems(
142                RunFixedMainLoop,
143                start_gizmo_context::<Config, Fixed>
144                    .in_set(bevy_app::RunFixedMainLoopSystems::BeforeFixedMainLoop),
145            )
146            .add_systems(FixedFirst, clear_gizmo_context::<Config, Fixed>)
147            .add_systems(FixedLast, collect_requested_gizmos::<Config, Fixed>)
148            .add_systems(
149                RunFixedMainLoop,
150                end_gizmo_context::<Config, Fixed>
151                    .in_set(bevy_app::RunFixedMainLoopSystems::AfterFixedMainLoop),
152            )
153            .add_systems(
154                Last,
155                (
156                    propagate_gizmos::<Config, Fixed>.before(GizmoMeshSystems),
157                    update_gizmo_meshes::<Config>.in_set(GizmoMeshSystems),
158                ),
159            );
160
161        self
162    }
163
164    fn insert_gizmo_config<Config: GizmoConfigGroup>(
165        &mut self,
166        group: Config,
167        config: GizmoConfig,
168    ) -> &mut Self {
169        self.init_gizmo_group::<Config>();
170
171        self.world_mut()
172            .get_resource_or_init::<GizmoConfigStore>()
173            .insert(config, group);
174
175        self
176    }
177}
178
179/// Holds handles to the line gizmos for each gizmo configuration group
180// As `TypeIdMap` iteration order depends on the order of insertions and deletions, this uses
181// `Option<Handle>` to be able to reserve the slot when creating the gizmo configuration group.
182// That way iteration order is stable across executions and depends on the order of configuration
183// group creation.
184#[derive(Resource, Default)]
185pub struct GizmoHandles {
186    handles: TypeIdMap<Option<Handle<GizmoAsset>>>,
187}
188
189impl GizmoHandles {
190    /// The handles to the gizmo assets of each gizmo configuration group.
191    pub fn handles(&self) -> &TypeIdMap<Option<Handle<GizmoAsset>>> {
192        &self.handles
193    }
194}
195
196/// Start a new gizmo clearing context.
197///
198/// Internally this pushes the parent default context into a swap buffer.
199/// Gizmo contexts should be handled like a stack, so if you push a new context,
200/// you must pop the context before the parent context ends.
201pub fn start_gizmo_context<Config, Clear>(
202    mut swap: ResMut<GizmoStorage<Config, Swap<Clear>>>,
203    mut default: ResMut<GizmoStorage<Config, ()>>,
204) where
205    Config: GizmoConfigGroup,
206    Clear: 'static + Send + Sync,
207{
208    default.swap(&mut *swap);
209}
210
211/// End this gizmo clearing context.
212///
213/// Pop the default gizmos context out of the [`Swap<Clear>`] gizmo storage.
214///
215/// This must be called before [`GizmoMeshSystems`] in the [`Last`] schedule.
216pub fn end_gizmo_context<Config, Clear>(
217    mut swap: ResMut<GizmoStorage<Config, Swap<Clear>>>,
218    mut default: ResMut<GizmoStorage<Config, ()>>,
219) where
220    Config: GizmoConfigGroup,
221    Clear: 'static + Send + Sync,
222{
223    default.clear();
224    default.swap(&mut *swap);
225}
226
227/// Collect the requested gizmos into a specific clear context.
228pub fn collect_requested_gizmos<Config, Clear>(
229    mut update: ResMut<GizmoStorage<Config, ()>>,
230    mut context: ResMut<GizmoStorage<Config, Clear>>,
231) where
232    Config: GizmoConfigGroup,
233    Clear: 'static + Send + Sync,
234{
235    context.append_storage(&update);
236    update.clear();
237}
238
239/// Clear out the contextual gizmos.
240pub fn clear_gizmo_context<Config, Clear>(mut context: ResMut<GizmoStorage<Config, Clear>>)
241where
242    Config: GizmoConfigGroup,
243    Clear: 'static + Send + Sync,
244{
245    context.clear();
246}
247
248/// Propagate the contextual gizmo into the `Update` storage for rendering.
249///
250/// This should be before [`GizmoMeshSystems`].
251pub fn propagate_gizmos<Config, Clear>(
252    mut update_storage: ResMut<GizmoStorage<Config, ()>>,
253    contextual_storage: Res<GizmoStorage<Config, Clear>>,
254) where
255    Config: GizmoConfigGroup,
256    Clear: 'static + Send + Sync,
257{
258    update_storage.append_storage(&*contextual_storage);
259}
260
261/// System set for updating the rendering meshes for drawing gizmos.
262#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
263pub struct GizmoMeshSystems;
264
265/// Prepare gizmos for rendering.
266///
267/// This also clears the default `GizmoStorage`.
268fn update_gizmo_meshes<Config: GizmoConfigGroup>(
269    mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
270    mut handles: ResMut<GizmoHandles>,
271    mut storage: ResMut<GizmoStorage<Config, ()>>,
272) {
273    if storage.list_positions.is_empty() && storage.strip_positions.is_empty() {
274        handles.handles.insert(TypeId::of::<Config>(), None);
275    } else if let Some(handle) = handles.handles.get_mut(&TypeId::of::<Config>()) {
276        if let Some(handle) = handle {
277            let gizmo = gizmo_assets.get_mut(handle.id()).unwrap();
278
279            gizmo.buffer.list_positions = mem::take(&mut storage.list_positions);
280            gizmo.buffer.list_colors = mem::take(&mut storage.list_colors);
281            gizmo.buffer.strip_positions = mem::take(&mut storage.strip_positions);
282            gizmo.buffer.strip_colors = mem::take(&mut storage.strip_colors);
283        } else {
284            let gizmo = GizmoAsset {
285                config_ty: TypeId::of::<Config>(),
286                buffer: GizmoBuffer {
287                    enabled: true,
288                    list_positions: mem::take(&mut storage.list_positions),
289                    list_colors: mem::take(&mut storage.list_colors),
290                    strip_positions: mem::take(&mut storage.strip_positions),
291                    strip_colors: mem::take(&mut storage.strip_colors),
292                    marker: PhantomData,
293                },
294            };
295
296            *handle = Some(gizmo_assets.add(gizmo));
297        }
298    }
299}
300
301/// A collection of gizmos.
302///
303/// Has the same gizmo drawing API as [`Gizmos`](crate::gizmos::Gizmos).
304#[derive(Asset, Debug, Clone, TypePath)]
305pub struct GizmoAsset {
306    /// vertex buffers.
307    buffer: GizmoBuffer<ErasedGizmoConfigGroup, ()>,
308    config_ty: TypeId,
309}
310
311impl GizmoAsset {
312    /// A reference to the gizmo's vertex buffer.
313    pub fn buffer(&self) -> &GizmoBuffer<ErasedGizmoConfigGroup, ()> {
314        &self.buffer
315    }
316}
317
318impl GizmoAsset {
319    /// Create a new [`GizmoAsset`].
320    pub fn new() -> Self {
321        GizmoAsset {
322            buffer: GizmoBuffer::default(),
323            config_ty: TypeId::of::<ErasedGizmoConfigGroup>(),
324        }
325    }
326
327    /// The type of the gizmo's configuration group.
328    pub fn config_typeid(&self) -> TypeId {
329        self.config_ty
330    }
331}
332
333impl Default for GizmoAsset {
334    fn default() -> Self {
335        GizmoAsset::new()
336    }
337}