use std::{fmt::Display}; use std::error::Error; use wgpu::RenderPass; use crate::solar_system::Kilometers; use crate::tacmap::camera::Camera; use crate::timeman::{DAY, SYSTEM_TICK_INTERVAL}; use crate::{solar_system::{SolarSystem, SystemId}, timeman::Second, vertex::{self, Vertex}, wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}}; #[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, pipeline: wgpu::RenderPipeline, body_instance_buffer: Option<(usize, wgpu::Buffer)> } impl BodyRenderer { pub fn new( wgpuctx: &WgpuCtx) -> Self { let shader = wgpuctx.create_shader( wgpu::include_wgsl!(concat!( env!("CARGO_MANIFEST_DIR"), "/assets/shaders/tacmap/body.wgsl") )); let render_pipeline = RenderPipelineBuilder::new(&shader) .add_bindgroup(&Camera::bindgroup_layout(wgpuctx)) .add_vertex_layout(BodyInstanceRaw::descr()) .cull_mode(None) .build(Some("Tactical map render pipeline"), wgpuctx); Self { needs_rebuild: true, last_time: None, pipeline: render_pipeline, 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; } self.needs_rebuild = false; 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.body_instance_buffer = Some((bodies.len(), buffer)); } pub fn update( &mut self, wgpuctx: &WgpuCtx, solar_system: &SolarSystem, 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 }) { return Ok(()); } self.last_time = Some(time); 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 = body.position(); let origin = match body.get_orbits() { Some(origin_id) => { let origin_body = &bodies[origin_id]; origin_body.position() }, None => cgmath::Vector3::new(0.0, 0.0, 0.0) }; BodyInstance { position: position, origin: origin, radius: body.radius() }.raw() }).collect::>(); wgpuctx.queue().write_buffer(bodies_buffer, 0, bytemuck::cast_slice(&body_instances)); Ok(()) } pub fn render( &self, pass: &mut RenderPass, camera: &Camera) { let (num_bodies, bodies_buffer) = match &self.body_instance_buffer { Some(tuple) => tuple, None => return }; pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, camera.bindgroup(), &[]); pass.set_vertex_buffer(0, bodies_buffer.slice(..)); pass.draw(0..6, 0..num_bodies.clone() as _); } } // impl RenderState struct BodyInstance { position: cgmath::Vector3, origin: cgmath::Vector3, radius: f32 } #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct BodyInstanceRaw { position: [f32;3], origin: [f32;3], radius: f32 } impl BodyInstance { fn raw(&self) -> BodyInstanceRaw { BodyInstanceRaw { position: [ self.position.x as f32, self.position.y as f32, self.position.z as f32 ], origin: [ self.origin.x as f32, self.origin.y as f32, self.origin.z as f32 ], radius: self.radius } } } // impl BodyInstance impl BodyInstanceRaw { const ATTRIBS: [wgpu::VertexAttribute;3] = wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32]; pub fn descr() -> wgpu::VertexBufferLayout<'static> { use std::mem; wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Instance, attributes: &Self::ATTRIBS } } } pub struct GridRenderer { pipeline: wgpu::RenderPipeline, } impl GridRenderer { pub fn new(wgpuctx: &WgpuCtx) -> Self { let shader = wgpuctx.create_shader( wgpu::include_wgsl!(concat!( env!("CARGO_MANIFEST_DIR"), "/assets/shaders/tacmap/grid.wgsl") )); let render_pipeline = RenderPipelineBuilder::new(&shader) .add_bindgroup(&Camera::bindgroup_layout(wgpuctx)) .cull_mode(None) .build(Some("Tactical map grid pipeline"), wgpuctx); Self { pipeline: render_pipeline } } pub fn render( &self, pass: &mut RenderPass, camera: &Camera ) { pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, camera.bindgroup(), &[]); pass.draw(0..6, 0..1); } } //impl GridRenderer pub struct OrbitRenderer { needs_rebuild: bool, last_time: Option, pipeline: wgpu::RenderPipeline, orbit_vertex_buffers: Option)>>, origin_buffer: Option, origin_bind_group: Option, origin_bind_group_layout: wgpu::BindGroupLayout } impl OrbitRenderer { pub fn new( wgpuctx: &WgpuCtx) -> Self { let shader = wgpuctx.create_shader( wgpu::include_wgsl!(concat!( env!("CARGO_MANIFEST_DIR"), "/assets/shaders/tacmap/orbit.wgsl") )); let origin_bind_group_layout = wgpuctx.device().create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None }, count: None } ] } ); let render_pipeline = RenderPipelineBuilder::new(&shader) .add_bindgroup(&Camera::bindgroup_layout(wgpuctx)) .add_bindgroup(&origin_bind_group_layout) .add_vertex_layout(OrbitVertex::descr()) .primitive_topology(wgpu::PrimitiveTopology::TriangleStrip) .cull_mode(None) .build(Some("Tactical map orbit render pipeline"), wgpuctx); Self { needs_rebuild: true, last_time: None, pipeline: render_pipeline, orbit_vertex_buffers: None, origin_buffer: None, origin_bind_group: None, origin_bind_group_layout: origin_bind_group_layout } } pub fn mark_to_rebuild(&mut self) { self.needs_rebuild = true; } pub fn rebuild( &mut self, wgpuctx: &WgpuCtx, solar_system: &SolarSystem) { if self.orbit_vertex_buffers.is_some() && !self.needs_rebuild { return; } self.needs_rebuild = false; match &self.orbit_vertex_buffers { Some(buffers) => { for buffer in buffers { match &buffer.1 { Some(v) => v.destroy(), None => {} } } self.orbit_vertex_buffers = None; }, None => {} }; //From the solar system's bodies, calculate the orbits. let bodies = solar_system.bodies(); match &self.origin_buffer { Some(buffer) => { buffer.destroy(); } None => {} }; let origin_buffer = wgpuctx.create_buffer( &wgpu::BufferDescriptor { label: None, size: (std::mem::size_of::<[f32;3]>() * bodies.len()) as _, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::STORAGE, mapped_at_creation: false } ); self.origin_bind_group = Some(wgpuctx.device().create_bind_group( &wgpu::BindGroupDescriptor { label: None, layout: &self.origin_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: origin_buffer.as_entire_binding() } ] } )); self.origin_buffer = Some(origin_buffer); self.orbit_vertex_buffers = Some(bodies.iter().map(|body| { if !body.does_orbit() { return (0, None); } let period = body.orbital_period(); let num_points = ((period / (SYSTEM_TICK_INTERVAL)) as usize).clamp(180, 360*4); let period_interval = period as f64 / num_points as f64; let mut points = vec![ OrbitVertex::default();(num_points+1)*2]; for i in 0..num_points { let position = body.calculate_orbit_at( (i as f64 * period_interval) as u64); points[i*2].origin_id = body.get_orbits().unwrap() as _; points[i*2].position = [ position.x as f32, position.y as f32, position.z as f32 ]; points[(i*2)+1] = points[i*2].clone(); } points[num_points*2] = points[0].clone(); points[num_points*2+1] = points[1].clone(); let buffer = wgpuctx.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: None, usage: wgpu::BufferUsages::VERTEX, contents: bytemuck::cast_slice(&points) } ); ((num_points+1)*2, Some(buffer)) }).collect::>()); } pub fn update( &mut self, wgpuctx: &WgpuCtx, solar_system: &SolarSystem, time: Second) { if self.last_time.is_some_and(|t| { t == time }) { return; } self.last_time = Some(time); let positions = solar_system.bodies().iter().map(|body| { let pos = solar_system.body_position(body); [ pos.x as f32, pos.y as f32, pos.z as f32 ] }).collect::>(); match &self.origin_buffer { Some(buffer) => { wgpuctx.queue().write_buffer(buffer, 0, bytemuck::cast_slice(&positions)); }, None => { return; } }; } pub fn render( &self, pass: &mut RenderPass, camera: &Camera) { let buffers = match &self.orbit_vertex_buffers { Some(v) => v, None => return }; pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, camera.bindgroup(), &[]); match &self.origin_bind_group { Some(bind_group) => { pass.set_bind_group(1, bind_group, &[]); }, None => { return; } } for (num_points, buffer) in buffers { match &buffer { Some(buffer) => { pass.set_vertex_buffer(0, buffer.slice(..)); pass.draw(0..(*num_points as u32), 0..1); }, None => {} } } } } #[repr(C)] #[derive(Copy, Clone, Default, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct OrbitVertex { pub origin_id: u32, pub position: [f32;3] } impl OrbitVertex { const ATTRIBS: [wgpu::VertexAttribute;2] = wgpu::vertex_attr_array![0 => Uint32, 1 => Float32x3]; pub fn descr() -> wgpu::VertexBufferLayout<'static> { use std::mem; wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &Self::ATTRIBS } } }