Skip to main content

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