bevy_gizmos/
lib.rs

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