1use core::f32::consts;
2
3use nalgebra::{Isometry3, Perspective3, Point3, Vector3};
4
5pub struct Camera {
6 pub position: Point3<f32>,
7 fov: f32,
8 pub near: f32,
9 pub far: f32,
10 view_matrix: nalgebra::Matrix4<f32>,
11 projection_matrix: nalgebra::Matrix4<f32>,
12 pub vp_matrix: nalgebra::Matrix4<f32>,
13 target: Point3<f32>,
14 aspect_ratio: f32,
15}
16
17impl Camera {
18 pub fn new(aspect_ratio: f32) -> Camera {
19 let mut ret = Camera {
20 position: Point3::new(0.0, 0.0, 0.0),
21 fov: consts::PI / 2.0,
22 view_matrix: nalgebra::Matrix4::identity(),
23 projection_matrix: nalgebra::Matrix4::identity(),
24 vp_matrix: nalgebra::Matrix4::identity(),
25 target: Point3::new(0.0, 0.0, 0.0),
26 aspect_ratio,
27 near: 0.4,
28 far: 20.0,
29 };
30
31 ret.update_projection();
32
33 ret
34 }
35
36 pub fn set_position(&mut self, pos: Point3<f32>) {
37 self.position = pos;
38
39 self.update_view();
40 }
41
42 pub fn set_fovy(&mut self, fovy: f32) {
43 self.fov = fovy;
44
45 self.update_projection();
46 }
47
48 pub fn set_near(&mut self, near: f32) {
49 self.near = near;
50
51 self.update_projection();
52 }
53
54 pub fn set_far(&mut self, far: f32) {
55 self.far = far;
56
57 self.update_projection();
58 }
59
60 pub fn set_near_far(&mut self, near: f32, far: f32) {
70 self.near = near;
71 self.far = far;
72
73 self.update_projection();
74 }
75
76 pub fn get_near_far_ratio(&self) -> f32 {
78 self.far / self.near
79 }
80
81 pub fn set_target(&mut self, target: Point3<f32>) {
82 self.target = target;
83 self.update_view();
84 }
85
86 pub fn get_direction(&self) -> Vector3<f32> {
87 let transpose = self.view_matrix; Vector3::new(transpose[(2, 0)], transpose[(2, 1)], transpose[(2, 2)])
90 }
91
92 pub fn get_aspect_ratio(&self) -> f32 {
93 self.aspect_ratio
94 }
95
96 fn update_view(&mut self) {
97 let view = Isometry3::look_at_rh(&self.position, &self.target, &Vector3::y());
98
99 self.view_matrix = view.to_homogeneous();
100 self.vp_matrix = self.projection_matrix * self.view_matrix;
101 }
102
103 fn update_projection(&mut self) {
104 let projection = Perspective3::new(self.aspect_ratio, self.fov, self.near, self.far);
105 self.projection_matrix = projection.to_homogeneous();
106 self.vp_matrix = self.projection_matrix * self.view_matrix;
107 }
108
109 #[cfg(feature = "dsp")]
110 pub fn smooth_track_dsp(&mut self, target: Point3<f32>, alpha: f32) {
112 let alpha_clamped = alpha.clamp(0.01, 1.0);
113 let cur = self.target;
114 let smoothed_x = cur.x + (target.x - cur.x) * alpha_clamped;
115 let smoothed_y = cur.y + (target.y - cur.y) * alpha_clamped;
116 let smoothed_z = cur.z + (target.z - cur.z) * alpha_clamped;
117 self.set_target(Point3::new(smoothed_x, smoothed_y, smoothed_z));
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_camera_creation() {
127 let camera = Camera::new(16.0 / 9.0);
128 assert!((camera.get_aspect_ratio() - 16.0 / 9.0).abs() < 0.001);
129 assert_eq!(camera.near, 0.4);
130 assert_eq!(camera.far, 20.0);
131 assert_eq!(camera.position, Point3::new(0.0, 0.0, 0.0));
132 }
133
134 #[test]
135 fn test_camera_set_position() {
136 let mut camera = Camera::new(1.0);
137 let new_pos = Point3::new(5.0, 10.0, 15.0);
138 camera.set_position(new_pos);
139 assert_eq!(camera.position, new_pos);
140 }
141
142 #[test]
143 fn test_camera_set_target() {
144 let mut camera = Camera::new(1.0);
145 let target = Point3::new(1.0, 2.0, 3.0);
146 camera.set_target(target);
147 assert_eq!(camera.target, target);
148 }
149
150 #[test]
151 fn test_camera_set_fovy() {
152 let mut camera = Camera::new(1.0);
153 let new_fov = core::f32::consts::PI / 4.0; camera.set_fovy(new_fov);
155 assert!((camera.fov - new_fov).abs() < 0.001);
156 }
157
158 #[test]
159 fn test_camera_get_direction() {
160 let mut camera = Camera::new(1.0);
161 camera.set_position(Point3::new(0.0, 0.0, 5.0));
162 camera.set_target(Point3::new(0.0, 0.0, 0.0));
163
164 let direction = camera.get_direction();
165 assert!(direction.magnitude() > 0.0);
167 }
168
169 #[test]
170 fn test_camera_vp_matrix_updates() {
171 let mut camera = Camera::new(1.0);
172 let initial_vp = camera.vp_matrix;
173
174 camera.set_position(Point3::new(5.0, 5.0, 5.0));
176 assert_ne!(camera.vp_matrix, initial_vp);
177
178 let after_pos = camera.vp_matrix;
179
180 camera.set_fovy(core::f32::consts::PI / 4.0);
182 assert_ne!(camera.vp_matrix, after_pos);
183 }
184
185 #[test]
186 fn test_camera_projection_target_center() {
187 let mut camera = Camera::new(1.0); camera.set_position(Point3::new(0.0, 0.0, 5.0));
189 camera.set_target(Point3::new(0.0, 0.0, 0.0));
190
191 let p_target = nalgebra::Vector4::new(0.0, 0.0, 0.0, 1.0);
193 let clip = camera.vp_matrix * p_target;
194
195 assert!(clip.x.abs() < 1e-4);
197 assert!(clip.y.abs() < 1e-4);
198 assert!(clip.w > 0.0); }
200
201 #[test]
202 fn test_camera_view_matrix_orthogonality() {
203 let mut camera = Camera::new(16.0 / 9.0);
204 camera.set_position(Point3::new(3.0, 4.0, 5.0));
205 camera.set_target(Point3::new(0.0, 1.0, 0.0));
206
207 let R = camera.view_matrix.fixed_view::<3, 3>(0, 0);
209 let I = R * R.transpose();
210
211 let identity = nalgebra::Matrix3::identity();
213 let diff = (I - identity).norm();
214 assert!(
215 diff < 1e-4,
216 "View matrix rotation is not orthogonal: diff = {}",
217 diff
218 );
219 }
220}