1#[repr(C)]
2#[derive(Debug, Clone, Copy)]
3pub(crate) struct CameraUniform {
4 view_proj: [[f32; 4]; 4],
5}
6
7impl CameraUniform {
8 pub(crate) fn new() -> Self {
9 Self {
10 view_proj: glam::Mat4::IDENTITY.to_cols_array_2d(),
11 }
12 }
13
14 pub(crate) fn update(&mut self, camera: &Camera) {
15 self.view_proj = camera.build_view_projection_matrix().to_cols_array_2d();
16 }
17}
18
19#[derive(Clone, Copy)]
20pub struct Camera {
21 pub position: glam::Vec3,
22 pub yaw: f32,
24 pub pitch: f32,
27 pub aspect: f32,
28 pub fov: f32,
30 pub znear: f32,
31 pub zfar: f32,
32}
33
34impl Camera {
35 pub fn forward(&self) -> glam::Vec3 {
36 glam::Vec3::new(
37 self.yaw.cos() * self.pitch.cos(),
38 self.pitch.sin(),
39 self.yaw.sin() * self.pitch.cos(),
40 )
41 }
42
43 pub fn yaw_pitch_from_forward(forward: glam::Vec3) -> (f32, f32) {
47 (forward.z.atan2(forward.x), forward.y.clamp(-1.0, 1.0).asin())
48 }
49
50 pub fn look_at(&mut self, target: glam::Vec3) {
53 let (yaw, pitch) = Self::yaw_pitch_from_forward((target - self.position).normalize());
54 self.yaw = yaw;
55 self.pitch = pitch;
56 }
57
58 pub fn build_view_projection_matrix(&self) -> glam::Mat4 {
59 let view = glam::camera::rh::view::look_to_mat4(self.position, self.forward(), glam::Vec3::Y);
60 let projection = glam::camera::rh::proj::directx::perspective(self.fov.to_radians(), self.aspect, self.znear, self.zfar);
61 projection * view
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn forward_matches_identity_yaw_pitch() {
71 let camera = Camera { position: glam::Vec3::ZERO, yaw: 0.0, pitch: 0.0, aspect: 1.0, fov: 45.0, znear: 0.1, zfar: 100.0 };
72 assert!(camera.forward().abs_diff_eq(glam::Vec3::X, 1e-5));
73 }
74
75 #[test]
76 fn look_at_and_forward_round_trip() {
77 let mut camera = Camera { position: glam::Vec3::new(1.0, 2.0, 3.0), yaw: 0.0, pitch: 0.0, aspect: 1.0, fov: 45.0, znear: 0.1, zfar: 100.0 };
78 let target = glam::Vec3::new(-4.0, 5.0, 10.0);
79
80 camera.look_at(target);
81
82 let expected_dir = (target - camera.position).normalize();
83 assert!(camera.forward().abs_diff_eq(expected_dir, 1e-4));
84 }
85}