summaryrefslogtreecommitdiff
path: root/src/camera.rs
blob: 2a2a2e6485546a64fb55e4d6d63d4d42de751eeb (plain)
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 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
		);

		let transform = projection_matrix * view_matrix;
		transform.into()
	}
}