Instanced line rendering for gizmos based on bevy_polyline (#8427)
				
					
				
			# Objective Adopt code from [bevy_polyline](https://github.com/ForesightMiningSoftwareCorporation/bevy_polyline) for gizmo line-rendering. This adds configurable width and perspective rendering for the lines. Many thanks to @mtsr for the initial work on bevy_polyline. Thanks to @aevyrie for maintaining it, @nicopap for adding the depth_bias feature and the other [contributors](https://github.com/ForesightMiningSoftwareCorporation/bevy_polyline/graphs/contributors) for squashing bugs and keeping bevy_polyline up-to-date. #### Before  #### After - with line perspective  Line perspective is not on by default because with perspective there is no default line width that works for every scene. <details><summary>After - without line perspective</summary> <p>  </p> </details> Somewhat unexpectedly, the performance is improved with this PR. At 200,000 lines in many_gizmos I get ~110 FPS on main and ~200 FPS with this PR. I'm guessing this is a CPU side difference as I would expect the rendering technique to be more expensive on the GPU to some extent, but I am not entirely sure. --------- Co-authored-by: Jonas Matser <github@jonasmatser.nl> Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Nicola Papale <nico@nicopap.ch> Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									3fba34c9e6
								
							
						
					
					
						commit
						001b3eb97c
					
				@ -8,6 +8,9 @@ repository = "https://github.com/bevyengine/bevy"
 | 
			
		||||
license = "MIT OR Apache-2.0"
 | 
			
		||||
keywords = ["bevy"]
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
webgl = []
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
# Bevy
 | 
			
		||||
bevy_pbr = { path = "../bevy_pbr", version = "0.11.0-dev", optional = true }
 | 
			
		||||
 | 
			
		||||
@ -19,36 +19,40 @@
 | 
			
		||||
use std::mem;
 | 
			
		||||
 | 
			
		||||
use bevy_app::{Last, Plugin, Update};
 | 
			
		||||
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
 | 
			
		||||
use bevy_asset::{load_internal_asset, AddAsset, Assets, Handle, HandleUntyped};
 | 
			
		||||
use bevy_core::cast_slice;
 | 
			
		||||
use bevy_ecs::{
 | 
			
		||||
    change_detection::DetectChanges,
 | 
			
		||||
    component::Component,
 | 
			
		||||
    entity::Entity,
 | 
			
		||||
    query::Without,
 | 
			
		||||
    query::{ROQueryItem, Without},
 | 
			
		||||
    reflect::ReflectComponent,
 | 
			
		||||
    schedule::IntoSystemConfigs,
 | 
			
		||||
    system::{Commands, Query, Res, ResMut, Resource},
 | 
			
		||||
    world::{FromWorld, World},
 | 
			
		||||
    system::{
 | 
			
		||||
        lifetimeless::{Read, SRes},
 | 
			
		||||
        Commands, Query, Res, ResMut, Resource, SystemParamItem,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
use bevy_math::Mat4;
 | 
			
		||||
use bevy_reflect::{
 | 
			
		||||
    std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect, TypeUuid,
 | 
			
		||||
    std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect, TypePath, TypeUuid,
 | 
			
		||||
};
 | 
			
		||||
use bevy_render::{
 | 
			
		||||
    color::Color,
 | 
			
		||||
    mesh::Mesh,
 | 
			
		||||
    extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
 | 
			
		||||
    primitives::Aabb,
 | 
			
		||||
    render_phase::AddRenderCommand,
 | 
			
		||||
    render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines},
 | 
			
		||||
    render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
 | 
			
		||||
    render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
 | 
			
		||||
    render_resource::{
 | 
			
		||||
        BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
 | 
			
		||||
        BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, BufferInitDescriptor,
 | 
			
		||||
        BufferUsages, Shader, ShaderStages, ShaderType, VertexAttribute, VertexBufferLayout,
 | 
			
		||||
        VertexFormat, VertexStepMode,
 | 
			
		||||
    },
 | 
			
		||||
    renderer::RenderDevice,
 | 
			
		||||
    Extract, ExtractSchedule, Render, RenderApp, RenderSet,
 | 
			
		||||
};
 | 
			
		||||
use bevy_transform::components::{GlobalTransform, Transform};
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "bevy_pbr")]
 | 
			
		||||
use bevy_pbr::MeshUniform;
 | 
			
		||||
#[cfg(feature = "bevy_sprite")]
 | 
			
		||||
use bevy_sprite::{Mesh2dHandle, Mesh2dUniform};
 | 
			
		||||
 | 
			
		||||
pub mod gizmos;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "bevy_sprite")]
 | 
			
		||||
