Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gizmo line joints #12252

Merged
merged 14 commits into from
Mar 11, 2024
16 changes: 16 additions & 0 deletions crates/bevy_gizmos/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ use std::{
ops::{Deref, DerefMut},
};

/// An enum configuring how line joins will be drawn.
#[derive(Debug, Copy, Clone, Reflect, PartialEq, Eq, Hash)]
pub enum GizmoLineJoint {
/// Extends both lines at the joining point until they meet in a sharp point.
Miter,
/// Draws a round corner with the specified resolution between the two lines.
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved
Round(u32),
/// Draws a bevel to connect the ends of both lines.
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved
Bevel,
}
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved

/// A trait used to create gizmo configs groups.
///
/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
Expand Down Expand Up @@ -135,6 +146,9 @@ pub struct GizmoConfig {
///
/// Gizmos will only be rendered to cameras with intersecting layers.
pub render_layers: RenderLayers,

/// Describe how lines should join
pub line_joints: Option<GizmoLineJoint>,
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved
}

impl Default for GizmoConfig {
Expand All @@ -145,6 +159,8 @@ impl Default for GizmoConfig {
line_perspective: false,
depth_bias: 0.,
render_layers: Default::default(),

line_joints: None,
}
}
}
Expand Down
147 changes: 133 additions & 14 deletions crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
aabb::{AabbGizmoConfigGroup, ShowAabbGizmo},
config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore},
config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
GizmoLineJoint,
},
gizmos::Gizmos,
primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d},
AppGizmoBuilder,
Expand Down Expand Up @@ -82,12 +85,14 @@ use bevy_render::{
};
use bevy_utils::TypeIdMap;
use config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoMeshConfig,
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoLineJoint,
GizmoMeshConfig,
};
use gizmos::GizmoStorage;
use std::{any::TypeId, mem};

const LINE_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(7414812689238026784);
const LINE_JOINT_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(1162780797909187908);

/// A [`Plugin`] that provides an immediate mode drawing api for visual debugging.
pub struct GizmoPlugin;
Expand All @@ -99,6 +104,12 @@ impl Plugin for GizmoPlugin {
bevy_log::error!("bevy_gizmos requires either bevy_pbr or bevy_sprite. Please enable one.");

load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
LINE_JOINT_SHADER_HANDLE,
"line_joints.wgsl",
Shader::from_wgsl
);

app.register_type::<GizmoConfig>()
.register_type::<GizmoConfigStore>()
Expand All @@ -116,7 +127,7 @@ impl Plugin for GizmoPlugin {

render_app.add_systems(
Render,
prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups),
(prepare_line_gizmo_bind_group).in_set(RenderSet::PrepareBindGroups),
lynn-lumen marked this conversation as resolved.
Show resolved Hide resolved
);

render_app.add_systems(ExtractSchedule, extract_gizmo_data);
Expand All @@ -133,15 +144,17 @@ impl Plugin for GizmoPlugin {
};

let render_device = render_app.world.resource::<RenderDevice>();
let layout = render_device.create_bind_group_layout(
let line_layout = render_device.create_bind_group_layout(
"LineGizmoUniform layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX,
uniform_buffer::<LineGizmoUniform>(true),
),
);

render_app.insert_resource(LineGizmoUniformBindgroupLayout { layout });
render_app.insert_resource(LineGizmoUniformBindgroupLayout {
layout: line_layout,
});
}
}

