From 961f8c6d405c9c6fcf9aaf4fb6f199b0e5c60d88 Mon Sep 17 00:00:00 2001 From: Jon Santmyer Date: Thu, 30 Apr 2026 10:06:32 -0400 Subject: add orbit rendering for bodies --- src/tacmap/render.rs | 268 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 238 insertions(+), 30 deletions(-) (limited to 'src/tacmap/render.rs') diff --git a/src/tacmap/render.rs b/src/tacmap/render.rs index c1256ff..c2bd354 100644 --- a/src/tacmap/render.rs +++ b/src/tacmap/render.rs @@ -38,11 +38,9 @@ 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)> } @@ -51,34 +49,22 @@ 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") + "/assets/shaders/tacmap/body.wgsl") )); let render_pipeline = RenderPipelineBuilder::new(&shader) .add_bindgroup(&Camera::bindgroup_layout(wgpuctx)) - .add_vertex_layout(Vertex::descr()) .add_vertex_layout(BodyInstanceRaw::descr()) + .cull_mode(None) .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 } } @@ -94,6 +80,7 @@ impl BodyRenderer if self.body_instance_buffer.is_some() && !self.needs_rebuild { return; } + self.needs_rebuild = false; match self.body_instance_buffer.as_mut() { Some(buffer) => { @@ -117,7 +104,6 @@ impl BodyRenderer ); self.last_time = None; - self.last_pos = None; self.body_instance_buffer = Some((bodies.len(), buffer)); } @@ -125,20 +111,16 @@ impl BodyRenderer &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(()) - } + 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, @@ -149,20 +131,18 @@ impl BodyRenderer let body_instances = bodies.iter().map(|body| { let position = solar_system.body_position(body); BodyInstance { - position: (position - self.last_pos.unwrap()), + position: position, 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, pass: &mut RenderPass, camera: &Camera) { @@ -174,8 +154,7 @@ impl BodyRenderer 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.set_vertex_buffer(0, bodies_buffer.slice(..)); pass.draw(0..6, 0..num_bodies.clone() as _); } @@ -205,12 +184,12 @@ impl BodyInstanceRaw attributes: &[ wgpu::VertexAttribute { offset: 0, - shader_location: 2, + shader_location: 0, format: wgpu::VertexFormat::Float32x3 }, wgpu::VertexAttribute { offset: mem::size_of::<[f32;3]>() as wgpu::BufferAddress, - shader_location: 3, + shader_location: 1, format: wgpu::VertexFormat::Float32 } ] @@ -246,7 +225,6 @@ impl GridRenderer pub fn render( &self, - wgpuctx: &WgpuCtx, pass: &mut RenderPass, camera: &Camera ) @@ -256,4 +234,234 @@ impl GridRenderer 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 period_interval = period as f64 / 360.0; + + let mut points = vec![ + OrbitVertex::default();361*2]; + for i in 0..361 { + 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(); + } + let buffer = wgpuctx.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: None, + usage: wgpu::BufferUsages::VERTEX, + contents: bytemuck::cast_slice(&points) + } + ); + (361*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 + } + } } -- cgit v1.2.3