From 93347346e8bd8f7412ae03a0858dd307a1df2e0d Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 20 Oct 2022 20:39:44 -0400 Subject: Moved files into workspace --- alligator_render/src/camera.rs | 187 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 alligator_render/src/camera.rs (limited to 'alligator_render/src/camera.rs') diff --git a/alligator_render/src/camera.rs b/alligator_render/src/camera.rs new file mode 100644 index 0000000..ecece90 --- /dev/null +++ b/alligator_render/src/camera.rs @@ -0,0 +1,187 @@ +use std::mem::size_of; + +use cgmath::{Matrix4, Vector2}; + +#[derive(Debug)] +pub struct Camera { + position: (f32, f32), + zoom: f32, + rotation: f32, + inverse_aspect_ratio: f32, + buffer: wgpu::Buffer, + bind_group: wgpu::BindGroup, +} + +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 new( + device: &wgpu::Device, + width: u32, + height: u32, + ) -> (Self, wgpu::BindGroupLayout) { + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Camera Uniform"), + size: size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let 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 bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Camera Bind Group"), + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + ( + Self { + position: (0.0, 0.0), + zoom: 1.0, + rotation: 0.0, + inverse_aspect_ratio: inverse_aspect_ratio(width, height), + buffer, + bind_group, + }, + bind_group_layout, + ) + } + + /// 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 + } + + /// Get the camera's current rotation, in radians + #[must_use] + pub const fn rotation(&self) -> f32 { + self.rotation + } + + /// Set the position of the camera + pub fn set_position(&mut self, x: f32, y: f32) { + #[cfg(debug_assertions)] + if !(-1000.0..1000.0).contains(&x) || !(-1000.0..1000.0).contains(&y) { + log::warn!( + "The position of the camera is (x: {}, y: {}). \ + Please keep both the x and y positions above -1000 and below 1000 units. \ + Otherwise, everything will look crazy. \ + For an explanation, see https://www.youtube.com/watch?v=Q2OGwnRik24", + x, + y + ); + } + + self.position = (x, y); + } + + /// Set the zoom of the camera + pub fn set_zoom(&mut self, zoom: f32) { + #[cfg(debug_assertions)] + if !(-1000.0..1000.0).contains(&zoom) { + log::warn!( + "The zoom of the camera is {}. \ + Please keep above -1000, and below 1000, or else smooth zoom may be difficult. \ + For an explanation, see https://www.youtube.com/watch?v=Q2OGwnRik24", + zoom + ); + } + + self.zoom = zoom; + } + + /// Set the camera rotation, in radians + pub fn set_rotation(&mut self, rotation: f32) { + self.rotation = rotation % std::f32::consts::TAU; + } + + /// 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); + } + + /// Create a matrix that can be multiplied by any vector to transform it + /// according to the current camera + #[allow(clippy::wrong_self_convention)] + fn to_matrix(&self) -> CameraUniform { + let cos = self.rotation.cos(); + let sin = self.rotation.sin(); + + let x_axis = Vector2::new(cos, -sin); + let y_axis = Vector2::new(sin, cos); + + let eye = Vector2::new(self.position.0, self.position.1); + let x_dot = -cgmath::dot(x_axis, eye); + let y_dot = -cgmath::dot(y_axis, eye); + + #[rustfmt::skip] + let view_matrix = Matrix4::new( + x_axis.x, y_axis.x, 0.0, 0.0, + x_axis.y, y_axis.y, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + x_dot, y_dot, 0.0, 1.0 + ); + + #[rustfmt::skip] + // TODO implement more scaling coordinate systems + let projection_matrix = Matrix4::new( + self.inverse_aspect_ratio * self.zoom, 0.0, 0.0, 0.0, + 0.0, self.zoom, 0.0, 0.0, + 0.0, 0.0, 1.0 / 256.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + + let transform = projection_matrix * view_matrix; + transform.into() + } + + /// Get the bind group for the camera + pub(crate) const fn bind_group(&self) -> &wgpu::BindGroup { + &self.bind_group + } + + /// Refresh the camera buffer for the next frame + #[profiling::function] + pub(crate) fn refresh(&self, queue: &wgpu::Queue) { + queue.write_buffer( + &self.buffer, + 0 as wgpu::BufferAddress, + bytemuck::cast_slice(&self.to_matrix()), + ); + } +} -- cgit v1.2.3