summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMicha White <botahamec@outlook.com>2022-09-24 18:20:46 -0400
committerMicha White <botahamec@outlook.com>2022-09-24 18:20:46 -0400
commitc29555bddac1c0027bf6e15d91e219adaa088065 (patch)
tree59e671e4ded20b58027987053e207e4fe6a51307 /src
parent7f364d2642784fcffea730b1f168270ce907a27c (diff)
Implemented a camera
Diffstat (limited to 'src')
-rw-r--r--src/camera.rs95
-rw-r--r--src/config.rs2
-rw-r--r--src/instance.rs4
-rw-r--r--src/lib.rs2
-rw-r--r--src/renderer.rs82
5 files changed, 174 insertions, 11 deletions
diff --git a/src/camera.rs b/src/camera.rs
new file mode 100644
index 0000000..2eb1730
--- /dev/null
+++ b/src/camera.rs
@@ -0,0 +1,95 @@
+use cgmath::{Matrix4, Vector3};
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Camera {
+ position: (f32, f32),
+ zoom: f32,
+ rotation: f32,
+ inverse_aspect_ratio: f32,
+}
+
+pub(crate) type CameraUniform = [[f32; 4]; 4];
+
+#[allow(clippy::cast_precision_loss)]
+fn inverse_aspect_ratio(width: u32, height: u32) -> f32 {
+ (height as f32) / (width as f32)
+}
+
+impl Camera {
+ /// Create a new camera, with a position of (0, 0), and a zoom of 1.0
+ pub(crate) fn from_size(width: u32, height: u32) -> Self {
+ Self {
+ position: (0.0, 0.0),
+ zoom: 1.0,
+ rotation: 0.0,
+ inverse_aspect_ratio: inverse_aspect_ratio(width, height),
+ }
+ }
+
+ /// Get the camera's current x position
+ #[must_use]
+ pub const fn x(&self) -> f32 {
+ self.position.0
+ }
+
+ /// Get the camera's current y position
+ #[must_use]
+ pub const fn y(&self) -> f32 {
+ self.position.1
+ }
+
+ /// Get the camera's current zoom
+ #[must_use]
+ pub const fn zoom(&self) -> f32 {
+ self.zoom
+ }
+
+ /// Set the position of the camera
+ pub fn set_position(&mut self, x: f32, y: f32) {
+ self.position = (x, y);
+ }
+
+ /// Set the aspect ratio of the camera
+ pub fn set_zoom(&mut self, zoom: f32) {
+ self.zoom = zoom;
+ }
+
+ /// Set the aspect ratio of the camera
+ pub(crate) fn set_size(&mut self, width: u32, height: u32) {
+ self.inverse_aspect_ratio = inverse_aspect_ratio(width, height);
+ }
+
+ #[allow(clippy::wrong_self_convention)]
+ pub(crate) fn to_matrix(&mut self) -> [[f32; 4]; 4] {
+ let cos_theta = self.rotation.cos();
+ let sin_theta = self.rotation.sin();
+
+ let x_axis = Vector3::new(cos_theta, -sin_theta, 0.0);
+ let y_axis = Vector3::new(sin_theta, cos_theta, 0.0);
+ let z_axis = Vector3::new(0.0, 0.0, 1.0);
+
+ let eye = Vector3::new(self.position.0, self.position.1, 0.0);
+ let x_dot = -cgmath::dot(x_axis, eye);
+ let y_dot = -cgmath::dot(y_axis, eye);
+ let z_dot = -cgmath::dot(z_axis, eye);
+
+ #[rustfmt::skip]
+ let view_matrix = Matrix4::new(
+ x_axis.x, y_axis.x, z_axis.x, 0.0,
+ x_axis.y, y_axis.y, z_axis.y, 0.0,
+ x_axis.x, y_axis.y, z_axis.z, 0.0,
+ x_dot, y_dot, z_dot, 1.0
+ );
+
+ #[rustfmt::skip]
+ // TODO implement more scaling coordinate systems
+ let projection_matrix = Matrix4::new(
+ self.inverse_aspect_ratio, 0.0, 0.0, 0.0,
+ 0.0, self.zoom, 0.0, 0.0,
+ 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0
+ );
+
+ (projection_matrix * view_matrix).into()
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index 312cf91..7eedb21 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -80,7 +80,7 @@ impl<'a> Default for RenderWindowConfig<'a> {
title: "Alligator Game",
low_power: true,
vsync: true,
- instance_capacity: 0,
+ instance_capacity: 0, // TODO this should probably be bigger
}
}
}
diff --git a/src/instance.rs b/src/instance.rs
index 554d02d..6ea5321 100644
--- a/src/instance.rs
+++ b/src/instance.rs
@@ -15,7 +15,7 @@ pub struct Instance {
/// Rotation, in radians
pub rotation: f32,
/// z-index
- pub z_index: i32,
+ pub z_index: u32,
}
impl Default for Instance {
@@ -32,7 +32,7 @@ impl Default for Instance {
impl Instance {
// whenever this is updated, please also update `sprite.wgsl`
const ATTRIBUTES: [wgpu::VertexAttribute; 4] =
- wgpu::vertex_attr_array![1 => Float32x2, 2 => Float32x2, 3 => Float32, 4 => Sint32];
+ wgpu::vertex_attr_array![1 => Float32x2, 2 => Float32x2, 3 => Float32, 4 => Uint32];
pub(crate) fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
// make sure these two don't conflict
diff --git a/src/lib.rs b/src/lib.rs
index fe58e8b..bacb53d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,11 +4,13 @@
#![warn(clippy::nursery)]
#![allow(clippy::module_name_repetitions)]
+pub mod camera;
pub mod config;
pub mod instance;
pub mod renderer;
mod vertex;
+pub(crate) use camera::Camera;
pub use config::RenderWindowConfig;
pub use instance::Instance;
pub use renderer::Renderer;
diff --git a/src/renderer.rs b/src/renderer.rs
index a86b05b..5820f1c 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -1,6 +1,9 @@
use std::{convert::TryInto, mem::size_of, num::NonZeroU32};
-use crate::{instance::InstanceId, vertex::SQUARE, Instance, RenderWindowConfig, Vertex};
+use crate::{
+ camera::CameraUniform, instance::InstanceId, vertex::SQUARE, Camera, Instance,
+ RenderWindowConfig, Vertex,
+};
use pollster::FutureExt;
use thiserror::Error;
use wgpu::{include_wgsl, util::DeviceExt};
@@ -48,6 +51,9 @@ pub struct Renderer {
instance_buffer: wgpu::Buffer,
instance_buffer_size: usize,
instances: Vec<Instance>,
+ camera: Camera,
+ camera_buffer: wgpu::Buffer,
+ camera_bind_group: wgpu::BindGroup,
window: Window,
}
@@ -78,17 +84,13 @@ impl Renderer {
fn sprite_render_pipeline(
device: &wgpu::Device,
texture_format: wgpu::TextureFormat,
+ render_pipeline_layout: &wgpu::PipelineLayout,
) -> wgpu::RenderPipeline {
let shader = device.create_shader_module(include_wgsl!("../shaders/sprite.wgsl"));
- let render_pipeline_layout =
- device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("Sprite Render Pipeline Layout"),
- ..Default::default()
- });
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Sprite Render Pipeline"),
- layout: Some(&render_pipeline_layout),
+ layout: Some(render_pipeline_layout),
// information about the vertex shader
vertex: wgpu::VertexState {
module: &shader,
@@ -183,8 +185,51 @@ impl Renderer {
);
surface.configure(&device, &surface_config);
+ // create the camera
+ let width = window.inner_size().width;
+ let height = window.inner_size().height;
+ let camera = Camera::from_size(width, height);
+ let camera_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("Camera Uniform"),
+ size: size_of::<CameraUniform>() as wgpu::BufferAddress,
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ let camera_bind_group_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("Camera Bind Group Layout"),
+ entries: &[wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ count: None,
+ }],
+ });
+
+ let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("Camera Bind Group"),
+ layout: &camera_bind_group_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: camera_buffer.as_entire_binding(),
+ }],
+ });
+
+ let render_pipeline_layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("Sprite Render Pipeline Layout"),
+ bind_group_layouts: &[&camera_bind_group_layout],
+ push_constant_ranges: &[],
+ });
+
// set up a pipeline for sprite rendering
- let render_pipeline = Self::sprite_render_pipeline(&device, surface_config.format);
+ let render_pipeline =
+ Self::sprite_render_pipeline(&device, surface_config.format, &render_pipeline_layout);
// the vertex buffer used for rendering squares
let square_vertices = SQUARE
@@ -214,6 +259,9 @@ impl Renderer {
instance_buffer,
instance_buffer_size,
instances,
+ camera,
+ camera_buffer,
+ camera_bind_group,
window,
})
}
@@ -231,6 +279,7 @@ impl Renderer {
self.surface_config.height = size.height;
self.surface_config.width = size.width;
+ self.camera.set_size(size.width, size.height);
self.reconfigure();
}
@@ -278,6 +327,11 @@ impl Renderer {
InstanceId(index)
}
+ /// Get an immutable reference to an instance
+ pub fn instance(&self, id: InstanceId) -> Option<&Instance> {
+ self.instances.get(id.0)
+ }
+
/// Get a mutable reference to an instance
pub fn instance_mut(&mut self, id: InstanceId) -> Option<&mut Instance> {
self.instances.get_mut(id.0)
@@ -288,6 +342,14 @@ impl Renderer {
self.instances.clear();
}
+ fn refresh_camera_buffer(&mut self) {
+ self.queue.write_buffer(
+ &self.camera_buffer,
+ 0 as wgpu::BufferAddress,
+ bytemuck::cast_slice(&self.camera.to_matrix()),
+ );
+ }
+
/// Renders a new frame to the window
///
/// # Errors
@@ -295,6 +357,7 @@ impl Renderer {
/// A number of problems could occur here. A timeout could occur while
/// trying to acquire the next frame. There may also be no more memory left
/// that can be used for the new frame.
+ // TODO this is too big
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
// the new texture we can render to
let output = self.surface.get_current_texture()?;
@@ -316,6 +379,8 @@ impl Renderer {
.try_into()
.expect("expected less than 3 billion instances");
+ self.refresh_camera_buffer();
+
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
@@ -331,6 +396,7 @@ impl Renderer {
});
render_pass.set_pipeline(&self.render_pipeline);
+ render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.square_vertex_buffer.slice(..));
render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..));
render_pass.draw(0..self.square_vertices, 0..num_instances);