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}