use std::error::Error; use crate::solar_system::Kilometers; use crate::timeman::Second; use crate::wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}; use super::*; 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 wgpu::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 } } }