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
25/// System set label for the systems handling the rendering of gizmos.
26#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)]
27pub enum GizmoRenderSystems {
28    /// Adds gizmos to the [`Transparent2d`](bevy_core_pipeline::core_2d::Transparent2d) render phase
29    #[cfg(feature = "bevy_sprite_render")]
30    QueueLineGizmos2d,
31    /// Adds gizmos to the [`Transparent3d`](bevy_core_pipeline::core_3d::Transparent3d) render phase
32    #[cfg(feature = "bevy_pbr")]
33    QueueLineGizmos3d,
34}
35
36/// Deprecated alias for [`GizmoRenderSystems`].
37#[deprecated(since = "0.17.0", note = "Renamed to `GizmoRenderSystems`.")]
38pub type GizmoRenderSystem = GizmoRenderSystems;
39
40#[cfg(feature = "bevy_render")]
41pub mod aabb;
42pub mod arcs;
43pub mod arrows;
44pub mod circles;
45pub mod config;
46pub mod cross;
47pub mod curves;
48pub mod gizmos;
49pub mod grid;
50pub mod primitives;
51pub mod retained;
52pub mod rounded_box;
53
54#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
55pub mod light;
56
57#[cfg(all(feature = "bevy_sprite_render", feature = "bevy_render"))]
58mod pipeline_2d;
59#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
60mod pipeline_3d;
61
62/// The gizmos prelude.
63///
64/// This includes the most common types in this crate, re-exported for your convenience.
65pub mod prelude {
66    #[cfg(feature = "bevy_render")]
67    pub use crate::aabb::{AabbGizmoConfigGroup, ShowAabbGizmo};
68
69    #[doc(hidden)]
70    pub use crate::{
71        config::{
72            DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
73            GizmoLineConfig, GizmoLineJoint, GizmoLineStyle,
74        },
75        gizmos::Gizmos,
76        primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d},
77        retained::Gizmo,
78        AppGizmoBuilder, GizmoAsset,
79    };
80
81    #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
82    pub use crate::light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo};
83}
84
85use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop};
86use bevy_asset::{Asset, AssetApp, Assets, Handle};
87use bevy_ecs::{
88    resource::Resource,
89    schedule::{IntoScheduleConfigs, SystemSet},
90    system::{Res, ResMut},
91};
92use bevy_reflect::TypePath;
93
94#[cfg(all(
95    feature = "bevy_render",
96    any(feature = "bevy_pbr", feature = "bevy_sprite_render")
97))]
98use {crate::config::GizmoMeshConfig, bevy_mesh::VertexBufferLayout};
99
100use crate::{config::ErasedGizmoConfigGroup, gizmos::GizmoBuffer};
101
102#[cfg(feature = "bevy_render")]
103use {
104    crate::retained::extract_linegizmos,
105    bevy_asset::AssetId,
106    bevy_ecs::{
107        component::Component,
108        entity::Entity,
109        query::ROQueryItem,
110        system::{
111            lifetimeless::{Read, SRes},
112            Commands, SystemParamItem,
113        },
114    },
115    bevy_math::{Affine3, Affine3A, Vec4},
116    bevy_render::{
117        extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
118        render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
119        render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
120        render_resource::{
121            binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout,
122            BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, ShaderStages,
123            ShaderType, VertexFormat,
124        },
125        renderer::RenderDevice,
126        sync_world::{MainEntity, TemporaryRenderEntity},
127        Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,
128    },
129    bytemuck::cast_slice,
130};
131
132#[cfg(all(
133    feature = "bevy_render",
134    any(feature = "bevy_pbr", feature = "bevy_sprite_render"),
135))]
136use bevy_render::render_resource::{VertexAttribute, VertexStepMode};
137use bevy_time::Fixed;
138use bevy_utils::TypeIdMap;
139#[cfg(feature = "bevy_render")]
140use config::GizmoLineJoint;
141use config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore};
142use core::{any::TypeId, marker::PhantomData, mem};
143use gizmos::{GizmoStorage, Swap};
144#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
145use light::LightGizmoPlugin;
146
147/// A [`Plugin`] that provides an immediate mode drawing api for visual debugging.
148///
149/// Requires to be loaded after [`PbrPlugin`](bevy_pbr::PbrPlugin) or [`SpriteRenderPlugin`](bevy_sprite_render::SpriteRenderPlugin).
150#[derive(Default)]
151pub struct GizmoPlugin;
152
153impl Plugin for GizmoPlugin {
154    fn build(&self, app: &mut App) {
155        #[cfg(feature = "bevy_render")]
156        {
157            use bevy_asset::embedded_asset;
158            embedded_asset!(app, "lines.wgsl");
159            embedded_asset!(app, "line_joints.wgsl");
160        }
161
162        app.init_asset::<GizmoAsset>()
163            .init_resource::<GizmoHandles>()
164            // We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist.
165            .init_gizmo_group::<DefaultGizmoConfigGroup>();
166
167        #[cfg(feature = "bevy_render")]
168        app.add_plugins(aabb::AabbGizmoPlugin)
169            .add_plugins(UniformComponentPlugin::<LineGizmoUniform>::default())
170            .add_plugins(RenderAssetPlugin::<GpuLineGizmo>::default());
171
172        #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
173        app.add_plugins(LightGizmoPlugin);
174
175        #[cfg(feature = "bevy_render")]
176        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
177            render_app.add_systems(RenderStartup, init_line_gizmo_uniform_bind_group_layout);
178
179            render_app.add_systems(
180                Render,
181                prepare_line_gizmo_bind_group.in_set(RenderSystems::PrepareBindGroups),
182            );
183
184            render_app.add_systems(ExtractSchedule, (extract_gizmo_data, extract_linegizmos));
185
186            #[cfg(feature = "bevy_sprite_render")]
187            if app.is_plugin_added::<bevy_sprite_render::SpriteRenderPlugin>() {
188                app.add_plugins(pipeline_2d::LineGizmo2dPlugin);
189            } else {
190                tracing::warn!("bevy_sprite_render feature is enabled but bevy_sprite_render::SpriteRenderPlugin was not detected. Are you sure you loaded GizmoPlugin after SpriteRenderPlugin?");
191            }
192            #[cfg(feature = "bevy_pbr")]
193            if app.is_plugin_added::<bevy_pbr::PbrPlugin>() {
194                app.add_plugins(pipeline_3d::LineGizmo3dPlugin);
195            } else {
196                tracing::warn!("bevy_pbr feature is enabled but bevy_pbr::PbrPlugin was not detected. Are you sure you loaded GizmoPlugin after PbrPlugin?");
197            }
198        } else {
199            tracing::warn!("bevy_render feature is enabled but RenderApp was not detected. Are you sure you loaded GizmoPlugin after RenderPlugin?");
200        }
201    }
202}
203
204/// A extension trait adding `App::init_gizmo_group` and `App::insert_gizmo_config`.
205pub trait AppGizmoBuilder {
206    /// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos&lt;Config&gt;](crate::gizmos::Gizmos).
207    ///
208    /// Configurations can be set using the [`GizmoConfigStore`] [`Resource`].
209    fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self;
210
211    /// Insert a [`GizmoConfig`] into a specific [`GizmoConfigGroup`].
212    ///
213    /// This method should be preferred over [`AppGizmoBuilder::init_gizmo_group`] if and only if you need to configure fields upon initialization.
214    fn insert_gizmo_config<Config: GizmoConfigGroup>(
215        &mut self,
216        group: Config,
217        config: GizmoConfig,
218    ) -> &mut Self;
219}
220
221impl AppGizmoBuilder for App {
222    fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self {
223        if self.world().contains_resource::<GizmoStorage<Config, ()>>() {
224            return self;
225        }
226
227        self.world_mut()
228            .get_resource_or_init::<GizmoConfigStore>()
229            .register::<Config>();
230
231        let mut handles = self.world_mut().get_resource_or_init::<GizmoHandles>();
232
233        handles.handles.insert(TypeId::of::<Config>(), None);
234
235        // These handles are safe to mutate in any order
236        self.allow_ambiguous_resource::<GizmoHandles>();
237
238        self.init_resource::<GizmoStorage<Config, ()>>()
239            .init_resource::<GizmoStorage<Config, Fixed>>()
240            .init_resource::<GizmoStorage<Config, Swap<Fixed>>>()
241            .add_systems(
242                RunFixedMainLoop,
243                start_gizmo_context::<Config, Fixed>
244                    .in_set(bevy_app::RunFixedMainLoopSystems::BeforeFixedMainLoop),
245            )
246            .add_systems(FixedFirst, clear_gizmo_context::<Config, Fixed>)
247            .add_systems(FixedLast, collect_requested_gizmos::<Config, Fixed>)
248            .add_systems(
249                RunFixedMainLoop,
250                end_gizmo_context::<Config, Fixed>
251                    .in_set(bevy_app::RunFixedMainLoopSystems::AfterFixedMainLoop),
252            )
253            .add_systems(
254                Last,
255                (
256                    propagate_gizmos::<Config, Fixed>.before(GizmoMeshSystems),
257                    update_gizmo_meshes::<Config>.in_set(GizmoMeshSystems),
258                ),
259            );
260
261        self
262    }
263
264    fn insert_gizmo_config<Config: GizmoConfigGroup>(
265        &mut self,
266        group: Config,
267        config: GizmoConfig,
268    ) -> &mut Self {
269        self.init_gizmo_group::<Config>();
270
271        self.world_mut()
272            .get_resource_or_init::<GizmoConfigStore>()
273            .insert(config, group);
274
275        self
276    }
277}
278
279/// Holds handles to the line gizmos for each gizmo configuration group
280// As `TypeIdMap` iteration order depends on the order of insertions and deletions, this uses
281// `Option<Handle>` to be able to reserve the slot when creating the gizmo configuration group.
282// That way iteration order is stable across executions and depends on the order of configuration
283// group creation.
284#[derive(Resource, Default)]
285struct GizmoHandles {
286    handles: TypeIdMap<Option<Handle<GizmoAsset>>>,
287}
288
289/// Start a new gizmo clearing context.
290///
291/// Internally this pushes the parent default context into a swap buffer.
292/// Gizmo contexts should be handled like a stack, so if you push a new context,
293/// you must pop the context before the parent context ends.
294pub fn start_gizmo_context<Config, Clear>(
295    mut swap: ResMut<GizmoStorage<Config, Swap<Clear>>>,
296    mut default: ResMut<GizmoStorage<Config, ()>>,
297) where
298    Config: GizmoConfigGroup,
299    Clear: 'static + Send + Sync,
300{
301    default.swap(&mut *swap);
302}
303
304/// End this gizmo clearing context.
305///
306/// Pop the default gizmos context out of the [`Swap<Clear>`] gizmo storage.
307///
308/// This must be called before [`GizmoMeshSystems`] in the [`Last`] schedule.
309pub fn end_gizmo_context<Config, Clear>(
310    mut swap: ResMut<GizmoStorage<Config, Swap<Clear>>>,
311    mut default: ResMut<GizmoStorage<Config, ()>>,
312) where
313    Config: GizmoConfigGroup,
314    Clear: 'static + Send + Sync,
315{
316    default.clear();
317    default.swap(&mut *swap);
318}
319
320/// Collect the requested gizmos into a specific clear context.
321pub fn collect_requested_gizmos<Config, Clear>(
322    mut update: ResMut<GizmoStorage<Config, ()>>,
323    mut context: ResMut<GizmoStorage<Config, Clear>>,
324) where
325    Config: GizmoConfigGroup,
326    Clear: 'static + Send + Sync,
327{
328    context.append_storage(&update);
329    update.clear();
330}
331
332/// Clear out the contextual gizmos.
333pub fn clear_gizmo_context<Config, Clear>(mut context: ResMut<GizmoStorage<Config, Clear>>)
334where
335    Config: GizmoConfigGroup,
336    Clear: 'static + Send + Sync,
337{
338    context.clear();
339}
340
341/// Propagate the contextual gizmo into the `Update` storage for rendering.
342///
343/// This should be before [`GizmoMeshSystems`].
344pub fn propagate_gizmos<Config, Clear>(
345    mut update_storage: ResMut<GizmoStorage<Config, ()>>,
346    contextual_storage: Res<GizmoStorage<Config, Clear>>,
347) where
348    Config: GizmoConfigGroup,
349    Clear: 'static + Send + Sync,
350{
351    update_storage.append_storage(&*contextual_storage);
352}
353
354/// System set for updating the rendering meshes for drawing gizmos.
355#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
356pub struct GizmoMeshSystems;
357
358/// Deprecated alias for [`GizmoMeshSystems`].
359#[deprecated(since = "0.17.0", note = "Renamed to `GizmoMeshSystems`.")]
360pub type UpdateGizmoMeshes = GizmoMeshSystems;
361
362/// Prepare gizmos for rendering.
363///
364/// This also clears the default `GizmoStorage`.
365fn update_gizmo_meshes<Config: GizmoConfigGroup>(
366    mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
367    mut handles: ResMut<GizmoHandles>,
368    mut storage: ResMut<GizmoStorage<Config, ()>>,
369) {
370    if storage.list_positions.is_empty() && storage.strip_positions.is_empty() {
371        handles.handles.insert(TypeId::of::<Config>(), None);
372    } else if let Some(handle) = handles.handles.get_mut(&TypeId::of::<Config>()) {
373        if let Some(handle) = handle {
374            let gizmo = gizmo_assets.get_mut(handle.id()).unwrap();
375
376            gizmo.buffer.list_positions = mem::take(&mut storage.list_positions);
377            gizmo.buffer.list_colors = mem::take(&mut storage.list_colors);
378            gizmo.buffer.strip_positions = mem::take(&mut storage.strip_positions);
379            gizmo.buffer.strip_colors = mem::take(&mut storage.strip_colors);
380        } else {
381            let gizmo = GizmoAsset {
382                config_ty: TypeId::of::<Config>(),
383                buffer: GizmoBuffer {
384                    enabled: true,
385                    list_positions: mem::take(&mut storage.list_positions),
386                    list_colors: mem::take(&mut storage.list_colors),
387                    strip_positions: mem::take(&mut storage.strip_positions),
388                    strip_colors: mem::take(&mut storage.strip_colors),
389                    marker: PhantomData,
390                },
391            };
392
393            *handle = Some(gizmo_assets.add(gizmo));
394        }
395    }
396}
397
398#[cfg(feature = "bevy_render")]
399fn init_line_gizmo_uniform_bind_group_layout(
400    mut commands: Commands,
401    render_device: Res<RenderDevice>,
402) {
403    let line_layout = render_device.create_bind_group_layout(
404        "LineGizmoUniform layout",
405        &BindGroupLayoutEntries::single(
406            ShaderStages::VERTEX,
407            uniform_buffer::<LineGizmoUniform>(true),
408        ),
409    );
410
411    commands.insert_resource(LineGizmoUniformBindgroupLayout {
412        layout: line_layout,
413    });
414}
415
416#[cfg(feature = "bevy_render")]
417fn extract_gizmo_data(
418    mut commands: Commands,
419    handles: Extract<Res<GizmoHandles>>,
420    config: Extract<Res<GizmoConfigStore>>,
421) {
422    use bevy_utils::once;
423    use config::GizmoLineStyle;
424    use tracing::warn;
425
426    for (group_type_id, handle) in &handles.handles {
427        let Some((config, _)) = config.get_config_dyn(group_type_id) else {
428            continue;
429        };
430
431        if !config.enabled {
432            continue;
433        }
434
435        let Some(handle) = handle else {
436            continue;
437        };
438
439        let joints_resolution = if let GizmoLineJoint::Round(resolution) = config.line.joints {
440            resolution
441        } else {
442            0
443        };
444
445        let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
446            gap_scale,
447            line_scale,
448        } = config.line.style
449        {
450            if gap_scale <= 0.0 {
451                once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero."));
452            }
453            if line_scale <= 0.0 {
454                once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero."));
455            }
456            (gap_scale, line_scale)
457        } else {
458            (1.0, 1.0)
459        };
460
461        commands.spawn((
462            LineGizmoUniform {
463                world_from_local: Affine3::from(&Affine3A::IDENTITY).to_transpose(),
464                line_width: config.line.width,
465                depth_bias: config.depth_bias,
466                joints_resolution,
467                gap_scale,
468                line_scale,
469                #[cfg(feature = "webgl")]
470                _padding: Default::default(),
471            },
472            #[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite_render"))]
473            GizmoMeshConfig {
474                line_perspective: config.line.perspective,
475                line_style: config.line.style,
476                line_joints: config.line.joints,
477                render_layers: config.render_layers.clone(),
478                handle: handle.clone(),
479            },
480            // The immediate mode API does not have a main world entity to refer to,
481            // but we do need MainEntity on this render entity for the systems to find it.
482            MainEntity::from(Entity::PLACEHOLDER),
483            TemporaryRenderEntity,
484        ));
485    }
486}
487
488#[cfg(feature = "bevy_render")]
489#[derive(Component, ShaderType, Clone, Copy)]
490struct LineGizmoUniform {
491    world_from_local: [Vec4; 3],
492    line_width: f32,
493    depth_bias: f32,
494    // Only used by gizmo line t if the current configs `line_joints` is set to `GizmoLineJoint::Round(_)`
495    joints_resolution: u32,
496    // Only used if the current configs `line_style` is set to `GizmoLineStyle::Dashed{_}`
497    gap_scale: f32,
498    line_scale: f32,
499    /// WebGL2 structs must be 16 byte aligned.
500    #[cfg(feature = "webgl")]
501    _padding: bevy_math::Vec3,
502}
503
504/// A collection of gizmos.
505///
506/// Has the same gizmo drawing API as [`Gizmos`](crate::gizmos::Gizmos).
507#[derive(Asset, Debug, Clone, TypePath)]
508pub struct GizmoAsset {
509    /// vertex buffers.
510    buffer: GizmoBuffer<ErasedGizmoConfigGroup, ()>,
511    config_ty: TypeId,
512}
513
514impl GizmoAsset {
515    /// Create a new [`GizmoAsset`].
516    pub fn new() -> Self {
517        GizmoAsset {
518            buffer: GizmoBuffer::default(),
519            config_ty: TypeId::of::<ErasedGizmoConfigGroup>(),
520        }
521    }
522
523    /// The type of the gizmo's configuration group.
524    pub fn config_typeid(&self) -> TypeId {
525        self.config_ty
526    }
527}
528
529impl Default for GizmoAsset {
530    fn default() -> Self {
531        GizmoAsset::new()
532    }
533}
534
535#[cfg(feature = "bevy_render")]
536#[derive(Debug, Clone)]
537struct GpuLineGizmo {
538    list_position_buffer: Buffer,
539    list_color_buffer: Buffer,
540    list_vertex_count: u32,
541    strip_position_buffer: Buffer,
542    strip_color_buffer: Buffer,
543    strip_vertex_count: u32,
544}
545
546#[cfg(feature = "bevy_render")]
547impl RenderAsset for GpuLineGizmo {
548    type SourceAsset = GizmoAsset;
549    type Param = SRes<RenderDevice>;
550
551    fn prepare_asset(
552        gizmo: Self::SourceAsset,
553        _: AssetId<Self::SourceAsset>,
554        render_device: &mut SystemParamItem<Self::Param>,
555        _: Option<&Self>,
556    ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
557        let list_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
558            usage: BufferUsages::VERTEX,
559            label: Some("LineGizmo Position Buffer"),
560            contents: cast_slice(&gizmo.buffer.list_positions),
561        });
562
563        let list_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
564            usage: BufferUsages::VERTEX,
565            label: Some("LineGizmo Color Buffer"),
566            contents: cast_slice(&gizmo.buffer.list_colors),
567        });
568
569        let strip_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
570            usage: BufferUsages::VERTEX,
571            label: Some("LineGizmo Strip Position Buffer"),
572            contents: cast_slice(&gizmo.buffer.strip_positions),
573        });
574
575        let strip_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
576            usage: BufferUsages::VERTEX,
577            label: Some("LineGizmo Strip Color Buffer"),
578            contents: cast_slice(&gizmo.buffer.strip_colors),
579        });
580
581        Ok(GpuLineGizmo {
582            list_position_buffer,
583            list_color_buffer,
584            list_vertex_count: gizmo.buffer.list_positions.len() as u32,
585            strip_position_buffer,
586            strip_color_buffer,
587            strip_vertex_count: gizmo.buffer.strip_positions.len() as u32,
588        })
589    }
590}
591
592#[cfg(feature = "bevy_render")]
593#[derive(Resource)]
594struct LineGizmoUniformBindgroupLayout {
595    layout: BindGroupLayout,
596}
597
598#[cfg(feature = "bevy_render")]
599#[derive(Resource)]
600struct LineGizmoUniformBindgroup {
601    bindgroup: BindGroup,
602}
603
604#[cfg(feature = "bevy_render")]
605fn prepare_line_gizmo_bind_group(
606    mut commands: Commands,
607    line_gizmo_uniform_layout: Res<LineGizmoUniformBindgroupLayout>,
608    render_device: Res<RenderDevice>,
609    line_gizmo_uniforms: Res<ComponentUniforms<LineGizmoUniform>>,
610) {
611    if let Some(binding) = line_gizmo_uniforms.uniforms().binding() {
612        commands.insert_resource(LineGizmoUniformBindgroup {
613            bindgroup: render_device.create_bind_group(
614                "LineGizmoUniform bindgroup",
615                &line_gizmo_uniform_layout.layout,
616                &BindGroupEntries::single(binding),
617            ),
618        });
619    }
620}
621
622#[cfg(feature = "bevy_render")]
623struct SetLineGizmoBindGroup<const I: usize>;
624#[cfg(feature = "bevy_render")]
625impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetLineGizmoBindGroup<I> {
626    type Param = SRes<LineGizmoUniformBindgroup>;
627    type ViewQuery = ();
628    type ItemQuery = Read<DynamicUniformIndex<LineGizmoUniform>>;
629
630    #[inline]
631    fn render<'w>(
632        _item: &P,
633        _view: ROQueryItem<'w, '_, Self::ViewQuery>,
634        uniform_index: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
635        bind_group: SystemParamItem<'w, '_, Self::Param>,
636        pass: &mut TrackedRenderPass<'w>,
637    ) -> RenderCommandResult {
638        let Some(uniform_index) = uniform_index else {
639            return RenderCommandResult::Skip;
640        };
641        pass.set_bind_group(
642            I,
643            &bind_group.into_inner().bindgroup,
644            &[uniform_index.index()],
645        );
646        RenderCommandResult::Success
647    }
648}
649
650#[cfg(feature = "bevy_render")]
651struct DrawLineGizmo<const STRIP: bool>;
652#[cfg(all(
653    feature = "bevy_render",
654    any(feature = "bevy_pbr", feature = "bevy_sprite_render")
655))]
656impl<P: PhaseItem, const STRIP: bool> RenderCommand<P> for DrawLineGizmo<STRIP> {
657    type Param = SRes<RenderAssets<GpuLineGizmo>>;
658    type ViewQuery = ();
659    type ItemQuery = Read<GizmoMeshConfig>;
660
661    #[inline]
662    fn render<'w>(
663        _item: &P,
664        _view: ROQueryItem<'w, '_, Self::ViewQuery>,
665        config: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
666        line_gizmos: SystemParamItem<'w, '_, Self::Param>,
667        pass: &mut TrackedRenderPass<'w>,
668    ) -> RenderCommandResult {
669        let Some(config) = config else {
670            return RenderCommandResult::Skip;
671        };
672        let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
673            return RenderCommandResult::Skip;
674        };
675
676        let vertex_count = if STRIP {
677            line_gizmo.strip_vertex_count
678        } else {
679            line_gizmo.list_vertex_count
680        };
681
682        if vertex_count < 2 {
683            return RenderCommandResult::Success;
684        }
685
686        let instances = if STRIP {
687            let item_size = VertexFormat::Float32x3.size();
688            let buffer_size = line_gizmo.strip_position_buffer.size() - item_size;
689
690            pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size));
691            pass.set_vertex_buffer(1, line_gizmo.strip_position_buffer.slice(item_size..));
692
693            let item_size = VertexFormat::Float32x4.size();
694            let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
695
696            pass.set_vertex_buffer(2, line_gizmo.strip_color_buffer.slice(..buffer_size));
697            pass.set_vertex_buffer(3, line_gizmo.strip_color_buffer.slice(item_size..));
698
699            vertex_count - 1
700        } else {
701            pass.set_vertex_buffer(0, line_gizmo.list_position_buffer.slice(..));
702            pass.set_vertex_buffer(1, line_gizmo.list_color_buffer.slice(..));
703
704            vertex_count / 2
705        };
706
707        pass.draw(0..6, 0..instances);
708
709        RenderCommandResult::Success
710    }
711}
712
713#[cfg(feature = "bevy_render")]
714struct DrawLineJointGizmo;
715#[cfg(all(
716    feature = "bevy_render",
717    any(feature = "bevy_pbr", feature = "bevy_sprite_render")
718))]
719impl<P: PhaseItem> RenderCommand<P> for DrawLineJointGizmo {
720    type Param = SRes<RenderAssets<GpuLineGizmo>>;
721    type ViewQuery = ();
722    type ItemQuery = Read<GizmoMeshConfig>;
723
724    #[inline]
725    fn render<'w>(
726        _item: &P,
727        _view: ROQueryItem<'w, '_, Self::ViewQuery>,
728        config: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
729        line_gizmos: SystemParamItem<'w, '_, Self::Param>,
730        pass: &mut TrackedRenderPass<'w>,
731    ) -> RenderCommandResult {
732        let Some(config) = config else {
733            return RenderCommandResult::Skip;
734        };
735        let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
736            return RenderCommandResult::Skip;
737        };
738
739        if line_gizmo.strip_vertex_count <= 2 {
740            return RenderCommandResult::Success;
741        };
742
743        if config.line_joints == GizmoLineJoint::None {
744            return RenderCommandResult::Success;
745        };
746
747        let instances = {
748            let item_size = VertexFormat::Float32x3.size();
749            // position_a
750            let buffer_size_a = line_gizmo.strip_position_buffer.size() - item_size * 2;
751            pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size_a));
752            // position_b
753            let buffer_size_b = line_gizmo.strip_position_buffer.size() - item_size;
754            pass.set_vertex_buffer(
755                1,
756                line_gizmo
757                    .strip_position_buffer
758                    .slice(item_size..buffer_size_b),
759            );
760            // position_c
761            pass.set_vertex_buffer(2, line_gizmo.strip_position_buffer.slice(item_size * 2..));
762
763            // color
764            let item_size = VertexFormat::Float32x4.size();
765            let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
766            // This corresponds to the color of position_b, hence starts from `item_size`
767            pass.set_vertex_buffer(
768                3,
769                line_gizmo.strip_color_buffer.slice(item_size..buffer_size),
770            );
771
772            line_gizmo.strip_vertex_count - 2
773        };
774
775        let vertices = match config.line_joints {
776            GizmoLineJoint::None => unreachable!(),
777            GizmoLineJoint::Miter => 6,
778            GizmoLineJoint::Round(resolution) => resolution * 3,
779            GizmoLineJoint::Bevel => 3,
780        };
781
782        pass.draw(0..vertices, 0..instances);
783
784        RenderCommandResult::Success
785    }
786}
787
788#[cfg(all(
789    feature = "bevy_render",
790    any(feature = "bevy_pbr", feature = "bevy_sprite_render")
791))]
792fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
793    use VertexFormat::*;
794    let mut position_layout = VertexBufferLayout {
795        array_stride: Float32x3.size(),
796        step_mode: VertexStepMode::Instance,
797        attributes: vec![VertexAttribute {
798            format: Float32x3,
799            offset: 0,
800            shader_location: 0,
801        }],
802    };
803
804    let mut color_layout = VertexBufferLayout {
805        array_stride: Float32x4.size(),
806        step_mode: VertexStepMode::Instance,
807        attributes: vec![VertexAttribute {
808            format: Float32x4,
809            offset: 0,
810            shader_location: 2,
811        }],
812    };
813
814    if strip {
815        vec![
816            position_layout.clone(),
817            {
818                position_layout.attributes[0].shader_location = 1;
819                position_layout
820            },
821            color_layout.clone(),
822            {
823                color_layout.attributes[0].shader_location = 3;
824                color_layout
825            },
826        ]
827    } else {
828        position_layout.array_stride *= 2;
829        position_layout.attributes.push(VertexAttribute {
830            format: Float32x3,
831            offset: Float32x3.size(),
832            shader_location: 1,
833        });
834
835        color_layout.array_stride *= 2;
836        color_layout.attributes.push(VertexAttribute {
837            format: Float32x4,
838            offset: Float32x4.size(),
839            shader_location: 3,
840        });
841
842        vec![position_layout, color_layout]
843    }
844}
845
846#[cfg(all(
847    feature = "bevy_render",
848    any(feature = "bevy_pbr", feature = "bevy_sprite_render")
849))]
850fn line_joint_gizmo_vertex_buffer_layouts() -> Vec<VertexBufferLayout> {
851    use VertexFormat::*;
852    let mut position_layout = VertexBufferLayout {
853        array_stride: Float32x3.size(),
854        step_mode: VertexStepMode::Instance,
855        attributes: vec![VertexAttribute {
856            format: Float32x3,
857            offset: 0,
858            shader_location: 0,
859        }],
860    };
861
862    let color_layout = VertexBufferLayout {
863        array_stride: Float32x4.size(),
864        step_mode: VertexStepMode::Instance,
865        attributes: vec![VertexAttribute {
866            format: Float32x4,
867            offset: 0,
868            shader_location: 3,
869        }],
870    };
871
872    vec![
873        position_layout.clone(),
874        {
875            position_layout.attributes[0].shader_location = 1;
876            position_layout.clone()
877        },
878        {
879            position_layout.attributes[0].shader_location = 2;
880            position_layout
881        },
882        color_layout.clone(),
883    ]
884}