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
7extern 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
46pub 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#[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 .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
125pub trait AppGizmoBuilder {
127 fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self;
131
132 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 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#[derive(Resource, Default)]
206pub struct GizmoHandles {
207 handles: TypeIdMap<Option<Handle<GizmoAsset>>>,
208}
209
210impl GizmoHandles {
211 pub fn handles(&self) -> &TypeIdMap<Option<Handle<GizmoAsset>>> {
213 &self.handles
214 }
215}
216
217pub 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
232pub 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
248pub 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
260pub 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
269pub 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#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
284pub struct GizmoMeshSystems;
285
286fn 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#[derive(Asset, Debug, Clone, TypePath)]
326pub struct GizmoAsset {
327 buffer: GizmoBuffer<ErasedGizmoConfigGroup, ()>,
329 config_ty: TypeId,
330}
331
332impl GizmoAsset {
333 pub fn buffer(&self) -> &GizmoBuffer<ErasedGizmoConfigGroup, ()> {
335 &self.buffer
336 }
337}
338
339impl GizmoAsset {
340 pub fn new() -> Self {
342 GizmoAsset {
343 buffer: GizmoBuffer::default(),
344 config_ty: TypeId::of::<ErasedGizmoConfigGroup>(),
345 }
346 }
347
348 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
360pub fn color_from_entity(entity: Entity) -> Color {
362 Oklcha::sequential_dispersed(entity.index_u32()).into()
363}