This commit is contained in:
Jonas H
2026-04-02 19:48:47 +02:00
parent 2846c04765
commit 16626cc277
7 changed files with 317 additions and 69 deletions

178
src/debug/gizmo.rs Normal file
View File

@@ -0,0 +1,178 @@
use bytemuck::cast_slice;
use glam::{Mat4, Vec3};
use wgpu::util::DeviceExt;
use crate::entity::EntityHandle;
use crate::loaders::mesh::{InstanceRaw, Mesh, Vertex};
use crate::render::{self, DrawCall, Pipeline};
use crate::utility::transform::Transform;
use crate::world::Storage;
const GIZMO_DISTANCE_SCALE: f32 = 0.1;
const ARROW_BASE: f32 = 0.85;
const ARROW_SPREAD: f32 = 0.08;
pub fn create_gizmo_mesh(device: &wgpu::Device) -> Mesh
{
let vertices = vec![
Vertex {
position: [0.0, 0.0, 0.0],
normal: [1.0, 0.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [1.0, 0.0, 0.0],
normal: [1.0, 0.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [0.0, 0.0, 0.0],
normal: [0.0, 1.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [0.0, 1.0, 0.0],
normal: [0.0, 1.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [0.0, 0.0, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
},
Vertex {
position: [0.0, 0.0, 1.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
},
Vertex {
position: [ARROW_BASE, ARROW_SPREAD, 0.0],
normal: [1.0, 0.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [ARROW_BASE, -ARROW_SPREAD, 0.0],
normal: [1.0, 0.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [ARROW_BASE, 0.0, ARROW_SPREAD],
normal: [1.0, 0.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [ARROW_BASE, 0.0, -ARROW_SPREAD],
normal: [1.0, 0.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [ARROW_SPREAD, ARROW_BASE, 0.0],
normal: [0.0, 1.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [-ARROW_SPREAD, ARROW_BASE, 0.0],
normal: [0.0, 1.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [0.0, ARROW_BASE, ARROW_SPREAD],
normal: [0.0, 1.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [0.0, ARROW_BASE, -ARROW_SPREAD],
normal: [0.0, 1.0, 0.0],
uv: [0.0, 0.0],
},
Vertex {
position: [ARROW_SPREAD, 0.0, ARROW_BASE],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
},
Vertex {
position: [-ARROW_SPREAD, 0.0, ARROW_BASE],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
},
Vertex {
position: [0.0, ARROW_SPREAD, ARROW_BASE],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
},
Vertex {
position: [0.0, -ARROW_SPREAD, ARROW_BASE],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
},
];
let indices = vec![
0, 1, 2, 3, 4, 5, 6, 1, 7, 1, 8, 1, 9, 1, 10, 3, 11, 3, 12, 3, 13, 3, 14, 5, 15, 5, 16, 5,
17, 5,
];
Mesh::new(device, &vertices, &indices)
}
pub fn create_gizmo_instance_buffer(device: &wgpu::Device) -> wgpu::Buffer
{
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Gizmo Instance Buffer"),
contents: cast_slice(&[InstanceRaw {
model: Mat4::IDENTITY.to_cols_array_2d(),
dissolve_amount: 0.0,
_padding: [0.0; 3],
}]),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
})
}
pub fn render_transform_gizmo(
entity: EntityHandle,
transforms: &Storage<Transform>,
camera_position: Vec3,
gizmo_mesh: &Mesh,
instance_buffer: &wgpu::Buffer,
) -> Vec<DrawCall>
{
let transform = match transforms.get(entity)
{
Some(t) => t,
None => return Vec::new(),
};
let distance = camera_position.distance(transform.position);
let scale = (distance * GIZMO_DISTANCE_SCALE).max(0.1);
let model = Mat4::from_scale_rotation_translation(
Vec3::splat(scale),
transform.rotation,
transform.position,
);
let instance_data = InstanceRaw {
model: model.to_cols_array_2d(),
dissolve_amount: 0.0,
_padding: [0.0; 3],
};
render::with_queue(|queue| {
queue.write_buffer(instance_buffer, 0, cast_slice(&[instance_data]));
});
vec![DrawCall {
vertex_buffer: gizmo_mesh.vertex_buffer.clone(),
index_buffer: gizmo_mesh.index_buffer.clone(),
num_indices: gizmo_mesh.num_indices,
model,
pipeline: Pipeline::GizmoLines,
instance_buffer: Some(instance_buffer.clone()),
num_instances: 1,
tile_scale: 4.0,
enable_dissolve: false,
enable_snow_light: false,
displacement_bind_group: None,
entity: None,
}]
}

View File

@@ -1,4 +1,5 @@
pub mod collider_debug; pub mod collider_debug;
pub mod gizmo;
pub mod mode; pub mod mode;
pub use mode::DebugMode; pub use mode::DebugMode;

View File

@@ -22,7 +22,7 @@ use crate::bundles::terrain::{TerrainBundle, TerrainConfig};
use crate::bundles::test_char::TestCharBundle; use crate::bundles::test_char::TestCharBundle;
use crate::bundles::Bundle; use crate::bundles::Bundle;
use crate::components::intent::{FollowPlayerIntent, StopFollowingIntent}; use crate::components::intent::{FollowPlayerIntent, StopFollowingIntent};
use crate::debug::{collider_debug, DebugMode}; use crate::debug::{collider_debug, gizmo, DebugMode};
use crate::editor::{editor_loop, EditorState, FrameStats}; use crate::editor::{editor_loop, EditorState, FrameStats};
use crate::entity::EntityHandle; use crate::entity::EntityHandle;
use crate::loaders::scene::Space; use crate::loaders::scene::Space;
@@ -281,6 +281,15 @@ fn toggle_editor(game: &mut Game)
.set_relative_mouse_mode(&game.window, false); .set_relative_mouse_mode(&game.window, false);
game.editor.right_mouse_held = false; game.editor.right_mouse_held = false;
game.input_state.mouse_captured = false; game.input_state.mouse_captured = false;
if game.world.gizmo_mesh.is_none()
{
game.world.gizmo_mesh = Some(render::with_device(|device| {
gizmo::create_gizmo_mesh(device)
}));
game.world.gizmo_instance_buffer = Some(render::with_device(|device| {
gizmo::create_gizmo_instance_buffer(device)
}));
}
} }
else else
{ {
@@ -477,6 +486,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
draw_calls.extend(collider_debug::render_collider_debug()); draw_calls.extend(collider_debug::render_collider_debug());
} }
if game.editor.active
{
if let Some(entity) = game.editor.selected_entity
{
let cam_pos = game.world.active_camera_position();
if let (Some(ref mesh), Some(ref buf)) =
(&game.world.gizmo_mesh, &game.world.gizmo_instance_buffer)
{
draw_calls.extend(gizmo::render_transform_gizmo(
entity,
&game.world.transforms,
cam_pos,
mesh,
buf,
));
}
}
}
game.stats.draw_call_count = draw_calls.len(); game.stats.draw_call_count = draw_calls.len();
game.stats.fps = 1.0 / delta; game.stats.fps = 1.0 / delta;
game.stats.frame_ms = delta * 1000.0; game.stats.frame_ms = delta * 1000.0;

View File

@@ -32,8 +32,8 @@ use crate::paths;
use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer}; use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer};
use crate::texture::{DitherTextures, FlowmapTexture}; use crate::texture::{DitherTextures, FlowmapTexture};
use pipeline::{ use pipeline::{
create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline, create_debug_lines_pipeline, create_gizmo_lines_pipeline, create_main_pipeline,
create_wireframe_pipeline, create_snow_clipmap_pipeline, create_wireframe_pipeline,
}; };
use std::num::NonZeroU64; use std::num::NonZeroU64;
@@ -53,6 +53,7 @@ pub struct Renderer
snow_clipmap_pipeline: wgpu::RenderPipeline, snow_clipmap_pipeline: wgpu::RenderPipeline,
wireframe_pipeline: Option<wgpu::RenderPipeline>, wireframe_pipeline: Option<wgpu::RenderPipeline>,
debug_lines_pipeline: Option<wgpu::RenderPipeline>, debug_lines_pipeline: Option<wgpu::RenderPipeline>,
gizmo_lines_pipeline: Option<wgpu::RenderPipeline>,
debug_overlay: Option<debug_overlay::DebugOverlay>, debug_overlay: Option<debug_overlay::DebugOverlay>,
billboard_pipeline: BillboardPipeline, billboard_pipeline: BillboardPipeline,
particle_pipeline: ParticlePipeline, particle_pipeline: ParticlePipeline,
@@ -531,6 +532,12 @@ impl Renderer
&bind_group_layout, &bind_group_layout,
)); ));
let gizmo_lines_pipeline = Some(create_gizmo_lines_pipeline(
&device,
config.format,
&bind_group_layout,
));
let billboard_pipeline = BillboardPipeline::new(&device, config.format); let billboard_pipeline = BillboardPipeline::new(&device, config.format);
let particle_pipeline = ParticlePipeline::new(&device, config.format); let particle_pipeline = ParticlePipeline::new(&device, config.format);
let font_atlas = font_atlas::FontAtlas::load(&device, &queue); let font_atlas = font_atlas::FontAtlas::load(&device, &queue);
@@ -564,6 +571,7 @@ impl Renderer
snow_clipmap_pipeline, snow_clipmap_pipeline,
wireframe_pipeline, wireframe_pipeline,
debug_lines_pipeline, debug_lines_pipeline,
gizmo_lines_pipeline,
debug_overlay, debug_overlay,
billboard_pipeline, billboard_pipeline,
particle_pipeline, particle_pipeline,
@@ -740,6 +748,10 @@ impl Renderer
.debug_lines_pipeline .debug_lines_pipeline
.as_ref() .as_ref()
.unwrap_or(&self.standard_pipeline), .unwrap_or(&self.standard_pipeline),
Pipeline::GizmoLines => self
.gizmo_lines_pipeline
.as_ref()
.unwrap_or(&self.standard_pipeline),
}; };
render_pass.set_pipeline(pipeline); render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, &self.bind_group, &[offset as u32]); render_pass.set_bind_group(0, &self.bind_group, &[offset as u32]);
@@ -770,7 +782,7 @@ impl Renderer
{ {
if matches!( if matches!(
draw_call.pipeline, draw_call.pipeline,
Pipeline::SnowClipmap | Pipeline::DebugLines Pipeline::SnowClipmap | Pipeline::DebugLines | Pipeline::GizmoLines
) )
{ {
continue; continue;
@@ -864,7 +876,7 @@ impl Renderer
if matches!( if matches!(
draw_call.pipeline, draw_call.pipeline,
Pipeline::SnowClipmap | Pipeline::DebugLines Pipeline::SnowClipmap | Pipeline::DebugLines | Pipeline::GizmoLines
) )
{ {
continue; continue;

View File

@@ -1,6 +1,92 @@
use crate::paths; use crate::paths;
use wesl::Wesl; use wesl::Wesl;
fn create_lines_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
bind_group_layout: &wgpu::BindGroupLayout,
fragment_entry_point: &str,
label_prefix: &str,
) -> wgpu::RenderPipeline
{
let compiler = Wesl::new(&paths::SHADERS_DIR);
let shader_source = compiler
.compile(&paths::shaders::MAIN_PACKAGE.parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader_label = format!("{label_prefix} Shader");
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(&shader_label),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let layout_label = format!("{label_prefix} Pipeline Layout");
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(&layout_label),
bind_group_layouts: &[bind_group_layout],
immediate_size: 0,
});
let pipeline_label = format!("{label_prefix} Pipeline");
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(&pipeline_label),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[
crate::loaders::mesh::Vertex::desc(),
crate::loaders::mesh::InstanceRaw::desc(),
],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some(fragment_entry_point),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::LineList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
})
}
pub fn create_gizmo_lines_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
create_lines_pipeline(device, format, bind_group_layout, "fs_gizmo", "Gizmo Lines")
}
pub fn create_shadow_pipeline( pub fn create_shadow_pipeline(
device: &wgpu::Device, device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout, bind_group_layout: &wgpu::BindGroupLayout,
@@ -215,70 +301,7 @@ pub fn create_debug_lines_pipeline(
bind_group_layout: &wgpu::BindGroupLayout, bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline ) -> wgpu::RenderPipeline
{ {
let compiler = Wesl::new(&paths::SHADERS_DIR); create_lines_pipeline(device, format, bind_group_layout, "fs_main", "Debug Lines")
let shader_source = compiler
.compile(&paths::shaders::MAIN_PACKAGE.parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Debug Lines Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Debug Lines Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
immediate_size: 0,
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Debug Lines Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[
crate::loaders::mesh::Vertex::desc(),
crate::loaders::mesh::InstanceRaw::desc(),
],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::LineList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
})
} }
pub fn create_snow_clipmap_pipeline( pub fn create_snow_clipmap_pipeline(

View File

@@ -143,6 +143,7 @@ pub enum Pipeline
Standard, Standard,
SnowClipmap, SnowClipmap,
DebugLines, DebugLines,
GizmoLines,
} }
pub struct DrawCall pub struct DrawCall

View File

@@ -19,6 +19,7 @@ use crate::components::{
}; };
use crate::debug::DebugMode; use crate::debug::DebugMode;
use crate::entity::{EntityHandle, EntityManager}; use crate::entity::{EntityHandle, EntityManager};
use crate::loaders::mesh::Mesh;
use crate::render::snow::SnowLayer; use crate::render::snow::SnowLayer;
use crate::states::state::StateMachine; use crate::states::state::StateMachine;
use crate::systems::particle::ParticleBuffers; use crate::systems::particle::ParticleBuffers;
@@ -125,6 +126,8 @@ pub struct World
pub snow_layer: Option<SnowLayer>, pub snow_layer: Option<SnowLayer>,
pub debug_mode: DebugMode, pub debug_mode: DebugMode,
pub was_dialog_active: bool, pub was_dialog_active: bool,
pub gizmo_mesh: Option<Mesh>,
pub gizmo_instance_buffer: Option<wgpu::Buffer>,
} }
impl World impl World
@@ -172,6 +175,8 @@ impl World
snow_layer: None, snow_layer: None,
debug_mode: DebugMode::default(), debug_mode: DebugMode::default(),
was_dialog_active: false, was_dialog_active: false,
gizmo_mesh: None,
gizmo_instance_buffer: None,
} }
} }