1use std::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>) -> &Self {
37 self.position = pos;
38
39 self.update_view();
40 self
41 }
42
43 pub fn set_near_far(&mut self, near: f32, far: f32) -> &Self {
44 self.near = near;
45 self.far = far;
46
47 self.update_projection()
48 }
49
50 pub fn set_fovy(&mut self, fovy: f32) -> &Self {
51 self.fov = fovy;
52
53 self.update_projection()
54 }
55
56 pub fn set_target(&mut self, target: Point3<f32>) -> &Self {
57 self.target = target;
58 self.update_view()
59 }
60
61 pub fn get_direction(&self) -> Vector3<f32> {
62 let dir = self.target - self.position;
64 dir.normalize()
65 }
66
67 fn update_view(&mut self) -> &Self {
68 let view = Isometry3::look_at_rh(&self.position, &self.target, &Vector3::y());
69
70 self.view_matrix = view.to_homogeneous();
71 self.vp_matrix = self.projection_matrix * self.view_matrix;
72 self
73 }
74
75 fn update_projection(&mut self) -> &Self {
76 let projection = Perspective3::new(self.aspect_ratio, self.fov, self.near, self.far);
77 self.projection_matrix = projection.to_homogeneous();
78 self.vp_matrix = self.projection_matrix * self.view_matrix;
79 self
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use nalgebra::{Point3, Vector3};
87 use std::f32::consts::PI;
88
89 fn approx_eq(a: f32, b: f32, epsilon: f32) -> bool {
91 (a - b).abs() < epsilon
92 }
93
94 fn points_approx_eq(a: Point3<f32>, b: Point3<f32>, epsilon: f32) -> bool {
96 approx_eq(a.x, b.x, epsilon) && approx_eq(a.y, b.y, epsilon) && approx_eq(a.z, b.z, epsilon)
97 }
98
99 fn matrices_approx_eq(
101 a: &nalgebra::Matrix4<f32>,
102 b: &nalgebra::Matrix4<f32>,
103 epsilon: f32,
104 ) -> bool {
105 for i in 0..4 {
106 for j in 0..4 {
107 if !approx_eq(a[(i, j)], b[(i, j)], epsilon) {
108 return false;
109 }
110 }
111 }
112 true
113 }
114
115 #[test]
116 fn test_camera_creation() {
117 let aspect_ratio = 16.0 / 9.0;
118 let camera = Camera::new(aspect_ratio);
119
120 assert!(points_approx_eq(
122 camera.position,
123 Point3::new(0.0, 0.0, 0.0),
124 0.001
125 ));
126 assert!(points_approx_eq(
127 camera.target,
128 Point3::new(0.0, 0.0, 0.0),
129 0.001
130 ));
131 assert!(approx_eq(camera.fov, PI / 2.0, 0.001));
132 assert!(approx_eq(camera.near, 0.4, 0.001));
133 assert!(approx_eq(camera.far, 20.0, 0.001));
134 assert!(approx_eq(camera.aspect_ratio, aspect_ratio, 0.001));
135
136 let identity = nalgebra::Matrix4::identity();
138 assert!(matrices_approx_eq(&camera.view_matrix, &identity, 0.001));
139 }
140
141 #[test]
142 fn test_set_position() {
143 let mut camera = Camera::new(1.0);
144 let new_position = Point3::new(1.0, 2.0, 3.0);
145
146 camera.set_position(new_position);
147
148 assert!(points_approx_eq(camera.position, new_position, 0.001));
150
151 let identity = nalgebra::Matrix4::identity();
153 assert!(!matrices_approx_eq(&camera.view_matrix, &identity, 0.001));
154
155 assert!(!matrices_approx_eq(&camera.vp_matrix, &identity, 0.001));
157 }
158
159 #[test]
160 fn test_set_target() {
161 let mut camera = Camera::new(1.0);
162 camera.set_position(Point3::new(0.0, 0.0, 10.0));
163
164 let new_target = Point3::new(0.0, 0.0, 0.0);
165 camera.set_target(new_target);
166
167 assert!(points_approx_eq(camera.target, new_target, 0.001));
169
170 let direction = camera.get_direction();
172 let expected_direction = Vector3::new(0.0, 0.0, -1.0);
173
174 assert!(approx_eq(direction.x, expected_direction.x, 0.001));
175 assert!(approx_eq(direction.y, expected_direction.y, 0.001));
176 assert!(approx_eq(direction.z, expected_direction.z, 0.001));
177 }
178
179 #[test]
180 fn test_set_near_far() {
181 let mut camera = Camera::new(1.0);
182 let new_near = 1.0;
183 let new_far = 100.0;
184
185 camera.set_near_far(new_near, new_far);
186
187 assert!(approx_eq(camera.near, new_near, 0.001));
189 assert!(approx_eq(camera.far, new_far, 0.001));
190
191 let before_projection = camera.projection_matrix.clone();
193 camera.set_near_far(2.0, 200.0);
194 assert!(!matrices_approx_eq(
195 &camera.projection_matrix,
196 &before_projection,
197 0.001
198 ));
199 }
200
201 #[test]
202 fn test_set_fovy() {
203 let mut camera = Camera::new(1.0);
204 let new_fov = PI / 4.0;
205
206 camera.set_fovy(new_fov);
207
208 assert!(approx_eq(camera.fov, new_fov, 0.001));
210
211 let before_projection = camera.projection_matrix.clone();
213 camera.set_fovy(PI / 3.0);
214 assert!(!matrices_approx_eq(
215 &camera.projection_matrix,
216 &before_projection,
217 0.001
218 ));
219 }
220
221 #[test]
222 fn test_method_chaining() {
223 let mut camera = Camera::new(1.0);
224
225 camera.set_position(Point3::new(1.0, 2.0, 3.0));
227 camera.set_target(Point3::new(0.0, 0.0, 0.0));
228 camera.set_near_far(0.1, 100.0);
229 camera.set_fovy(PI / 3.0);
230
231 assert!(points_approx_eq(
233 camera.position,
234 Point3::new(1.0, 2.0, 3.0),
235 0.001
236 ));
237 assert!(points_approx_eq(
238 camera.target,
239 Point3::new(0.0, 0.0, 0.0),
240 0.001
241 ));
242 assert!(approx_eq(camera.near, 0.1, 0.001));
243 assert!(approx_eq(camera.far, 100.0, 0.001));
244 assert!(approx_eq(camera.fov, PI / 3.0, 0.001));
245 }
246
247 #[test]
248 fn test_get_direction() {
249 let mut camera = Camera::new(1.0);
250
251 camera.set_position(Point3::new(0.0, 0.0, 0.0));
253 camera.set_target(Point3::new(0.0, 0.0, -1.0));
254
255 let direction = camera.get_direction();
256 assert!(approx_eq(direction.x, 0.0, 0.001));
257 assert!(approx_eq(direction.y, 0.0, 0.001));
258 assert!(approx_eq(direction.z, -1.0, 0.001));
259
260 camera.set_position(Point3::new(10.0, 0.0, 0.0));
262 camera.set_target(Point3::new(0.0, 0.0, 0.0));
263
264 let direction = camera.get_direction();
265 assert!(approx_eq(direction.x, -1.0, 0.001));
268 assert!(approx_eq(direction.y, 0.0, 0.001));
269 assert!(approx_eq(direction.z, 0.0, 0.001));
270 }
271
272 #[test]
273 fn test_look_at_matrix() {
274 let mut camera = Camera::new(1.0);
275
276 camera.set_position(Point3::new(0.0, 0.0, 0.0));
278 camera.set_target(Point3::new(0.0, 0.0, -1.0));
279
280 let object_position = Point3::new(0.0, 0.0, -5.0);
282
283 let transformed = camera.view_matrix.transform_point(&object_position);
285
286 assert!(approx_eq(transformed.x, 0.0, 0.001));
288 assert!(approx_eq(transformed.y, 0.0, 0.001));
289 assert!(transformed.z < 0.0);
290
291 camera.set_position(Point3::new(0.0, 0.0, 5.0));
293 camera.set_target(Point3::new(0.0, 0.0, 0.0));
294
295 let transformed = camera.view_matrix.transform_point(&object_position);
297
298 assert!(transformed.z < 0.0);
300 }
301}