use std::{fmt::Display, num::NonZero}; use std::error::Error; use crate::canvas::Canvas; use crate::solar_system::Kilometers; use crate::tacmap::camera::Camera; use crate::wgpuctx::RenderPassBuilder; use crate::{solar_system::{SolarSystem, SystemId}, timeman::Second, vertex::{self, Vertex}, wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}}; struct BodyInstance { position: cgmath::Vector3, radius: f32 } #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct BodyInstanceRaw { position: [f32;3], radius: f32 } #[derive(Debug, Clone)] pub struct NeedsRebuildError; impl Display for NeedsRebuildError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "RenderState needs to be rebuilt before updating!") } } impl Error for NeedsRebuildError {} pub struct BodyRenderer { needs_rebuild: bool, last_time: Option, last_pos: Option>, pipeline: wgpu::RenderPipeline, body_vertex_buffer: wgpu::Buffer, body_instance_buffer: Option<(usize, wgpu::Buffer)> } impl BodyRenderer { pub fn new( wgpuctx: &WgpuCtx) -> Self { let quad_vertices = vertex::QUAD_VERTICES; let body_vertex_buffer = wgpuctx.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: None, contents: bytemuck::cast_slice(quad_vertices), usage: wgpu::BufferUsages::VERTEX } ); let shader = wgpuctx.create_shader( wgpu::include_wgsl!(concat!( env!("CARGO_MANIFEST_DIR"), "/assets/shaders/tacbody.wgsl") )); let render_pipeline = RenderPipelineBuilder::new(&shader) .add_bindgroup(&Camera::bindgroup_layout(wgpuctx)) .add_vertex_layout(Vertex::descr()) .add_vertex_layout(BodyInstanceRaw::descr()) .build(Some("Tactical map render pipeline"), wgpuctx); Self { needs_rebuild: true, last_time: None, last_pos: None, pipeline: render_pipeline, body_vertex_buffer, body_instance_buffer: None } } pub fn mark_to_rebuild(&mut self) { self.needs_rebuild = true; } pub fn rebuild( &mut self, wgpuctx: &WgpuCtx, solar_system: &SolarSystem) { if self.body_instance_buffer.is_some() && !self.needs_rebuild { return; } match self.body_instance_buffer.as_mut() { Some(buffer) => { buffer.1.destroy(); self.body_instance_buffer = None; } None => {} } let bodies = solar_system.bodies(); let buffer_len = bodies.len() * size_of::(); let buffer = wgpuctx.create_buffer( &wgpu::BufferDescriptor { label: Some("Tactical map bodies instance buffer"), size: buffer_len as u64, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX, mapped_at_creation: false } ); self.last_time = None; self.last_pos = None; self.body_instance_buffer = Some((bodies.len(), buffer)); } pub fn update( &mut self, wgpuctx: &WgpuCtx, solar_system: &SolarSystem, camera: &Camera, time: Second) -> Result<(), Box> { //If the last updated time is the same, we don't need to update //the positions of all the bodies. if self.last_time.is_some_and(|last_time| { last_time == time }) { if self.last_pos.is_some_and(|last_pos| { last_pos == camera.get_abs_position() }) { return Ok(()) } } self.last_time = Some(time); self.last_pos = Some(camera.get_abs_position()); let (_, bodies_buffer) = match &self.body_instance_buffer { Some(tuple) => tuple, None => return Err(Box::new(NeedsRebuildError)) }; let bodies = solar_system.bodies(); let body_instances = bodies.iter().map(|body| { let position = solar_system.body_position(body); BodyInstance { position: (position - self.last_pos.unwrap()), radius: body.radius() }.raw() }).collect::>(); wgpuctx.queue().write_buffer(bodies_buffer, 0, bytemuck::cast_slice(&body_instances)); //Update the canvas texture. Ok(()) } pub fn render( &self, wgpuctx: &WgpuCtx, canvas: &Canvas, camera: &Camera) { let (num_bodies, bodies_buffer) = match &self.body_instance_buffer { Some(tuple) => tuple, None => return }; let mut encoder = wgpuctx.create_default_encoder("Tactical map renderer encoder"); let view = canvas.view(); { camera.stage_changes(&mut encoder); } { let mut pass = RenderPassBuilder::new("Tactiacal map render pass", view) .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.1, a: 1.0 }) .build(&mut encoder); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, camera.bindgroup(), &[]); pass.set_vertex_buffer(0, self.body_vertex_buffer.slice(..)); pass.set_vertex_buffer(1, bodies_buffer.slice(..)); pass.draw(0..6, 0..num_bodies.clone() as _); } wgpuctx.submit_encoder(encoder); } } // impl RenderState impl BodyInstance { fn raw(&self) -> BodyInstanceRaw { BodyInstanceRaw { position: [ self.position.x as f32, self.position.y as f32, self.position.z as f32 ], radius: self.radius } } } // impl BodyInstance impl BodyInstanceRaw { fn descr() -> wgpu::VertexBufferLayout<'static> { use std::mem; wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Instance, attributes: &[ wgpu::VertexAttribute { offset: 0, shader_location: 2, format: wgpu::VertexFormat::Float32x3 }, wgpu::VertexAttribute { offset: mem::size_of::<[f32;3]>() as wgpu::BufferAddress, shader_location: 3, format: wgpu::VertexFormat::Float32 } ] } } }