Expand Down Expand Up @@ -225,6 +238,7 @@ fn update_gizmo_meshes<T: GizmoConfigGroup>(
mut line_gizmos: ResMut<Assets<LineGizmo>>,
mut handles: ResMut<LineGizmoHandles>,
mut storage: ResMut<GizmoStorage<T>>,
config_store: Res<GizmoConfigStore>,
) {
if storage.list_positions.is_empty() {
handles.list.insert(TypeId::of::<T>(), None);
Expand All @@ -247,6 +261,7 @@ fn update_gizmo_meshes<T: GizmoConfigGroup>(
}
}

let (config, _) = config_store.config::<T>();
if storage.strip_positions.is_empty() {
handles.strip.insert(TypeId::of::<T>(), None);
} else if let Some(handle) = handles.strip.get_mut(&TypeId::of::<T>()) {
Expand All @@ -255,9 +270,11 @@ fn update_gizmo_meshes<T: GizmoConfigGroup>(

strip.positions = mem::take(&mut storage.strip_positions);
strip.colors = mem::take(&mut storage.strip_colors);
strip.joins = config.line_joints;
} else {
let mut strip = LineGizmo {
strip: true,
joins: config.line_joints,
..Default::default()
};

Expand Down Expand Up @@ -287,10 +304,19 @@ fn extract_gizmo_data(
continue;
};

let joins_resolution = config.line_joints.map_or(0, |join| {
if let GizmoLineJoint::Round(resolution) = join {
resolution
} else {
0
}
});

commands.spawn((
LineGizmoUniform {
line_width: config.line_width,
depth_bias: config.depth_bias,
joins_resolution,
#[cfg(feature = "webgl")]
_padding: Default::default(),
},
Expand All @@ -304,9 +330,11 @@ fn extract_gizmo_data(
struct LineGizmoUniform {
line_width: f32,
depth_bias: f32,
// Only used by gizmo line joins if the current configs `line_joins` is set to `GizmoLineJoins::Round(_)`
joins_resolution: u32,
/// WebGL2 structs must be 16 byte aligned.
#[cfg(feature = "webgl")]
_padding: bevy_math::Vec2,
_padding: f32,
}

#[derive(Asset, Debug, Default, Clone, TypePath)]
Expand All @@ -315,6 +343,8 @@ struct LineGizmo {
colors: Vec<LinearRgba>,
/// Whether this gizmo's topology is a line-strip or line-list
strip: bool,
/// Whether this gizmo should draw line joins. This is only applicable if the gizmo's topology is line-strip.
joins: Option<GizmoLineJoint>,
}

#[derive(Debug, Clone)]
Expand All @@ -323,6 +353,7 @@ struct GpuLineGizmo {
color_buffer: Buffer,
vertex_count: u32,
strip: bool,
joints: Option<GizmoLineJoint>,
}

impl RenderAsset for LineGizmo {
Expand Down Expand Up @@ -356,6 +387,7 @@ impl RenderAsset for LineGizmo {
color_buffer,
vertex_count: self.positions.len() as u32,
strip: self.strip,
joints: self.joins,
})
}
}
Expand Down Expand Up @@ -439,15 +471,11 @@ impl<P: PhaseItem> RenderCommand<P> 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 {
Expand All @@ -463,6 +491,57 @@ impl<P: PhaseItem> RenderCommand<P> for DrawLineGizmo {
}
}

struct DrawLineJointGizmo;
impl<P: PhaseItem> RenderCommand<P> for DrawLineJointGizmo {
type Param = SRes<RenderAssets<LineGizmo>>;
type ViewQuery = ();
type ItemQuery = Read<Handle<LineGizmo>>;

#[inline]
fn render<'w>(
_item: &P,
_view: ROQueryItem<'w, Self::ViewQuery>,
handle: Option<ROQueryItem<'w, Self::ItemQuery>>,
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;
}

let Some(joints) = line_gizmo.joints else {
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 joints {
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<VertexBufferLayout> {
use VertexFormat::*;
let mut position_layout = VertexBufferLayout {
Expand Down Expand Up @@ -490,11 +569,13 @@ fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
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
},
]
Expand All @@ -516,3 +597,41 @@ fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
vec![position_layout, color_layout]
}
}

fn line_joint_gizmo_vertex_buffer_layouts() -> Vec<VertexBufferLayout> {
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(),
]
}
Loading