1use core::f32::consts;
2
3#[cfg(feature = "render-layers")]
4use crate::render_layers::RenderLayers;
5use embedded_graphics_core::geometry::Point;
6use nalgebra::{Isometry3, Perspective3, Point3, Vector3};
7
8#[derive(Debug, Clone, Copy)]
10pub struct Ray {
11 pub origin: Vector3<f32>,
13 pub direction: Vector3<f32>,
15}
16
17impl Ray {
18 pub fn new(origin: Vector3<f32>, direction: Vector3<f32>) -> Self {
20 Self {
21 origin,
22 direction: direction.normalize(),
23 }
24 }
25
26 pub fn from_screen_point(point: Point, camera: &Camera, width: usize, height: usize) -> Self {
28 let ndc_x = (2.0 * point.x as f32 / width as f32) - 1.0;
29 let ndc_y = 1.0 - (2.0 * point.y as f32 / height as f32);
30
31 let inv_vp = camera
32 .vp_matrix
33 .try_inverse()
34 .unwrap_or(nalgebra::Matrix4::identity());
35 let near_h = inv_vp * nalgebra::Vector4::new(ndc_x, ndc_y, -1.0, 1.0);
36 let far_h = inv_vp * nalgebra::Vector4::new(ndc_x, ndc_y, 1.0, 1.0);
37
38 let near_pt = Vector3::new(near_h.x, near_h.y, near_h.z) / near_h.w.abs().max(1e-6);
39 let far_pt = Vector3::new(far_h.x, far_h.y, far_h.z) / far_h.w.abs().max(1e-6);
40
41 let direction = (far_pt - near_pt).normalize();
42 Self {
43 origin: camera.position.coords,
44 direction,
45 }
46 }
47}
48
49pub struct Camera {
50 pub position: Point3<f32>,
51 fov: f32,
52 pub near: f32,
53 pub far: f32,
54 pub view_matrix: nalgebra::Matrix4<f32>,
55 projection_matrix: nalgebra::Matrix4<f32>,
56 pub vp_matrix: nalgebra::Matrix4<f32>,
57 target: Point3<f32>,
58 aspect_ratio: f32,
59 #[cfg(feature = "render-layers")]
61 pub layers: RenderLayers,
62}
63
64impl Camera {
65 pub fn new(aspect_ratio: f32) -> Camera {
66 let mut ret = Camera {
67 position: Point3::new(0.0, 0.0, 0.0),
68 fov: consts::PI / 2.0,
69 view_matrix: nalgebra::Matrix4::identity(),
70 projection_matrix: nalgebra::Matrix4::identity(),
71 vp_matrix: nalgebra::Matrix4::identity(),
72 target: Point3::new(0.0, 0.0, 0.0),
73 aspect_ratio,
74 near: 0.4,
75 far: 20.0,
76 #[cfg(feature = "render-layers")]
77 layers: RenderLayers::DEFAULT,
78 };
79
80 ret.update_projection();
81
82 ret
83 }
84
85 pub fn set_position(&mut self, pos: Point3<f32>) {
86 self.position = pos;
87
88 self.update_view();
89 }
90
91 pub fn set_fovy(&mut self, fovy: f32) {
92 self.fov = fovy;
93
94 self.update_projection();
95 }
96
97 pub fn fovy(&self) -> f32 {
99 self.fov
100 }
101
102 #[cfg(feature = "render-layers")]
104 pub fn set_layers(&mut self, layers: RenderLayers) {
105 self.layers = layers;
106 }
107
108 pub fn set_near(&mut self, near: f32) {
109 self.near = near;
110
111 self.update_projection();
112 }
113
114 pub fn set_far(&mut self, far: f32) {
115 self.far = far;
116
117 self.update_projection();
118 }
119
120 pub fn set_near_far(&mut self, near: f32, far: f32) {
130 self.near = near;
131 self.far = far;
132
133 self.update_projection();
134 }
135
136 pub fn get_near_far_ratio(&self) -> f32 {
138 self.far / self.near
139 }
140
141 pub fn set_target(&mut self, target: Point3<f32>) {
142 self.target = target;
143 self.update_view();
144 }
145
146 pub fn get_direction(&self) -> Vector3<f32> {
147 let transpose = self.view_matrix; Vector3::new(transpose[(2, 0)], transpose[(2, 1)], transpose[(2, 2)])
150 }
151
152 pub fn get_aspect_ratio(&self) -> f32 {
153 self.aspect_ratio
154 }
155
156 fn update_view(&mut self) {
157 let view = Isometry3::look_at_rh(&self.position, &self.target, &Vector3::y());
158
159 self.view_matrix = view.to_homogeneous();
160 self.vp_matrix = self.projection_matrix * self.view_matrix;
161 }
162
163 fn update_projection(&mut self) {
164 let projection = Perspective3::new(self.aspect_ratio, self.fov, self.near, self.far);
165 self.projection_matrix = projection.to_homogeneous();
166 self.vp_matrix = self.projection_matrix * self.view_matrix;
167 }
168
169 #[cfg(feature = "dsp")]
170 pub fn smooth_track_dsp(&mut self, target: Point3<f32>, alpha: f32) {
172 let alpha_clamped = alpha.clamp(0.01, 1.0);
173 let cur = self.target;
174 let smoothed_x = cur.x + (target.x - cur.x) * alpha_clamped;
175 let smoothed_y = cur.y + (target.y - cur.y) * alpha_clamped;
176 let smoothed_z = cur.z + (target.z - cur.z) * alpha_clamped;
177 self.set_target(Point3::new(smoothed_x, smoothed_y, smoothed_z));
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_camera_creation() {
187 let camera = Camera::new(16.0 / 9.0);
188 assert!((camera.get_aspect_ratio() - 16.0 / 9.0).abs() < 0.001);
189 assert_eq!(camera.near, 0.4);
190 assert_eq!(camera.far, 20.0);
191 assert_eq!(camera.position, Point3::new(0.0, 0.0, 0.0));
192 }
193
194 #[test]
195 fn test_camera_set_position() {
196 let mut camera = Camera::new(1.0);
197 let new_pos = Point3::new(5.0, 10.0, 15.0);
198 camera.set_position(new_pos);
199 assert_eq!(camera.position, new_pos);
200 }
201
202 #[test]
203 fn test_camera_set_target() {
204 let mut camera = Camera::new(1.0);
205 let target = Point3::new(1.0, 2.0, 3.0);
206 camera.set_target(target);
207 assert_eq!(camera.target, target);
208 }
209
210 #[test]
211 fn test_camera_set_fovy() {
212 let mut camera = Camera::new(1.0);
213 let new_fov = core::f32::consts::PI / 4.0; camera.set_fovy(new_fov);
215 assert!((camera.fov - new_fov).abs() < 0.001);
216 }
217
218 #[test]
219 fn test_camera_get_direction() {
220 let mut camera = Camera::new(1.0);
221 camera.set_position(Point3::new(0.0, 0.0, 5.0));
222 camera.set_target(Point3::new(0.0, 0.0, 0.0));
223
224 let direction = camera.get_direction();
225 assert!(direction.magnitude() > 0.0);
227 }
228
229 #[test]
230 fn test_camera_vp_matrix_updates() {
231 let mut camera = Camera::new(1.0);
232 let initial_vp = camera.vp_matrix;
233
234 camera.set_position(Point3::new(5.0, 5.0, 5.0));
236 assert_ne!(camera.vp_matrix, initial_vp);
237
238 let after_pos = camera.vp_matrix;
239
240 camera.set_fovy(core::f32::consts::PI / 4.0);
242 assert_ne!(camera.vp_matrix, after_pos);
243 }
244
245 #[test]
246 fn test_camera_projection_target_center() {
247 let mut camera = Camera::new(1.0); camera.set_position(Point3::new(0.0, 0.0, 5.0));
249 camera.set_target(Point3::new(0.0, 0.0, 0.0));
250
251 let p_target = nalgebra::Vector4::new(0.0, 0.0, 0.0, 1.0);
253 let clip = camera.vp_matrix * p_target;
254
255 assert!(clip.x.abs() < 1e-4);
257 assert!(clip.y.abs() < 1e-4);
258 assert!(clip.w > 0.0); }
260
261 #[test]
262 #[allow(non_snake_case)]
263 fn test_camera_view_matrix_orthogonality() {
264 let mut camera = Camera::new(16.0 / 9.0);
265 camera.set_position(Point3::new(3.0, 4.0, 5.0));
266 camera.set_target(Point3::new(0.0, 1.0, 0.0));
267
268 let R = camera.view_matrix.fixed_view::<3, 3>(0, 0);
270 let I = R * R.transpose();
271
272 let identity = nalgebra::Matrix3::identity();
274 let diff = (I - identity).norm();
275 assert!(
276 diff < 1e-4,
277 "View matrix rotation is not orthogonal: diff = {}",
278 diff
279 );
280 }
281}