aboutsummaryrefslogtreecommitdiff
path: root/stockton-render/src/draw/camera.rs
diff options
context:
space:
mode:
Diffstat (limited to 'stockton-render/src/draw/camera.rs')
-rw-r--r--stockton-render/src/draw/camera.rs237
1 files changed, 37 insertions, 200 deletions
diff --git a/stockton-render/src/draw/camera.rs b/stockton-render/src/draw/camera.rs
index 11bf617..aa5efac 100644
--- a/stockton-render/src/draw/camera.rs
+++ b/stockton-render/src/draw/camera.rs
@@ -17,19 +17,14 @@
//! Things related to converting 3D world space to 2D screen space
-use stockton_types::{Matrix4, Vector3};
+use legion::maybe_changed;
-use na::{look_at_lh, perspective_lh_zo, Mat4, Vec4};
-use std::f32::consts::PI;
+use nalgebra_glm::look_at_lh;
+use nalgebra_glm::perspective_lh_zo;
-/// 90 degrees in radians
-const R89: f32 = (PI / 180.0) * 89.0;
-
-/// 90 degrees in radians
-const R90: f32 = PI / 2.0;
-
-/// 180 degrees in radians
-const R180: f32 = PI;
+use crate::Renderer;
+use stockton_types::components::{CameraSettings, Transform};
+use stockton_types::Vector3;
fn euler_to_direction(euler: &Vector3) -> Vector3 {
let pitch = euler.x;
@@ -43,193 +38,35 @@ fn euler_to_direction(euler: &Vector3) -> Vector3 {
)
}
-pub struct CameraSettings {
- /// Position of the camera (world units)
- pub position: Vector3,
-
- /// Rotation of the camera (euler angles in radians)
- pub rotation: Vector3,
-
- /// The up direction (normalized)
- pub up: Vector3,
-
- /// FOV (radians)
- pub fov: f32,
-
- /// Near clipping plane (world units)
- pub near: f32,
-
- /// Far clipping plane (world units)
- pub far: f32,
-}
-
-/// Holds settings related to the projection of world space to screen space
-/// Also holds maths for generating important matrices
-pub struct WorkingCamera {
- /// Settings for the camera
- settings: CameraSettings,
-
- /// Aspect ratio as a fraction
- aspect_ratio: f32,
-
- /// Cached view projection matrix
- vp_matrix: Mat4,
-
- /// If true, cached value needs updated
- is_dirty: bool,
-}
-
-impl WorkingCamera {
- /// Return a camera with default settings
- pub fn defaults(aspect_ratio: f32) -> WorkingCamera {
- WorkingCamera::with_settings(
- CameraSettings {
- position: Vector3::new(0.0, 0.0, 0.0),
- rotation: Vector3::new(0.0, R90, 0.0),
- up: Vector3::new(0.0, 1.0, 0.0),
- fov: f32::to_radians(90.0),
- near: 0.1,
- far: 1024.0,
- },
- aspect_ratio,
- )
- }
-
- /// Return a camera with the given settings
- pub fn with_settings(settings: CameraSettings, aspect_ratio: f32) -> WorkingCamera {
- WorkingCamera {
- aspect_ratio,
- settings,
- vp_matrix: Mat4::identity(),
- is_dirty: true,
- }
- }
-
- /// Get the VP matrix, updating cache if needed
- pub fn get_matrix(&mut self) -> &Mat4 {
- // Update matrix if needed
- if self.is_dirty {
- self.vp_matrix = self.calc_vp_matrix();
- self.is_dirty = false;
- }
-
- // Return the matrix
- &self.vp_matrix
- }
-
- /// Returns a matrix that transforms from world space to screen space
- fn calc_vp_matrix(&self) -> Matrix4 {
- // Get look direction from euler angles
- let direction = euler_to_direction(&self.settings.rotation);
-
- // Converts world space to camera space
- let view_matrix = look_at_lh(
- &self.settings.position,
- &(self.settings.position + direction),
- &self.settings.up,
- );
-
- // Converts camera space to screen space
- let projection_matrix = {
- let mut temp = perspective_lh_zo(
- self.aspect_ratio,
- self.settings.fov,
- self.settings.near,
- self.settings.far,
- );
-
- // Vulkan's co-ord system is different from OpenGLs
- temp[(1, 1)] *= -1.0;
-
- temp
- };
-
- // Chain them together into a single matrix
- projection_matrix * view_matrix
- }
-
- /// Update the aspect ratio
- pub fn update_aspect_ratio(&mut self, new: f32) {
- self.aspect_ratio = new;
- self.is_dirty = true;
- }
-
- /// Apply rotation of the camera
- /// `euler` should be euler angles in degrees
- pub fn rotate(&mut self, euler: Vector3) {
- // TODO
- self.settings.rotation += euler;
-
- // Clamp -pi/2 < pitch < pi/2
- if self.settings.rotation.x > R89 {
- self.settings.rotation.x = R89;
- } else if self.settings.rotation.x <= -R89 {
- self.settings.rotation.x = -R89;
- }
-
- // -pi < yaw <= pi
- if self.settings.rotation.y <= -R180 {
- self.settings.rotation.y = R180 - self.settings.rotation.y % -R180;
- } else if self.settings.rotation.y > 180.0 {
- self.settings.rotation.y = -R180 + self.settings.rotation.y % R180;
- }
-
- self.is_dirty = true;
- }
-
- /// Move the camera by `delta`, relative to the camera's rotation
- pub fn move_camera_relative(&mut self, delta: Vector3) {
- let rot_matrix = Mat4::from_euler_angles(
- -self.settings.rotation.x,
- self.settings.rotation.y,
- self.settings.rotation.z,
- );
-
- let new = rot_matrix * Vec4::new(delta.x, delta.y, delta.z, 1.0);
- self.settings.position.x += new.x;
- self.settings.position.y += new.y;
- self.settings.position.z += new.z;
-
- self.is_dirty = true;
- }
-
- pub fn camera_pos(&self) -> Vector3 {
- self.settings.position
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::draw::camera::WorkingCamera;
- use stockton_types::Matrix4;
- use stockton_types::Vector3;
-
- fn contains_nan(mat: &Matrix4) -> bool {
- for x in mat.iter() {
- if (*x).is_nan() {
- return true;
- }
- }
- false
- }
-
- #[test]
- fn camera_vp() {
- let mut camera = WorkingCamera::defaults(16.0 / 9.0);
-
- let old = camera.calc_vp_matrix();
- println!("initial vp matrix: {:?}", old);
-
- assert!(!contains_nan(&old), "No NaNs for initial matrix");
-
- // Do a 180
- camera.rotate(Vector3::new(0.0, 180.0, 0.0));
-
- let new = camera.calc_vp_matrix();
- assert!(!contains_nan(&new), "No NaNs after rotating");
-
- println!("new vp matrix: {:?}", new);
-
- assert!(old != new, "VP Matrix changes when camera rotates");
- }
+#[system(for_each)]
+#[filter(maybe_changed::<Transform>() | maybe_changed::<CameraSettings>())]
+pub fn calc_vp_matrix(
+ transform: &Transform,
+ settings: &CameraSettings,
+ #[resource] renderer: &mut Renderer<'static>,
+) {
+ let ratio = renderer.context.target_chain.properties.extent.width as f32
+ / renderer.context.target_chain.properties.extent.height as f32;
+ // Get look direction from euler angles
+ let direction = euler_to_direction(&transform.rotation);
+
+ // Converts world space to camera space
+ let view_matrix = look_at_lh(
+ &transform.position,
+ &(transform.position + direction),
+ &Vector3::new(0.0, 1.0, 0.0), // TODO
+ );
+
+ // Converts camera space to screen space
+ let projection_matrix = {
+ let mut temp = perspective_lh_zo(ratio, settings.fov, settings.near, settings.far);
+
+ // Vulkan's co-ord system is different from OpenGLs
+ temp[(1, 1)] *= -1.0;
+
+ temp
+ };
+
+ // Chain them together into a single matrix
+ renderer.context.vp_matrix = projection_matrix * view_matrix
}