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}