138 lines
3.9 KiB
Rust
138 lines
3.9 KiB
Rust
use glam::{Mat4, Vec3, vec3};
|
|
|
|
/// A camera always directed at the origin.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct OrbitalCamera {
|
|
/// Horizontal position (angle), in radians from +X towards +Y.
|
|
pub position_yaw: f32,
|
|
|
|
/// Vertical position (angle), in radians from XY plane towards +Z.
|
|
pub position_pitch: f32,
|
|
|
|
/// Distance from the origin.
|
|
pub distance: f32,
|
|
}
|
|
|
|
impl OrbitalCamera {
|
|
pub fn position(&self) -> Vec3 {
|
|
let (y, x) = self.position_yaw.sin_cos();
|
|
let (z, xy) = self.position_pitch.sin_cos();
|
|
self.distance * vec3(xy * x, xy * y, z)
|
|
}
|
|
|
|
pub fn transform(&self) -> Mat4 {
|
|
// for yaw=0, pitch=0:
|
|
// X -> -Z
|
|
// Y -> -X
|
|
// Z -> Y
|
|
Mat4::from_translation(vec3(0., 0., self.distance))
|
|
* Mat4::from_cols_array_2d(&[
|
|
[0., 0., -1., 0.],
|
|
[-1., 0., 0., 0.],
|
|
[0., 1., 0., 0.],
|
|
[0., 0., 0., 1.],
|
|
]) * Mat4::from_euler(
|
|
glam::EulerRot::ZYZ,
|
|
0.,
|
|
self.position_pitch,
|
|
-self.position_yaw,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::f32::consts::PI;
|
|
|
|
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 {
|
|
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);
|
|
assert_ulps_eq!(camera_pos_deg(0., 90.), vec3(0., 0., 1.), max_ulps = 3);
|
|
assert_ulps_eq!(camera_pos_deg(90., 90.), vec3(0., 0., 1.), max_ulps = 3);
|
|
|
|
let s2 = 0.5f32.sqrt();
|
|
assert_ulps_eq!(camera_pos_deg(45., 0.), vec3(s2, s2, 0.), max_ulps = 3);
|
|
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., -30., -45., -89.] {
|
|
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:?}"
|
|
);
|
|
|
|
let mapped = tfm.transform_vector3(vec3(0., 0., 1.));
|
|
assert!(
|
|
mapped.y > 0.,
|
|
"up is not up: yaw={yaw:?}°, pitch={pitch:?}°, pos={pos:?}, mapped={mapped:?}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_isometric_camera() {
|
|
let camera = OrbitalCamera {
|
|
position_yaw: PI / 4.,
|
|
position_pitch: 0.5f32.sqrt().atan(),
|
|
distance: 1.0,
|
|
};
|
|
|
|
let s13 = (1.0f32 / 3.).sqrt();
|
|
assert_ulps_eq!(camera.position(), Vec3::splat(s13), max_ulps = 3);
|
|
|
|
let tfm = camera.transform();
|
|
let o = tfm.transform_point3(vec3(0., 0., 0.));
|
|
let x = tfm.transform_point3(vec3(1., 0., 0.));
|
|
let y = tfm.transform_point3(vec3(0., 1., 0.));
|
|
let z = tfm.transform_point3(vec3(0., 0., 1.));
|
|
let s16 = (1.0f32 / 6.).sqrt();
|
|
let s12 = (1.0f32 / 2.).sqrt();
|
|
let s23 = (2.0f32 / 3.).sqrt();
|
|
let d = 1.0 - s13;
|
|
assert_ulps_eq!(o, vec3(0., 0., 1.), max_ulps = 3);
|
|
assert_ulps_eq!(z, vec3(0., s23, d), max_ulps = 3);
|
|
assert_ulps_eq!(x, vec3(s12, -s16, d), max_ulps = 3);
|
|
assert_ulps_eq!(y, vec3(-s12, -s16, d), max_ulps = 3);
|
|
}
|
|
}
|