use std::error::Error; use super::*; use crate::solar_system::body::{BodyId, OrbitalBody}; use crate::timeman::{SYSTEM_TICK_INTERVAL, Second}; use crate::wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}; 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; } fn create_orbit_buffer_for_body( &self, wgpuctx: &WgpuCtx, body: &OrbitalBody, time: Second) -> (usize, Option) { let orbit = match body.get_orbit() { Some(orbit) => orbit, None => return (0, None) }; let period = body.orbital_period(); let num_points = ((period / (SYSTEM_TICK_INTERVAL)) as usize).clamp(90, 360); 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( time + (i as f64 * period_interval) as Second); points[i*2].origin_id = orbit.parent() as _; points[i*2].position = [ position.x as f32, position.y as f32, position.z as f32 ]; points[(i*2)+1] = points[i*2]; } points[num_points*2] = points[0]; points[num_points*2+1] = points[1]; 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)) } 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); } pub fn update( &mut self, wgpuctx: &WgpuCtx, solar_system: &SolarSystem, time: Second) -> Result<(), Box> { if self.last_time.is_some_and(|t| { t == time }) { return Ok(()); } let tick_time = match self.last_time { Some(last_time) => { let this_tick = time / SYSTEM_TICK_INTERVAL; let last_tick = last_time / SYSTEM_TICK_INTERVAL; if this_tick == last_tick { return Ok(()) } this_tick * SYSTEM_TICK_INTERVAL } None => 0 }; 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 Err(Box::new(NeedsRebuildError)); } }; let bodies = solar_system.bodies(); self.orbit_vertex_buffers = Some(bodies.iter().map(|body| { self.create_orbit_buffer_for_body(wgpuctx, body, tick_time) }).collect::>()); Ok(()) } pub fn render( &self, pass: &mut wgpu::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 } } }