use cgmath::{Matrix4, Vector2};
#[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) {
debug_assert!(
x <= 1000.0 && y <= 1000.0,
"The x-position of the camera is ({}, {}), which is too large. \
Please keep both the x and y positions 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) {
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) -> 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
);
self.rotation += 0.01;
let transform = projection_matrix * view_matrix;
transform.into()
}
}
|