tirbofish/dropbear · commit
a75bf7707079ec6c32154552c01b1fde793aa163
feature: debug drawing, such as rays and objects and lines and stuff like that
todo: change colliderwireframe to use DebugDraw instead.
Signature present but could not be verified.
Unverified
@@ -11,66 +11,87 @@ pub struct ResizableBuffer<T> { _marker: PhantomData<T>, } -#[derive(Debug, Clone)] -pub struct UniformBuffer<T> { - buffer: wgpu::Buffer, - label: String, - _marker: PhantomData<T>, -} - -impl<T: bytemuck::Pod> UniformBuffer<T> { - pub fn new(device: &wgpu::Device, label: &str) -> Self { - let size = (std::mem::size_of::<T>() as wgpu::BufferAddress).max(16); +impl<T: bytemuck::Pod> ResizableBuffer<T> { + pub fn new( + device: &wgpu::Device, + initial_capacity: usize, + usage: wgpu::BufferUsages, + label: &str, + ) -> Self { + let size = (initial_capacity * std::mem::size_of::<T>()) as wgpu::BufferAddress; let buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some(label), - size, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + size: size.max(16), + usage, mapped_at_creation: false, }); + log::debug!("Registered new resizable buffer: {:?} (usage={:?})", label, usage); Self { buffer, + capacity: initial_capacity, + usage, label: label.to_string(), _marker: PhantomData, } } - pub fn write(&self, queue: &wgpu::Queue, value: &T) { + pub fn write(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, data: &[T]) { puffin::profile_function!(&self.label); - queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(value)); + if data.is_empty() { + return; + } + + if data.len() > self.capacity { + self.capacity = data.len().max(self.capacity * 2); + + let new_size = (self.capacity * std::mem::size_of::<T>()) as wgpu::BufferAddress; + + log::debug!( + "Resizing buffer '{}' to hold {} items", + self.label, + self.capacity + ); + + self.buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(&self.label), + size: new_size, + usage: self.usage, + mapped_at_creation: false, + }); + } + + queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data)); } pub fn buffer(&self) -> &wgpu::Buffer { &self.buffer } - pub fn label(&self) -> &str { - &self.label + pub fn slice(&self, count: usize) -> wgpu::BufferSlice<'_> { + let byte_count = (count * std::mem::size_of::<T>()) as wgpu::BufferAddress; + self.buffer.slice(0..byte_count) } } -/// A wrapper to a [wgpu::Buffer] that stores #[derive(Debug, Clone)] -pub struct StorageBuffer<T> { +pub struct UniformBuffer<T> { buffer: wgpu::Buffer, label: String, _marker: PhantomData<T>, } -impl<T: bytemuck::Pod> StorageBuffer<T> { - /// Creates a storage buffer intended to be written by the CPU and read by the GPU. - /// - /// Note: whether it is bound as read-only is controlled by the bind group layout - /// (`BufferBindingType::Storage { read_only: true }`). +impl<T: bytemuck::Pod> UniformBuffer<T> { pub fn new(device: &wgpu::Device, label: &str) -> Self { let size = (std::mem::size_of::<T>() as wgpu::BufferAddress).max(16); let buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some(label), size, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); + log::debug!("Registered new uniform buffer: {:?}", label); Self { buffer, label: label.to_string(), @@ -79,7 +100,7 @@ impl<T: bytemuck::Pod> StorageBuffer<T> { } pub fn write(&self, queue: &wgpu::Queue, value: &T) { - puffin::profile_function!(self.label()); + puffin::profile_function!(&self.label); queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(value)); } @@ -92,64 +113,56 @@ impl<T: bytemuck::Pod> StorageBuffer<T> { } } -impl<T: bytemuck::Pod> ResizableBuffer<T> { - pub fn new( - device: &wgpu::Device, - initial_capacity: usize, - usage: wgpu::BufferUsages, - label: &str, - ) -> Self { - let size = (initial_capacity * std::mem::size_of::<T>()) as wgpu::BufferAddress; +/// A wrapper to a [wgpu::Buffer] that stores +#[derive(Debug, Clone)] +pub struct StorageBuffer<T> { + buffer: wgpu::Buffer, + label: String, + _marker: PhantomData<T>, +} + +impl<T: bytemuck::Pod> StorageBuffer<T> { + pub fn new_read_only(device: &wgpu::Device, label: &str) -> Self { + Self::new(device, label, true) + } + + pub fn new_read_write(device: &wgpu::Device, label: &str) -> Self { + Self::new(device, label, false) + } + + fn new(device: &wgpu::Device, label: &str, read_only: bool) -> Self { + let usage = if read_only { + wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST + } else { + wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC + }; + + let size = (std::mem::size_of::<T>() as wgpu::BufferAddress).max(16); let buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some(label), - size: size.max(16), + size, usage, mapped_at_creation: false, }); + log::debug!("Registered new storage buffer: {:?} (read_only: {})", label, read_only); Self { buffer, - capacity: initial_capacity, - usage, label: label.to_string(), _marker: PhantomData, } } - pub fn write(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, data: &[T]) { - puffin::profile_function!(&self.label); - if data.is_empty() { - return; - } - - if data.len() > self.capacity { - self.capacity = data.len().max(self.capacity * 2); - - let new_size = (self.capacity * std::mem::size_of::<T>()) as wgpu::BufferAddress; - - log::debug!( - "Resizing buffer '{}' to hold {} items", - self.label, - self.capacity - ); - - self.buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&self.label), - size: new_size, - usage: self.usage, - mapped_at_creation: false, - }); - } - - queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data)); + pub fn write(&self, queue: &wgpu::Queue, value: &T) { + puffin::profile_function!(self.label()); + queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(value)); } pub fn buffer(&self) -> &wgpu::Buffer { &self.buffer } - pub fn slice(&self, count: usize) -> wgpu::BufferSlice<'_> { - let byte_count = (count * std::mem::size_of::<T>()) as wgpu::BufferAddress; - self.buffer.slice(0..byte_count) + pub fn label(&self) -> &str { + &self.label } -} +} @@ -1,59 +1,519 @@ use std::sync::Arc; -use wgpu::{RenderPipeline, RenderPipelineDescriptor, VertexState}; -use crate::graphics::SharedGraphicsContext; - -pub struct DebugLine { - -} +use glam::{Mat4, Quat, Vec3, Vec4}; +use wgpu::{BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, BufferUsages, CompareFunction, DepthStencilState, LoadOp, MultisampleState, Operations, PrimitiveState, PrimitiveTopology, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, ShaderStages, StoreOp, TextureFormat, VertexBufferLayout, VertexState}; +use crate::buffer::{ResizableBuffer, UniformBuffer}; +use crate::graphics::{CommandEncoder, SharedGraphicsContext}; +use crate::shader::Shader; pub struct DebugDraw { pipeline: Arc<DebugDrawPipeline>, + vertices: Vec<DebugVertex>, + vertex_buffer: ResizableBuffer<DebugVertex>, } +// main parts impl DebugDraw { - pub fn draw_line(&self) { + /// Creates a new [`DebugDraw`] instance, setting up all `wgpu` related structs such as pipelines + /// and buffers. + pub fn new(graphics: Arc<SharedGraphicsContext>) -> Self { + let pipeline = Arc::new(DebugDrawPipeline::new(graphics.clone())); + let vertices = vec![]; + let vertex_buffer = ResizableBuffer::new( + &graphics.device, + 1024, + BufferUsages::VERTEX | BufferUsages::COPY_DST, + "debug draw vertex buffer" + ); + + Self { + pipeline, + vertices, + vertex_buffer, + } + } + + /// Flushes away all of the vertices to be drawn, and renders them at that instant. + pub fn flush(&mut self, graphics: Arc<SharedGraphicsContext>, encoder: &mut CommandEncoder, view_proj: Mat4) { + if self.vertices.is_empty() { + return; + } + + self.vertex_buffer.write( + &graphics.device, + &graphics.queue, + bytemuck::cast_slice(&self.vertices) + ); + self.pipeline.draw( + graphics, + encoder, + view_proj, + &self.vertex_buffer, + self.vertices.len() as u32, + ); + + self.vertices.clear(); } } +// helpers +impl DebugDraw { + // primitives + /// Draws a line between 2 points with a specific colour. Probably the most primitive of them all. + pub fn draw_line(&mut self, a: Vec3, b: Vec3, colour: [f32; 4]) { + let a = a.to_array(); + let b = b.to_array(); + self.vertices.push(DebugVertex { position: [a[0], a[1], a[2], 0.0], colour }); + self.vertices.push(DebugVertex { position: [b[0], b[1], b[2], 0.0], colour }); + } + + /// A wrapper for [draw_line](Self::draw_line), which draws a line from an origin and at a direction + /// with a specific colour. + pub fn draw_ray(&mut self, origin: Vec3, dir: Vec3, colour: [f32; 4]) { + self.draw_line(origin, origin + dir, colour); + } + + /// Draws a line from `a` to `b` with a specific colour and an arrowhead at the end. + pub fn draw_arrow(&mut self, a: Vec3, b: Vec3, colour: [f32; 4]) { + self.draw_line(a, b, colour); + + // arrowhead — two short lines branching back from the tip + let dir = (b - a).normalize(); + let len = (b - a).length() * 0.15; + + // find a perpendicular axis + let up = if dir.dot(Vec3::Y).abs() < 0.99 { Vec3::Y } else { Vec3::Z }; + let right = dir.cross(up).normalize(); + + let tip = b; + let base = tip - dir * len; + self.draw_line(tip, base + right * len * 0.5, colour); + self.draw_line(tip, base - right * len * 0.5, colour); + } + + /// Draws a cross/asterisk at `pos` with the given `size`. + pub fn draw_point(&mut self, pos: Vec3, size: f32, colour: [f32; 4]) { + let h = size * 0.5; + self.draw_line(pos - Vec3::X * h, pos + Vec3::X * h, colour); + self.draw_line(pos - Vec3::Y * h, pos + Vec3::Y * h, colour); + self.draw_line(pos - Vec3::Z * h, pos + Vec3::Z * h, colour); + } + + // shapes + /// Draws a circle in 3D space at `center` with the given `radius`. + /// + /// `normal` defines the axis the circle faces. e.g. `Vec3::Y` for a flat ground circle. + pub fn draw_circle(&mut self, center: Vec3, radius: f32, normal: Vec3, colour: [f32; 4]) { + let segments = 32; + + // build tangent frame from normal + let up = if normal.dot(Vec3::Y).abs() < 0.99 { Vec3::Y } else { Vec3::Z }; + let tangent = normal.cross(up).normalize(); + let bitangent = normal.cross(tangent).normalize(); + + let mut prev = center + tangent * radius; + for i in 1..=segments { + let angle = (i as f32 / segments as f32) * std::f32::consts::TAU; + let next = center + (tangent * angle.cos() + bitangent * angle.sin()) * radius; + self.draw_line(prev, next, colour); + prev = next; + } + } + + /// Draws 3 circles at [`Vec3::X`], [`Vec3::Y`], and [`Vec3::Z`] to make an imitation of a sphere. + /// + /// To see a proper sphere, use [`draw_globe`](Self::draw_globe). + pub fn draw_sphere(&mut self, center: Vec3, radius: f32, colour: [f32; 4]) { + self.draw_circle(center, radius, Vec3::X, colour); + self.draw_circle(center, radius, Vec3::Y, colour); + self.draw_circle(center, radius, Vec3::Z, colour); + } + + /// Draws a wireframe sphere using latitude and longitude lines, giving a globe-like appearance. + /// + /// `lat_lines` controls the number of horizontal rings (latitude), + /// `lon_lines` controls the number of vertical rings (longitude). + pub fn draw_globe(&mut self, center: Vec3, radius: f32, lat_lines: u32, lon_lines: u32, color: [f32; 4]) { + // latitude rings (horizontal circles stacked along Y axis) + for i in 1..lat_lines { + let angle = std::f32::consts::PI * (i as f32 / lat_lines as f32); // 0..PI + let y = angle.cos() * radius; + let ring_radius = angle.sin() * radius; + self.draw_circle(center + Vec3::Y * y, ring_radius, Vec3::Y, color); + } + + // longitude rings (vertical circles rotating around Y axis) + for i in 0..lon_lines { + let angle = std::f32::consts::TAU * (i as f32 / lon_lines as f32); // 0..TAU + let normal = Vec3::new(angle.cos(), 0.0, angle.sin()); + self.draw_circle(center, radius, normal, color); + } + } + + /// Draws a wireframe axis-aligned bounding box (AABB) from `min` to `max`. + /// + /// Also used for rendering a cube at a minimum position and a maximum position. + pub fn draw_aabb(&mut self, min: Vec3, max: Vec3, colour: [f32; 4]) { + let corners = [ + Vec3::new(min.x, min.y, min.z), + Vec3::new(max.x, min.y, min.z), + Vec3::new(max.x, max.y, min.z), + Vec3::new(min.x, max.y, min.z), + Vec3::new(min.x, min.y, max.z), + Vec3::new(max.x, min.y, max.z), + Vec3::new(max.x, max.y, max.z), + Vec3::new(min.x, max.y, max.z), + ]; + + // bottom face + self.draw_line(corners[0], corners[1], colour); + self.draw_line(corners[1], corners[2], colour); + self.draw_line(corners[2], corners[3], colour); + self.draw_line(corners[3], corners[0], colour); + // top face + self.draw_line(corners[4], corners[5], colour); + self.draw_line(corners[5], corners[6], colour); + self.draw_line(corners[6], corners[7], colour); + self.draw_line(corners[7], corners[4], colour); + // verticals + self.draw_line(corners[0], corners[4], colour); + self.draw_line(corners[1], corners[5], colour); + self.draw_line(corners[2], corners[6], colour); + self.draw_line(corners[3], corners[7], colour); + } + + /// Draws a wireframe oriented bounding box (OBB) at `center`. + /// + /// `half_extents` defines the box dimensions along each local axis. + /// `rotation` orients the box in world space. + pub fn draw_obb(&mut self, center: Vec3, half_extents: Vec3, rotation: Quat, colour: [f32; 4]) { + // rotate the 8 unit corners then scale by half_extents + let corners_local = [ + Vec3::new(-1.0, -1.0, -1.0), + Vec3::new( 1.0, -1.0, -1.0), + Vec3::new( 1.0, 1.0, -1.0), + Vec3::new(-1.0, 1.0, -1.0), + Vec3::new(-1.0, -1.0, 1.0), + Vec3::new( 1.0, -1.0, 1.0), + Vec3::new( 1.0, 1.0, 1.0), + Vec3::new(-1.0, 1.0, 1.0), + ]; + + let corners: Vec<Vec3> = corners_local + .iter() + .map(|&c| center + rotation * (c * half_extents)) + .collect(); + + // same edge layout as AABB + self.draw_line(corners[0], corners[1], colour); + self.draw_line(corners[1], corners[2], colour); + self.draw_line(corners[2], corners[3], colour); + self.draw_line(corners[3], corners[0], colour); + self.draw_line(corners[4], corners[5], colour); + self.draw_line(corners[5], corners[6], colour); + self.draw_line(corners[6], corners[7], colour); + self.draw_line(corners[7], corners[4], colour); + self.draw_line(corners[0], corners[4], colour); + self.draw_line(corners[1], corners[5], colour); + self.draw_line(corners[2], corners[6], colour); + self.draw_line(corners[3], corners[7], colour); + } + + /// Draws a wireframe capsule between points `a` (bottom) and `b` (top) with the given `radius`. + /// + /// Rendered as two end circles, four connecting lines, and hemispherical arcs on each cap. + pub fn draw_capsule(&mut self, a: Vec3, b: Vec3, radius: f32, colour: [f32; 4]) { + let axis = (b - a).normalize(); + + // two end circles + self.draw_circle(a, radius, axis, colour); + self.draw_circle(b, radius, axis, colour); + + // 4 connecting lines along the sides + let up = if axis.dot(Vec3::Y).abs() < 0.99 { Vec3::Y } else { Vec3::Z }; + let tangent = axis.cross(up).normalize(); + let bitangent = axis.cross(tangent).normalize(); + + for dir in [tangent, -tangent, bitangent, -bitangent] { + self.draw_line(a + dir * radius, b + dir * radius, colour); + } + } + + /// Draws a wireframe cone from `apex` extending in `dir`. + /// + /// `angle` is the half-angle of the cone in radians. + /// + /// `length` controls how far the cone extends from the apex. + pub fn draw_cone(&mut self, apex: Vec3, dir: Vec3, angle: f32, length: f32, colour: [f32; 4]) { + let base_center = apex + dir.normalize() * length; + let base_radius = length * angle.tan(); + + // base circle + self.draw_circle(base_center, base_radius, dir, colour); + + // 4 lines from apex to base rim + let up = if dir.normalize().dot(Vec3::Y).abs() < 0.99 { Vec3::Y } else { Vec3::Z }; + let tangent = dir.cross(up).normalize(); + let bitangent = dir.cross(tangent).normalize(); + + for side in [tangent, -tangent, bitangent, -bitangent] { + self.draw_line(apex, base_center + side * base_radius, colour); + } + } + + /// Draws a wireframe frustum by unprojecting the 8 NDC corners using the inverse of `view_proj`. + /// + /// Pass in `camera.view_proj()` to visualise the camera's view frustum. + pub fn draw_frustum(&mut self, view_proj: Mat4, colour: [f32; 4]) { + // NDC corners, unproject back to world space + let ndc_corners = [ + Vec3::new(-1.0, -1.0, 0.0), // near + Vec3::new( 1.0, -1.0, 0.0), + Vec3::new( 1.0, 1.0, 0.0), + Vec3::new(-1.0, 1.0, 0.0), + Vec3::new(-1.0, -1.0, 1.0), // far + Vec3::new( 1.0, -1.0, 1.0), + Vec3::new( 1.0, 1.0, 1.0), + Vec3::new(-1.0, 1.0, 1.0), + ]; + + let inv = view_proj.inverse(); + + let corners: Vec<Vec3> = ndc_corners.iter().map(|&ndc| { + let clip = Vec4::new(ndc.x, ndc.y, ndc.z, 1.0); + let world = inv * clip; + world.truncate() / world.w + }).collect(); + + // near face + self.draw_line(corners[0], corners[1], colour); + self.draw_line(corners[1], corners[2], colour); + self.draw_line(corners[2], corners[3], colour); + self.draw_line(corners[3], corners[0], colour); + // far face + self.draw_line(corners[4], corners[5], colour); + self.draw_line(corners[5], corners[6], colour); + self.draw_line(corners[6], corners[7], colour); + self.draw_line(corners[7], corners[4], colour); + // connecting edges + self.draw_line(corners[0], corners[4], colour); + self.draw_line(corners[1], corners[5], colour); + self.draw_line(corners[2], corners[6], colour); + self.draw_line(corners[3], corners[7], colour); + } + + // curves and paths + + /// Draws a polyline through a slice of points, connecting each adjacent pair with a line. + pub fn draw_polyline(&mut self, points: &[Vec3], colour: [f32; 4]) { + for window in points.windows(2) { + self.draw_line(window[0], window[1], colour); + } + } + + /// Draws a Catmull-Rom spline through `points`, tessellated into `segments_per_span` line segments per span. + /// Requires at least 4 control points. The curve passes through all points except the first and last, + /// which act as phantom tangent guides. + /// + /// Duplicate the first and last point if you want the curve to reach the endpoints. + pub fn draw_spline(&mut self, points: &[Vec3], segments_per_span: u32, colour: [f32; 4]) { + if points.len() < 4 { + return; + } + + for i in 0..points.len().saturating_sub(3) { + let (p0, p1, p2, p3) = (points[i], points[i+1], points[i+2], points[i+3]); + let mut prev = p1; // catmull-rom passes through p1..p_{n-2} + for j in 1..=segments_per_span { + let t = j as f32 / segments_per_span as f32; + let next = catmull_rom(p0, p1, p2, p3, t); + self.draw_line(prev, next, colour); + prev = next; + } + } + } + + /// Draws a point cross at each vertex position in `vertices`. + pub fn draw_vertices(&mut self, vertices: &[Vec3], colour: [f32; 4]) { + for &v in vertices { + self.draw_point(v, 0.05, colour); + } + } +} + +fn catmull_rom(p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, t: f32) -> Vec3 { + let t2 = t * t; + let t3 = t2 * t; + 0.5 * ( + (2.0 * p1) + + (-p0 + p2) * t + + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3 + ) +} + pub struct DebugDrawPipeline { + uniform: UniformBuffer<Mat4>, + bind_group: wgpu::BindGroup, pipeline: RenderPipeline, } impl DebugDrawPipeline { pub fn new(graphics: Arc<SharedGraphicsContext>) -> Self { + let shader = Shader::new(graphics.clone(), include_str!("shaders/basic.wgsl"), Some("basic shader module")); + + let bind_group_layout = graphics.device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("basic camera bind group layout"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + } + ], + }); + + let camera_uniform: UniformBuffer<Mat4> = UniformBuffer::new(&graphics.device, "basic camera uniform"); + + let bind_group = graphics.device.create_bind_group(&BindGroupDescriptor { + label: Some("basic camera bind group"), + layout: &bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::Buffer(camera_uniform.buffer().as_entire_buffer_binding()), + } + ], + }); + let pipeline_layout = graphics.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("debug draw pipeline layout"), - bind_group_layouts: &[], + bind_group_layouts: &[ + &bind_group_layout + ], push_constant_ranges: &[], }); + let hdr_format = graphics.hdr.read().format(); + let sample_count: u32 = (*graphics.antialiasing.read()).into(); + let pipeline = graphics.device.create_render_pipeline(&RenderPipelineDescriptor { label: Some("debug draw render pipeline"), layout: Some(&pipeline_layout), vertex: VertexState { - module: &(), - entry_point: None, + module: &shader.module, + entry_point: Some("vs_main"), compilation_options: Default::default(), - buffers: &[], + buffers: &[DebugVertex::LAYOUT], + }, + fragment: Some(wgpu::FragmentState { + module: &shader.module, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: hdr_format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: PrimitiveState { + topology: PrimitiveTopology::LineList, + strip_index_format: None, + front_face: Default::default(), + cull_mode: None, + unclipped_depth: false, + polygon_mode: Default::default(), + conservative: false, + }, + depth_stencil: Some(DepthStencilState { + format: TextureFormat::Depth32Float, + depth_write_enabled: false, // don't write to depth, just read + depth_compare: CompareFunction::Less, + stencil: Default::default(), + bias: Default::default(), + }), + multisample: MultisampleState { + count: sample_count, + mask: !0, + alpha_to_coverage_enabled: false, }, - primitive: Default::default(), - depth_stencil: None, - multisample: Default::default(), - fragment: None, multiview: None, cache: None, }); + + Self { + pipeline, + uniform: camera_uniform, + bind_group, + } } - pub fn draw(&self, graphics: Arc<SharedGraphicsContext>) { + pub fn draw( + &self, + graphics: Arc<SharedGraphicsContext>, + encoder: &mut CommandEncoder, + view_proj: Mat4, + vertex_buffer: &ResizableBuffer<DebugVertex>, + vertex_count: u32, + ) { + // update camera uniform + self.uniform.write(&graphics.queue, &view_proj); + + if vertex_count == 0 { + return; + } + + let hdr = graphics.hdr.read(); + let mut pass = encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("debug draw pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view: hdr.view(), + depth_slice: None, + resolve_target: hdr.resolve_target(), + ops: Operations { + load: LoadOp::Load, // draw on top of existing frame + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &graphics.depth_texture.view, + depth_ops: Some(Operations { + load: LoadOp::Load, // read existing depth + store: StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.bind_group, &[]); + pass.set_vertex_buffer(0, vertex_buffer.buffer().slice(..)); + pass.draw(0..vertex_count, 0..1); } } #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct DebugVertex { - pub position: [f32; 3], - pub color: [f32; 4], + pub position: [f32; 4], // ignore w value, used to ensure 16 bit pads + pub colour: [f32; 4], +} + +impl DebugVertex { + pub const LAYOUT: VertexBufferLayout<'static> = VertexBufferLayout { + array_stride: size_of::<Self>() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array![ + 0 => Float32x4, + 1 => Float32x4, + ], + }; } @@ -1,3 +1,4 @@ +use crate::debug::DebugDraw; use crate::model::Vertex; use crate::{BindGroupLayouts, texture}; use crate::{State, egui_renderer::EguiRenderer}; @@ -33,6 +34,7 @@ pub struct SharedGraphicsContext { pub hdr: Arc<RwLock<HdrPipeline>>, pub antialiasing: Arc<RwLock<AntiAliasingMode>>, pub layouts: Arc<BindGroupLayouts>, + pub debug_draw: Arc<Mutex<Option<DebugDraw>>>, } impl SharedGraphicsContext { @@ -60,6 +62,7 @@ impl SharedGraphicsContext { surface_config: state.config.clone(), antialiasing: state.antialiasing.clone(), layouts: state.layouts.clone(), + debug_draw: state.debug_draw.clone(), } } } @@ -24,7 +24,7 @@ pub mod texture; pub mod utils; pub mod multisampling; pub mod billboarding; -mod debug; +pub mod debug; features! { pub mod feature_list { @@ -71,6 +71,7 @@ use crate::egui_renderer::EguiRenderer; use crate::graphics::{CommandEncoder, SharedGraphicsContext}; use crate::mipmap::MipMapper; use crate::texture::{Texture, TextureBuilder}; +use crate::debug::DebugDraw; use crate::pipelines::hdr::HdrPipeline; use crate::scene::Scene; @@ -388,6 +389,7 @@ pub struct State { physics_accumulator: Duration, pub scene_manager: scene::Manager, + pub debug_draw: Arc<Mutex<Option<DebugDraw>>>, // pub yakui_renderer: Arc<Mutex<yakui_wgpu::YakuiWgpu>>, // pub yakui_texture: yakui::TextureId, } @@ -588,6 +590,7 @@ Hardware: antialiasing: Arc::new(RwLock::new(antialiasing)), hdr, layouts: Arc::new(layouts), + debug_draw: Arc::new(Mutex::new(None)), }; Ok(result) @@ -95,7 +95,7 @@ impl DropbearShaderPipeline for LightCubePipeline { }); let storage_buffer = - StorageBuffer::new(&graphics.device, "light cube pipeline storage buffer"); + StorageBuffer::new_read_only(&graphics.device, "light cube pipeline storage buffer"); Self { shader, @@ -1,29 +1,26 @@ // basic.wgsl - shader used for drawing debug stuff like lines and shii -struct CameraUniform { - view_proj: mat4x4<f32>, -} -@group(0) @binding(0) var<uniform> camera: CameraUniform; +@group(0) @binding(0) var<uniform> camera: mat4x4<f32>; -struct VertexIn { - @location(0) position: vec3<f32>, - @location(1) color: vec4<f32>, +struct VertexInput { + @location(0) position: vec4<f32>, // ignore w value + @location(1) colour: vec4<f32>, } -struct VertexOut { +struct VertexOutput { @builtin(position) clip_position: vec4<f32>, - @location(0) color: vec4<f32>, + @location(0) colour: vec4<f32>, } @vertex -fn vs_main(in: VertexIn) -> VertexOut { - var out: VertexOut; - out.clip_position = camera.view_proj * vec4<f32>(in.position, 1.0); - out.color = in.color; +fn vs_main(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + out.clip_position = camera * vec4<f32>(in.position.xyz, 1.0); + out.colour = in.colour; return out; } @fragment -fn fs_main(in: VertexOut) -> @location(0) vec4<f32> { - return in.color; +fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { + return in.colour; } @@ -0,0 +1,194 @@ +use crate::ptr::GraphicsContextPtr; +use crate::scripting::result::DropbearNativeResult; +use crate::types::{NColour, NQuaternion, NVector3}; +use dropbear_engine::graphics::SharedGraphicsContext; +use glam::{Quat, Vec3}; + +macro_rules! with_debug { + ($graphics:expr, |$dd:ident| $body:expr) => { + if let Some($dd) = $graphics.debug_draw.lock().as_mut() { + $body + } + }; +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawLine"), + c +)] +fn debug_draw_line( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + start: &NVector3, + end: &NVector3, + colour: &NColour, +) -> DropbearNativeResult<()> { + let a = Vec3::new(start.x as f32, start.y as f32, start.z as f32); + let b = Vec3::new(end.x as f32, end.y as f32, end.z as f32); + with_debug!(graphics, |dd| dd.draw_line(a, b, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawRay"), + c +)] +fn debug_draw_ray( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + origin: &NVector3, + dir: &NVector3, + colour: &NColour, +) -> DropbearNativeResult<()> { + let o = Vec3::new(origin.x as f32, origin.y as f32, origin.z as f32); + let d = Vec3::new(dir.x as f32, dir.y as f32, dir.z as f32); + with_debug!(graphics, |dd| dd.draw_ray(o, d, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawArrow"), + c +)] +fn debug_draw_arrow( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + start: &NVector3, + end: &NVector3, + colour: &NColour, +) -> DropbearNativeResult<()> { + let a = Vec3::new(start.x as f32, start.y as f32, start.z as f32); + let b = Vec3::new(end.x as f32, end.y as f32, end.z as f32); + with_debug!(graphics, |dd| dd.draw_arrow(a, b, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawPoint"), + c +)] +fn debug_draw_point( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + pos: &NVector3, + size: f32, + colour: &NColour, +) -> DropbearNativeResult<()> { + let p = Vec3::new(pos.x as f32, pos.y as f32, pos.z as f32); + with_debug!(graphics, |dd| dd.draw_point(p, size, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawCircle"), + c +)] +fn debug_draw_circle( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + center: &NVector3, + radius: f32, + normal: &NVector3, + colour: &NColour, +) -> DropbearNativeResult<()> { + let c = Vec3::new(center.x as f32, center.y as f32, center.z as f32); + let n = Vec3::new(normal.x as f32, normal.y as f32, normal.z as f32); + with_debug!(graphics, |dd| dd.draw_circle(c, radius, n, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawSphere"), + c +)] +fn debug_draw_sphere( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + center: &NVector3, + radius: f32, + colour: &NColour, +) -> DropbearNativeResult<()> { + let c = Vec3::new(center.x as f32, center.y as f32, center.z as f32); + with_debug!(graphics, |dd| dd.draw_sphere(c, radius, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawGlobe"), + c +)] +fn debug_draw_globe( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + center: &NVector3, + radius: f32, + lat_lines: u32, + lon_lines: u32, + colour: &NColour, +) -> DropbearNativeResult<()> { + let c = Vec3::new(center.x as f32, center.y as f32, center.z as f32); + with_debug!(graphics, |dd| dd.draw_globe(c, radius, lat_lines, lon_lines, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawAabb"), + c +)] +fn debug_draw_aabb( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + min: &NVector3, + max: &NVector3, + colour: &NColour, +) -> DropbearNativeResult<()> { + let mn = Vec3::new(min.x as f32, min.y as f32, min.z as f32); + let mx = Vec3::new(max.x as f32, max.y as f32, max.z as f32); + with_debug!(graphics, |dd| dd.draw_aabb(mn, mx, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawObb"), + c +)] +fn debug_draw_obb( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + center: &NVector3, + half_extents: &NVector3, + rotation: &NQuaternion, + colour: &NColour, +) -> DropbearNativeResult<()> { + let c = Vec3::new(center.x as f32, center.y as f32, center.z as f32); + let he = Vec3::new(half_extents.x as f32, half_extents.y as f32, half_extents.z as f32); + let r = Quat::from_xyzw(rotation.x as f32, rotation.y as f32, rotation.z as f32, rotation.w as f32); + with_debug!(graphics, |dd| dd.draw_obb(c, he, r, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawCapsule"), + c +)] +fn debug_draw_capsule( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + a: &NVector3, + b: &NVector3, + radius: f32, + colour: &NColour, +) -> DropbearNativeResult<()> { + let va = Vec3::new(a.x as f32, a.y as f32, a.z as f32); + let vb = Vec3::new(b.x as f32, b.y as f32, b.z as f32); + with_debug!(graphics, |dd| dd.draw_capsule(va, vb, radius, colour.to_f32_array())); + Ok(()) +} + +#[dropbear_macro::export( + kotlin(class = "com.dropbear.rendering.DebugDrawNative", func = "drawCone"), + c +)] +fn debug_draw_cone( + #[dropbear_macro::define(GraphicsContextPtr)] graphics: &SharedGraphicsContext, + apex: &NVector3, + dir: &NVector3, + angle: f32, + length: f32, + colour: &NColour, +) -> DropbearNativeResult<()> { + let a = Vec3::new(apex.x as f32, apex.y as f32, apex.z as f32); + let d = Vec3::new(dir.x as f32, dir.y as f32, dir.z as f32); + with_debug!(graphics, |dd| dd.draw_cone(a, d, angle, length, colour.to_f32_array())); + Ok(()) +} @@ -5,6 +5,7 @@ pub mod camera; pub mod command; pub mod component; pub mod config; +pub mod debug; pub mod engine; pub mod entity; pub mod entity_status; @@ -6,7 +6,7 @@ use crate::scripting::jni::utils::{FromJObject, ToJObject}; use crate::scripting::native::DropbearNativeError; use crate::scripting::result::DropbearNativeResult; use crate::states::SerializedLight; -use crate::types::NVector3; +use crate::types::{NColour, NVector3}; use dropbear_engine::attenuation::ATTENUATION_PRESETS; use dropbear_engine::entity::{EntityTransform, Transform, inspect_rotation_dquat}; use dropbear_engine::graphics::SharedGraphicsContext; @@ -334,15 +334,6 @@ impl InspectableComponent for Light { } } -#[repr(C)] -#[derive(Clone, Copy, Debug)] -struct NColour { - r: u8, - g: u8, - b: u8, - a: u8, -} - impl NColour { fn to_linear_rgb(self) -> DVec3 { DVec3::new( @@ -15,6 +15,26 @@ use rapier3d::prelude::ColliderHandle; #[repr(C)] #[derive(Clone, Copy, Debug)] +pub struct NColour { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl NColour { + pub fn to_f32_array(self) -> [f32; 4] { + [ + self.r as f32 / 255.0, + self.g as f32 / 255.0, + self.b as f32 / 255.0, + self.a as f32 / 255.0, + ] + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct NVector2 { pub x: f64, pub y: f64, @@ -398,6 +398,7 @@ impl PlayMode { )); self.billboard_pipeline = Some(BillboardPipeline::new(graphics.clone())); + *graphics.debug_draw.lock() = Some(dropbear_engine::debug::DebugDraw::new(graphics.clone())); let sky_texture_result = HdrLoader::from_equirectangular_bytes( &graphics.device, @@ -1454,6 +1454,12 @@ impl Scene for PlayMode { } } + // debug draw flush + if let Some(debug_draw) = graphics.debug_draw.lock().as_mut() { + let view_proj = Mat4::from_cols_array_2d(&camera.uniform.view_proj); + debug_draw.flush(graphics.clone(), &mut encoder, view_proj); + } + hdr.process(&mut encoder, &graphics.viewport_texture.view); if let Err(e) = encoder.submit() { @@ -520,6 +520,17 @@ int32_t dropbear_collider_set_collider_restitution(PhysicsStatePtr physics, cons int32_t dropbear_collider_set_collider_rotation(PhysicsStatePtr physics, const NCollider* collider, const NVector3* rotation); int32_t dropbear_collider_set_collider_shape(PhysicsStatePtr physics, const NCollider* collider, const ColliderShape* shape); int32_t dropbear_collider_set_collider_translation(PhysicsStatePtr physics, const NCollider* collider, const NVector3* translation); +int32_t dropbear_debug_debug_draw_aabb(GraphicsContextPtr graphics, const NVector3* min, const NVector3* max, const NColour* colour); +int32_t dropbear_debug_debug_draw_arrow(GraphicsContextPtr graphics, const NVector3* start, const NVector3* end, const NColour* colour); +int32_t dropbear_debug_debug_draw_capsule(GraphicsContextPtr graphics, const NVector3* a, const NVector3* b, float radius, const NColour* colour); +int32_t dropbear_debug_debug_draw_circle(GraphicsContextPtr graphics, const NVector3* center, float radius, const NVector3* normal, const NColour* colour); +int32_t dropbear_debug_debug_draw_cone(GraphicsContextPtr graphics, const NVector3* apex, const NVector3* dir, float angle, float length, const NColour* colour); +int32_t dropbear_debug_debug_draw_globe(GraphicsContextPtr graphics, const NVector3* center, float radius, uint32_t lat_lines, uint32_t lon_lines, const NColour* colour); +int32_t dropbear_debug_debug_draw_line(GraphicsContextPtr graphics, const NVector3* start, const NVector3* end, const NColour* colour); +int32_t dropbear_debug_debug_draw_obb(GraphicsContextPtr graphics, const NVector3* center, const NVector3* half_extents, const NQuaternion* rotation, const NColour* colour); +int32_t dropbear_debug_debug_draw_point(GraphicsContextPtr graphics, const NVector3* pos, float size, const NColour* colour); +int32_t dropbear_debug_debug_draw_ray(GraphicsContextPtr graphics, const NVector3* origin, const NVector3* dir, const NColour* colour); +int32_t dropbear_debug_debug_draw_sphere(GraphicsContextPtr graphics, const NVector3* center, float radius, const NColour* colour); int32_t dropbear_engine_get_asset(AssetRegistryPtr asset, const char* label, const AssetKind* kind, uint64_t* out0, bool* out0_present); int32_t dropbear_engine_get_entity(WorldPtr world, const char* label, uint64_t* out0); int32_t dropbear_engine_quit(CommandBufferPtr command_buffer); @@ -0,0 +1,86 @@ +package com.dropbear.rendering + +import com.dropbear.math.Quaterniond +import com.dropbear.math.Vector3d +import com.dropbear.utils.Colour + +/** + * Functions related to drawing to aid with debugging, such as wireframe lines and shapes. + * + * All draws are cleared at the end of each frame automatically. + */ +object DebugDraw { + /** Draws a line between [start] and [end] with the given [colour]. */ + fun drawLine(start: Vector3d, end: Vector3d, colour: Colour = Colour.WHITE) = + drawLineNative(start, end, colour) + + /** Draws a ray from [origin] in the direction [dir] with the given [colour]. */ + fun drawRay(origin: Vector3d, dir: Vector3d, colour: Colour = Colour.WHITE) = + drawRayNative(origin, dir, colour) + + /** Draws a line from [start] to [end] with an arrowhead at [end]. */ + fun drawArrow(start: Vector3d, end: Vector3d, colour: Colour = Colour.WHITE) = + drawArrowNative(start, end, colour) + + /** Draws a cross at [pos] with the given [size]. */ + fun drawPoint(pos: Vector3d, size: Double = 0.1, colour: Colour = Colour.WHITE) = + drawPointNative(pos, size.toFloat(), colour) + + /** + * Draws a circle at [center] with the given [radius]. + * [normal] defines the axis the circle faces (e.g. [Vector3d.up] for a flat ground circle). + */ + fun drawCircle(center: Vector3d, radius: Double, normal: Vector3d = Vector3d.up(), colour: Colour = Colour.WHITE) = + drawCircleNative(center, radius.toFloat(), normal, colour) + + /** + * Draws three axis-aligned circles at [center] to approximate a sphere outline. + * For a proper globe, see [drawGlobe]. + */ + fun drawSphere(center: Vector3d, radius: Double, colour: Colour = Colour.WHITE) = + drawSphereNative(center, radius.toFloat(), colour) + + /** + * Draws a wireframe globe at [center] using latitude and longitude rings. + * [latLines] controls horizontal rings; [lonLines] controls vertical rings. + */ + fun drawGlobe(center: Vector3d, radius: Double, latLines: Int = 8, lonLines: Int = 8, colour: Colour = Colour.WHITE) = + drawGlobeNative(center, radius.toFloat(), latLines, lonLines, colour) + + /** Draws a wireframe axis-aligned bounding box (AABB) from [min] to [max]. */ + fun drawAabb(min: Vector3d, max: Vector3d, colour: Colour = Colour.WHITE) = + drawAabbNative(min, max, colour) + + /** + * Draws a wireframe oriented bounding box (OBB) at [center]. + * [halfExtents] defines the local half-sizes along each axis. + * [rotation] orients the box in world space. + */ + fun drawObb(center: Vector3d, halfExtents: Vector3d, rotation: Quaterniond = Quaterniond.identity(), colour: Colour = Colour.WHITE) = + drawObbNative(center, halfExtents, rotation, colour) + + /** + * Draws a wireframe capsule between [a] (bottom) and [b] (top) with the given [radius]. + */ + fun drawCapsule(a: Vector3d, b: Vector3d, radius: Double, colour: Colour = Colour.WHITE) = + drawCapsuleNative(a, b, radius.toFloat(), colour) + + /** + * Draws a wireframe cone from [apex] extending in [dir]. + * [angle] is the half-angle in radians. [length] controls how far the cone extends. + */ + fun drawCone(apex: Vector3d, dir: Vector3d, angle: Double, length: Double, colour: Colour = Colour.WHITE) = + drawConeNative(apex, dir, angle.toFloat(), length.toFloat(), colour) +} + +internal expect fun DebugDraw.drawLineNative(start: Vector3d, end: Vector3d, colour: Colour) +internal expect fun DebugDraw.drawRayNative(origin: Vector3d, dir: Vector3d, colour: Colour) +internal expect fun DebugDraw.drawArrowNative(start: Vector3d, end: Vector3d, colour: Colour) +internal expect fun DebugDraw.drawPointNative(pos: Vector3d, size: Float, colour: Colour) +internal expect fun DebugDraw.drawCircleNative(center: Vector3d, radius: Float, normal: Vector3d, colour: Colour) +internal expect fun DebugDraw.drawSphereNative(center: Vector3d, radius: Float, colour: Colour) +internal expect fun DebugDraw.drawGlobeNative(center: Vector3d, radius: Float, latLines: Int, lonLines: Int, colour: Colour) +internal expect fun DebugDraw.drawAabbNative(min: Vector3d, max: Vector3d, colour: Colour) +internal expect fun DebugDraw.drawObbNative(center: Vector3d, halfExtents: Vector3d, rotation: Quaterniond, colour: Colour) +internal expect fun DebugDraw.drawCapsuleNative(a: Vector3d, b: Vector3d, radius: Float, colour: Colour) +internal expect fun DebugDraw.drawConeNative(apex: Vector3d, dir: Vector3d, angle: Float, length: Float, colour: Colour) @@ -0,0 +1,24 @@ +package com.dropbear.rendering; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Quaterniond; +import com.dropbear.math.Vector3d; +import com.dropbear.utils.Colour; + +public class DebugDrawNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native void drawLine(long graphicsContextPtr, Vector3d start, Vector3d end, Colour colour); + public static native void drawRay(long graphicsContextPtr, Vector3d origin, Vector3d dir, Colour colour); + public static native void drawArrow(long graphicsContextPtr, Vector3d start, Vector3d end, Colour colour); + public static native void drawPoint(long graphicsContextPtr, Vector3d pos, float size, Colour colour); + public static native void drawCircle(long graphicsContextPtr, Vector3d center, float radius, Vector3d normal, Colour colour); + public static native void drawSphere(long graphicsContextPtr, Vector3d center, float radius, Colour colour); + public static native void drawGlobe(long graphicsContextPtr, Vector3d center, float radius, int latLines, int lonLines, Colour colour); + public static native void drawAabb(long graphicsContextPtr, Vector3d min, Vector3d max, Colour colour); + public static native void drawObb(long graphicsContextPtr, Vector3d center, Vector3d halfExtents, Quaterniond rotation, Colour colour); + public static native void drawCapsule(long graphicsContextPtr, Vector3d a, Vector3d b, float radius, Colour colour); + public static native void drawCone(long graphicsContextPtr, Vector3d apex, Vector3d dir, float angle, float length, Colour colour); +} @@ -0,0 +1,41 @@ +package com.dropbear.rendering + +import com.dropbear.DropbearEngine +import com.dropbear.math.Quaterniond +import com.dropbear.math.Vector3d +import com.dropbear.utils.Colour + +private val g get() = DropbearEngine.native.graphicsContextHandle + +internal actual fun DebugDraw.drawLineNative(start: Vector3d, end: Vector3d, colour: Colour) = + DebugDrawNative.drawLine(g, start, end, colour) + +internal actual fun DebugDraw.drawRayNative(origin: Vector3d, dir: Vector3d, colour: Colour) = + DebugDrawNative.drawRay(g, origin, dir, colour) + +internal actual fun DebugDraw.drawArrowNative(start: Vector3d, end: Vector3d, colour: Colour) = + DebugDrawNative.drawArrow(g, start, end, colour) + +internal actual fun DebugDraw.drawPointNative(pos: Vector3d, size: Float, colour: Colour) = + DebugDrawNative.drawPoint(g, pos, size, colour) + +internal actual fun DebugDraw.drawCircleNative(center: Vector3d, radius: Float, normal: Vector3d, colour: Colour) = + DebugDrawNative.drawCircle(g, center, radius, normal, colour) + +internal actual fun DebugDraw.drawSphereNative(center: Vector3d, radius: Float, colour: Colour) = + DebugDrawNative.drawSphere(g, center, radius, colour) + +internal actual fun DebugDraw.drawGlobeNative(center: Vector3d, radius: Float, latLines: Int, lonLines: Int, colour: Colour) = + DebugDrawNative.drawGlobe(g, center, radius, latLines, lonLines, colour) + +internal actual fun DebugDraw.drawAabbNative(min: Vector3d, max: Vector3d, colour: Colour) = + DebugDrawNative.drawAabb(g, min, max, colour) + +internal actual fun DebugDraw.drawObbNative(center: Vector3d, halfExtents: Vector3d, rotation: Quaterniond, colour: Colour) = + DebugDrawNative.drawObb(g, center, halfExtents, rotation, colour) + +internal actual fun DebugDraw.drawCapsuleNative(a: Vector3d, b: Vector3d, radius: Float, colour: Colour) = + DebugDrawNative.drawCapsule(g, a, b, radius, colour) + +internal actual fun DebugDraw.drawConeNative(apex: Vector3d, dir: Vector3d, angle: Float, length: Float, colour: Colour) = + DebugDrawNative.drawCone(g, apex, dir, angle, length, colour) @@ -0,0 +1,144 @@ +@file:OptIn(ExperimentalForeignApi::class) + +package com.dropbear.rendering + +import com.dropbear.DropbearEngine +import com.dropbear.ffi.generated.* +import com.dropbear.math.Quaterniond +import com.dropbear.math.Vector3d +import com.dropbear.utils.Colour +import kotlinx.cinterop.* + +internal actual fun DebugDraw.drawLineNative(start: Vector3d, end: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_line(g, allocVec3(start).ptr, allocVec3(end).ptr, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawRayNative(origin: Vector3d, dir: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_ray(g, allocVec3(origin).ptr, allocVec3(dir).ptr, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawArrowNative(start: Vector3d, end: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_arrow(g, allocVec3(start).ptr, allocVec3(end).ptr, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawPointNative(pos: Vector3d, size: Float, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_point(g, allocVec3(pos).ptr, size, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawCircleNative(center: Vector3d, radius: Float, normal: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_circle(g, allocVec3(center).ptr, radius, allocVec3(normal).ptr, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawSphereNative(center: Vector3d, radius: Float, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_sphere(g, allocVec3(center).ptr, radius, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawGlobeNative(center: Vector3d, radius: Float, latLines: Int, lonLines: Int, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_globe(g, allocVec3(center).ptr, radius, latLines.toUInt(), lonLines.toUInt(), allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawAabbNative(min: Vector3d, max: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_aabb(g, allocVec3(min).ptr, allocVec3(max).ptr, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawObbNative(center: Vector3d, halfExtents: Vector3d, rotation: Quaterniond, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_obb(g, allocVec3(center).ptr, allocVec3(halfExtents).ptr, allocQuat(rotation).ptr, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawCapsuleNative(a: Vector3d, b: Vector3d, radius: Float, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_capsule(g, allocVec3(a).ptr, allocVec3(b).ptr, radius, allocColour(colour).ptr) +} + +internal actual fun DebugDraw.drawConeNative(apex: Vector3d, dir: Vector3d, angle: Float, length: Float, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + dropbear_debug_draw_cone(g, allocVec3(apex).ptr, allocVec3(dir).ptr, angle, length, allocColour(colour).ptr) +} + + +internal actual fun DebugDraw.drawLineNative(start: Vector3d, end: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val s = allocVec3(start); val e = allocVec3(end) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_line(g, s.ptr, e.ptr, c) +} + +internal actual fun DebugDraw.drawRayNative(origin: Vector3d, dir: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val o = allocVec3(origin); val d = allocVec3(dir) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_ray(g, o.ptr, d.ptr, c) +} + +internal actual fun DebugDraw.drawArrowNative(start: Vector3d, end: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val s = allocVec3(start); val e = allocVec3(end) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_arrow(g, s.ptr, e.ptr, c) +} + +internal actual fun DebugDraw.drawPointNative(pos: Vector3d, size: Float, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val p = allocVec3(pos) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_point(g, p.ptr, size, c) +} + +internal actual fun DebugDraw.drawCircleNative(center: Vector3d, radius: Float, normal: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val ct = allocVec3(center); val n = allocVec3(normal) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_circle(g, ct.ptr, radius, n.ptr, c) +} + +internal actual fun DebugDraw.drawSphereNative(center: Vector3d, radius: Float, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val ct = allocVec3(center) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_sphere(g, ct.ptr, radius, c) +} + +internal actual fun DebugDraw.drawGlobeNative(center: Vector3d, radius: Float, latLines: Int, lonLines: Int, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val ct = allocVec3(center) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_globe(g, ct.ptr, radius, latLines.toUInt(), lonLines.toUInt(), c) +} + +internal actual fun DebugDraw.drawAabbNative(min: Vector3d, max: Vector3d, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val mn = allocVec3(min); val mx = allocVec3(max) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_aabb(g, mn.ptr, mx.ptr, c) +} + +internal actual fun DebugDraw.drawObbNative(center: Vector3d, halfExtents: Vector3d, rotation: Quaterniond, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val ct = allocVec3(center); val he = allocVec3(halfExtents) + val rot = allocQuat(rotation) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_obb(g, ct.ptr, he.ptr, rot.ptr, c) +} + +internal actual fun DebugDraw.drawCapsuleNative(a: Vector3d, b: Vector3d, radius: Float, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val va = allocVec3(a); val vb = allocVec3(b) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_capsule(g, va.ptr, vb.ptr, radius, c) +} + +internal actual fun DebugDraw.drawConeNative(apex: Vector3d, dir: Vector3d, angle: Float, length: Float, colour: Colour) = memScoped { + val g = DropbearEngine.native.graphicsContextHandle ?: return@memScoped + val a = allocVec3(apex); val d = allocVec3(dir) + val c = nColour(colour).toCValue(this) + dropbear_debug_draw_cone(g, a.ptr, d.ptr, angle, length, c) +}