summaryrefslogtreecommitdiffstats
path: root/src/tacmap/render.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tacmap/render.rs')
-rw-r--r--src/tacmap/render.rs233
1 files changed, 233 insertions, 0 deletions
diff --git a/src/tacmap/render.rs b/src/tacmap/render.rs
new file mode 100644
index 0000000..c22878e
--- /dev/null
+++ b/src/tacmap/render.rs
@@ -0,0 +1,233 @@
+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::Point3<Kilometers>,
+ 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<Second>,
+
+ 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,
+ 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::<BodyInstanceRaw>();
+
+ 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<dyn Error>>
+ {
+ //If the last updated time is the same, we don't need to update
+ //the positions of all the bodies.
+ match self.last_time {
+ Some(last_time) => {
+ if last_time == time {
+ return Ok(());
+ }
+ }
+ None => {}
+ }
+
+ 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 = match body.position(time) {
+ Some(pos) => pos,
+ None => return BodyInstanceRaw {
+ position: [0.0, 0.0, 0.0],
+ radius: body.radius() }
+ };
+ BodyInstance {
+ position: position,
+ radius: body.radius()
+ }.raw()
+ }).collect::<Vec<_>>();
+
+ 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::<BodyInstanceRaw>() 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
+ }
+ ]
+ }
+ }
+}