From 664364734807a4462575448d93f82912f0164a6b Mon Sep 17 00:00:00 2001 From: kokosha Date: Sat, 14 Dec 2024 10:45:49 -0300 Subject: [PATCH] Add visual debugging for entities - Display rectangles and bounding boxes for the sprites of each entity. - Highlight the center of each bounding box and create an axis to facilitate alignment verification. - Implement a mode where billboards always faces the current camera. - Reduce the billboard size for the player entity. - Refactor the code to reuse existing functions. - Convert sRGB to linear color inside AABB/Circle/Rectangle pipeline. --- korangar/src/graphics/engine.rs | 19 +- korangar/src/graphics/instruction.rs | 9 + korangar/src/graphics/passes/forward/aabb.rs | 2 +- .../src/graphics/passes/forward/circle.rs | 2 +- korangar/src/graphics/passes/forward/mod.rs | 4 + .../src/graphics/passes/forward/rectangle.rs | 216 ++++++++++++++++++ .../passes/forward/shader/rectangle.wgsl | 50 ++++ korangar/src/graphics/settings.rs | 4 + .../src/interface/windows/settings/render.rs | 2 + korangar/src/loaders/animation/mod.rs | 163 +++++++++---- korangar/src/main.rs | 24 +- korangar/src/world/animation/mod.rs | 109 ++++++--- korangar/src/world/entity/mod.rs | 22 +- korangar/src/world/map/mod.rs | 10 +- 14 files changed, 559 insertions(+), 77 deletions(-) create mode 100644 korangar/src/graphics/passes/forward/rectangle.rs create mode 100644 korangar/src/graphics/passes/forward/shader/rectangle.wgsl diff --git a/korangar/src/graphics/engine.rs b/korangar/src/graphics/engine.rs index bb72f504..7b73513c 100644 --- a/korangar/src/graphics/engine.rs +++ b/korangar/src/graphics/engine.rs @@ -104,6 +104,8 @@ struct EngineContext { #[cfg(feature = "debug")] forward_circle_drawer: ForwardCircleDrawer, #[cfg(feature = "debug")] + forward_rectangle_drawer: ForwardRectangleDrawer, + #[cfg(feature = "debug")] post_processing_buffer_drawer: PostProcessingBufferDrawer, #[cfg(feature = "debug")] picker_marker_drawer: PickerMarkerDrawer, @@ -288,9 +290,10 @@ impl GraphicsEngine { #[cfg(feature = "debug")] forward_aabb_drawer, - #[cfg(feature = "debug")] forward_circle_drawer, + #[cfg(feature = "debug")] + forward_rectangle_drawer, } = ForwardResources::create( &self.capabilities, &self.device, @@ -371,6 +374,8 @@ impl GraphicsEngine { #[cfg(feature = "debug")] forward_circle_drawer, #[cfg(feature = "debug")] + forward_rectangle_drawer, + #[cfg(feature = "debug")] post_processing_buffer_drawer, #[cfg(feature = "debug")] picker_marker_drawer, @@ -488,6 +493,8 @@ impl GraphicsEngine { forward_aabb_drawer, #[cfg(feature = "debug")] forward_circle_drawer, + #[cfg(feature = "debug")] + forward_rectangle_drawer, } = ForwardResources::create( &self.capabilities, &self.device, @@ -531,6 +538,7 @@ impl GraphicsEngine { { engine_context.forward_aabb_drawer = forward_aabb_drawer; engine_context.forward_circle_drawer = forward_circle_drawer; + engine_context.forward_rectangle_drawer = forward_rectangle_drawer; engine_context.post_processing_buffer_drawer = post_processing_buffer_drawer; } } @@ -776,6 +784,7 @@ impl GraphicsEngine { scope.spawn(|_| { context.post_processing_buffer_drawer.prepare(&self.device, instruction); context.forward_circle_drawer.prepare(&self.device, instruction); + context.forward_rectangle_drawer.prepare(&self.device, instruction); }); context.global_context.prepare(&self.device, instruction); @@ -812,6 +821,7 @@ impl GraphicsEngine { visitor.upload(&mut context.forward_aabb_drawer); visitor.upload(&mut context.post_processing_buffer_drawer); visitor.upload(&mut context.forward_circle_drawer); + visitor.upload(&mut context.forward_rectangle_drawer); visitor.upload(&mut context.picker_marker_drawer); } @@ -1000,6 +1010,7 @@ impl GraphicsEngine { { engine_context.forward_aabb_drawer.draw(&mut render_pass, None); engine_context.forward_circle_drawer.draw(&mut render_pass, None); + engine_context.forward_rectangle_drawer.draw(&mut render_pass, None); } if instruction.water.is_some() { @@ -1177,6 +1188,8 @@ struct ForwardResources { forward_aabb_drawer: ForwardAabbDrawer, #[cfg(feature = "debug")] forward_circle_drawer: ForwardCircleDrawer, + #[cfg(feature = "debug")] + forward_rectangle_drawer: ForwardRectangleDrawer, } impl ForwardResources { @@ -1194,6 +1207,8 @@ impl ForwardResources { let forward_aabb_drawer = ForwardAabbDrawer::new(capabilities, device, queue, global_context, forward_pass_context); #[cfg(feature = "debug")] let forward_circle_drawer = ForwardCircleDrawer::new(capabilities, device, queue, global_context, forward_pass_context); + #[cfg(feature = "debug")] + let forward_rectangle_drawer = ForwardRectangleDrawer::new(capabilities, device, queue, global_context, forward_pass_context); Self { forward_entity_drawer, @@ -1203,6 +1218,8 @@ impl ForwardResources { forward_aabb_drawer, #[cfg(feature = "debug")] forward_circle_drawer, + #[cfg(feature = "debug")] + forward_rectangle_drawer, } } } diff --git a/korangar/src/graphics/instruction.rs b/korangar/src/graphics/instruction.rs index 6bdbebcc..5b745a0c 100644 --- a/korangar/src/graphics/instruction.rs +++ b/korangar/src/graphics/instruction.rs @@ -48,6 +48,8 @@ pub struct RenderInstruction<'a> { #[cfg(feature = "debug")] pub circles: &'a [DebugCircleInstruction], #[cfg(feature = "debug")] + pub rectangles: &'a [DebugRectangleInstruction], + #[cfg(feature = "debug")] pub marker: &'a [MarkerInstruction], } @@ -238,3 +240,10 @@ pub struct DebugCircleInstruction { pub screen_position: ScreenPosition, pub screen_size: ScreenSize, } + +#[cfg(feature = "debug")] +#[derive(Copy, Clone, Debug)] +pub struct DebugRectangleInstruction { + pub world: Matrix4, + pub color: Color, +} diff --git a/korangar/src/graphics/passes/forward/aabb.rs b/korangar/src/graphics/passes/forward/aabb.rs index 68f0d128..fb671d09 100644 --- a/korangar/src/graphics/passes/forward/aabb.rs +++ b/korangar/src/graphics/passes/forward/aabb.rs @@ -194,7 +194,7 @@ impl Prepare for ForwardAabbDrawer { for instruction in instructions.aabb.iter() { self.instance_data.push(InstanceData { world: instruction.world.into(), - color: instruction.color.into(), + color: instruction.color.components_linear(), }); } } diff --git a/korangar/src/graphics/passes/forward/circle.rs b/korangar/src/graphics/passes/forward/circle.rs index 93f9a2b1..4089e465 100644 --- a/korangar/src/graphics/passes/forward/circle.rs +++ b/korangar/src/graphics/passes/forward/circle.rs @@ -149,7 +149,7 @@ impl Prepare for ForwardCircleDrawer { for instruction in instructions.circles.iter() { self.instance_data.push(InstanceData { position: instruction.position.to_homogeneous().into(), - color: instruction.color.into(), + color: instruction.color.components_linear(), screen_position: instruction.screen_position.into(), screen_size: instruction.screen_size.into(), }); diff --git a/korangar/src/graphics/passes/forward/mod.rs b/korangar/src/graphics/passes/forward/mod.rs index 314148b0..3ff78a0b 100644 --- a/korangar/src/graphics/passes/forward/mod.rs +++ b/korangar/src/graphics/passes/forward/mod.rs @@ -5,6 +5,8 @@ mod circle; mod entity; mod indicator; mod model; +#[cfg(feature = "debug")] +mod rectangle; #[cfg(feature = "debug")] pub(crate) use aabb::ForwardAabbDrawer; @@ -13,6 +15,8 @@ pub(crate) use circle::ForwardCircleDrawer; pub(crate) use entity::ForwardEntityDrawer; pub(crate) use indicator::ForwardIndicatorDrawer; pub(crate) use model::ForwardModelDrawer; +#[cfg(feature = "debug")] +pub(crate) use rectangle::ForwardRectangleDrawer; use wgpu::{ BindGroupLayout, Color, CommandEncoder, Device, LoadOp, Operations, Queue, RenderPass, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, StoreOp, TextureFormat, diff --git a/korangar/src/graphics/passes/forward/rectangle.rs b/korangar/src/graphics/passes/forward/rectangle.rs new file mode 100644 index 00000000..ba5f0b1a --- /dev/null +++ b/korangar/src/graphics/passes/forward/rectangle.rs @@ -0,0 +1,216 @@ +use std::num::NonZeroU64; + +use bytemuck::{Pod, Zeroable}; +use cgmath::Point3; +use wgpu::util::StagingBelt; +use wgpu::{ + include_wgsl, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, + BindingType, BufferBindingType, BufferUsages, ColorTargetState, ColorWrites, CommandEncoder, CompareFunction, DepthBiasState, + DepthStencilState, Device, FragmentState, IndexFormat, MultisampleState, PipelineCompilationOptions, PipelineLayoutDescriptor, + PrimitiveState, PrimitiveTopology, Queue, RenderPass, RenderPipeline, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderStages, + StencilState, VertexState, +}; + +use crate::graphics::passes::{ + BindGroupCount, ColorAttachmentCount, DepthAttachmentCount, Drawer, ForwardRenderPassContext, RenderPassContext, +}; +use crate::graphics::{Capabilities, GlobalContext, Prepare, RenderInstruction, SimpleVertex}; +use crate::Buffer; + +const SHADER: ShaderModuleDescriptor = include_wgsl!("shader/rectangle.wgsl"); +const DRAWER_NAME: &str = "screen rectangle"; +const INITIAL_INSTRUCTION_SIZE: usize = 256; +const INDEX_COUNT: usize = 8; + +#[derive(Copy, Clone, Pod, Zeroable)] +#[repr(C)] +struct InstanceData { + world: [[f32; 4]; 4], + color: [f32; 4], +} + +pub(crate) struct ForwardRectangleDrawer { + vertex_buffer: Buffer, + index_buffer: Buffer, + instance_data_buffer: Buffer, + bind_group_layout: BindGroupLayout, + bind_group: BindGroup, + pipeline: RenderPipeline, + draw_count: usize, + instance_data: Vec, +} + +impl Drawer<{ BindGroupCount::Two }, { ColorAttachmentCount::One }, { DepthAttachmentCount::One }> for ForwardRectangleDrawer { + type Context = ForwardRenderPassContext; + type DrawData<'data> = Option<()>; + + fn new( + _capabilities: &Capabilities, + device: &Device, + queue: &Queue, + global_context: &GlobalContext, + render_pass_context: &Self::Context, + ) -> Self { + let shader_module = device.create_shader_module(SHADER); + + // Vertices are defined in world coordinates (Same as WGPU's NDC). + let vertex_data = [ + SimpleVertex::new(Point3::new(-1.0, 0.0, 1.0)), + SimpleVertex::new(Point3::new(-1.0, 2.0, 1.0)), + SimpleVertex::new(Point3::new(1.0, 0.0, 1.0)), + SimpleVertex::new(Point3::new(1.0, 2.0, 1.0)), + ]; + + let index_data: [u16; INDEX_COUNT] = [0, 1, 2, 3, 1, 3, 0, 2]; + + let vertex_buffer = Buffer::with_data( + device, + queue, + format!("{DRAWER_NAME} box vertex"), + BufferUsages::VERTEX | BufferUsages::COPY_DST, + &vertex_data, + ); + + let index_buffer = Buffer::with_data( + device, + queue, + format!("{DRAWER_NAME} box index"), + BufferUsages::INDEX | BufferUsages::COPY_DST, + &index_data, + ); + + let instance_data_buffer = Buffer::with_capacity( + device, + format!("{DRAWER_NAME} instance data"), + BufferUsages::COPY_DST | BufferUsages::STORAGE, + (size_of::() * INITIAL_INSTRUCTION_SIZE) as _, + ); + + let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some(DRAWER_NAME), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: NonZeroU64::new(size_of::() as _), + }, + count: None, + }], + }); + + let bind_group = Self::create_bind_group(device, &bind_group_layout, &instance_data_buffer); + + let bind_group_layouts = Self::Context::bind_group_layout(device); + + let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some(DRAWER_NAME), + bind_group_layouts: &[bind_group_layouts[0], bind_group_layouts[1], &bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some(DRAWER_NAME), + layout: Some(&pipeline_layout), + vertex: VertexState { + module: &shader_module, + entry_point: Some("vs_main"), + compilation_options: PipelineCompilationOptions::default(), + buffers: &[SimpleVertex::buffer_layout()], + }, + fragment: Some(FragmentState { + module: &shader_module, + entry_point: Some("fs_main"), + compilation_options: PipelineCompilationOptions::default(), + targets: &[Some(ColorTargetState { + format: render_pass_context.color_attachment_formats()[0], + blend: None, + write_mask: ColorWrites::default(), + })], + }), + multiview: None, + primitive: PrimitiveState { + topology: PrimitiveTopology::LineList, + ..Default::default() + }, + depth_stencil: Some(DepthStencilState { + format: render_pass_context.depth_attachment_output_format()[0], + depth_write_enabled: false, + depth_compare: CompareFunction::Always, + stencil: StencilState::default(), + bias: DepthBiasState::default(), + }), + multisample: MultisampleState { + count: global_context.msaa.sample_count(), + ..Default::default() + }, + cache: None, + }); + + Self { + vertex_buffer, + index_buffer, + instance_data_buffer, + bind_group_layout, + bind_group, + pipeline, + draw_count: 0, + instance_data: Vec::default(), + } + } + + fn draw(&mut self, pass: &mut RenderPass<'_>, _draw_data: Self::DrawData<'_>) { + if self.draw_count == 0 { + return; + } + + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(2, &self.bind_group, &[]); + pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint16); + pass.draw_indexed(0..INDEX_COUNT as u32, 0, 0..self.draw_count as u32); + } +} + +impl Prepare for ForwardRectangleDrawer { + fn prepare(&mut self, _device: &Device, instructions: &RenderInstruction) { + self.draw_count = instructions.rectangles.len(); + + if self.draw_count == 0 { + return; + } + + self.instance_data.clear(); + + for instruction in instructions.rectangles.iter() { + self.instance_data.push(InstanceData { + world: instruction.world.into(), + color: instruction.color.components_linear(), + }); + } + } + + fn upload(&mut self, device: &Device, staging_belt: &mut StagingBelt, command_encoder: &mut CommandEncoder) { + let recreated = self + .instance_data_buffer + .write(device, staging_belt, command_encoder, &self.instance_data); + + if recreated { + self.bind_group = Self::create_bind_group(device, &self.bind_group_layout, &self.instance_data_buffer); + } + } +} + +impl ForwardRectangleDrawer { + fn create_bind_group(device: &Device, bind_group_layout: &BindGroupLayout, instance_data_buffer: &Buffer) -> BindGroup { + device.create_bind_group(&BindGroupDescriptor { + label: Some(DRAWER_NAME), + layout: bind_group_layout, + entries: &[BindGroupEntry { + binding: 0, + resource: instance_data_buffer.as_entire_binding(), + }], + }) + } +} diff --git a/korangar/src/graphics/passes/forward/shader/rectangle.wgsl b/korangar/src/graphics/passes/forward/shader/rectangle.wgsl new file mode 100644 index 00000000..f006bb86 --- /dev/null +++ b/korangar/src/graphics/passes/forward/shader/rectangle.wgsl @@ -0,0 +1,50 @@ +struct GlobalUniforms { + view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, + inverse_projection: mat4x4, + inverse_view_projection: mat4x4, + indicator_positions: mat4x4, + indicator_color: vec4, + ambient_color: vec4, + camera_position: vec4, + forward_size: vec2, + interface_size: vec2, + pointer_position: vec2, + animation_timer: f32, + day_timer: f32, + point_light_count: u32, + enhanced_lighting: u32, + shadow_quality: u32, +} + +struct InstanceData { + world: mat4x4, + color: vec4, +} + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} + +@group(0) @binding(0) var global_uniforms: GlobalUniforms; +@group(2) @binding(0) var instance_data: array; + +@vertex +fn vs_main( + @location(0) position: vec3, + @builtin(instance_index) instance_index: u32, +) -> VertexOutput { + let instance = instance_data[instance_index]; + + var output: VertexOutput; + output.position = global_uniforms.view_projection * instance.world * vec4(position, 1.0); + output.color = instance.color; + return output; +} + +@fragment +fn fs_main(input: VertexOutput) -> @location(0) vec4 { + return input.color; +} diff --git a/korangar/src/graphics/settings.rs b/korangar/src/graphics/settings.rs index 4ba24196..1e394da7 100644 --- a/korangar/src/graphics/settings.rs +++ b/korangar/src/graphics/settings.rs @@ -176,6 +176,10 @@ pub struct RenderSettings { #[new(value = "true")] pub show_entities: bool, #[new(value = "true")] + pub show_entities_paper: bool, + #[new(default)] + pub show_entities_debug: bool, + #[new(value = "true")] pub show_water: bool, #[new(value = "true")] pub show_indicators: bool, diff --git a/korangar/src/interface/windows/settings/render.rs b/korangar/src/interface/windows/settings/render.rs index d4ed395e..f8e8bb80 100644 --- a/korangar/src/interface/windows/settings/render.rs +++ b/korangar/src/interface/windows/settings/render.rs @@ -26,6 +26,8 @@ fn general_expandable(settings: &PlainTrackedState) -> ElementCe render_state_button("show wireframe", settings.mapped(|settings| &settings.show_wireframe)), render_state_button("frustum culling", settings.mapped(|settings| &settings.frustum_culling)), render_state_button("show bounding boxes", settings.mapped(|settings| &settings.show_bounding_boxes)), + render_state_button("show entities debug", settings.mapped(|settings| &settings.show_entities_debug)), + render_state_button("show entities paper", settings.mapped(|settings| &settings.show_entities_paper)), ]; Expandable::new("general".to_string(), buttons, true).wrap() diff --git a/korangar/src/loaders/animation/mod.rs b/korangar/src/loaders/animation/mod.rs index 371a6aac..78eddcc1 100644 --- a/korangar/src/loaders/animation/mod.rs +++ b/korangar/src/loaders/animation/mod.rs @@ -2,6 +2,8 @@ use std::cmp::{max, min}; use std::num::{NonZeroU32, NonZeroUsize}; use std::sync::Arc; +#[cfg(feature = "debug")] +use cgmath::SquareMatrix; use cgmath::{Matrix4, Rad, Vector2}; use korangar_util::container::SimpleCache; use num::Zero; @@ -158,6 +160,12 @@ impl AnimationLoader { top_left: Vector2::zero(), offset, frame_parts: vec![frame_part], + #[cfg(feature = "debug")] + offset_matrix: Matrix4::identity(), + #[cfg(feature = "debug")] + horizontal_matrix: Matrix4::identity(), + #[cfg(feature = "debug")] + vertical_matrix: Matrix4::identity(), }; motion_frames.push(frame); @@ -225,12 +233,12 @@ impl AnimationLoader { // The player sprite is generally 110 pixels, with an additional 40 pixels added // for extra space, making the total height 150 pixels. let extra_size = 20; - min_top = -80; - max_bottom = 30; + min_top = -90; + max_bottom = 20; min_top -= extra_size; max_bottom += extra_size; - min_left = -150; - max_right = 150; + min_left = -50; + max_right = 50; } else { animations[action_index].frames.iter().for_each(|frame| { let center_x = (frame.size.x - 1) / 2; @@ -243,29 +251,15 @@ impl AnimationLoader { }); } - fn calculate_new_size(min_top: i32, max_bottom: i32, min_left: i32, max_right: i32) -> Vector2 { - // Create a rectangle centered on the y-axis, extending to the maximum of - // max_left and max_right. - let size_x = 2 * max(i32::abs(min_left), i32::abs(max_right)) + 1; - - // The size_y is defined to be odd, as it ranges from [0, 2k], - // resulting in a size of 2k+1. - let mut padding = 1; - if max_bottom % 2 == min_top % 2 { - padding = 0; - } - let size_y = max_bottom - min_top + padding + 1; - - return Vector2::new(size_x, size_y); - } animations[action_index].frames.iter_mut().for_each(|frame| { frame.size = calculate_new_size(min_top, max_bottom, min_left, max_right); // Set the origin point at (0, 0) correctly by applying an offset in y by // max_bottom. frame.offset = Vector2::new(0, -max_bottom); + + #[cfg(feature = "debug")] + get_cross_matrix(frame); for frame_part in frame.frame_parts.iter_mut() { - // Determine the top-left corner of the frame rectangle - // and the top-left corner of the frame part rectangle. let frame_top_left = frame.offset - (frame.size - Vector2::new(1, 1)) / 2; let frame_part_top_left = frame_part.offset - (frame_part.size - Vector2::new(1, 1)) / 2; @@ -284,30 +278,10 @@ impl AnimationLoader { let texture_bottom_left = convert_coordinates(bottom_left, frame.size); let texture_bottom_right = convert_coordinates(bottom_right, frame.size); - // 1 - Move to the center of the frame rectangle with coordinates - // (-1, 2), (-1, 0), (1, 2), (1, 0). - // 2 - Rotate the image by specified angle. - // 3 - Return to the origin to apply the scaling. - let translation_to_center_matrix = Matrix4::from_translation(texture_frame_center.extend(0.0)); - let rotation_matrix = Matrix4::from_angle_z(Rad(-frame_part.angle)); - let translation_to_origin_matrix = Matrix4::from_translation((-texture_frame_center).extend(0.0)); - let final_rotation_matrix = translation_to_center_matrix * rotation_matrix * translation_to_origin_matrix; - - // Scale the vertices (-1, 2), (-1, 0), (1, 2), (1, 0) to - // match the texture coordinates as specified above. - let scale_matrix = Matrix4::from_nonuniform_scale( - (texture_bottom_right.x - texture_bottom_left.x) / 2.0, - (texture_top_left.y - texture_bottom_left.y) / 2.0, - 1.0, - ); - - // Translate the scaled rectangle from the new texture center to the - // texture center. - let texture_center = (texture_top_left + texture_bottom_right) / 2.0; - let texture_frame_new_center = Vector2::new(0.0, (texture_top_left.y - texture_bottom_left.y) / 2.0); - let translation_matrix = Matrix4::from_translation(texture_center.extend(1.0) - texture_frame_new_center.extend(1.0)); - - frame_part.affine_matrix = translation_matrix * scale_matrix * final_rotation_matrix; + let rotation_matrix = calculate_recenter_rotation_matrix(texture_frame_center, -frame_part.angle); + let scale_matrix = calculate_scale_matrix(texture_top_left, texture_bottom_left, texture_bottom_right); + let translation_matrix = calculate_translation_matrix(texture_top_left, texture_bottom_left, texture_bottom_right); + frame_part.affine_matrix = translation_matrix * scale_matrix * rotation_matrix; } }); } @@ -338,6 +312,93 @@ impl AnimationLoader { } } +fn calculate_new_size(min_top: i32, max_bottom: i32, min_left: i32, max_right: i32) -> Vector2 { + // Create a rectangle centered on the y-axis, extending to the maximum of + // max_left and max_right. + let size_x = 2 * max(i32::abs(min_left), i32::abs(max_right)) + 1; + + // The size_y is defined to be odd, as it ranges from [0, 2k], + // resulting in a size of 2k+1. + let mut padding = 1; + if max_bottom % 2 == min_top % 2 { + padding = 0; + } + let size_y = max_bottom - min_top + padding + 1; + + return Vector2::new(size_x, size_y); +} + +#[cfg(feature = "debug")] +fn get_cross_matrix(frame: &mut AnimationFrame) { + let frame_top_left = frame.offset - (frame.size - Vector2::new(1, 1)) / 2; + let bounding_box_offset = Vector2::new(0, -(frame.size.y - 1) / 2) - frame.offset; + let bounding_box_top_left = bounding_box_offset - (frame.size - Vector2::new(1, 1)) / 2; + + let new_vector = frame.size; + let top_left = bounding_box_top_left - frame_top_left; + let bottom_left = top_left + new_vector.y * Vector2::::unit_y(); + let bottom_right = top_left + new_vector; + + let texture_top_left = convert_coordinates(top_left, frame.size); + let texture_bottom_left = convert_coordinates(bottom_left, frame.size); + let texture_bottom_right = convert_coordinates(bottom_right, frame.size); + let translation_matrix = calculate_translation_matrix(texture_top_left, texture_bottom_left, texture_bottom_right); + frame.offset_matrix = translation_matrix; + + let bounding_box_origin = Vector2::new((frame.size.x - 1) / 2, frame.size.y) + frame.offset; + + let texture_top_left = convert_coordinates(Vector2::new(0, bounding_box_origin.y), frame.size); + let texture_bottom_left = convert_coordinates(Vector2::new(0, bounding_box_origin.y), frame.size); + let texture_bottom_right = convert_coordinates(Vector2::new(frame.size.x, bounding_box_origin.y), frame.size); + let scale_matrix = calculate_scale_matrix(texture_top_left, texture_bottom_left, texture_bottom_right); + let translation_matrix = calculate_translation_matrix(texture_top_left, texture_bottom_left, texture_bottom_right); + frame.horizontal_matrix = translation_matrix * scale_matrix; + + let texture_top_left = convert_coordinates(Vector2::new(bounding_box_origin.x, 0), frame.size); + let texture_bottom_left = convert_coordinates(Vector2::new(bounding_box_origin.x, frame.size.y), frame.size); + let texture_bottom_right = convert_coordinates(Vector2::new(bounding_box_origin.x, frame.size.y), frame.size); + let scale_matrix = calculate_scale_matrix(texture_top_left, texture_bottom_left, texture_bottom_right); + let translation_matrix = calculate_translation_matrix(texture_top_left, texture_bottom_left, texture_bottom_right); + frame.vertical_matrix = translation_matrix * scale_matrix; +} + +fn calculate_recenter_rotation_matrix(texture_frame_center: Vector2, angle: f32) -> Matrix4 { + // 1 - Move to the center of the frame rectangle with coordinates + // (-1, 2), (-1, 0), (1, 2), (1, 0). + // 2 - Rotate the image by specified angle. + // 3 - Return to the origin to apply the scaling. + let translation_to_center_matrix = Matrix4::from_translation(texture_frame_center.extend(0.0)); + let rotation_matrix = Matrix4::from_angle_z(Rad(angle)); + let translation_to_origin_matrix = Matrix4::from_translation((-texture_frame_center).extend(0.0)); + translation_to_center_matrix * rotation_matrix * translation_to_origin_matrix +} + +fn calculate_scale_matrix( + texture_top_left: Vector2, + texture_bottom_left: Vector2, + texture_bottom_right: Vector2, +) -> Matrix4 { + // Scale the vertices (-1, 2), (-1, 0), (1, 2), (1, 0) to + // match the texture coordinates as specified above. + return Matrix4::from_nonuniform_scale( + (texture_bottom_right.x - texture_bottom_left.x) / 2.0, + (texture_top_left.y - texture_bottom_left.y) / 2.0, + 1.0, + ); +} + +fn calculate_translation_matrix( + texture_top_left: Vector2, + texture_bottom_left: Vector2, + texture_bottom_right: Vector2, +) -> Matrix4 { + // Translate the scaled rectangle from the new texture center to the + // texture center. + let texture_center = (texture_top_left + texture_bottom_right) / 2.0; + let texture_frame_new_center = Vector2::new(0.0, (texture_top_left.y - texture_bottom_left.y) / 2.0); + Matrix4::from_translation(texture_center.extend(1.0) - texture_frame_new_center.extend(1.0)) +} + /// This function converts to the "normalized" coordinates of a frame part /// inside the frame's bounding rectangle, defined by the vertices [-1, 0], /// [-1, 2], [1, 0], and [1, 2]. @@ -378,6 +439,12 @@ fn merge_frame(frames: &mut [AnimationFrame]) -> AnimationFrame { top_left: Vector2::zero(), offset: Vector2::zero(), frame_parts: vec![frame_part], + #[cfg(feature = "debug")] + offset_matrix: Matrix4::identity(), + #[cfg(feature = "debug")] + horizontal_matrix: Matrix4::identity(), + #[cfg(feature = "debug")] + vertical_matrix: Matrix4::identity(), }; return frame; } @@ -415,5 +482,11 @@ fn merge_frame(frames: &mut [AnimationFrame]) -> AnimationFrame { top_left: Vector2::zero(), offset: Vector2::new(top_left_x + (new_width - 1) / 2, top_left_y + (new_height - 1) / 2), frame_parts: new_frame_parts, + #[cfg(feature = "debug")] + offset_matrix: Matrix4::identity(), + #[cfg(feature = "debug")] + horizontal_matrix: Matrix4::identity(), + #[cfg(feature = "debug")] + vertical_matrix: Matrix4::identity(), } } diff --git a/korangar/src/main.rs b/korangar/src/main.rs index 8951d550..64a925cc 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -177,6 +177,8 @@ struct Client { aabb_instructions: Vec, #[cfg(feature = "debug")] circle_instructions: Vec, + #[cfg(feature = "debug")] + rectangle_instructions: Vec, model_batches: Vec, model_instructions: Vec, entity_instructions: Vec, @@ -415,6 +417,8 @@ impl Client { let aabb_instructions = Vec::default(); #[cfg(feature = "debug")] let circle_instructions = Vec::default(); + #[cfg(feature = "debug")] + let rectangle_instructions = Vec::default(); let model_batches = Vec::default(); let model_instructions = Vec::default(); let entity_instructions = Vec::default(); @@ -584,6 +588,8 @@ impl Client { aabb_instructions, #[cfg(feature = "debug")] circle_instructions, + #[cfg(feature = "debug")] + rectangle_instructions, model_batches, model_instructions, entity_instructions, @@ -684,6 +690,8 @@ impl Client { self.aabb_instructions.clear(); #[cfg(feature = "debug")] self.circle_instructions.clear(); + #[cfg(feature = "debug")] + self.rectangle_instructions.clear(); self.model_batches.clear(); self.model_instructions.clear(); self.entity_instructions.clear(); @@ -2086,8 +2094,20 @@ impl Client { &self.pathing_texture, ); + let entity_camera = match true { + #[cfg(feature = "debug")] + _ if self.render_settings.get().show_entities_paper => &self.player_camera, + _ => current_camera, + }; + #[cfg_attr(feature = "debug", korangar_debug::debug_condition(render_settings.show_entities))] - self.map.render_entities(&mut self.entity_instructions, entities, current_camera); + self.map.render_entities(&mut self.entity_instructions, entities, entity_camera); + + #[cfg(feature = "debug")] + if render_settings.show_entities_debug { + self.map + .render_entities_debug(&mut self.rectangle_instructions, entities, entity_camera); + } #[cfg_attr(feature = "debug", korangar_debug::debug_condition(render_settings.show_water))] self.map.render_water(&mut water_instruction, client_tick); @@ -2278,6 +2298,8 @@ impl Client { #[cfg(feature = "debug")] circles: &self.circle_instructions, #[cfg(feature = "debug")] + rectangles: &self.rectangle_instructions, + #[cfg(feature = "debug")] marker: self.debug_marker_renderer.get_instructions(), }; diff --git a/korangar/src/world/animation/mod.rs b/korangar/src/world/animation/mod.rs index 539b68bf..df3f3c65 100644 --- a/korangar/src/world/animation/mod.rs +++ b/korangar/src/world/animation/mod.rs @@ -5,10 +5,15 @@ use korangar_interface::elements::PrototypeElement; use korangar_util::container::Cacheable; use ragnarok_packets::EntityId; +#[cfg(feature = "debug")] +use crate::graphics::DebugRectangleInstruction; use crate::graphics::{Color, EntityInstruction}; use crate::loaders::{Actions, AnimationState, Sprite}; use crate::world::{Camera, EntityType}; +const TILE_SIZE: f32 = 10.0; +const SPRITE_SCALE: f32 = 1.4; + #[derive(Clone, PrototypeElement)] pub struct AnimationData { pub animation_pair: Vec, @@ -42,6 +47,12 @@ pub struct AnimationFrame { pub top_left: Vector2, pub size: Vector2, pub frame_parts: Vec, + #[cfg(feature = "debug")] + pub offset_matrix: Matrix4, + #[cfg(feature = "debug")] + pub horizontal_matrix: Matrix4, + #[cfg(feature = "debug")] + pub vertical_matrix: Matrix4, } #[derive(Clone)] @@ -72,19 +83,7 @@ impl Default for AnimationFramePart { } impl AnimationData { - pub fn render( - &self, - instructions: &mut Vec, - camera: &dyn Camera, - entity_id: EntityId, - entity_position: Point3, - animation_state: &AnimationState, - head_direction: usize, - add_to_picker: bool, - ) { - const TILE_SIZE: f32 = 10.0; - const SPRITE_SCALE: f32 = 1.4; - + pub fn get_frame(&self, animation_state: &AnimationState, camera: &dyn Camera, head_direction: usize) -> &AnimationFrame { let camera_direction = camera.camera_direction(); let direction = (camera_direction + head_direction) % 8; let aa = animation_state.action * 8 + direction; @@ -110,27 +109,49 @@ impl AnimationData { if self.entity_type == EntityType::Player && animation_state.action == 0 { frame = &animation.frames[0]; } + return frame; + } + + pub fn calculate_world_matrix(&self, camera: &dyn Camera, frame: &AnimationFrame, entity_position: Point3) -> Matrix4 { + // The vertex position is calculated from the center of image, so we need + // to add half of the height. + let center_position = Vector2::new(-frame.offset.x as f32, frame.offset.y as f32 + ((frame.size.y - 1) / 2) as f32); + let origin = Point3::from_vec(center_position.extend(0.0)) * SPRITE_SCALE / TILE_SIZE; + let size = Vector2::new(frame.size.x as f32, frame.size.y as f32) * SPRITE_SCALE / TILE_SIZE; + let world_matrix = camera.billboard_matrix(entity_position, origin, size); + + return world_matrix; + } + + pub fn get_texture_coordinates(&self) -> (Vector2, Vector2) { + let cell_count = Vector2::new(1, 1); + let cell_position = Vector2::new(0, 0); + let texture_size = Vector2::new(1.0 / cell_count.x as f32, 1.0 / cell_count.y as f32); + let texture_position = Vector2::new(texture_size.x * cell_position.x as f32, texture_size.y * cell_position.y as f32); + return (texture_size, texture_position); + } + + pub fn render( + &self, + instructions: &mut Vec, + camera: &dyn Camera, + entity_id: EntityId, + entity_position: Point3, + animation_state: &AnimationState, + head_direction: usize, + add_to_picker: bool, + ) { + let frame = self.get_frame(animation_state, camera, head_direction); + let world_matrix = self.calculate_world_matrix(camera, frame, entity_position); for (index, frame_part) in frame.frame_parts.iter().enumerate() { let animation_index = frame_part.animation_index; let sprite_number = frame_part.sprite_number; let texture = &self.animation_pair[animation_index].sprites.textures[sprite_number]; - // The vertex position is calculated from the center of image, so we need to - // add half of the height. - let position = Vector2::new( - -animation.frames[0].offset.x as f32, - animation.frames[0].offset.y as f32 + ((animation.frames[time].size.y - 1) / 2) as f32, - ); - let origin = Point3::from_vec(position.extend(0.0)) * SPRITE_SCALE / TILE_SIZE; let frame_size = Vector2::new(frame.size.x as f32, frame.size.y as f32); - let size = frame_size * SPRITE_SCALE / TILE_SIZE; - let world_matrix = camera.billboard_matrix(entity_position, origin, size); - let cell_count = Vector2::new(1, 1); - let cell_position = Vector2::new(0, 0); - let texture_size = Vector2::new(1.0 / cell_count.x as f32, 1.0 / cell_count.y as f32); - let texture_position = Vector2::new(texture_size.x * cell_position.x as f32, texture_size.y * cell_position.y as f32); + let (texture_size, texture_position) = self.get_texture_coordinates(); let (depth_offset, curvature) = camera.calculate_depth_offset_and_curvature(&world_matrix, SPRITE_SCALE, SPRITE_SCALE); let position = world_matrix.transform_point(Point3::from_value(0.0)); @@ -154,4 +175,40 @@ impl AnimationData { }); } } + + #[cfg(feature = "debug")] + pub fn render_debug( + &self, + instructions: &mut Vec, + camera: &dyn Camera, + entity_position: Point3, + animation_state: &AnimationState, + head_direction: usize, + color_external: Color, + color_internal: Color, + ) { + let frame = self.get_frame(animation_state, camera, head_direction); + let world_matrix = self.calculate_world_matrix(camera, frame, entity_position); + let world_shift_offset = world_matrix * frame.offset_matrix; + + instructions.push(DebugRectangleInstruction { + world: world_shift_offset, + color: color_external, + }); + instructions.push(DebugRectangleInstruction { + world: world_shift_offset * frame.horizontal_matrix, + color: color_external, + }); + instructions.push(DebugRectangleInstruction { + world: world_shift_offset * frame.vertical_matrix, + color: color_external, + }); + + for frame_part in frame.frame_parts.iter() { + instructions.push(DebugRectangleInstruction { + world: world_matrix * frame_part.affine_matrix, + color: color_internal, + }); + } + } } diff --git a/korangar/src/world/entity/mod.rs b/korangar/src/world/entity/mod.rs index ced38e85..f81f1110 100644 --- a/korangar/src/world/entity/mod.rs +++ b/korangar/src/world/entity/mod.rs @@ -13,6 +13,8 @@ use ragnarok_packets::{AccountId, CharacterInformation, ClientTick, EntityId, Se #[cfg(feature = "debug")] use wgpu::{BufferUsages, Device, Queue}; +#[cfg(feature = "debug")] +use crate::graphics::DebugRectangleInstruction; use crate::graphics::EntityInstruction; use crate::interface::application::InterfaceSettings; use crate::interface::layout::{ScreenPosition, ScreenSize}; @@ -26,7 +28,7 @@ use crate::renderer::MarkerRenderer; use crate::world::MarkerIdentifier; use crate::world::{AnimationData, Camera, Map}; #[cfg(feature = "debug")] -use crate::{Buffer, ModelVertex}; +use crate::{Buffer, Color, ModelVertex}; const MALE_HAIR_LOOKUP: &[usize] = &[2, 2, 1, 7, 5, 4, 3, 6, 8, 9, 10, 12, 11]; const FEMALE_HAIR_LOOKUP: &[usize] = &[2, 2, 4, 7, 1, 5, 3, 6, 12, 10, 9, 11, 8]; @@ -774,6 +776,19 @@ impl Common { ); } + #[cfg(feature = "debug")] + pub fn render_debug(&self, instructions: &mut Vec, camera: &dyn Camera) { + self.animation_data.render_debug( + instructions, + camera, + self.position, + &self.animation_state, + self.head_direction, + Color::rgb_u8(255, 0, 0), + Color::rgb_u8(0, 255, 0), + ); + } + #[cfg(feature = "debug")] pub fn render_marker( &self, @@ -1133,6 +1148,11 @@ impl Entity { self.get_common().render(instructions, camera, add_to_picker); } + #[cfg(feature = "debug")] + pub fn render_debug(&self, instructions: &mut Vec, camera: &dyn Camera) { + self.get_common().render_debug(instructions, camera); + } + #[cfg(feature = "debug")] pub fn get_pathing_vertex_buffer(&self) -> Option<&Arc>> { self.get_common() diff --git a/korangar/src/world/map/mod.rs b/korangar/src/world/map/mod.rs index c4e79a8c..64944a81 100644 --- a/korangar/src/world/map/mod.rs +++ b/korangar/src/world/map/mod.rs @@ -29,7 +29,7 @@ use crate::graphics::ModelBatch; #[cfg(feature = "debug")] use crate::graphics::RenderSettings; #[cfg(feature = "debug")] -use crate::graphics::{DebugAabbInstruction, DebugCircleInstruction}; +use crate::graphics::{DebugAabbInstruction, DebugCircleInstruction, DebugRectangleInstruction}; use crate::graphics::{EntityInstruction, IndicatorInstruction, ModelInstruction, Texture, WaterInstruction}; #[cfg(feature = "debug")] use crate::interface::application::InterfaceSettings; @@ -312,6 +312,14 @@ impl Map { }); } + #[cfg(feature = "debug")] + #[korangar_debug::profile] + pub fn render_entities_debug(&self, instructions: &mut Vec, entities: &[Entity], camera: &dyn Camera) { + entities.iter().for_each(|entity| { + entity.render_debug(instructions, camera); + }); + } + #[cfg(feature = "debug")] #[korangar_debug::profile] pub fn render_bounding(