Gizmo line styles (#12394)
# Objective - Adds line styles to bevy gizmos, suggestion of #9400 - Currently solid and dotted lines are implemented but this can easily be extended to support dashed lines as well if that is wanted. ## Solution - Adds the enum `GizmoLineStyle` and uses it in each `GizmoConfig` to configure the style of the line. - Each "dot" in a dotted line has the same width and height as the `line_width` of the corresponding line. --- ## Changelog - Added `GizmoLineStyle` to `bevy_gizmos` - Added a `line_style: GizmoLineStyle ` attribute to `GizmoConfig` - Updated the `lines.wgsl` shader and the pipelines accordingly. ## Migration Guide - Any manually created `GizmoConfig` must now include the `line_style` attribute ## Additional information Some pretty pictures :) This is the 3d_gizmos example with/without `line_perspective`: <img width="1440" alt="Screenshot 2024-03-09 at 23 25 53" src="https://github.com/bevyengine/bevy/assets/62256001/b1b97311-e78d-4de3-8dfe-9e48a35bb27d"> <img width="1440" alt="Screenshot 2024-03-09 at 23 25 39" src="https://github.com/bevyengine/bevy/assets/62256001/50ee8ecb-5290-484d-ba36-7fd028374f7f"> And the 2d example: <img width="1440" alt="Screenshot 2024-03-09 at 23 25 06" src="https://github.com/bevyengine/bevy/assets/62256001/4452168f-d605-4333-bfa5-5461d268b132"> --------- Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com>
This commit is contained in:
parent
b35974010b
commit
97a5059535
@ -30,6 +30,17 @@ pub enum GizmoLineJoint {
|
||||
Bevel,
|
||||
}
|
||||
|
||||
/// An enum used to configure the style of gizmo lines, similar to CSS line-style
|
||||
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, Reflect)]
|
||||
#[non_exhaustive]
|
||||
pub enum GizmoLineStyle {
|
||||
/// A solid line without any decorators
|
||||
#[default]
|
||||
Solid,
|
||||
/// A dotted line
|
||||
Dotted,
|
||||
}
|
||||
|
||||
/// A trait used to create gizmo configs groups.
|
||||
///
|
||||
/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
|
||||
@ -135,6 +146,8 @@ pub struct GizmoConfig {
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
pub line_perspective: bool,
|
||||
/// Determine the style of gizmo lines.
|
||||
pub line_style: GizmoLineStyle,
|
||||
/// How closer to the camera than real geometry the line should be.
|
||||
///
|
||||
/// In 2D this setting has no effect and is effectively always -1.
|
||||
@ -163,6 +176,7 @@ impl Default for GizmoConfig {
|
||||
enabled: true,
|
||||
line_width: 2.,
|
||||
line_perspective: false,
|
||||
line_style: GizmoLineStyle::Solid,
|
||||
depth_bias: 0.,
|
||||
render_layers: Default::default(),
|
||||
|
||||
@ -174,6 +188,7 @@ impl Default for GizmoConfig {
|
||||
#[derive(Component)]
|
||||
pub(crate) struct GizmoMeshConfig {
|
||||
pub line_perspective: bool,
|
||||
pub line_style: GizmoLineStyle,
|
||||
pub render_layers: RenderLayers,
|
||||
}
|
||||
|
||||
@ -181,6 +196,7 @@ impl From<&GizmoConfig> for GizmoMeshConfig {
|
||||
fn from(item: &GizmoConfig) -> Self {
|
||||
GizmoMeshConfig {
|
||||
line_perspective: item.line_perspective,
|
||||
line_style: item.line_style,
|
||||
render_layers: item.render_layers,
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ pub mod prelude {
|
||||
aabb::{AabbGizmoConfigGroup, ShowAabbGizmo},
|
||||
config::{
|
||||
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
|
||||
GizmoLineJoint,
|
||||
GizmoLineJoint, GizmoLineStyle,
|
||||
},
|
||||
gizmos::Gizmos,
|
||||
light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo},
|
||||
|
@ -26,19 +26,20 @@ struct VertexInput {
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) uv: f32,
|
||||
};
|
||||
|
||||
const EPSILON: f32 = 4.88e-04;
|
||||
|
||||
@vertex
|
||||
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.)
|
||||
var positions = array<vec2<f32>, 6>(
|
||||
vec2(-0.5, 0.),
|
||||
vec2(-0.5, 1.),
|
||||
vec2(0.5, 1.),
|
||||
vec2(-0.5, 0.),
|
||||
vec2(0.5, 1.),
|
||||
vec2(0.5, 0.)
|
||||
);
|
||||
let position = positions[vertex.index];
|
||||
|
||||
@ -49,23 +50,52 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
|
||||
clip_a = clip_near_plane(clip_a, clip_b);
|
||||
clip_b = clip_near_plane(clip_b, clip_a);
|
||||
|
||||
let clip = mix(clip_a, clip_b, position.z);
|
||||
let clip = mix(clip_a, clip_b, position.y);
|
||||
|
||||
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);
|
||||
let y_basis = normalize(screen_b - screen_a);
|
||||
let x_basis = vec2(-y_basis.y, y_basis.x);
|
||||
|
||||
var color = mix(vertex.color_a, vertex.color_b, position.z);
|
||||
var color = mix(vertex.color_a, vertex.color_b, position.y);
|
||||
|
||||
var line_width = line_gizmo.line_width;
|
||||
var alpha = 1.;
|
||||
|
||||
var uv: f32;
|
||||
#ifdef PERSPECTIVE
|
||||
line_width /= clip.w;
|
||||
|
||||
// get height of near clipping plane in world space
|
||||
let pos0 = view.inverse_projection * vec4(0, -1, 0, 1); // Bottom of the screen
|
||||
let pos1 = view.inverse_projection * vec4(0, 1, 0, 1); // Top of the screen
|
||||
let near_clipping_plane_height = length(pos0.xyz - pos1.xyz);
|
||||
|
||||
// We can't use vertex.position_X because we may have changed the clip positions with clip_near_plane
|
||||
let position_a = view.inverse_view_proj * clip_a;
|
||||
let position_b = view.inverse_view_proj * clip_b;
|
||||
let world_distance = length(position_a.xyz - position_b.xyz);
|
||||
|
||||
// Offset to compensate for moved clip positions. If removed dots on lines will slide when position a is ofscreen.
|
||||
let clipped_offset = length(position_a.xyz - vertex.position_a);
|
||||
|
||||
uv = (clipped_offset + position.y * world_distance) * resolution.y / near_clipping_plane_height / line_gizmo.line_width;
|
||||
#else
|
||||
// Get the distance of b to the camera along camera axes
|
||||
let camera_b = view.inverse_projection * clip_b;
|
||||
|
||||
// This differentiates between orthographic and perspective cameras.
|
||||
// For orthographic cameras no depth adaptment (depth_adaptment = 1) is needed.
|
||||
var depth_adaptment: f32;
|
||||
if (clip_b.w == 1.0) {
|
||||
depth_adaptment = 1.0;
|
||||
}
|
||||
else {
|
||||
depth_adaptment = -camera_b.z;
|
||||
}
|
||||
uv = position.y * depth_adaptment * length(screen_b - screen_a) / line_gizmo.line_width;
|
||||
#endif
|
||||
|
||||
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
|
||||
@ -74,8 +104,8 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
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;
|
||||
let x_offset = line_width * position.x * x_basis;
|
||||
let screen = mix(screen_a, screen_b, position.y) + x_offset;
|
||||
|
||||
var depth: f32;
|
||||
if line_gizmo.depth_bias >= 0. {
|
||||
@ -93,7 +123,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
|
||||
|
||||
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
|
||||
|
||||
return VertexOutput(clip_position, color);
|
||||
return VertexOutput(clip_position, color, uv);
|
||||
}
|
||||
|
||||
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
|
||||
@ -111,7 +141,9 @@ fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
|
||||
}
|
||||
|
||||
struct FragmentInput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) uv: f32,
|
||||
};
|
||||
|
||||
struct FragmentOutput {
|
||||
@ -119,6 +151,17 @@ struct FragmentOutput {
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fragment(in: FragmentInput) -> FragmentOutput {
|
||||
fn fragment_solid(in: FragmentInput) -> FragmentOutput {
|
||||
return FragmentOutput(in.color);
|
||||
}
|
||||
@fragment
|
||||
fn fragment_dotted(in: FragmentInput) -> FragmentOutput {
|
||||
var alpha: f32;
|
||||
#ifdef PERSPECTIVE
|
||||
alpha = 1 - floor(in.uv % 2.0);
|
||||
#else
|
||||
alpha = 1 - floor((in.uv * in.position.w) % 2.0);
|
||||
#endif
|
||||
|
||||
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
config::{GizmoLineJoint, GizmoMeshConfig},
|
||||
config::{GizmoLineJoint, GizmoLineStyle, 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,
|
||||
@ -83,6 +83,7 @@ impl FromWorld for LineGizmoPipeline {
|
||||
struct LineGizmoPipelineKey {
|
||||
mesh_key: Mesh2dPipelineKey,
|
||||
strip: bool,
|
||||
line_style: GizmoLineStyle,
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for LineGizmoPipeline {
|
||||
@ -105,6 +106,11 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
|
||||
self.uniform_layout.clone(),
|
||||
];
|
||||
|
||||
let fragment_entry_point = match key.line_style {
|
||||
GizmoLineStyle::Solid => "fragment_solid",
|
||||
GizmoLineStyle::Dotted => "fragment_dotted",
|
||||
};
|
||||
|
||||
RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: LINE_SHADER_HANDLE,
|
||||
@ -115,7 +121,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
|
||||
fragment: Some(FragmentState {
|
||||
shader: LINE_SHADER_HANDLE,
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
entry_point: fragment_entry_point.into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format,
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
@ -271,6 +277,7 @@ fn queue_line_gizmos_2d(
|
||||
LineGizmoPipelineKey {
|
||||
mesh_key,
|
||||
strip: line_gizmo.strip,
|
||||
line_style: config.line_style,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts,
|
||||
line_joint_gizmo_vertex_buffer_layouts, prelude::GizmoLineJoint, DrawLineGizmo,
|
||||
config::{GizmoLineJoint, GizmoLineStyle, 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,
|
||||
};
|
||||
@ -86,6 +86,7 @@ struct LineGizmoPipelineKey {
|
||||
view_key: MeshPipelineKey,
|
||||
strip: bool,
|
||||
perspective: bool,
|
||||
line_style: GizmoLineStyle,
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for LineGizmoPipeline {
|
||||
@ -114,6 +115,11 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
|
||||
|
||||
let layout = vec![view_layout, self.uniform_layout.clone()];
|
||||
|
||||
let fragment_entry_point = match key.line_style {
|
||||
GizmoLineStyle::Solid => "fragment_solid",
|
||||
GizmoLineStyle::Dotted => "fragment_dotted",
|
||||
};
|
||||
|
||||
RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: LINE_SHADER_HANDLE,
|
||||
@ -124,7 +130,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
|
||||
fragment: Some(FragmentState {
|
||||
shader: LINE_SHADER_HANDLE,
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
entry_point: fragment_entry_point.into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format,
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
@ -329,6 +335,7 @@ fn queue_line_gizmos_3d(
|
||||
view_key,
|
||||
strip: line_gizmo.strip,
|
||||
perspective: config.line_perspective,
|
||||
line_style: config.line_style,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -24,7 +24,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
"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\n\
|
||||
Press 'J' or 'K' to cycle through line joints for straight or round gizmos",
|
||||
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
|
||||
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||
font_size: 24.,
|
||||
@ -107,6 +108,12 @@ fn update_config(
|
||||
if keyboard.just_pressed(KeyCode::Digit1) {
|
||||
config.enabled ^= true;
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyU) {
|
||||
config.line_style = match config.line_style {
|
||||
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
|
||||
_ => GizmoLineStyle::Solid,
|
||||
};
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyJ) {
|
||||
config.line_joints = match config.line_joints {
|
||||
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
|
||||
@ -128,6 +135,12 @@ fn update_config(
|
||||
if keyboard.just_pressed(KeyCode::Digit2) {
|
||||
my_config.enabled ^= true;
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyI) {
|
||||
my_config.line_style = match my_config.line_style {
|
||||
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
|
||||
_ => GizmoLineStyle::Solid,
|
||||
};
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyK) {
|
||||
my_config.line_joints = match my_config.line_joints {
|
||||
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
|
||||
|
@ -59,6 +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 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
|
||||
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
|
||||
TextStyle {
|
||||
font_size: 20.,
|
||||
@ -169,6 +170,12 @@ fn update_config(
|
||||
if keyboard.just_pressed(KeyCode::Digit1) {
|
||||
config.enabled ^= true;
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyU) {
|
||||
config.line_style = match config.line_style {
|
||||
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
|
||||
_ => GizmoLineStyle::Solid,
|
||||
};
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyJ) {
|
||||
config.line_joints = match config.line_joints {
|
||||
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
|
||||
@ -190,6 +197,12 @@ fn update_config(
|
||||
if keyboard.just_pressed(KeyCode::Digit2) {
|
||||
my_config.enabled ^= true;
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyI) {
|
||||
my_config.line_style = match my_config.line_style {
|
||||
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
|
||||
_ => GizmoLineStyle::Solid,
|
||||
};
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyK) {
|
||||
my_config.line_joints = match my_config.line_joints {
|
||||
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
|
||||
|
Loading…
Reference in New Issue
Block a user