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