diff --git a/src/camera.rs b/src/camera.rs index 209cb5b..6540447 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,4 +1,6 @@ -use glam::{Vec3, vec3}; +use std::f32::consts::PI; + +use glam::{Mat4, Vec3, vec3}; /// A camera always directed at the origin. #[derive(Debug, Clone, Copy)] @@ -19,24 +21,38 @@ impl OrbitalCamera { let (z, xy) = self.position_pitch.sin_cos(); self.distance * vec3(xy * x, xy * y, z) } + + pub fn transform(&self) -> Mat4 { + Mat4::from_translation(vec3(0., 0., self.distance)) + * Mat4::from_euler(glam::EulerRot::ZXZ, 0., PI / 2., -PI / 2.) + * Mat4::from_euler( + glam::EulerRot::ZYZ, + 0., + self.position_pitch, + -self.position_yaw, + ) + } } #[cfg(test)] mod tests { - use approx::assert_ulps_eq; + use approx::{abs_diff_eq, assert_ulps_eq}; use glam::vec3; use super::*; + fn camera_deg(yaw: f32, pitch: f32) -> OrbitalCamera { + OrbitalCamera { + position_yaw: yaw.to_radians(), + position_pitch: pitch.to_radians(), + distance: 1.0, + } + } + #[test] fn test_orbital_camera_position() { fn camera_pos_deg(yaw: f32, pitch: f32) -> Vec3 { - OrbitalCamera { - position_yaw: yaw.to_radians(), - position_pitch: pitch.to_radians(), - distance: 1.0, - } - .position() + camera_deg(yaw, pitch).position() } assert_ulps_eq!(camera_pos_deg(0., 0.), vec3(1., 0., 0.), max_ulps = 3); assert_ulps_eq!(camera_pos_deg(90., 0.), vec3(0., 1., 0.), max_ulps = 3); @@ -48,4 +64,34 @@ mod tests { assert_ulps_eq!(camera_pos_deg(0., 45.), vec3(s2, 0., s2), max_ulps = 3); assert_ulps_eq!(camera_pos_deg(45., 45.), vec3(0.5, 0.5, s2), max_ulps = 3); } + + #[test] + fn test_orbital_camera_forward() { + const EPSILON: f32 = 1e-5; + for pitch in [0., 30., 45., 89., 90.] { + for yaw in [0., 30., 45., 89., 90.] { + let camera = camera_deg(yaw, pitch); + let pos = camera.position(); + let tfm = camera.transform(); + + let mapped = tfm.transform_vector3(-pos); + assert!( + abs_diff_eq!(mapped, vec3(0., 0., 1.), epsilon = EPSILON), + "direction not mapped to +Z: yaw={yaw:?}°, pitch={pitch:?}°, pos={pos:?}, mapped={mapped:?}" + ); + + let mapped = tfm.transform_point3(pos); + assert!( + abs_diff_eq!(mapped, Vec3::ZERO, epsilon = EPSILON), + "pos not mapped to origin: yaw={yaw:?}°, pitch={pitch:?}°, pos={pos:?}, mapped={mapped:?}" + ); + + let mapped = tfm.transform_point3(Vec3::ZERO); + assert!( + abs_diff_eq!(mapped, vec3(0., 0., 1.), epsilon = EPSILON), + "origin not mapped to (0, 0, 1): yaw={yaw:?}°, pitch={pitch:?}°, pos={pos:?}, mapped={mapped:?}" + ); + } + } + } }