RenderCommand for DrawLineGizmo {
}
let instances = if line_gizmo.strip {
- let item_size = VertexFormat::Float32x3.size();
- let buffer_size = line_gizmo.position_buffer.size() - item_size;
- pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..buffer_size));
- pass.set_vertex_buffer(1, line_gizmo.position_buffer.slice(item_size..));
+ pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..));
+ pass.set_vertex_buffer(1, line_gizmo.position_buffer.slice(..));
- let item_size = VertexFormat::Float32x4.size();
- let buffer_size = line_gizmo.color_buffer.size() - item_size;
- pass.set_vertex_buffer(2, line_gizmo.color_buffer.slice(..buffer_size));
- pass.set_vertex_buffer(3, line_gizmo.color_buffer.slice(item_size..));
+ pass.set_vertex_buffer(2, line_gizmo.color_buffer.slice(..));
+ pass.set_vertex_buffer(3, line_gizmo.color_buffer.slice(..));
u32::max(line_gizmo.vertex_count, 1) - 1
} else {
@@ -470,6 +496,58 @@ impl RenderCommand for DrawLineGizmo {
}
}
+struct DrawLineJointGizmo;
+impl RenderCommand for DrawLineJointGizmo {
+ type Param = SRes>;
+ type ViewQuery = ();
+ type ItemQuery = Read>;
+
+ #[inline]
+ fn render<'w>(
+ _item: &P,
+ _view: ROQueryItem<'w, Self::ViewQuery>,
+ handle: Option>,
+ line_gizmos: SystemParamItem<'w, '_, Self::Param>,
+ pass: &mut TrackedRenderPass<'w>,
+ ) -> RenderCommandResult {
+ let Some(handle) = handle else {
+ return RenderCommandResult::Failure;
+ };
+ let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else {
+ return RenderCommandResult::Failure;
+ };
+
+ if line_gizmo.vertex_count <= 2 || !line_gizmo.strip {
+ return RenderCommandResult::Success;
+ };
+
+ if line_gizmo.joints == GizmoLineJoint::None {
+ return RenderCommandResult::Success;
+ };
+
+ let instances = {
+ pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..));
+ pass.set_vertex_buffer(1, line_gizmo.position_buffer.slice(..));
+ pass.set_vertex_buffer(2, line_gizmo.position_buffer.slice(..));
+
+ pass.set_vertex_buffer(3, line_gizmo.color_buffer.slice(..));
+
+ u32::max(line_gizmo.vertex_count, 2) - 2
+ };
+
+ let vertices = match line_gizmo.joints {
+ GizmoLineJoint::None => unreachable!(),
+ GizmoLineJoint::Miter => 6,
+ GizmoLineJoint::Round(resolution) => resolution * 3,
+ GizmoLineJoint::Bevel => 3,
+ };
+
+ pass.draw(0..vertices, 0..instances);
+
+ RenderCommandResult::Success
+ }
+}
+
fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec {
use VertexFormat::*;
let mut position_layout = VertexBufferLayout {
@@ -497,11 +575,13 @@ fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec {
position_layout.clone(),
{
position_layout.attributes[0].shader_location = 1;
+ position_layout.attributes[0].offset = Float32x3.size();
position_layout
},
color_layout.clone(),
{
color_layout.attributes[0].shader_location = 3;
+ color_layout.attributes[0].offset = Float32x4.size();
color_layout
},
]
@@ -523,3 +603,41 @@ fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec {
vec![position_layout, color_layout]
}
}
+
+fn line_joint_gizmo_vertex_buffer_layouts() -> Vec {
+ use VertexFormat::*;
+ let mut position_layout = VertexBufferLayout {
+ array_stride: Float32x3.size(),
+ step_mode: VertexStepMode::Instance,
+ attributes: vec![VertexAttribute {
+ format: Float32x3,
+ offset: 0,
+ shader_location: 0,
+ }],
+ };
+
+ let color_layout = VertexBufferLayout {
+ array_stride: Float32x4.size(),
+ step_mode: VertexStepMode::Instance,
+ attributes: vec![VertexAttribute {
+ format: Float32x4,
+ offset: Float32x4.size(),
+ shader_location: 3,
+ }],
+ };
+
+ vec![
+ position_layout.clone(),
+ {
+ position_layout.attributes[0].shader_location = 1;
+ position_layout.attributes[0].offset = Float32x3.size();
+ position_layout.clone()
+ },
+ {
+ position_layout.attributes[0].shader_location = 2;
+ position_layout.attributes[0].offset = 2 * Float32x3.size();
+ position_layout
+ },
+ color_layout.clone(),
+ ]
+}
diff --git a/crates/bevy_gizmos/src/line_joints.wgsl b/crates/bevy_gizmos/src/line_joints.wgsl
new file mode 100644
index 0000000000000..974a5266286be
--- /dev/null
+++ b/crates/bevy_gizmos/src/line_joints.wgsl
@@ -0,0 +1,248 @@
+#import bevy_render::view::View
+
+@group(0) @binding(0) var view: View;
+
+
+struct LineGizmoUniform {
+ line_width: f32,
+ depth_bias: f32,
+ resolution: u32,
+#ifdef SIXTEEN_BYTE_ALIGNMENT
+ // WebGL2 structs must be 16 byte aligned.
+ _padding: f32,
+#endif
+}
+
+@group(1) @binding(0) var joints_gizmo: LineGizmoUniform;
+
+struct VertexInput {
+ @location(0) position_a: vec3,
+ @location(1) position_b: vec3,
+ @location(2) position_c: vec3,
+ @location(3) color: vec4,
+ @builtin(vertex_index) index: u32,
+};
+
+struct VertexOutput {
+ @builtin(position) clip_position: vec4,
+ @location(0) color: vec4,
+};
+
+const EPSILON: f32 = 4.88e-04;
+
+@vertex
+fn vertex_bevel(vertex: VertexInput) -> VertexOutput {
+ var positions = array, 3>(
+ vec2(0, 0),
+ vec2(0, 0.5),
+ vec2(0.5, 0),
+ );
+ var position = positions[vertex.index];
+
+ var clip_a = view.view_proj * vec4(vertex.position_a, 1.);
+ var clip_b = view.view_proj * vec4(vertex.position_b, 1.);
+ var clip_c = view.view_proj * vec4(vertex.position_c, 1.);
+
+ // Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
+ clip_a = clip_near_plane(clip_a, clip_c);
+ clip_b = clip_near_plane(clip_b, clip_a);
+ clip_c = clip_near_plane(clip_c, clip_b);
+ clip_a = clip_near_plane(clip_a, clip_c);
+
+ 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 screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
+
+ var color = vertex.color;
+ var line_width = joints_gizmo.line_width;
+
+#ifdef PERSPECTIVE
+ line_width /= clip_b.w;
+#endif
+
+ // Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
+ if line_width > 0.0 && line_width < 1. {
+ color.a *= line_width;
+ line_width = 1.;
+ }
+
+ let ab = normalize(screen_b - screen_a);
+ let cb = normalize(screen_b - screen_c);
+ let ab_norm = vec2(-ab.y, ab.x);
+ let cb_norm = vec2(cb.y, -cb.x);
+ let tangent = normalize(ab - cb);
+ let normal = vec2(-tangent.y, tangent.x);
+ let sigma = sign(dot(ab + cb, normal));
+
+ var p0 = line_width * sigma * ab_norm;
+ var p1 = line_width * sigma * cb_norm;
+
+ let screen = screen_b + position.x * p0 + position.y * p1;
+
+ let depth = depth(clip_b);
+
+ var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
+ return VertexOutput(clip_position, color);
+}
+
+@vertex
+fn vertex_miter(vertex: VertexInput) -> VertexOutput {
+ var positions = array, 6>(
+ vec3(0, 0, 0),
+ vec3(0.5, 0, 0),
+ vec3(0, 0.5, 0),
+ vec3(0, 0, 0),
+ vec3(0, 0.5, 0),
+ vec3(0, 0, 0.5),
+ );
+ var position = positions[vertex.index];
+
+ var clip_a = view.view_proj * vec4(vertex.position_a, 1.);
+ var clip_b = view.view_proj * vec4(vertex.position_b, 1.);
+ var clip_c = view.view_proj * vec4(vertex.position_c, 1.);
+
+ // Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
+ clip_a = clip_near_plane(clip_a, clip_c);
+ clip_b = clip_near_plane(clip_b, clip_a);
+ clip_c = clip_near_plane(clip_c, clip_b);
+ clip_a = clip_near_plane(clip_a, clip_c);
+
+ 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 screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
+
+ var color = vertex.color;
+ var line_width = joints_gizmo.line_width;
+
+#ifdef PERSPECTIVE
+ line_width /= clip_b.w;
+#endif
+
+ // Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
+ if line_width > 0.0 && line_width < 1. {
+ color.a *= line_width;
+ line_width = 1.;
+ }
+
+ let ab = normalize(screen_b - screen_a);
+ let cb = normalize(screen_b - screen_c);
+ let ab_norm = vec2(-ab.y, ab.x);
+ let cb_norm = vec2(cb.y, -cb.x);
+ let tangent = normalize(ab - cb);
+ let normal = vec2(-tangent.y, tangent.x);
+ let sigma = sign(dot(ab + cb, normal));
+
+ var p0 = line_width * sigma * ab_norm;
+ var p1 = line_width * sigma * normal / dot(normal, ab_norm);
+ var p2 = line_width * sigma * cb_norm;
+
+ var screen = screen_b + position.x * p0 + position.y * p1 + position.z * p2;
+
+ var depth = depth(clip_b);
+
+ var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
+ return VertexOutput(clip_position, color);
+}
+
+@vertex
+fn vertex_round(vertex: VertexInput) -> VertexOutput {
+ var clip_a = view.view_proj * vec4(vertex.position_a, 1.);
+ var clip_b = view.view_proj * vec4(vertex.position_b, 1.);
+ var clip_c = view.view_proj * vec4(vertex.position_c, 1.);
+
+ // Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
+ clip_a = clip_near_plane(clip_a, clip_c);
+ clip_b = clip_near_plane(clip_b, clip_a);
+ clip_c = clip_near_plane(clip_c, clip_b);
+ clip_a = clip_near_plane(clip_a, clip_c);
+
+ 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 screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
+
+ var color = vertex.color;
+ var line_width = joints_gizmo.line_width;
+
+#ifdef PERSPECTIVE
+ line_width /= clip_b.w;
+#endif
+
+ // Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
+ if line_width > 0.0 && line_width < 1. {
+ color.a *= line_width;
+ line_width = 1.;
+ }
+
+ let ab = normalize(screen_b - screen_a);
+ let cb = normalize(screen_b - screen_c);
+ let ab_norm = vec2(-ab.y, ab.x);
+ let cb_norm = vec2(cb.y, -cb.x);
+
+ // We render `joints_gizmo.resolution`triangles. The vertices in each triangle are ordered as follows:
+ // - 0: The 'center' vertex at `screen_b`.
+ // - 1: The vertex closer to the ab line.
+ // - 2: The vertex closer to the cb line.
+ var in_triangle_index = f32(vertex.index) % 3.0;
+ var tri_index = floor(f32(vertex.index) / 3.0);
+ var radius = sign(in_triangle_index) * 0.5 * line_width;
+ var theta = acos(dot(ab_norm, cb_norm));
+ let sigma = sign(dot(ab_norm, cb));
+ var angle = theta * (tri_index + in_triangle_index - 1) / f32(joints_gizmo.resolution);
+ var position_x = sigma * radius * cos(angle);
+ var position_y = radius * sin(angle);
+
+ var screen = screen_b + position_x * ab_norm + position_y * ab;
+
+ var depth = depth(clip_b);
+
+ var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
+ return VertexOutput(clip_position, color);
+}
+
+fn clip_near_plane(a: vec4, b: vec4) -> vec4 {
+ // Move a if a is behind the near plane and b is in front.
+ if a.z > a.w && b.z <= b.w {
+ // Interpolate a towards b until it's at the near plane.
+ let distance_a = a.z - a.w;
+ let distance_b = b.z - b.w;
+ // Add an epsilon to the interpolator to ensure that the point is
+ // not just behind the clip plane due to floating-point imprecision.
+ let t = distance_a / (distance_a - distance_b) + EPSILON;
+ return mix(a, b, t);
+ }
+ return a;
+}
+
+fn depth(clip: vec4) -> f32 {
+ var depth: f32;
+ if joints_gizmo.depth_bias >= 0. {
+ depth = clip.z * (1. - joints_gizmo.depth_bias);
+ } else {
+ // 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 homogeneous 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 convenient for them
+ depth = clip.z * exp2(-joints_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON));
+ }
+ return depth;
+}
+
+struct FragmentInput {
+ @location(0) color: vec4,
+};
+
+struct FragmentOutput {
+ @location(0) color: vec4,
+};
+
+@fragment
+fn fragment(in: FragmentInput) -> FragmentOutput {
+ // return FragmentOutput(vec4(1, 1, 1, 1));
+ return FragmentOutput(in.color);
+}
\ No newline at end of file
diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs
index f55453fa67035..8e44a6ddafc57 100644
--- a/crates/bevy_gizmos/src/pipeline_2d.rs
+++ b/crates/bevy_gizmos/src/pipeline_2d.rs
@@ -1,6 +1,8 @@
use crate::{
- config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts, DrawLineGizmo, GizmoRenderSystem,
- LineGizmo, LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_SHADER_HANDLE,
+ config::{GizmoLineJoint, GizmoMeshConfig},
+ line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
+ DrawLineJointGizmo, GizmoRenderSystem, LineGizmo, LineGizmoUniformBindgroupLayout,
+ SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
};
use bevy_app::{App, Plugin};
use bevy_asset::Handle;
@@ -21,6 +23,7 @@ use bevy_render::{
Render, RenderApp, RenderSet,
};
use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup};
+use bevy_utils::tracing::error;
use bevy_utils::FloatOrd;
pub struct LineGizmo2dPlugin;
@@ -33,14 +36,16 @@ impl Plugin for LineGizmo2dPlugin {
render_app
.add_render_command::()
+ .add_render_command::()
.init_resource::>()
+ .init_resource::>()
.configure_sets(
Render,
GizmoRenderSystem::QueueLineGizmos2d.in_set(RenderSet::Queue),
)
.add_systems(
Render,
- queue_line_gizmos_2d
+ (queue_line_gizmos_2d, queue_line_joint_gizmos_2d)
.in_set(GizmoRenderSystem::QueueLineGizmos2d)
.after(prepare_assets::),
);
@@ -52,6 +57,7 @@ impl Plugin for LineGizmo2dPlugin {
};
render_app.init_resource::();
+ render_app.init_resource::();
}
}
@@ -130,12 +136,103 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
}
}
+#[derive(Clone, Resource)]
+struct LineJointGizmoPipeline {
+ mesh_pipeline: Mesh2dPipeline,
+ uniform_layout: BindGroupLayout,
+}
+
+impl FromWorld for LineJointGizmoPipeline {
+ fn from_world(render_world: &mut World) -> Self {
+ LineJointGizmoPipeline {
+ mesh_pipeline: render_world.resource::().clone(),
+ uniform_layout: render_world
+ .resource::()
+ .layout
+ .clone(),
+ }
+ }
+}
+
+#[derive(PartialEq, Eq, Hash, Clone)]
+struct LineJointGizmoPipelineKey {
+ mesh_key: Mesh2dPipelineKey,
+ joints: GizmoLineJoint,
+}
+
+impl SpecializedRenderPipeline for LineJointGizmoPipeline {
+ type Key = LineJointGizmoPipelineKey;
+
+ fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
+ let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) {
+ ViewTarget::TEXTURE_FORMAT_HDR
+ } else {
+ TextureFormat::bevy_default()
+ };
+
+ let shader_defs = vec![
+ #[cfg(feature = "webgl")]
+ "SIXTEEN_BYTE_ALIGNMENT".into(),
+ ];
+
+ let layout = vec![
+ self.mesh_pipeline.view_layout.clone(),
+ self.uniform_layout.clone(),
+ ];
+
+ if key.joints == GizmoLineJoint::None {
+ error!("There is no entry point for line joints with GizmoLineJoints::None. Please consider aborting the drawing process before reaching this stage.");
+ };
+
+ let entry_point = match key.joints {
+ GizmoLineJoint::Miter => "vertex_miter",
+ GizmoLineJoint::Round(_) => "vertex_round",
+ GizmoLineJoint::None | GizmoLineJoint::Bevel => "vertex_bevel",
+ };
+
+ RenderPipelineDescriptor {
+ vertex: VertexState {
+ shader: LINE_JOINT_SHADER_HANDLE,
+ entry_point: entry_point.into(),
+ shader_defs: shader_defs.clone(),
+ buffers: line_joint_gizmo_vertex_buffer_layouts(),
+ },
+ fragment: Some(FragmentState {
+ shader: LINE_JOINT_SHADER_HANDLE,
+ shader_defs,
+ entry_point: "fragment".into(),
+ targets: vec![Some(ColorTargetState {
+ format,
+ blend: Some(BlendState::ALPHA_BLENDING),
+ write_mask: ColorWrites::ALL,
+ })],
+ }),
+ layout,
+ primitive: PrimitiveState::default(),
+ depth_stencil: None,
+ multisample: MultisampleState {
+ count: key.mesh_key.msaa_samples(),
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ label: Some("LineJointGizmo Pipeline 2D".into()),
+ push_constant_ranges: vec![],
+ }
+ }
+}
+
type DrawLineGizmo2d = (
SetItemPipeline,
SetMesh2dViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo,
);
+type DrawLineJointGizmo2d = (
+ SetItemPipeline,
+ SetMesh2dViewBindGroup<0>,
+ SetLineGizmoBindGroup<1>,
+ DrawLineJointGizmo,
+);
#[allow(clippy::too_many_arguments)]
fn queue_line_gizmos_2d(
@@ -188,3 +285,61 @@ fn queue_line_gizmos_2d(
}
}
}
+
+#[allow(clippy::too_many_arguments)]
+fn queue_line_joint_gizmos_2d(
+ draw_functions: Res>,
+ pipeline: Res,
+ mut pipelines: ResMut>,
+ pipeline_cache: Res,
+ msaa: Res,
+ line_gizmos: Query<(Entity, &Handle, &GizmoMeshConfig)>,
+ line_gizmo_assets: Res>,
+ mut views: Query<(
+ &ExtractedView,
+ &mut RenderPhase,
+ Option<&RenderLayers>,
+ )>,
+) {
+ let draw_function = draw_functions
+ .read()
+ .get_id::()
+ .unwrap();
+
+ for (view, mut transparent_phase, render_layers) in &mut views {
+ let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
+ | Mesh2dPipelineKey::from_hdr(view.hdr);
+
+ for (entity, handle, config) in &line_gizmos {
+ let render_layers = render_layers.copied().unwrap_or_default();
+ if !config.render_layers.intersects(&render_layers) {
+ continue;
+ }
+
+ let Some(line_gizmo) = line_gizmo_assets.get(handle) else {
+ continue;
+ };
+
+ if !line_gizmo.strip || line_gizmo.joints == GizmoLineJoint::None {
+ continue;
+ }
+
+ let pipeline = pipelines.specialize(
+ &pipeline_cache,
+ &pipeline,
+ LineJointGizmoPipelineKey {
+ mesh_key,
+ joints: line_gizmo.joints,
+ },
+ );
+ transparent_phase.add(Transparent2d {
+ entity,
+ draw_function,
+ pipeline,
+ sort_key: FloatOrd(f32::INFINITY),
+ batch_range: 0..1,
+ dynamic_offset: None,
+ });
+ }
+ }
+}
diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs
index bd5064e39d789..ecefb13d510cf 100644
--- a/crates/bevy_gizmos/src/pipeline_3d.rs
+++ b/crates/bevy_gizmos/src/pipeline_3d.rs
@@ -1,6 +1,8 @@
use crate::{
- config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts, DrawLineGizmo, GizmoRenderSystem,
- LineGizmo, LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_SHADER_HANDLE,
+ config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts,
+ line_joint_gizmo_vertex_buffer_layouts, prelude::GizmoLineJoint, DrawLineGizmo,
+ DrawLineJointGizmo, GizmoRenderSystem, LineGizmo, LineGizmoUniformBindgroupLayout,
+ SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
};
use bevy_app::{App, Plugin};
use bevy_asset::Handle;
@@ -25,6 +27,7 @@ use bevy_render::{
view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
Render, RenderApp, RenderSet,
};
+use bevy_utils::tracing::error;
pub struct LineGizmo3dPlugin;
impl Plugin for LineGizmo3dPlugin {
@@ -35,14 +38,16 @@ impl Plugin for LineGizmo3dPlugin {
render_app
.add_render_command::()
+ .add_render_command::()
.init_resource::>()
+ .init_resource::>()
.configure_sets(
Render,
GizmoRenderSystem::QueueLineGizmos3d.in_set(RenderSet::Queue),
)
.add_systems(
Render,
- queue_line_gizmos_3d
+ (queue_line_gizmos_3d, queue_line_joint_gizmos_3d)
.in_set(GizmoRenderSystem::QueueLineGizmos3d)
.after(prepare_assets::),
);
@@ -54,6 +59,7 @@ impl Plugin for LineGizmo3dPlugin {
};
render_app.init_resource::();
+ render_app.init_resource::();
}
}
@@ -145,12 +151,116 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
}
}
+#[derive(Clone, Resource)]
+struct LineJointGizmoPipeline {
+ mesh_pipeline: MeshPipeline,
+ uniform_layout: BindGroupLayout,
+}
+
+impl FromWorld for LineJointGizmoPipeline {
+ fn from_world(render_world: &mut World) -> Self {
+ LineJointGizmoPipeline {
+ mesh_pipeline: render_world.resource::().clone(),
+ uniform_layout: render_world
+ .resource::()
+ .layout
+ .clone(),
+ }
+ }
+}
+
+#[derive(PartialEq, Eq, Hash, Clone)]
+struct LineJointGizmoPipelineKey {
+ view_key: MeshPipelineKey,
+ perspective: bool,
+ joints: GizmoLineJoint,
+}
+
+impl SpecializedRenderPipeline for LineJointGizmoPipeline {
+ type Key = LineJointGizmoPipelineKey;
+
+ fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
+ let mut shader_defs = vec![
+ #[cfg(feature = "webgl")]
+ "SIXTEEN_BYTE_ALIGNMENT".into(),
+ ];
+
+ if key.perspective {
+ shader_defs.push("PERSPECTIVE".into());
+ }
+
+ let format = if key.view_key.contains(MeshPipelineKey::HDR) {
+ ViewTarget::TEXTURE_FORMAT_HDR
+ } else {
+ TextureFormat::bevy_default()
+ };
+
+ let view_layout = self
+ .mesh_pipeline
+ .get_view_layout(key.view_key.into())
+ .clone();
+
+ let layout = vec![view_layout, self.uniform_layout.clone()];
+
+ if key.joints == GizmoLineJoint::None {
+ error!("There is no entry point for line joints with GizmoLineJoints::None. Please consider aborting the drawing process before reaching this stage.");
+ };
+
+ let entry_point = match key.joints {
+ GizmoLineJoint::Miter => "vertex_miter",
+ GizmoLineJoint::Round(_) => "vertex_round",
+ GizmoLineJoint::None | GizmoLineJoint::Bevel => "vertex_bevel",
+ };
+
+ RenderPipelineDescriptor {
+ vertex: VertexState {
+ shader: LINE_JOINT_SHADER_HANDLE,
+ entry_point: entry_point.into(),
+ shader_defs: shader_defs.clone(),
+ buffers: line_joint_gizmo_vertex_buffer_layouts(),
+ },
+ fragment: Some(FragmentState {
+ shader: LINE_JOINT_SHADER_HANDLE,
+ shader_defs,
+ entry_point: "fragment".into(),
+ targets: vec![Some(ColorTargetState {
+ format,
+ blend: Some(BlendState::ALPHA_BLENDING),
+ write_mask: ColorWrites::ALL,
+ })],
+ }),
+ layout,
+ primitive: PrimitiveState::default(),
+ depth_stencil: Some(DepthStencilState {
+ format: CORE_3D_DEPTH_FORMAT,
+ depth_write_enabled: true,
+ depth_compare: CompareFunction::Greater,
+ stencil: StencilState::default(),
+ bias: DepthBiasState::default(),
+ }),
+ multisample: MultisampleState {
+ count: key.view_key.msaa_samples(),
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ label: Some("LineJointGizmo Pipeline".into()),
+ push_constant_ranges: vec![],
+ }
+ }
+}
+
type DrawLineGizmo3d = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo,
);
+type DrawLineJointGizmo3d = (
+ SetItemPipeline,
+ SetMeshViewBindGroup<0>,
+ SetLineGizmoBindGroup<1>,
+ DrawLineJointGizmo,
+);
#[allow(clippy::too_many_arguments)]
fn queue_line_gizmos_3d(
@@ -233,3 +343,92 @@ fn queue_line_gizmos_3d(
}
}
}
+
+#[allow(clippy::too_many_arguments)]
+fn queue_line_joint_gizmos_3d(
+ draw_functions: Res>,
+ pipeline: Res,
+ mut pipelines: ResMut>,
+ pipeline_cache: Res,
+ msaa: Res,
+ line_gizmos: Query<(Entity, &Handle, &GizmoMeshConfig)>,
+ line_gizmo_assets: Res>,
+ mut views: Query<(
+ &ExtractedView,
+ &mut RenderPhase,
+ Option<&RenderLayers>,
+ (
+ Has,
+ Has,
+ Has,
+ Has,
+ ),
+ )>,
+) {
+ let draw_function = draw_functions
+ .read()
+ .get_id::()
+ .unwrap();
+
+ for (
+ view,
+ mut transparent_phase,
+ render_layers,
+ (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
+ ) in &mut views
+ {
+ let render_layers = render_layers.copied().unwrap_or_default();
+
+ let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
+ | MeshPipelineKey::from_hdr(view.hdr);
+
+ if normal_prepass {
+ view_key |= MeshPipelineKey::NORMAL_PREPASS;
+ }
+
+ if depth_prepass {
+ view_key |= MeshPipelineKey::DEPTH_PREPASS;
+ }
+
+ if motion_vector_prepass {
+ view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
+ }
+
+ if deferred_prepass {
+ view_key |= MeshPipelineKey::DEFERRED_PREPASS;
+ }
+
+ for (entity, handle, config) in &line_gizmos {
+ if !config.render_layers.intersects(&render_layers) {
+ continue;
+ }
+
+ let Some(line_gizmo) = line_gizmo_assets.get(handle) else {
+ continue;
+ };
+
+ if !line_gizmo.strip || line_gizmo.joints == GizmoLineJoint::None {
+ continue;
+ }
+
+ let pipeline = pipelines.specialize(
+ &pipeline_cache,
+ &pipeline,
+ LineJointGizmoPipelineKey {
+ view_key,
+ perspective: config.line_perspective,
+ joints: line_gizmo.joints,
+ },
+ );
+
+ transparent_phase.add(Transparent3d {
+ entity,
+ draw_function,
+ pipeline,
+ distance: 0.,
+ batch_range: 0..1,
+ dynamic_offset: None,
+ });
+ }
+ }
+}
diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs
index a4604ed1b46c8..ccd4b1171f699 100644
--- a/examples/gizmos/2d_gizmos.rs
+++ b/examples/gizmos/2d_gizmos.rs
@@ -23,7 +23,8 @@ fn setup(mut commands: Commands, asset_server: Res) {
commands.spawn(TextBundle::from_section(
"Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
- Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos",
+ Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
+ Press 'J' or 'K' to cycle through line joints for straight or round gizmos",
TextStyle {
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 24.,
@@ -106,6 +107,14 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit1) {
config.enabled ^= true;
}
+ if keyboard.just_pressed(KeyCode::KeyJ) {
+ config.line_joints = match config.line_joints {
+ GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
+ GizmoLineJoint::Miter => GizmoLineJoint::Round(4),
+ GizmoLineJoint::Round(_) => GizmoLineJoint::None,
+ GizmoLineJoint::None => GizmoLineJoint::Bevel,
+ };
+ }
let (my_config, _) = config_store.config_mut::();
if keyboard.pressed(KeyCode::ArrowUp) {
@@ -119,4 +128,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit2) {
my_config.enabled ^= true;
}
+ if keyboard.just_pressed(KeyCode::KeyK) {
+ my_config.line_joints = match my_config.line_joints {
+ GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
+ GizmoLineJoint::Miter => GizmoLineJoint::Round(4),
+ GizmoLineJoint::Round(_) => GizmoLineJoint::None,
+ GizmoLineJoint::None => GizmoLineJoint::Bevel,
+ };
+ }
}
diff --git a/examples/gizmos/3d_gizmos.rs b/examples/gizmos/3d_gizmos.rs
index 973d3f13815c1..b3f76575dc831 100644
--- a/examples/gizmos/3d_gizmos.rs
+++ b/examples/gizmos/3d_gizmos.rs
@@ -59,8 +59,7 @@ fn setup(
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
Press 'A' to show all AABB boxes\n\
- Press 'K' or 'J' to cycle through primitives rendered with gizmos\n\
- Press 'H' or 'L' to decrease/increase the amount of segments in the primitives",
+ Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
TextStyle {
font_size: 20.,
..default()
@@ -170,6 +169,14 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit1) {
config.enabled ^= true;
}
+ if keyboard.just_pressed(KeyCode::KeyJ) {
+ config.line_joints = match config.line_joints {
+ GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
+ GizmoLineJoint::Miter => GizmoLineJoint::Round(4),
+ GizmoLineJoint::Round(_) => GizmoLineJoint::None,
+ GizmoLineJoint::None => GizmoLineJoint::Bevel,
+ };
+ }
let (my_config, _) = config_store.config_mut::();
if keyboard.pressed(KeyCode::ArrowUp) {
@@ -183,6 +190,14 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit2) {
my_config.enabled ^= true;
}
+ if keyboard.just_pressed(KeyCode::KeyK) {
+ my_config.line_joints = match my_config.line_joints {
+ GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
+ GizmoLineJoint::Miter => GizmoLineJoint::Round(4),
+ GizmoLineJoint::Round(_) => GizmoLineJoint::None,
+ GizmoLineJoint::None => GizmoLineJoint::Bevel,
+ };
+ }
if keyboard.just_pressed(KeyCode::KeyA) {
// AABB gizmos are normally only drawn on entities with a ShowAabbGizmo component