@ -74,7 +78,10 @@ impl Plugin for GizmoPlugin {
 | 
			
		||||
    fn build(&self, app: &mut bevy_app::App) {
 | 
			
		||||
        load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl);
 | 
			
		||||
 | 
			
		||||
        app.init_resource::<MeshHandles>()
 | 
			
		||||
        app.add_plugin(UniformComponentPlugin::<LineGizmoUniform>::default())
 | 
			
		||||
            .add_asset::<LineGizmo>()
 | 
			
		||||
            .add_plugin(RenderAssetPlugin::<LineGizmo>::default())
 | 
			
		||||
            .init_resource::<LineGizmoHandles>()
 | 
			
		||||
            .init_resource::<GizmoConfig>()
 | 
			
		||||
            .init_resource::<GizmoStorage>()
 | 
			
		||||
            .add_systems(Last, update_gizmo_meshes)
 | 
			
		||||
@ -88,47 +95,35 @@ impl Plugin for GizmoPlugin {
 | 
			
		||||
 | 
			
		||||
        let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
 | 
			
		||||
 | 
			
		||||
        render_app.add_systems(ExtractSchedule, extract_gizmo_data);
 | 
			
		||||
        render_app
 | 
			
		||||
            .add_systems(ExtractSchedule, extract_gizmo_data)
 | 
			
		||||
            .add_systems(Render, queue_line_gizmo_bind_group.in_set(RenderSet::Queue));
 | 
			
		||||
 | 
			
		||||
        #[cfg(feature = "bevy_sprite")]
 | 
			
		||||
        {
 | 
			
		||||
            use bevy_core_pipeline::core_2d::Transparent2d;
 | 
			
		||||
            use pipeline_2d::*;
 | 
			
		||||
 | 
			
		||||
            render_app
 | 
			
		||||
                .add_render_command::<Transparent2d, DrawGizmoLines>()
 | 
			
		||||
                .init_resource::<SpecializedMeshPipelines<GizmoLinePipeline>>()
 | 
			
		||||
                .add_systems(Render, queue_gizmos_2d.in_set(RenderSet::Queue));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        app.add_plugin(pipeline_2d::LineGizmo2dPlugin);
 | 
			
		||||
        #[cfg(feature = "bevy_pbr")]
 | 
			
		||||
        {
 | 
			
		||||
            use bevy_core_pipeline::core_3d::Opaque3d;
 | 
			
		||||
            use pipeline_3d::*;
 | 
			
		||||
 | 
			
		||||
            render_app
 | 
			
		||||
                .add_render_command::<Opaque3d, DrawGizmoLines>()
 | 
			
		||||
                .init_resource::<SpecializedMeshPipelines<GizmoPipeline>>()
 | 
			
		||||
                .add_systems(Render, queue_gizmos_3d.in_set(RenderSet::Queue));
 | 
			
		||||
        }
 | 
			
		||||
        app.add_plugin(pipeline_3d::LineGizmo3dPlugin);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn finish(&self, app: &mut bevy_app::App) {
 | 
			
		||||
        let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
 | 
			
		||||
 | 
			
		||||
        #[cfg(feature = "bevy_sprite")]
 | 
			
		||||
        {
 | 
			
		||||
            use pipeline_2d::*;
 | 
			
		||||
        let render_device = render_app.world.resource::<RenderDevice>();
 | 
			
		||||
        let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
 | 
			
		||||
            entries: &[BindGroupLayoutEntry {
 | 
			
		||||
                binding: 0,
 | 
			
		||||
                visibility: ShaderStages::VERTEX,
 | 
			
		||||
                ty: BindingType::Buffer {
 | 
			
		||||
                    ty: BufferBindingType::Uniform,
 | 
			
		||||
                    has_dynamic_offset: true,
 | 
			
		||||
                    min_binding_size: Some(LineGizmoUniform::min_size()),
 | 
			
		||||
                },
 | 
			
		||||
                count: None,
 | 
			
		||||
            }],
 | 
			
		||||
            label: Some("LineGizmoUniform layout"),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
            render_app.init_resource::<GizmoLinePipeline>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[cfg(feature = "bevy_pbr")]
 | 
			
		||||
        {
 | 
			
		||||
            use pipeline_3d::*;
 | 
			
		||||
 | 
			
		||||
            render_app.init_resource::<GizmoPipeline>();
 | 
			
		||||
        }
 | 
			
		||||
        render_app.insert_resource(LineGizmoUniformBindgroupLayout { layout });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -139,12 +134,29 @@ pub struct GizmoConfig {
 | 
			
		||||
    ///
 | 
			
		||||
    /// Defaults to `true`.
 | 
			
		||||
    pub enabled: bool,
 | 
			
		||||
    /// Draw gizmos on top of everything else, ignoring depth.
 | 
			
		||||
    /// Line width specified in pixels.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This setting only affects 3D. In 2D, gizmos are always drawn on top.
 | 
			
		||||
    /// If `line_perspective` is `true` then this is the size in pixels at the camera's near plane.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Defaults to `2.0`.
 | 
			
		||||
    pub line_width: f32,
 | 
			
		||||
    /// Apply perspective to gizmo lines.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This setting only affects 3D, non-orhographic cameras.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Defaults to `false`.
 | 
			
		||||
    pub on_top: bool,
 | 
			
		||||
    pub line_perspective: bool,
 | 
			
		||||
    /// How closer to the camera than real geometry the line should be.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Value between -1 and 1 (inclusive).
 | 
			
		||||
    /// * 0 means that there is no change to the line position when rendering
 | 
			
		||||
    /// * 1 means it is furthest away from camera as possible
 | 
			
		||||
    /// * -1 means that it will always render in front of other things.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This is typically useful if you are drawing wireframes on top of polygons
 | 
			
		||||
    /// and your wireframe is z-fighting (flickering on/off) with your main model.
 | 
			
		||||
    /// You would set this value to a negative number close to 0.0.
 | 
			
		||||
    pub depth_bias: f32,
 | 
			
		||||
    /// Configuration for the [`AabbGizmo`].
 | 
			
		||||
    pub aabb: AabbGizmoConfig,
 | 
			
		||||
}
 | 
			
		||||
@ -153,7 +165,9 @@ impl Default for GizmoConfig {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            enabled: true,
 | 
			
		||||
            on_top: false,
 | 
			
		||||
            line_width: 2.,
 | 
			
		||||
            line_perspective: false,
 | 
			
		||||
            depth_bias: 0.,
 | 
			
		||||
            aabb: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -227,77 +241,59 @@ fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform {
 | 
			
		||||
        )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Resource)]
 | 
			
		||||
struct MeshHandles {
 | 
			
		||||
    list: Option<Handle<Mesh>>,
 | 
			
		||||
    strip: Option<Handle<Mesh>>,
 | 
			
		||||
#[derive(Resource, Default)]
 | 
			
		||||
struct LineGizmoHandles {
 | 
			
		||||
    list: Option<Handle<LineGizmo>>,
 | 
			
		||||
    strip: Option<Handle<LineGizmo>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromWorld for MeshHandles {
 | 
			
		||||
    fn from_world(_world: &mut World) -> Self {
 | 
			
		||||
        MeshHandles {
 | 
			
		||||
            list: None,
 | 
			
		||||
            strip: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Component)]
 | 
			
		||||
struct GizmoMesh;
 | 
			
		||||
 | 
			
		||||
fn update_gizmo_meshes(
 | 
			
		||||
    mut meshes: ResMut<Assets<Mesh>>,
 | 
			
		||||
    mut handles: ResMut<MeshHandles>,
 | 
			
		||||
    mut line_gizmos: ResMut<Assets<LineGizmo>>,
 | 
			
		||||
    mut handles: ResMut<LineGizmoHandles>,
 | 
			
		||||
    mut storage: ResMut<GizmoStorage>,
 | 
			
		||||
) {
 | 
			
		||||
    if storage.list_positions.is_empty() {
 | 
			
		||||
        handles.list = None;
 | 
			
		||||
    } else if let Some(handle) = handles.list.as_ref() {
 | 
			
		||||
        let list_mesh = meshes.get_mut(handle).unwrap();
 | 
			
		||||
        let list = line_gizmos.get_mut(handle).unwrap();
 | 
			
		||||
 | 
			
		||||
        let positions = mem::take(&mut storage.list_positions);
 | 
			
		||||
        list_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
 | 
			
		||||
 | 
			
		||||
        let colors = mem::take(&mut storage.list_colors);
 | 
			
		||||
        list_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
 | 
			
		||||
        list.positions = mem::take(&mut storage.list_positions);
 | 
			
		||||
        list.colors = mem::take(&mut storage.list_colors);
 | 
			
		||||
    } else {
 | 
			
		||||
        let mut list_mesh = Mesh::new(PrimitiveTopology::LineList);
 | 
			
		||||
        let mut list = LineGizmo {
 | 
			
		||||
            strip: false,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let positions = mem::take(&mut storage.list_positions);
 | 
			
		||||
        list_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
 | 
			
		||||
        list.positions = mem::take(&mut storage.list_positions);
 | 
			
		||||
        list.colors = mem::take(&mut storage.list_colors);
 | 
			
		||||
 | 
			
		||||
        let colors = mem::take(&mut storage.list_colors);
 | 
			
		||||
        list_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
 | 
			
		||||
 | 
			
		||||
        handles.list = Some(meshes.add(list_mesh));
 | 
			
		||||
        handles.list = Some(line_gizmos.add(list));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if storage.strip_positions.is_empty() {
 | 
			
		||||
        handles.strip = None;
 | 
			
		||||
    } else if let Some(handle) = handles.strip.as_ref() {
 | 
			
		||||
        let strip_mesh = meshes.get_mut(handle).unwrap();
 | 
			
		||||
        let strip = line_gizmos.get_mut(handle).unwrap();
 | 
			
		||||
 | 
			
		||||
        let positions = mem::take(&mut storage.strip_positions);
 | 
			
		||||
        strip_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
 | 
			
		||||
 | 
			
		||||
        let colors = mem::take(&mut storage.strip_colors);
 | 
			
		||||
        strip_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
 | 
			
		||||
        strip.positions = mem::take(&mut storage.strip_positions);
 | 
			
		||||
        strip.colors = mem::take(&mut storage.strip_colors);
 | 
			
		||||
    } else {
 | 
			
		||||
        let mut strip_mesh = Mesh::new(PrimitiveTopology::LineStrip);
 | 
			
		||||
        let mut strip = LineGizmo {
 | 
			
		||||
            strip: true,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let positions = mem::take(&mut storage.strip_positions);
 | 
			
		||||
        strip_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
 | 
			
		||||
        strip.positions = mem::take(&mut storage.strip_positions);
 | 
			
		||||
        strip.colors = mem::take(&mut storage.strip_colors);
 | 
			
		||||
 | 
			
		||||
        let colors = mem::take(&mut storage.strip_colors);
 | 
			
		||||
        strip_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
 | 
			
		||||
 | 
			
		||||
        handles.strip = Some(meshes.add(strip_mesh));
 | 
			
		||||
        handles.strip = Some(line_gizmos.add(strip));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_gizmo_data(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    handles: Extract<Res<MeshHandles>>,
 | 
			
		||||
    handles: Extract<Res<LineGizmoHandles>>,
 | 
			
		||||
    config: Extract<Res<GizmoConfig>>,
 | 
			
		||||
) {
 | 
			
		||||
    if config.is_changed() {
 | 
			
		||||
@ -308,35 +304,206 @@ fn extract_gizmo_data(
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let transform = Mat4::IDENTITY;
 | 
			
		||||
    let inverse_transpose_model = transform.inverse().transpose();
 | 
			
		||||
    commands.spawn_batch(
 | 
			
		||||
        [handles.list.clone(), handles.strip.clone()]
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .flatten()
 | 
			
		||||
            .map(move |handle| {
 | 
			
		||||
                (
 | 
			
		||||
                    GizmoMesh,
 | 
			
		||||
                    #[cfg(feature = "bevy_pbr")]
 | 
			
		||||
                    (
 | 
			
		||||
                        handle.clone_weak(),
 | 
			
		||||
                        MeshUniform {
 | 
			
		||||
                            flags: 0,
 | 
			
		||||
                            transform,
 | 
			
		||||
                            previous_transform: transform,
 | 
			
		||||
                            inverse_transpose_model,
 | 
			
		||||
                        },
 | 
			
		||||
                    ),
 | 
			
		||||
                    #[cfg(feature = "bevy_sprite")]
 | 
			
		||||
                    (
 | 
			
		||||
                        Mesh2dHandle(handle),
 | 
			
		||||
                        Mesh2dUniform {
 | 
			
		||||
                            flags: 0,
 | 
			
		||||
                            transform,
 | 
			
		||||
                            inverse_transpose_model,
 | 
			
		||||
                        },
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
            }),
 | 
			
		||||
    );
 | 
			
		||||
    for handle in [&handles.list, &handles.strip].into_iter().flatten() {
 | 
			
		||||
        commands.spawn((
 | 
			
		||||
            LineGizmoUniform {
 | 
			
		||||
                line_width: config.line_width,
 | 
			
		||||
                depth_bias: config.depth_bias,
 | 
			
		||||
                #[cfg(feature = "webgl")]
 | 
			
		||||
                _padding: Default::default(),
 | 
			
		||||
            },
 | 
			
		||||
            handle.clone_weak(),
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Component, ShaderType, Clone, Copy)]
 | 
			
		||||
struct LineGizmoUniform {
 | 
			
		||||
    line_width: f32,
 | 
			
		||||
    depth_bias: f32,
 | 
			
		||||
    /// WebGL2 structs must be 16 byte aligned.
 | 
			
		||||
    #[cfg(feature = "webgl")]
 | 
			
		||||
    _padding: bevy_math::Vec2,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Clone, TypeUuid, TypePath)]
 | 
			
		||||
#[uuid = "02b99cbf-bb26-4713-829a-aee8e08dedc0"]
 | 
			
		||||
struct LineGizmo {
 | 
			
		||||
    positions: Vec<[f32; 3]>,
 | 
			
		||||
    colors: Vec<[f32; 4]>,
 | 
			
		||||
    /// Whether this gizmo's topology is a line-strip or line-list
 | 
			
		||||
    strip: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
struct GpuLineGizmo {
 | 
			
		||||
    position_buffer: Buffer,
 | 
			
		||||
    color_buffer: Buffer,
 | 
			
		||||
    vertex_count: u32,
 | 
			
		||||
    strip: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RenderAsset for LineGizmo {
 | 
			
		||||
    type ExtractedAsset = LineGizmo;
 | 
			
		||||
 | 
			
		||||
    type PreparedAsset = GpuLineGizmo;
 | 
			
		||||
 | 
			
		||||
    type Param = SRes<RenderDevice>;
 | 
			
		||||
 | 
			
		||||
    fn extract_asset(&self) -> Self::ExtractedAsset {
 | 
			
		||||
        self.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn prepare_asset(
 | 
			
		||||
        line_gizmo: Self::ExtractedAsset,
 | 
			
		||||
        render_device: &mut SystemParamItem<Self::Param>,
 | 
			
		||||
    ) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
 | 
			
		||||
        let position_buffer_data = cast_slice(&line_gizmo.positions);
 | 
			
		||||
        let position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
 | 
			
		||||
            usage: BufferUsages::VERTEX,
 | 
			
		||||
            label: Some("LineGizmo Position Buffer"),
 | 
			
		||||
            contents: position_buffer_data,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let color_buffer_data = cast_slice(&line_gizmo.colors);
 | 
			
		||||
        let color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
 | 
			
		||||
            usage: BufferUsages::VERTEX,
 | 
			
		||||
            label: Some("LineGizmo Color Buffer"),
 | 
			
		||||
            contents: color_buffer_data,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Ok(GpuLineGizmo {
 | 
			
		||||
            position_buffer,
 | 
			
		||||
            color_buffer,
 | 
			
		||||
            vertex_count: line_gizmo.positions.len() as u32,
 | 
			
		||||
            strip: line_gizmo.strip,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Resource)]
 | 
			
		||||
struct LineGizmoUniformBindgroupLayout {
 | 
			
		||||
    layout: BindGroupLayout,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Resource)]
 | 
			
		||||
struct LineGizmoUniformBindgroup {
 | 
			
		||||
    bindgroup: BindGroup,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn queue_line_gizmo_bind_group(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    line_gizmo_uniform_layout: Res<LineGizmoUniformBindgroupLayout>,
 | 
			
		||||
    render_device: Res<RenderDevice>,
 | 
			
		||||
    line_gizmo_uniforms: Res<ComponentUniforms<LineGizmoUniform>>,
 | 
			
		||||
) {
 | 
			
		||||
    if let Some(binding) = line_gizmo_uniforms.uniforms().binding() {
 | 
			
		||||
        commands.insert_resource(LineGizmoUniformBindgroup {
 | 
			
		||||
            bindgroup: render_device.create_bind_group(&BindGroupDescriptor {
 | 
			
		||||
                entries: &[BindGroupEntry {
 | 
			
		||||
                    binding: 0,
 | 
			
		||||
                    resource: binding,
 | 
			
		||||
                }],
 | 
			
		||||
                label: Some("LineGizmoUniform bindgroup"),
 | 
			
		||||
                layout: &line_gizmo_uniform_layout.layout,
 | 
			
		||||
            }),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct SetLineGizmoBindGroup<const I: usize>;
 | 
			
		||||
impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetLineGizmoBindGroup<I> {
 | 
			
		||||
    type ViewWorldQuery = ();
 | 
			
		||||
    type ItemWorldQuery = Read<DynamicUniformIndex<LineGizmoUniform>>;
 | 
			
		||||
    type Param = SRes<LineGizmoUniformBindgroup>;
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn render<'w>(
 | 
			
		||||
        _item: &P,
 | 
			
		||||
        _view: ROQueryItem<'w, Self::ViewWorldQuery>,
 | 
			
		||||
        uniform_index: ROQueryItem<'w, Self::ItemWorldQuery>,
 | 
			
		||||
        bind_group: SystemParamItem<'w, '_, Self::Param>,
 | 
			
		||||
        pass: &mut TrackedRenderPass<'w>,
 | 
			
		||||
    ) -> RenderCommandResult {
 | 
			
		||||
        pass.set_bind_group(
 | 
			
		||||
            I,
 | 
			
		||||
            &bind_group.into_inner().bindgroup,
 | 
			
		||||
            &[uniform_index.index()],
 | 
			
		||||
        );
 | 
			
		||||
        RenderCommandResult::Success
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct DrawLineGizmo;
 | 
			
		||||
impl<P: PhaseItem> RenderCommand<P> for DrawLineGizmo {
 | 
			
		||||
    type ViewWorldQuery = ();
 | 
			
		||||
    type ItemWorldQuery = Read<Handle<LineGizmo>>;
 | 
			
		||||
    type Param = SRes<RenderAssets<LineGizmo>>;
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn render<'w>(
 | 
			
		||||
        _item: &P,
 | 
			
		||||
        _view: ROQueryItem<'w, Self::ViewWorldQuery>,
 | 
			
		||||
        handle: ROQueryItem<'w, Self::ItemWorldQuery>,
 | 
			
		||||
        line_gizmos: SystemParamItem<'w, '_, Self::Param>,
 | 
			
		||||
        pass: &mut TrackedRenderPass<'w>,
 | 
			
		||||
    ) -> RenderCommandResult {
 | 
			
		||||
        let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else {
 | 
			
		||||
            return RenderCommandResult::Failure;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..));
 | 
			
		||||
        pass.set_vertex_buffer(1, line_gizmo.color_buffer.slice(..));
 | 
			
		||||
 | 
			
		||||
        let instances = if line_gizmo.strip {
 | 
			
		||||
            u32::max(line_gizmo.vertex_count, 1) - 1
 | 
			
		||||
        } else {
 | 
			
		||||
            line_gizmo.vertex_count / 2
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        pass.draw(0..6, 0..instances);
 | 
			
		||||
 | 
			
		||||
        RenderCommandResult::Success
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
 | 
			
		||||
    let stride_multiplier = if strip { 1 } else { 2 };
 | 
			
		||||
    use VertexFormat::*;
 | 
			
		||||
    vec![
 | 
			
		||||
        // Positions
 | 
			
		||||
        VertexBufferLayout {
 | 
			
		||||
            array_stride: Float32x3.size() * stride_multiplier,
 | 
			
		||||
            step_mode: VertexStepMode::Instance,
 | 
			
		||||
            attributes: vec![
 | 
			
		||||
                VertexAttribute {
 | 
			
		||||
                    format: Float32x3,
 | 
			
		||||
                    offset: 0,
 | 
			
		||||
                    shader_location: 0,
 | 
			
		||||
                },
 | 
			
		||||
                VertexAttribute {
 | 
			
		||||
                    format: Float32x3,
 | 
			
		||||
                    offset: Float32x3.size(),
 | 
			
		||||
                    shader_location: 1,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        // Colors
 | 
			
		||||
        VertexBufferLayout {
 | 
			
		||||
            array_stride: Float32x4.size() * stride_multiplier,
 | 
			
		||||
            step_mode: VertexStepMode::Instance,
 | 
			
		||||
            attributes: vec![
 | 
			
		||||
                VertexAttribute {
 | 
			
		||||
                    format: Float32x4,
 | 
			
		||||
                    offset: 0,
 | 
			
		||||
                    shader_location: 2,
 | 
			
		||||
                },
 | 
			
		||||
                VertexAttribute {
 | 
			
		||||
                    format: Float32x4,
 | 
			
		||||
                    offset: Float32x4.size(),
 | 
			
		||||
                    shader_location: 3,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,48 +1,105 @@
 | 
			
		||||
#ifdef GIZMO_LINES_3D
 | 
			
		||||
#ifdef GIZMO_3D
 | 
			
		||||
    #import bevy_pbr::mesh_view_bindings
 | 
			
		||||
#else
 | 
			
		||||
    #import bevy_sprite::mesh2d_view_bindings
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
struct VertexInput {
 | 
			
		||||
    @location(0) pos: vec3<f32>,
 | 
			
		||||
    @location(1) color: vec4<f32>,
 | 
			
		||||
struct LineGizmoUniform {
 | 
			
		||||
    line_width: f32,
 | 
			
		||||
    depth_bias: f32,
 | 
			
		||||
#ifdef SIXTEEN_BYTE_ALIGNMENT
 | 
			
		||||
    // WebGL2 structs must be 16 byte aligned.
 | 
			
		||||
    _padding: vec2<f32>,
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@group(1) @binding(0)
 | 
			
		||||
var<uniform> line_gizmo: LineGizmoUniform;
 | 
			
		||||
 | 
			
		||||
struct VertexInput {
 | 
			
		||||
    @location(0) position_a: vec3<f32>,
 | 
			
		||||
    @location(1) position_b: vec3<f32>,
 | 
			
		||||
    @location(2) color_a: vec4<f32>,
 | 
			
		||||
    @location(3) color_b: vec4<f32>,
 | 
			
		||||
    @builtin(vertex_index) index: u32,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct VertexOutput {
 | 
			
		||||
    @builtin(position) pos: vec4<f32>,
 | 
			
		||||
    @builtin(position) clip_position: vec4<f32>,
 | 
			
		||||
    @location(0) color: vec4<f32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct FragmentOutput {
 | 
			
		||||
#ifdef GIZMO_LINES_3D
 | 
			
		||||
    @builtin(frag_depth) depth: f32,
 | 
			
		||||
#endif
 | 
			
		||||
    @location(0) color: vec4<f32>,
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@vertex
 | 
			
		||||
fn vertex(in: VertexInput) -> VertexOutput {
 | 
			
		||||
    var out: VertexOutput;
 | 
			
		||||
fn vertex(vertex: VertexInput) -> VertexOutput {
 | 
			
		||||
    var positions = array<vec3<f32>, 6>(
 | 
			
		||||
        vec3(0., -0.5, 0.),
 | 
			
		||||
        vec3(0., -0.5, 1.),
 | 
			
		||||
        vec3(0., 0.5, 1.),
 | 
			
		||||
        vec3(0., -0.5, 0.),
 | 
			
		||||
        vec3(0., 0.5, 1.),
 | 
			
		||||
        vec3(0., 0.5, 0.)
 | 
			
		||||
    );
 | 
			
		||||
    let position = positions[vertex.index];
 | 
			
		||||
 | 
			
		||||
    out.pos = view.view_proj * vec4<f32>(in.pos, 1.0);
 | 
			
		||||
    out.color = in.color;
 | 
			
		||||
    // algorithm based on https://wwwtyro.net/2019/11/18/instanced-lines.html
 | 
			
		||||
    let clip_a = view.view_proj * vec4(vertex.position_a, 1.);
 | 
			
		||||
    let clip_b = view.view_proj * vec4(vertex.position_b, 1.);
 | 
			
		||||
    let clip = mix(clip_a, clip_b, position.z);
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
    let resolution = view.viewport.zw;
 | 
			
		||||
    let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
 | 
			
		||||
    let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
 | 
			
		||||
 | 
			
		||||
    let x_basis = normalize(screen_a - screen_b);
 | 
			
		||||
    let y_basis = vec2(-x_basis.y, x_basis.x);
 | 
			
		||||
 | 
			
		||||
    var color = mix(vertex.color_a, vertex.color_b, position.z);
 | 
			
		||||
 | 
			
		||||
    var line_width = line_gizmo.line_width;
 | 
			
		||||
    var alpha = 1.;
 | 
			
		||||
 | 
			
		||||
#ifdef PERSPECTIVE
 | 
			
		||||
    line_width /= clip.w;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    // Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
 | 
			
		||||
    if line_width < 1. {
 | 
			
		||||
        color.a *= line_width;
 | 
			
		||||
        line_width = 1.;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let offset = line_width * (position.x * x_basis + position.y * y_basis);
 | 
			
		||||
    let screen = mix(screen_a, screen_b, position.z) + offset;
 | 
			
		||||
 | 
			
		||||
    var depth: f32;
 | 
			
		||||
    if line_gizmo.depth_bias >= 0. {
 | 
			
		||||
        depth = clip.z * (1. - line_gizmo.depth_bias);
 | 
			
		||||
    } else {
 | 
			
		||||
        let epsilon = 4.88e-04;
 | 
			
		||||
        // depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w
 | 
			
		||||
        // and when equal to 0.0, it is exactly equal to depth.
 | 
			
		||||
        // the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0 
 | 
			
		||||
        // clip.w represents the near plane in homogenous clip space in bevy, having a depth
 | 
			
		||||
        // of this value means nothing can be in front of this
 | 
			
		||||
        // The reason this uses an exponential function is that it makes it much easier for the 
 | 
			
		||||
        // user to chose a value that is convinient for them
 | 
			
		||||
        depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - epsilon));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
 | 
			
		||||
 | 
			
		||||
    return VertexOutput(clip_position, color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct FragmentInput {
 | 
			
		||||
    @location(0) color: vec4<f32>,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct FragmentOutput {
 | 
			
		||||
    @location(0) color: vec4<f32>,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@fragment
 | 
			
		||||
fn fragment(in: VertexOutput) -> FragmentOutput {
 | 
			
		||||
    var out: FragmentOutput;
 | 
			
		||||
 | 
			
		||||
#ifdef GIZMO_LINES_3D
 | 
			
		||||
#ifdef DEPTH_TEST
 | 
			
		||||
    out.depth = in.pos.z;
 | 
			
		||||
#else
 | 
			
		||||
    out.depth = 1.0;
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    out.color = in.color;
 | 
			
		||||
    return out;
 | 
			
		||||
fn fragment(in: FragmentInput) -> FragmentOutput {
 | 
			
		||||
    return FragmentOutput(in.color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,68 +1,101 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    line_gizmo_vertex_buffer_layouts, DrawLineGizmo, LineGizmo, LineGizmoUniformBindgroupLayout,
 | 
			
		||||
    SetLineGizmoBindGroup, LINE_SHADER_HANDLE,
 | 
			
		||||
};
 | 
			
		||||
use bevy_app::{App, Plugin};
 | 
			
		||||
use bevy_asset::Handle;
 | 
			
		||||
use bevy_core_pipeline::core_2d::Transparent2d;
 | 
			
		||||
 | 
			
		||||
use bevy_ecs::{
 | 
			
		||||
    prelude::Entity,
 | 
			
		||||
    query::With,
 | 
			
		||||
    schedule::IntoSystemConfigs,
 | 
			
		||||
    system::{Query, Res, ResMut, Resource},
 | 
			
		||||
    world::{FromWorld, World},
 | 
			
		||||
};
 | 
			
		||||
use bevy_render::{
 | 
			
		||||
    mesh::{Mesh, MeshVertexBufferLayout},
 | 
			
		||||
    render_asset::RenderAssets,
 | 
			
		||||
    render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
 | 
			
		||||
    render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
 | 
			
		||||
    render_resource::*,
 | 
			
		||||
    texture::BevyDefault,
 | 
			
		||||
    view::{ExtractedView, Msaa, ViewTarget},
 | 
			
		||||
    Render, RenderApp, RenderSet,
 | 
			
		||||
};
 | 
			
		||||
use bevy_sprite::*;
 | 
			
		||||
use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup};
 | 
			
		||||
use bevy_utils::FloatOrd;
 | 
			
		||||
 | 
			
		||||
use crate::{GizmoMesh, LINE_SHADER_HANDLE};
 | 
			
		||||
pub struct LineGizmo2dPlugin;
 | 
			
		||||
 | 
			
		||||
#[derive(Resource)]
 | 
			
		||||
pub(crate) struct GizmoLinePipeline {
 | 
			
		||||
    mesh_pipeline: Mesh2dPipeline,
 | 
			
		||||
    shader: Handle<Shader>,
 | 
			
		||||
impl Plugin for LineGizmo2dPlugin {
 | 
			
		||||
    fn build(&self, app: &mut App) {
 | 
			
		||||
        let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
 | 
			
		||||
 | 
			
		||||
        render_app
 | 
			
		||||
            .add_render_command::<Transparent2d, DrawLineGizmo2d>()
 | 
			
		||||
            .init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
 | 
			
		||||
            .add_systems(Render, queue_line_gizmos_2d.in_set(RenderSet::Queue));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn finish(&self, app: &mut App) {
 | 
			
		||||
        let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
 | 
			
		||||
 | 
			
		||||
        render_app.init_resource::<LineGizmoPipeline>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromWorld for GizmoLinePipeline {
 | 
			
		||||
#[derive(Clone, Resource)]
 | 
			
		||||
struct LineGizmoPipeline {
 | 
			
		||||
    mesh_pipeline: Mesh2dPipeline,
 | 
			
		||||
    uniform_layout: BindGroupLayout,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromWorld for LineGizmoPipeline {
 | 
			
		||||
    fn from_world(render_world: &mut World) -> Self {
 | 
			
		||||
        GizmoLinePipeline {
 | 
			
		||||
        LineGizmoPipeline {
 | 
			
		||||
            mesh_pipeline: render_world.resource::<Mesh2dPipeline>().clone(),
 | 
			
		||||
            shader: LINE_SHADER_HANDLE.typed(),
 | 
			
		||||
            uniform_layout: render_world
 | 
			
		||||
                .resource::<LineGizmoUniformBindgroupLayout>()
 | 
			
		||||
                .layout
 | 
			
		||||
                .clone(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SpecializedMeshPipeline for GizmoLinePipeline {
 | 
			
		||||
    type Key = Mesh2dPipelineKey;
 | 
			
		||||
#[derive(PartialEq, Eq, Hash, Clone)]
 | 
			
		||||
struct LineGizmoPipelineKey {
 | 
			
		||||
    mesh_key: Mesh2dPipelineKey,
 | 
			
		||||
    strip: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    fn specialize(
 | 
			
		||||
        &self,
 | 
			
		||||
        key: Self::Key,
 | 
			
		||||
        layout: &MeshVertexBufferLayout,
 | 
			
		||||
    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
 | 
			
		||||
        let vertex_buffer_layout = layout.get_layout(&[
 | 
			
		||||
            Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
 | 
			
		||||
            Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
 | 
			
		||||
        ])?;
 | 
			
		||||
impl SpecializedRenderPipeline for LineGizmoPipeline {
 | 
			
		||||
    type Key = LineGizmoPipelineKey;
 | 
			
		||||
 | 
			
		||||
        let format = if key.contains(Mesh2dPipelineKey::HDR) {
 | 
			
		||||
    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
 | 
			
		||||
        let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) {
 | 
			
		||||
            ViewTarget::TEXTURE_FORMAT_HDR
 | 
			
		||||
        } else {
 | 
			
		||||
            TextureFormat::bevy_default()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(RenderPipelineDescriptor {
 | 
			
		||||
        let shader_defs = vec![
 | 
			
		||||
            #[cfg(feature = "webgl")]
 | 
			
		||||
            "SIXTEEN_BYTE_ALIGNMENT".into(),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let layout = vec![
 | 
			
		||||
            self.mesh_pipeline.view_layout.clone(),
 | 
			
		||||
            self.uniform_layout.clone(),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        RenderPipelineDescriptor {
 | 
			
		||||
            vertex: VertexState {
 | 
			
		||||
                shader: self.shader.clone_weak(),
 | 
			
		||||
                shader: LINE_SHADER_HANDLE.typed(),
 | 
			
		||||
                entry_point: "vertex".into(),
 | 
			
		||||
                shader_defs: vec![],
 | 
			
		||||
                buffers: vec![vertex_buffer_layout],
 | 
			
		||||
                shader_defs: shader_defs.clone(),
 | 
			
		||||
                buffers: line_gizmo_vertex_buffer_layouts(key.strip),
 | 
			
		||||
            },
 | 
			
		||||
            fragment: Some(FragmentState {
 | 
			
		||||
                shader: self.shader.clone_weak(),
 | 
			
		||||
                shader_defs: vec![],
 | 
			
		||||
                shader: LINE_SHADER_HANDLE.typed(),
 | 
			
		||||
                shader_defs,
 | 
			
		||||
                entry_point: "fragment".into(),
 | 
			
		||||
                targets: vec![Some(ColorTargetState {
 | 
			
		||||
                    format,
 | 
			
		||||
@ -70,57 +103,61 @@ impl SpecializedMeshPipeline for GizmoLinePipeline {
 | 
			
		||||
                    write_mask: ColorWrites::ALL,
 | 
			
		||||
                })],
 | 
			
		||||
            }),
 | 
			
		||||
            layout: vec![self.mesh_pipeline.view_layout.clone()],
 | 
			
		||||
            primitive: PrimitiveState {
 | 
			
		||||
                topology: key.primitive_topology(),
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            },
 | 
			
		||||
            layout,
 | 
			
		||||
            primitive: PrimitiveState::default(),
 | 
			
		||||
            depth_stencil: None,
 | 
			
		||||
            multisample: MultisampleState {
 | 
			
		||||
                count: key.msaa_samples(),
 | 
			
		||||
                count: key.mesh_key.msaa_samples(),
 | 
			
		||||
                mask: !0,
 | 
			
		||||
                alpha_to_coverage_enabled: false,
 | 
			
		||||
            },
 | 
			
		||||
            label: Some("LineGizmo Pipeline 2D".into()),
 | 
			
		||||
            push_constant_ranges: vec![],
 | 
			
		||||
            label: Some("gizmo_2d_pipeline".into()),
 | 
			
		||||
        })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) type DrawGizmoLines = (
 | 
			
		||||
type DrawLineGizmo2d = (
 | 
			
		||||
    SetItemPipeline,
 | 
			
		||||
    SetMesh2dViewBindGroup<0>,
 | 
			
		||||
    SetMesh2dBindGroup<1>,
 | 
			
		||||
    DrawMesh2d,
 | 
			
		||||
    SetLineGizmoBindGroup<1>,
 | 
			
		||||
    DrawLineGizmo,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
pub(crate) fn queue_gizmos_2d(
 | 
			
		||||
fn queue_line_gizmos_2d(
 | 
			
		||||
    draw_functions: Res<DrawFunctions<Transparent2d>>,
 | 
			
		||||
    pipeline: Res<GizmoLinePipeline>,
 | 
			
		||||
    pipeline: Res<LineGizmoPipeline>,
 | 
			
		||||
    mut pipelines: ResMut<SpecializedRenderPipelines<LineGizmoPipeline>>,
 | 
			
		||||
    pipeline_cache: Res<PipelineCache>,
 | 
			
		||||
    mut specialized_pipelines: ResMut<SpecializedMeshPipelines<GizmoLinePipeline>>,
 | 
			
		||||
    gpu_meshes: Res<RenderAssets<Mesh>>,
 | 
			
		||||
    msaa: Res<Msaa>,
 | 
			
		||||
    mesh_handles: Query<(Entity, &Mesh2dHandle), With<GizmoMesh>>,
 | 
			
		||||
    line_gizmos: Query<(Entity, &Handle<LineGizmo>)>,
 | 
			
		||||
    line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
 | 
			
		||||
    mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent2d>)>,
 | 
			
		||||
) {
 | 
			
		||||
    let draw_function = draw_functions.read().get_id::<DrawGizmoLines>().unwrap();
 | 
			
		||||
    let key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples());
 | 
			
		||||
    for (view, mut phase) in &mut views {
 | 
			
		||||
        let key = key | Mesh2dPipelineKey::from_hdr(view.hdr);
 | 
			
		||||
        for (entity, mesh_handle) in &mesh_handles {
 | 
			
		||||
            let Some(mesh) = gpu_meshes.get(&mesh_handle.0) else { continue; };
 | 
			
		||||
    let draw_function = draw_functions.read().get_id::<DrawLineGizmo2d>().unwrap();
 | 
			
		||||
 | 
			
		||||
            let key = key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
 | 
			
		||||
            let pipeline = specialized_pipelines
 | 
			
		||||
                .specialize(&pipeline_cache, &pipeline, key, &mesh.layout)
 | 
			
		||||
                .unwrap();
 | 
			
		||||
            phase.add(Transparent2d {
 | 
			
		||||
    for (view, mut transparent_phase) in &mut views {
 | 
			
		||||
        let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
 | 
			
		||||
            | Mesh2dPipelineKey::from_hdr(view.hdr);
 | 
			
		||||
 | 
			
		||||
        for (entity, handle) in &line_gizmos {
 | 
			
		||||
            let Some(line_gizmo) = line_gizmo_assets.get(handle) else { continue };
 | 
			
		||||
 | 
			
		||||
            let pipeline = pipelines.specialize(
 | 
			
		||||
                &pipeline_cache,
 | 
			
		||||
                &pipeline,
 | 
			
		||||
                LineGizmoPipelineKey {
 | 
			
		||||
                    mesh_key,
 | 
			
		||||
                    strip: line_gizmo.strip,
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            transparent_phase.add(Transparent2d {
 | 
			
		||||
                entity,
 | 
			
		||||
                draw_function,
 | 
			
		||||
                pipeline,
 | 
			
		||||
                sort_key: FloatOrd(f32::MAX),
 | 
			
		||||
                sort_key: FloatOrd(0.),
 | 
			
		||||
                batch_range: None,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,53 +1,83 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    line_gizmo_vertex_buffer_layouts, DrawLineGizmo, GizmoConfig, LineGizmo,
 | 
			
		||||
    LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_SHADER_HANDLE,
 | 
			
		||||
};
 | 
			
		||||
use bevy_app::{App, Plugin};
 | 
			
		||||
use bevy_asset::Handle;
 | 
			
		||||
use bevy_core_pipeline::core_3d::Opaque3d;
 | 
			
		||||
use bevy_core_pipeline::core_3d::Transparent3d;
 | 
			
		||||
 | 
			
		||||
use bevy_ecs::{
 | 
			
		||||
    entity::Entity,
 | 
			
		||||
    query::With,
 | 
			
		||||
    prelude::Entity,
 | 
			
		||||
    schedule::IntoSystemConfigs,
 | 
			
		||||
    system::{Query, Res, ResMut, Resource},
 | 
			
		||||
    world::{FromWorld, World},
 | 
			
		||||
};
 | 
			
		||||
use bevy_pbr::*;
 | 
			
		||||
use bevy_render::{
 | 
			
		||||
    mesh::Mesh,
 | 
			
		||||
    render_resource::Shader,
 | 
			
		||||
    view::{ExtractedView, ViewTarget},
 | 
			
		||||
use bevy_pbr::{
 | 
			
		||||
    MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup, MAX_CASCADES_PER_LIGHT,
 | 
			
		||||
    MAX_DIRECTIONAL_LIGHTS,
 | 
			
		||||
};
 | 
			
		||||
use bevy_render::{
 | 
			
		||||
    mesh::MeshVertexBufferLayout,
 | 
			
		||||
    render_asset::RenderAssets,
 | 
			
		||||
    render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
 | 
			
		||||
    render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
 | 
			
		||||
    render_resource::*,
 | 
			
		||||
    texture::BevyDefault,
 | 
			
		||||
    view::Msaa,
 | 
			
		||||
    view::{ExtractedView, Msaa, ViewTarget},
 | 
			
		||||
    Render, RenderApp, RenderSet,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{GizmoConfig, GizmoMesh, LINE_SHADER_HANDLE};
 | 
			
		||||
pub struct LineGizmo3dPlugin;
 | 
			
		||||
impl Plugin for LineGizmo3dPlugin {
 | 
			
		||||
    fn build(&self, app: &mut App) {
 | 
			
		||||
        let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
 | 
			
		||||
 | 
			
		||||
#[derive(Resource)]
 | 
			
		||||
pub(crate) struct GizmoPipeline {
 | 
			
		||||
    mesh_pipeline: MeshPipeline,
 | 
			
		||||
    shader: Handle<Shader>,
 | 
			
		||||
        render_app
 | 
			
		||||
            .add_render_command::<Transparent3d, DrawLineGizmo3d>()
 | 
			
		||||
            .init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
 | 
			
		||||
            .add_systems(Render, queue_line_gizmos_3d.in_set(RenderSet::Queue));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn finish(&self, app: &mut App) {
 | 
			
		||||
        let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return };
 | 
			
		||||
 | 
			
		||||
        render_app.init_resource::<LineGizmoPipeline>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromWorld for GizmoPipeline {
 | 
			
		||||
#[derive(Clone, Resource)]
 | 
			
		||||
struct LineGizmoPipeline {
 | 
			
		||||
    mesh_pipeline: MeshPipeline,
 | 
			
		||||
    uniform_layout: BindGroupLayout,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromWorld for LineGizmoPipeline {
 | 
			
		||||
    fn from_world(render_world: &mut World) -> Self {
 | 
			
		||||
        GizmoPipeline {
 | 
			
		||||
        LineGizmoPipeline {
 | 
			
		||||
            mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
 | 
			
		||||
            shader: LINE_SHADER_HANDLE.typed(),
 | 
			
		||||
            uniform_layout: render_world
 | 
			
		||||
                .resource::<LineGizmoUniformBindgroupLayout>()
 | 
			
		||||
                .layout
 | 
			
		||||
                .clone(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SpecializedMeshPipeline for GizmoPipeline {
 | 
			
		||||
    type Key = (bool, MeshPipelineKey);
 | 
			
		||||
#[derive(PartialEq, Eq, Hash, Clone)]
 | 
			
		||||
struct LineGizmoPipelineKey {
 | 
			
		||||
    mesh_key: MeshPipelineKey,
 | 
			
		||||
    strip: bool,
 | 
			
		||||
    perspective: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SpecializedRenderPipeline for LineGizmoPipeline {
 | 
			
		||||
    type Key = LineGizmoPipelineKey;
 | 
			
		||||
 | 
			
		||||
    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
 | 
			
		||||
        let mut shader_defs = vec![
 | 
			
		||||
            "GIZMO_3D".into(),
 | 
			
		||||
            #[cfg(feature = "webgl")]
 | 
			
		||||
            "SIXTEEN_BYTE_ALIGNMENT".into(),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
    fn specialize(
 | 
			
		||||
        &self,
 | 
			
		||||
        (depth_test, key): Self::Key,
 | 
			
		||||
        layout: &MeshVertexBufferLayout,
 | 
			
		||||
    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
 | 
			
		||||
        let mut shader_defs = Vec::new();
 | 
			
		||||
        shader_defs.push("GIZMO_LINES_3D".into());
 | 
			
		||||
        shader_defs.push(ShaderDefVal::Int(
 | 
			
		||||
            "MAX_DIRECTIONAL_LIGHTS".to_string(),
 | 
			
		||||
            MAX_DIRECTIONAL_LIGHTS as i32,
 | 
			
		||||
@ -56,109 +86,106 @@ impl SpecializedMeshPipeline for GizmoPipeline {
 | 
			
		||||
            "MAX_CASCADES_PER_LIGHT".to_string(),
 | 
			
		||||
            MAX_CASCADES_PER_LIGHT as i32,
 | 
			
		||||
        ));
 | 
			
		||||
        if depth_test {
 | 
			
		||||
            shader_defs.push("DEPTH_TEST".into());
 | 
			
		||||
 | 
			
		||||
        if key.perspective {
 | 
			
		||||
            shader_defs.push("PERSPECTIVE".into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let vertex_buffer_layout = layout.get_layout(&[
 | 
			
		||||
            Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
 | 
			
		||||
            Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
 | 
			
		||||
        ])?;
 | 
			
		||||
        let bind_group_layout = match key.msaa_samples() {
 | 
			
		||||
            1 => vec![self.mesh_pipeline.view_layout.clone()],
 | 
			
		||||
            _ => {
 | 
			
		||||
                shader_defs.push("MULTISAMPLED".into());
 | 
			
		||||
                vec![self.mesh_pipeline.view_layout_multisampled.clone()]
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let format = if key.contains(MeshPipelineKey::HDR) {
 | 
			
		||||
        let format = if key.mesh_key.contains(MeshPipelineKey::HDR) {
 | 
			
		||||
            ViewTarget::TEXTURE_FORMAT_HDR
 | 
			
		||||
        } else {
 | 
			
		||||
            TextureFormat::bevy_default()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(RenderPipelineDescriptor {
 | 
			
		||||
        let view_layout = if key.mesh_key.msaa_samples() == 1 {
 | 
			
		||||
            self.mesh_pipeline.view_layout.clone()
 | 
			
		||||
        } else {
 | 
			
		||||
            self.mesh_pipeline.view_layout_multisampled.clone()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let layout = vec![view_layout, self.uniform_layout.clone()];
 | 
			
		||||
 | 
			
		||||
        RenderPipelineDescriptor {
 | 
			
		||||
            vertex: VertexState {
 | 
			
		||||
                shader: self.shader.clone_weak(),
 | 
			
		||||
                shader: LINE_SHADER_HANDLE.typed(),
 | 
			
		||||
                entry_point: "vertex".into(),
 | 
			
		||||
                shader_defs: shader_defs.clone(),
 | 
			
		||||
                buffers: vec![vertex_buffer_layout],
 | 
			
		||||
                buffers: line_gizmo_vertex_buffer_layouts(key.strip),
 | 
			
		||||
            },
 | 
			
		||||
            fragment: Some(FragmentState {
 | 
			
		||||
                shader: self.shader.clone_weak(),
 | 
			
		||||
                shader: LINE_SHADER_HANDLE.typed(),
 | 
			
		||||
                shader_defs,
 | 
			
		||||
                entry_point: "fragment".into(),
 | 
			
		||||
                targets: vec![Some(ColorTargetState {
 | 
			
		||||
                    format,
 | 
			
		||||
                    blend: None,
 | 
			
		||||
                    blend: Some(BlendState::ALPHA_BLENDING),
 | 
			
		||||
                    write_mask: ColorWrites::ALL,
 | 
			
		||||
                })],
 | 
			
		||||
            }),
 | 
			
		||||
            layout: bind_group_layout,
 | 
			
		||||
            primitive: PrimitiveState {
 | 
			
		||||
                topology: key.primitive_topology(),
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            },
 | 
			
		||||
            layout,
 | 
			
		||||
            primitive: PrimitiveState::default(),
 | 
			
		||||
            depth_stencil: Some(DepthStencilState {
 | 
			
		||||
                format: TextureFormat::Depth32Float,
 | 
			
		||||
                depth_write_enabled: true,
 | 
			
		||||
                depth_compare: CompareFunction::Greater,
 | 
			
		||||
                stencil: Default::default(),
 | 
			
		||||
                bias: Default::default(),
 | 
			
		||||
                stencil: StencilState::default(),
 | 
			
		||||
                bias: DepthBiasState::default(),
 | 
			
		||||
            }),
 | 
			
		||||
            multisample: MultisampleState {
 | 
			
		||||
                count: key.msaa_samples(),
 | 
			
		||||
                count: key.mesh_key.msaa_samples(),
 | 
			
		||||
                mask: !0,
 | 
			
		||||
                alpha_to_coverage_enabled: false,
 | 
			
		||||
            },
 | 
			
		||||
            label: Some("LineGizmo Pipeline".into()),
 | 
			
		||||
            push_constant_ranges: vec![],
 | 
			
		||||
            label: Some("gizmo_3d_pipeline".into()),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) type DrawGizmoLines = (
 | 
			
		||||
    SetItemPipeline,
 | 
			
		||||
    SetMeshViewBindGroup<0>,
 | 
			
		||||
    SetMeshBindGroup<1>,
 | 
			
		||||
    DrawMesh,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
pub(crate) fn queue_gizmos_3d(
 | 
			
		||||
    draw_functions: Res<DrawFunctions<Opaque3d>>,
 | 
			
		||||
    pipeline: Res<GizmoPipeline>,
 | 
			
		||||
    mut pipelines: ResMut<SpecializedMeshPipelines<GizmoPipeline>>,
 | 
			
		||||
    pipeline_cache: Res<PipelineCache>,
 | 
			
		||||
    render_meshes: Res<RenderAssets<Mesh>>,
 | 
			
		||||
    msaa: Res<Msaa>,
 | 
			
		||||
    mesh_handles: Query<(Entity, &Handle<Mesh>), With<GizmoMesh>>,
 | 
			
		||||
    config: Res<GizmoConfig>,
 | 
			
		||||
    mut views: Query<(&ExtractedView, &mut RenderPhase<Opaque3d>)>,
 | 
			
		||||
) {
 | 
			
		||||
    let draw_function = draw_functions.read().get_id::<DrawGizmoLines>().unwrap();
 | 
			
		||||
    let key = MeshPipelineKey::from_msaa_samples(msaa.samples());
 | 
			
		||||
    for (view, mut phase) in &mut views {
 | 
			
		||||
        let key = key | MeshPipelineKey::from_hdr(view.hdr);
 | 
			
		||||
        for (entity, mesh_handle) in &mesh_handles {
 | 
			
		||||
            if let Some(mesh) = render_meshes.get(mesh_handle) {
 | 
			
		||||
                let key = key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
 | 
			
		||||
                let pipeline = pipelines
 | 
			
		||||
                    .specialize(
 | 
			
		||||
                        &pipeline_cache,
 | 
			
		||||
                        &pipeline,
 | 
			
		||||
                        (!config.on_top, key),
 | 
			
		||||
                        &mesh.layout,
 | 
			
		||||
                    )
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                phase.add(Opaque3d {
 | 
			
		||||
                    entity,
 | 
			
		||||
                    pipeline,
 | 
			
		||||
                    draw_function,
 | 
			
		||||
                    distance: 0.,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DrawLineGizmo3d = (
 | 
			
		||||
    SetItemPipeline,
 | 
			
		||||
    SetMeshViewBindGroup<0>,
 | 
			
		||||
    SetLineGizmoBindGroup<1>,
 | 
			
		||||
    DrawLineGizmo,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
fn queue_line_gizmos_3d(
 | 
			
		||||
    draw_functions: Res<DrawFunctions<Transparent3d>>,
 | 
			
		||||
    pipeline: Res<LineGizmoPipeline>,
 | 
			
		||||
    mut pipelines: ResMut<SpecializedRenderPipelines<LineGizmoPipeline>>,
 | 
			
		||||
    pipeline_cache: Res<PipelineCache>,
 | 
			
		||||
    msaa: Res<Msaa>,
 | 
			
		||||
    config: Res<GizmoConfig>,
 | 
			
		||||
    line_gizmos: Query<(Entity, &Handle<LineGizmo>)>,
 | 
			
		||||
    line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
 | 
			
		||||
    mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
 | 
			
		||||
) {
 | 
			
		||||
    let draw_function = draw_functions.read().get_id::<DrawLineGizmo3d>().unwrap();
 | 
			
		||||
 | 
			
		||||
    for (view, mut transparent_phase) in &mut views {
 | 
			
		||||
        let mesh_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
 | 
			
		||||
            | MeshPipelineKey::from_hdr(view.hdr);
 | 
			
		||||
 | 
			
		||||
        for (entity, handle) in &line_gizmos {
 | 
			
		||||
            let Some(line_gizmo) = line_gizmo_assets.get(handle) else { continue };
 | 
			
		||||
 | 
			
		||||
            let pipeline = pipelines.specialize(
 | 
			
		||||
                &pipeline_cache,
 | 
			
		||||
                &pipeline,
 | 
			
		||||
                LineGizmoPipelineKey {
 | 
			
		||||
                    mesh_key,
 | 
			
		||||
                    strip: line_gizmo.strip,
 | 
			
		||||
                    perspective: config.line_perspective,
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            transparent_phase.add(Transparent3d {
 | 
			
		||||
                entity,
 | 
			
		||||
                draw_function,
 | 
			
		||||
                pipeline,
 | 
			
		||||
                distance: 0.,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -75,7 +75,7 @@ x11 = ["bevy_winit/x11"]
 | 
			
		||||
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
 | 
			
		||||
 | 
			
		||||
# Optimise for WebGL2
 | 
			
		||||
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"]
 | 
			
		||||
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl", "bevy_gizmos?/webgl"]
 | 
			
		||||
 | 
			
		||||
# enable systems that allow for automated testing on CI
 | 
			
		||||
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_time/bevy_ci_testing", "bevy_render?/bevy_ci_testing", "bevy_render?/ci_limits"]
 | 
			
		||||
 | 
			
		||||
@ -8,12 +8,21 @@ fn main() {
 | 
			
		||||
    App::new()
 | 
			
		||||
        .add_plugins(DefaultPlugins)
 | 
			
		||||
        .add_systems(Startup, setup)
 | 
			
		||||
        .add_systems(Update, system)
 | 
			
		||||
        .add_systems(Update, (system, update_config))
 | 
			
		||||
        .run();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn setup(mut commands: Commands) {
 | 
			
		||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
 | 
			
		||||
    commands.spawn(Camera2dBundle::default());
 | 
			
		||||
    // text
 | 
			
		||||
    commands.spawn(TextBundle::from_section(
 | 
			
		||||
        "Hold 'Left' or 'Right' to change the line width",
 | 
			
		||||
        TextStyle {
 | 
			
		||||
            font: asset_server.load("fonts/FiraMono-Medium.ttf"),
 | 
			
		||||
            font_size: 24.,
 | 
			
		||||
            color: Color::WHITE,
 | 
			
		||||
        },
 | 
			
		||||
    ));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn system(mut gizmos: Gizmos, time: Res<Time>) {
 | 
			
		||||
@ -45,3 +54,12 @@ fn system(mut gizmos: Gizmos, time: Res<Time>) {
 | 
			
		||||
    // 1 and 32, using the arc length as scalar.
 | 
			
		||||
    gizmos.arc_2d(Vec2::ZERO, sin / 10., PI / 2., 350., Color::ORANGE_RED);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn update_config(mut config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyCode>>, time: Res<Time>) {
 | 
			
		||||
    if keyboard.pressed(KeyCode::Right) {
 | 
			
		||||
        config.line_width += 5. * time.delta_seconds();
 | 
			
		||||
    }
 | 
			
		||||
    if keyboard.pressed(KeyCode::Left) {
 | 
			
		||||
        config.line_width -= 5. * time.delta_seconds();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
//! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging.
 | 
			
		||||
 | 
			
		||||
use std::f32::consts::PI;
 | 
			
		||||
 | 
			
		||||
use bevy::prelude::*;
 | 
			
		||||
@ -47,7 +48,9 @@ fn setup(
 | 
			
		||||
    // example instructions
 | 
			
		||||
    commands.spawn(
 | 
			
		||||
        TextBundle::from_section(
 | 
			
		||||
            "Press 't' to toggle drawing gizmos on top of everything else in the scene",
 | 
			
		||||
            "Press 'D' to toggle drawing gizmos on top of everything else in the scene\n\
 | 
			
		||||
            Press 'P' to toggle perspective for line gizmos\n\
 | 
			
		||||
            Hold 'Left' or 'Right' to change the line width",
 | 
			
		||||
            TextStyle {
 | 
			
		||||
                font_size: 20.,
 | 
			
		||||
                ..default()
 | 
			
		||||
@ -64,7 +67,7 @@ fn setup(
 | 
			
		||||
 | 
			
		||||
fn system(mut gizmos: Gizmos, time: Res<Time>) {
 | 
			
		||||
    gizmos.cuboid(
 | 
			
		||||
        Transform::from_translation(Vec3::Y * -0.5).with_scale(Vec3::new(5., 1., 2.)),
 | 
			
		||||
        Transform::from_translation(Vec3::Y * 0.5).with_scale(Vec3::splat(1.)),
 | 
			
		||||
        Color::BLACK,
 | 
			
		||||
    );
 | 
			
		||||
    gizmos.rect(
 | 
			
		||||
@ -74,12 +77,7 @@ fn system(mut gizmos: Gizmos, time: Res<Time>) {
 | 
			
		||||
        Color::GREEN,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    gizmos.sphere(
 | 
			
		||||
        Vec3::new(1., 0.5, 0.),
 | 
			
		||||
        Quat::IDENTITY,
 | 
			
		||||
        0.5,
 | 
			
		||||
        Color::RED.with_a(0.5),
 | 
			
		||||
    );
 | 
			
		||||
    gizmos.sphere(Vec3::new(1., 0.5, 0.), Quat::IDENTITY, 0.5, Color::RED);
 | 
			
		||||
 | 
			
		||||
    for y in [0., 0.5, 1.] {
 | 
			
		||||
        gizmos.ray(
 | 
			
		||||
@ -106,8 +104,21 @@ fn rotate_camera(mut query: Query<&mut Transform, With<Camera>>, time: Res<Time>
 | 
			
		||||
    transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 2.));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn update_config(mut gizmo_config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyCode>>) {
 | 
			
		||||
    if keyboard.just_pressed(KeyCode::T) {
 | 
			
		||||
        gizmo_config.on_top = !gizmo_config.on_top;
 | 
			
		||||
fn update_config(mut config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyCode>>, time: Res<Time>) {
 | 
			
		||||
    if keyboard.just_pressed(KeyCode::D) {
 | 
			
		||||
        config.depth_bias = if config.depth_bias == 0. { -1. } else { 0. };
 | 
			
		||||
    }
 | 
			
		||||
    if keyboard.just_pressed(KeyCode::P) {
 | 
			
		||||
        // Toggle line_perspective
 | 
			
		||||
        config.line_perspective ^= true;
 | 
			
		||||
        // Increase the line width when line_perspective is on
 | 
			
		||||
        config.line_width *= if config.line_perspective { 5. } else { 1. / 5. };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if keyboard.pressed(KeyCode::Right) {
 | 
			
		||||
        config.line_width += 5. * time.delta_seconds();
 | 
			
		||||
    }
 | 
			
		||||
    if keyboard.pressed(KeyCode::Left) {
 | 
			
		||||
        config.line_width -= 5. * time.delta_seconds();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,10 +23,6 @@ fn main() {
 | 
			
		||||
        line_count: 50_000,
 | 
			
		||||
        fancy: false,
 | 
			
		||||
    })
 | 
			
		||||
    .insert_resource(GizmoConfig {
 | 
			
		||||
        on_top: false,
 | 
			
		||||
        ..default()
 | 
			
		||||
    })
 | 
			
		||||
    .add_systems(Startup, setup)
 | 
			
		||||
    .add_systems(Update, (input, ui_system));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user