peridot_math/
gaming.rs

1//! Peridot Extended Mathematics: Gaming Utils(Camera, ModelMatrix)
2
3use crate::linarg::*;
4use crate::{One, Zero};
5use std::ops::Range;
6
7/// How the camera will project vertices?
8#[derive(Debug, Clone)]
9pub enum ProjectionMethod {
10    /// The orthographic projection
11    Orthographic { size: f32 },
12    /// The perspective projection. requires fov(unit: radians)
13    Perspective { fov: f32 },
14    /// The perspective projection but computed from physically-based units(millimeters).
15    Physical {
16        focal_length: f32,
17        sensor_size: crate::Vector2F32,
18        screen_fitting: PhysicalScreenFitting,
19        lens_shift: crate::Vector2F32,
20    },
21    /// UI layouting optimized projection: (0, 0)-(design_width, design_height) will be mapped to (-1, -1)-(1, 1)
22    /// This projection ignores aspect ratio.
23    UI {
24        design_width: f32,
25        design_height: f32,
26    },
27}
28
29#[derive(Debug, Clone, Copy)]
30pub enum PhysicalScreenFitting {
31    // 縦のみ合わせる
32    Vertical,
33    // 横のみ合わせる
34    Horizontal,
35    // 画面からはみ出さないように調整
36    Shrink,
37    // 画面をすべて覆うように調整
38    Enlarge,
39}
40
41/// A camera
42/// ## Examples
43///
44/// ```
45/// # use peridot_math::*;
46/// let c = Camera {
47///     projection: Some(ProjectionMethod::Orthographic { size: 5.0 }),
48///     position: Vector3::ZERO, rotation: Quaternion::ONE,
49///     depth_range: 1.0 .. 9.0
50/// };
51/// let (mv, mp) = c.matrixes(1.0);
52/// assert_eq!(mv.clone() * Vector3(5.0, 0.0, 1.0), Vector4(5.0, 0.0, 1.0, 1.0));
53/// assert_eq!(mp * mv * Vector3(5.0, 0.0, 1.0), Vector4(1.0, 0.0, 0.0, 1.0));
54/// ```
55pub struct Camera {
56    /// Projection method of the camera. `None` indicates no projection(only adjust aspect ratio)
57    pub projection: Option<ProjectionMethod>,
58    /// Eye position of the camera.
59    pub position: Vector3F32,
60    /// Eye direction of the camera.
61    pub rotation: QuaternionF32,
62    /// Z range to be rendered.
63    pub depth_range: Range<f32>,
64}
65
66impl Camera {
67    /// calculates the camera projection matrix
68    pub fn projection_matrix(&self, aspect_wh: f32) -> Matrix4F32 {
69        match self.projection {
70            Some(ProjectionMethod::Perspective { fov }) => {
71                let scaling_tan = (fov / 2.0).tan();
72                let zdiff = self.depth_range.end - self.depth_range.start;
73                let zscale = (
74                    self.depth_range.end / zdiff,
75                    -(self.depth_range.end * self.depth_range.start) / zdiff,
76                );
77
78                Matrix4(
79                    [(aspect_wh * scaling_tan).recip(), 0.0, 0.0, 0.0],
80                    [0.0, -scaling_tan.recip(), 0.0, 0.0],
81                    [0.0, 0.0, zscale.0, zscale.1],
82                    [0.0, 0.0, 1.0, 0.0],
83                )
84            }
85            Some(ProjectionMethod::Orthographic { size }) => {
86                let zdiff = self.depth_range.end - self.depth_range.start;
87                let t = Matrix4::translation(Vector3(0.0, 0.0, -self.depth_range.start));
88                let s = Matrix4::scale(Vector4(
89                    (aspect_wh * size).recip(),
90                    -size.recip(),
91                    zdiff.recip(),
92                    1.0,
93                ));
94
95                s * t
96            }
97            Some(ProjectionMethod::Physical {
98                focal_length,
99                sensor_size,
100                screen_fitting,
101                lens_shift,
102            }) => {
103                let focal_length_meters = focal_length / 1000.0;
104                let sensor_width_meters = sensor_size.0 / 1000.0;
105                let sensor_height_meters = sensor_size.1 / 1000.0;
106                let zd = self.depth_range.end - self.depth_range.start;
107
108                let (scaling_x, scaling_y);
109                match screen_fitting {
110                    PhysicalScreenFitting::Vertical => {
111                        // let scaling_tan = sensor_height_meters / focal_length_meters;
112
113                        // scaling_x = (aspect_wh * scaling_tan).recip();
114                        scaling_x = focal_length_meters / (aspect_wh * sensor_height_meters);
115                        // scaling_y = scaling_tan.recip();
116                        scaling_y = focal_length_meters / sensor_height_meters;
117                    }
118                    PhysicalScreenFitting::Horizontal => {
119                        // let scaling_tan = sensor_width_meters / focal_length_meters;
120
121                        // scaling_x = scaling_tan.recip();
122                        scaling_x = focal_length_meters / sensor_width_meters;
123                        // scaling_y = (scaling_tan / aspect_wh).recip();
124                        scaling_y = (focal_length_meters * aspect_wh) / sensor_width_meters;
125                    }
126                    PhysicalScreenFitting::Shrink => {
127                        if sensor_size.aspect_wh() < aspect_wh {
128                            // let scaling_tan = sensor_height_meters / focal_length_meters;
129
130                            // scaling_x = (aspect_wh * scaling_tan).recip();
131                            scaling_x = focal_length_meters / (aspect_wh * sensor_height_meters);
132                            // scaling_y = scaling_tan.recip();
133                            scaling_y = focal_length_meters / sensor_height_meters;
134                        } else {
135                            // let scaling_tan = sensor_width_meters / focal_length_meters;
136
137                            // scaling_x = scaling_tan.recip();
138                            scaling_x = focal_length_meters / sensor_width_meters;
139                            // scaling_y = (scaling_tan / aspect_wh).recip();
140                            scaling_y = (focal_length_meters * aspect_wh) / sensor_width_meters;
141                        }
142                    }
143                    PhysicalScreenFitting::Enlarge => {
144                        if sensor_size.aspect_wh() > aspect_wh {
145                            // let scaling_tan = sensor_height_meters / focal_length_meters;
146
147                            // scaling_x = (aspect_wh * scaling_tan).recip();
148                            scaling_x = focal_length_meters / (aspect_wh * sensor_height_meters);
149                            // scaling_y = scaling_tan.recip();
150                            scaling_y = focal_length_meters / sensor_height_meters;
151                        } else {
152                            // let scaling_tan = sensor_width_meters / focal_length_meters;
153
154                            // scaling_x = scaling_tan.recip();
155                            scaling_x = focal_length_meters / sensor_width_meters;
156                            // scaling_y = (scaling_tan / aspect_wh).recip();
157                            scaling_y = (focal_length_meters * aspect_wh) / sensor_width_meters;
158                        }
159                    }
160                }
161
162                // z = (z - znear) / (zfar - znear) = z / (zfar - znear) - znear / (zfar - znear)
163
164                let projection = Matrix4(
165                    [scaling_x, 0.0, 0.0, 0.0],
166                    [0.0, -scaling_y, 0.0, 0.0],
167                    [0.0, 0.0, zd.recip(), -self.depth_range.start / zd],
168                    [0.0, 0.0, 1.0, 0.0],
169                );
170                let lens_shift = Matrix4::translation(lens_shift.with_z(0.0));
171
172                lens_shift * projection
173            }
174            Some(ProjectionMethod::UI {
175                design_width,
176                design_height,
177            }) => {
178                let zdiff = self.depth_range.end - self.depth_range.start;
179                let t = Matrix4::translation(Vector3(-1.0, -1.0, 0.0));
180                let s = Matrix4::scale(Vector4(
181                    2.0 / design_width,
182                    2.0 / design_height,
183                    zdiff.recip(),
184                    1.0,
185                ));
186
187                t * s
188            }
189            None => Matrix4::scale(Vector4(aspect_wh.recip(), 1.0, 1.0, 1.0)),
190        }
191    }
192    /// calculates the camera view matrix
193    pub fn view_matrix(&self) -> Matrix4F32 {
194        Matrix4F32::from(-self.rotation) * Matrix4F32::translation(-self.position)
195    }
196    /// calculates the camera transform(view and projection) matrix
197    pub fn view_projection_matrix(&self, aspect_wh: f32) -> Matrix4F32 {
198        let (v, p) = self.matrixes(aspect_wh);
199        p * v
200    }
201    /// calculates the camera view matrix and the projection matrix(returns in this order)
202    pub fn matrixes(&self, aspect_wh: f32) -> (Matrix4F32, Matrix4F32) {
203        (self.view_matrix(), self.projection_matrix(aspect_wh))
204    }
205
206    /// Sets rotation of the camera to look at a point
207    pub fn look_at(&mut self, target: Vector3F32) {
208        let eyedir = (target - self.position).normalize();
209        let basedir = Vector3(0.0f32, 0.0, 1.0);
210
211        let axis = basedir.cross(&eyedir);
212        if axis.len2() == 0.0 {
213            // same direction as basedir
214            self.rotation = Quaternion::<f32>::ONE;
215            return;
216        }
217        let angle = basedir.dot(eyedir).acos();
218        self.rotation = Quaternion::<f32>::new(-angle, axis.normalize());
219    }
220}
221impl Default for Camera {
222    /// Default value of the Camera, that has identity view transform and Perspective projection with fov=60deg.
223    fn default() -> Self {
224        Camera {
225            projection: Some(ProjectionMethod::Perspective {
226                fov: 60.0f32.to_radians(),
227            }),
228            position: Vector3::ZERO,
229            rotation: Quaternion::ONE,
230            depth_range: 0.0..1.0,
231        }
232    }
233}