Skip to main content

truck_platform/
camera.rs

1use crate::*;
2
3impl Ray {
4    /// Returns the origin of the ray
5    #[inline(always)]
6    pub const fn origin(&self) -> Point3 { self.origin }
7    /// Returns the (normalized) direction of the ray
8    #[inline(always)]
9    pub const fn direction(&self) -> Vector3 { self.direction }
10}
11
12impl Camera {
13    /// Returns the position of camera,
14    /// the forth column of the camera matrix.
15    ///
16    /// # Examples
17    /// ```
18    /// use truck_platform::*;
19    /// use truck_base::cgmath64::*;
20    /// let mut camera = Camera::default();
21    /// camera.matrix = Matrix4::from_translation(Vector3::new(1.0, 2.0, 3.0));
22    /// assert_eq!(camera.position(), Point3::new(1.0, 2.0, 3.0));
23    /// ```
24    #[inline(always)]
25    pub fn position(&self) -> Point3 { Point3::from_vec(self.matrix[3].truncate()) }
26
27    /// Returns the eye direction of camera.
28    /// the inverse of the z-axis of the camera matrix.
29    ///
30    /// # Examples
31    /// ```
32    /// use std::f64::consts::PI;
33    /// use truck_platform::*;
34    /// use truck_base::{cgmath64::*, tolerance::Tolerance};
35    /// let mut camera = Camera::default();
36    /// camera.matrix = Matrix4::from_axis_angle(
37    ///     Vector3::new(1.0, 1.0, 1.0).normalize(),
38    ///     Rad(2.0 * PI / 3.0),
39    /// );
40    /// assert!(camera.eye_direction().near(&-Vector3::unit_x()));
41    /// ```
42    #[inline(always)]
43    pub fn eye_direction(&self) -> Vector3 { -self.matrix[2].truncate() }
44
45    /// Returns the direction of the head vector, the y-axis of the camera matrix.
46    /// # Examples
47    /// ```
48    /// use std::f64::consts::PI;
49    /// use truck_platform::*;
50    /// use truck_base::{cgmath64::*, tolerance::Tolerance};
51    /// let mut camera = Camera::default();
52    /// camera.matrix = Matrix4::from_axis_angle(
53    ///     Vector3::new(1.0, 1.0, 1.0).normalize(),
54    ///     Rad(2.0 * PI / 3.0),
55    /// );
56    /// assert!(camera.head_direction().near(&Vector3::unit_z()));
57    /// ```
58    #[inline(always)]
59    pub fn head_direction(&self) -> Vector3 { self.matrix[1].truncate() }
60
61    /// Returns the projection type of the camera.
62    /// # Examples
63    /// ```
64    /// use truck_platform::*;
65    /// // the projection type of the default camera is perspective.
66    /// assert_eq!(Camera::default().projection_type(), ProjectionType::Perspective);
67    /// ```
68    #[inline(always)]
69    pub const fn projection_type(&self) -> ProjectionType { self.projection_type }
70
71    /// Creates a perspective camera.
72    /// # Arguments
73    /// * `matrix`:  camera matrix
74    /// * `field_of_view`: FOV, based on the vertical direction of the screen.
75    /// * `near_clip`: distance to the nearest face of the view volume
76    /// * `far_clip`: distance to the farthest face of the view volume
77    /// # Examples
78    /// ```
79    /// use std::f64::consts::PI;
80    /// use truck_base::{cgmath64::*, tolerance::Tolerance};
81    /// use truck_platform::*;
82    /// let matrix = Matrix4::look_at_rh(
83    ///     Point3::new(1.0, 1.0, 1.0),
84    ///     Point3::origin(),
85    ///     Vector3::new(0.0, 1.0, 0.0),
86    /// );
87    /// let camera = Camera::perspective_camera(
88    ///     // depends on the difference of the style with cgmath,
89    ///     // the matrix must be inverted
90    ///     matrix.invert().unwrap(),
91    ///     Rad(PI / 4.0),
92    ///     0.1,
93    ///     1.0,
94    /// );
95    /// assert!(camera.eye_direction().near(&-Vector3::new(1.0, 1.0, 1.0).normalize()));
96    /// assert_eq!(camera.projection_type(), ProjectionType::Perspective);
97    /// ```
98    #[inline(always)]
99    pub fn perspective_camera<R: Into<Rad<f64>>>(
100        matrix: Matrix4,
101        field_of_view: R,
102        near_clip: f64,
103        far_clip: f64,
104    ) -> Camera {
105        let projection = perspective(field_of_view.into(), 1.0, near_clip, far_clip);
106        Camera {
107            matrix,
108            projection,
109            projection_type: ProjectionType::Perspective,
110        }
111    }
112
113    /// Creates a parallel camera.
114    /// # Arguments
115    /// * `matrix`:  camera matrix
116    /// * `screen_size`: screen size, based on the vertical direction of the screen.`
117    /// * `near_clip`: distance to the nearest face of the view volume
118    /// * `far_clip`: distance to the farthest face of the view volume
119    /// # Examples
120    /// ```
121    /// use truck_base::{cgmath64::*, tolerance::Tolerance};
122    /// use truck_platform::*;
123    /// let matrix = Matrix4::look_at_rh(
124    ///     Point3::new(1.0, 1.0, 1.0),
125    ///     Point3::origin(),
126    ///     Vector3::new(0.0, 1.0, 0.0),
127    /// );
128    /// let camera = Camera::parallel_camera(
129    ///     // depends on the difference of the style with cgmath,
130    ///     // the matrix must be inverted
131    ///     matrix.invert().unwrap(),
132    ///     1.0,
133    ///     0.1,
134    ///     1.0,
135    /// );
136    /// assert!(camera.head_direction().near(&Vector3::new(-0.5, 1.0, -0.5).normalize()));
137    /// assert_eq!(camera.projection_type(), ProjectionType::Parallel);
138    /// ```
139    #[inline(always)]
140    pub fn parallel_camera(
141        matrix: Matrix4,
142        screen_size: f64,
143        near_clip: f64,
144        far_clip: f64,
145    ) -> Camera {
146        let a = screen_size / 2.0;
147        let projection = Matrix4::new(
148            1.0 / a,
149            0.0,
150            0.0,
151            0.0,
152            0.0,
153            1.0 / a,
154            0.0,
155            0.0,
156            0.0,
157            0.0,
158            -1.0 / (far_clip - near_clip),
159            0.0,
160            0.0,
161            0.0,
162            -near_clip / (far_clip - near_clip),
163            1.0,
164        );
165        Camera {
166            matrix,
167            projection,
168            projection_type: ProjectionType::Parallel,
169        }
170    }
171
172    /// Returns the projection matrix into the normalized view volume.
173    /// # Arguments
174    /// `as_rat`: the aspect ratio, x-resolution / y-resulution.
175    /// # Examples
176    /// ```
177    /// // perspective camera
178    /// use std::f64::consts::PI;
179    /// use truck_base::{assert_near, cgmath64::*, tolerance::*};
180    /// use truck_platform::*;
181    ///
182    /// let fov = PI / 4.0;
183    /// let as_rat = 1.2;
184    /// let matrix = Matrix4::look_at_rh(
185    ///     Point3::new(1.0, 1.0, 1.0),
186    ///     Point3::origin(),
187    ///     Vector3::new(0.0, 1.0, 0.0),
188    /// );
189    /// let camera = Camera::perspective_camera(
190    ///     matrix.invert().unwrap(),
191    ///     Rad(fov),
192    ///     0.1,
193    ///     10.0,
194    /// );
195    ///
196    /// // calculation by the ray-tracing
197    /// let pt = Point3::new(-1.5, -1.4, -2.5);
198    /// let vec = pt - camera.position();
199    /// let far = 1.0 / (fov / 2.0).tan();
200    /// let dir = camera.eye_direction();
201    /// let y_axis = camera.head_direction();
202    /// let x_axis = dir.cross(y_axis);
203    /// let proj_length = dir.dot(vec);
204    /// let h = (vec - proj_length * dir) * far / proj_length;
205    /// let u = h.dot(x_axis) / as_rat;
206    /// let v = h.dot(y_axis);
207    ///
208    /// // check the answer
209    /// let uv = camera.projection(as_rat).transform_point(pt);
210    /// assert_near!(u, uv[0]);
211    /// assert_near!(v, uv[1]);
212    /// ```
213    /// ```
214    /// // parallel camera
215    /// use truck_base::{assert_near, cgmath64::*, tolerance::*};
216    /// use truck_platform::*;
217    ///
218    /// let size = 3.0;
219    /// let as_rat = 1.2;
220    /// let matrix = Matrix4::look_at_rh(
221    ///     Point3::new(1.0, 1.0, 1.0),
222    ///     Point3::origin(),
223    ///     Vector3::new(0.0, 1.0, 0.0),
224    /// );
225    /// let camera = Camera::parallel_camera(
226    ///     matrix.invert().unwrap(),
227    ///     size,
228    ///     0.1,
229    ///     10.0,
230    /// );
231    ///
232    /// // calculation by the ray-tracing
233    /// let pt = Point3::new(-1.5, -1.4, -2.5);
234    /// let vec = pt - camera.position();
235    /// let dir = camera.eye_direction();
236    /// let y_axis = camera.head_direction();
237    /// let x_axis = dir.cross(y_axis);
238    /// let h = vec - vec.dot(dir) * dir;
239    /// let u = h.dot(x_axis) / (size / 2.0) / as_rat;
240    /// let v = h.dot(y_axis) / (size / 2.0);
241    ///
242    /// // check the answer
243    /// let uv = camera.projection(as_rat).transform_point(pt);
244    /// assert_near!(u, uv[0]);
245    /// assert_near!(v, uv[1]);
246    /// ```
247    #[inline(always)]
248    pub fn projection(&self, as_rat: f64) -> Matrix4 {
249        Matrix4::from_nonuniform_scale(1.0 / as_rat, 1.0, 1.0)
250            * self.projection
251            * self.matrix.invert().unwrap()
252    }
253
254    fn camera_info(&self, as_rat: f64) -> CameraInfo {
255        CameraInfo {
256            camera_matrix: self.matrix.cast().unwrap().into(),
257            camera_projection: self.projection(as_rat).cast().unwrap().into(),
258        }
259    }
260
261    /// Creates a `UNIFORM` buffer of camera.
262    ///
263    /// The bind group provides [`Scene`] holds this uniform buffer.
264    ///
265    /// # Shader Example
266    /// ```glsl
267    /// layout(set = 0, binding = 0) uniform Camera {
268    ///     mat4 camera_matrix;     // the camera matrix
269    ///     mat4 camera_projection; // the projection into the normalized view volume
270    /// };
271    /// ```
272    pub fn buffer(&self, as_rat: f64, device: &Device) -> BufferHandler {
273        BufferHandler::from_slice(&[self.camera_info(as_rat)], device, BufferUsages::UNIFORM)
274    }
275
276    /// Returns the ray from camera with aspect-ratio = 1.0.
277    ///
278    /// # Examples
279    /// ```
280    /// // Perspective case
281    /// use std::f64::consts::PI;
282    /// use truck_base::{assert_near, cgmath64::*, tolerance::Tolerance};
283    /// use truck_platform::*;
284    ///
285    ///
286    /// let matrix = Matrix4::look_at_rh(
287    ///     Point3::new(1.0, 1.0, 1.0),
288    ///     Point3::origin(),
289    ///     Vector3::new(0.0, 1.0, 0.0),
290    /// );
291    /// let camera = Camera::perspective_camera(
292    ///     // depends on the difference of the style with cgmath,
293    ///     // the matrix must be inverted
294    ///     matrix.invert().unwrap(),
295    ///     Rad(PI / 4.0),
296    ///     0.1,
297    ///     1.0,
298    /// );
299    ///
300    /// // take a point in the 3D space
301    /// let point = Point3::new(0.1, 0.15, 0.0);
302    /// // project to the normalized view volume
303    /// let uvz = camera.projection(1.0).transform_point(point);
304    /// // coordinate on the screen
305    /// let uv = Point2::new(uvz.x, uvz.y);
306    ///
307    /// let ray = camera.ray(uv);
308    /// // the origin of the ray is camera position
309    /// assert_near!(ray.origin(), camera.position());
310    /// // the direction of the ray is the normalized vector of point - camera.position().
311    /// assert_near!(ray.direction(), (point - camera.position()).normalize());
312    /// ```
313    /// ```
314    /// // Parallel case
315    /// use truck_base::{assert_near, cgmath64::*, tolerance::*};
316    /// use truck_platform::*;
317    ///
318    /// let matrix = Matrix4::look_at_rh(
319    ///     Point3::new(1.0, 1.0, 1.0),
320    ///     Point3::origin(),
321    ///     Vector3::new(0.0, 1.0, 0.0),
322    /// );
323    /// let camera = Camera::parallel_camera(
324    ///     matrix.invert().unwrap(),
325    ///     3.0,
326    ///     0.1,
327    ///     10.0,
328    /// );
329    ///
330    /// // take a point in the 3D space
331    /// let point = Point3::new(0.1, 0.15, 0.0);
332    /// // the projection of the point to the screen
333    /// let projed = point
334    ///     - camera.eye_direction() * camera.eye_direction().dot(point - camera.position());
335    /// // project to the normalized view volume
336    /// let uvz = camera.projection(1.0).transform_point(point);
337    /// // coordinate on the screen
338    /// let uv = Point2::new(uvz.x, uvz.y);
339    ///
340    /// let ray = camera.ray(uv);
341    /// // the origin of the ray is the projection of the point.
342    /// assert_near!(ray.origin(), projed);
343    /// // the direction of the ray is eye direction.
344    /// assert_near!(ray.direction(), camera.eye_direction());
345    /// ```
346    pub fn ray(&self, coord: Point2) -> Ray {
347        match self.projection_type {
348            ProjectionType::Perspective => {
349                let mat = self
350                    .projection(1.0)
351                    .invert()
352                    .expect("non-invertible projection");
353                let x = mat.transform_point(Point3::new(coord.x, coord.y, 0.5));
354                let y = mat.transform_point(Point3::new(coord.x, coord.y, 1.0));
355                Ray {
356                    origin: self.position(),
357                    direction: (y - x).normalize(),
358                }
359            }
360            ProjectionType::Parallel => {
361                let a = self.projection[0][0];
362                let axis_x = self.matrix[0].truncate() / a;
363                let axis_y = self.matrix[1].truncate() / a;
364                Ray {
365                    origin: self.position() + coord.x * axis_x + coord.y * axis_y,
366                    direction: self.eye_direction(),
367                }
368            }
369        }
370    }
371}
372
373impl Default for Camera {
374    #[inline(always)]
375    fn default() -> Camera {
376        Camera::perspective_camera(
377            Matrix4::identity(),
378            Rad(std::f64::consts::PI / 4.0),
379            0.1,
380            10.0,
381        )
382    }
383}