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
// TODO this can still be a little smaller
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::<CameraUniform>() 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) {
debug_assert!(
(-1000.0..1000.0).contains(&x) && (-1000.0..1000.0).contains(&y),
"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) {
debug_assert!(
(-1000.0..1000.0).contains(&zoom),
"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);
}
#[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, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
let transform = projection_matrix * view_matrix;
transform.into()
}
pub(crate) const fn bind_group(&self) -> &wgpu::BindGroup {
&self.bind_group
}
#[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()),
);
}
}
|