1 | use std::f64::consts::PI; |
2 | use std::ops::Mul; |
3 | |
4 | /// The projection matrix which is used to project the 3D space to the 2D display panel |
5 | #[derive (Clone, Debug, Copy)] |
6 | pub struct ProjectionMatrix([[f64; 4]; 4]); |
7 | |
8 | impl AsRef<[[f64; 4]; 4]> for ProjectionMatrix { |
9 | fn as_ref(&self) -> &[[f64; 4]; 4] { |
10 | &self.0 |
11 | } |
12 | } |
13 | |
14 | impl AsMut<[[f64; 4]; 4]> for ProjectionMatrix { |
15 | fn as_mut(&mut self) -> &mut [[f64; 4]; 4] { |
16 | &mut self.0 |
17 | } |
18 | } |
19 | |
20 | impl From<[[f64; 4]; 4]> for ProjectionMatrix { |
21 | fn from(data: [[f64; 4]; 4]) -> Self { |
22 | ProjectionMatrix(data) |
23 | } |
24 | } |
25 | |
26 | impl Default for ProjectionMatrix { |
27 | fn default() -> Self { |
28 | ProjectionMatrix::rotate(PI, y:0.0, z:0.0) |
29 | } |
30 | } |
31 | |
32 | impl Mul<ProjectionMatrix> for ProjectionMatrix { |
33 | type Output = ProjectionMatrix; |
34 | fn mul(self, other: ProjectionMatrix) -> ProjectionMatrix { |
35 | let mut ret: ProjectionMatrix = ProjectionMatrix::zero(); |
36 | for r: usize in 0..4 { |
37 | for c: usize in 0..4 { |
38 | for k: usize in 0..4 { |
39 | ret.0[r][c] += other.0[r][k] * self.0[k][c]; |
40 | } |
41 | } |
42 | } |
43 | ret.normalize(); |
44 | ret |
45 | } |
46 | } |
47 | |
48 | impl Mul<(i32, i32, i32)> for ProjectionMatrix { |
49 | type Output = (i32, i32); |
50 | fn mul(self, (x: i32, y: i32, z: i32): (i32, i32, i32)) -> (i32, i32) { |
51 | let (x: f64, y: f64, z: f64) = (x as f64, y as f64, z as f64); |
52 | let m: [[f64; 4]; 4] = self.0; |
53 | ( |
54 | (x * m[0][0] + y * m[0][1] + z * m[0][2] + m[0][3]) as i32, |
55 | (x * m[1][0] + y * m[1][1] + z * m[1][2] + m[1][3]) as i32, |
56 | ) |
57 | } |
58 | } |
59 | |
60 | impl Mul<(f64, f64, f64)> for ProjectionMatrix { |
61 | type Output = (i32, i32); |
62 | fn mul(self, (x: f64, y: f64, z: f64): (f64, f64, f64)) -> (i32, i32) { |
63 | let m: [[f64; 4]; 4] = self.0; |
64 | ( |
65 | (x * m[0][0] + y * m[0][1] + z * m[0][2] + m[0][3]) as i32, |
66 | (x * m[1][0] + y * m[1][1] + z * m[1][2] + m[1][3]) as i32, |
67 | ) |
68 | } |
69 | } |
70 | |
71 | impl ProjectionMatrix { |
72 | /// Returns the identity matrix |
73 | pub fn one() -> Self { |
74 | ProjectionMatrix([ |
75 | [1.0, 0.0, 0.0, 0.0], |
76 | [0.0, 1.0, 0.0, 0.0], |
77 | [0.0, 0.0, 1.0, 0.0], |
78 | [0.0, 0.0, 0.0, 1.0], |
79 | ]) |
80 | } |
81 | /// Returns the zero maxtrix |
82 | pub fn zero() -> Self { |
83 | ProjectionMatrix([[0.0; 4]; 4]) |
84 | } |
85 | /// Returns the matrix which shift the coordinate |
86 | pub fn shift(x: f64, y: f64, z: f64) -> Self { |
87 | ProjectionMatrix([ |
88 | [1.0, 0.0, 0.0, x], |
89 | [0.0, 1.0, 0.0, y], |
90 | [0.0, 0.0, 1.0, z], |
91 | [0.0, 0.0, 0.0, 1.0], |
92 | ]) |
93 | } |
94 | /// Returns the matrix which rotates the coordinate |
95 | #[allow (clippy::many_single_char_names)] |
96 | pub fn rotate(x: f64, y: f64, z: f64) -> Self { |
97 | let (c, b, a) = (x, y, z); |
98 | ProjectionMatrix([ |
99 | [ |
100 | a.cos() * b.cos(), |
101 | a.cos() * b.sin() * c.sin() - a.sin() * c.cos(), |
102 | a.cos() * b.sin() * c.cos() + a.sin() * c.sin(), |
103 | 0.0, |
104 | ], |
105 | [ |
106 | a.sin() * b.cos(), |
107 | a.sin() * b.sin() * c.sin() + a.cos() * c.cos(), |
108 | a.sin() * b.sin() * c.cos() - a.cos() * c.sin(), |
109 | 0.0, |
110 | ], |
111 | [-b.sin(), b.cos() * c.sin(), b.cos() * c.cos(), 0.0], |
112 | [0.0, 0.0, 0.0, 1.0], |
113 | ]) |
114 | } |
115 | /// Returns the matrix that applies a scale factor |
116 | pub fn scale(factor: f64) -> Self { |
117 | ProjectionMatrix([ |
118 | [1.0, 0.0, 0.0, 0.0], |
119 | [0.0, 1.0, 0.0, 0.0], |
120 | [0.0, 0.0, 1.0, 0.0], |
121 | [0.0, 0.0, 0.0, 1.0 / factor], |
122 | ]) |
123 | } |
124 | /// Normalize the matrix, this will make the metric unit to 1 |
125 | pub fn normalize(&mut self) { |
126 | if self.0[3][3] > 1e-20 { |
127 | for r in 0..4 { |
128 | for c in 0..4 { |
129 | self.0[r][c] /= self.0[3][3]; |
130 | } |
131 | } |
132 | } |
133 | } |
134 | |
135 | /// Get the distance of the point in guest coordinate from the screen in pixels |
136 | pub fn projected_depth(&self, (x, y, z): (i32, i32, i32)) -> i32 { |
137 | let r = &self.0[2]; |
138 | (r[0] * x as f64 + r[1] * y as f64 + r[2] * z as f64 + r[3]) as i32 |
139 | } |
140 | } |
141 | |
142 | /// The helper struct to build a projection matrix |
143 | #[derive (Copy, Clone)] |
144 | pub struct ProjectionMatrixBuilder { |
145 | /// Specifies the yaw of the 3D coordinate system |
146 | pub yaw: f64, |
147 | /// Specifies the pitch of the 3D coordinate system |
148 | pub pitch: f64, |
149 | /// Specifies the scale of the 3D coordinate system |
150 | pub scale: f64, |
151 | pivot_before: (i32, i32, i32), |
152 | pivot_after: (i32, i32), |
153 | } |
154 | |
155 | impl Default for ProjectionMatrixBuilder { |
156 | fn default() -> Self { |
157 | Self { |
158 | yaw: 0.5, |
159 | pitch: 0.15, |
160 | scale: 1.0, |
161 | pivot_after: (0, 0), |
162 | pivot_before: (0, 0, 0), |
163 | } |
164 | } |
165 | } |
166 | |
167 | impl ProjectionMatrixBuilder { |
168 | /// Creates a new, default projection matrix builder object. |
169 | pub fn new() -> Self { |
170 | Self::default() |
171 | } |
172 | |
173 | /// Set the pivot point, which means the 3D coordinate "before" should be mapped into |
174 | /// the 2D coordinatet "after" |
175 | pub fn set_pivot(&mut self, before: (i32, i32, i32), after: (i32, i32)) -> &mut Self { |
176 | self.pivot_before = before; |
177 | self.pivot_after = after; |
178 | self |
179 | } |
180 | |
181 | /// Build the matrix based on the configuration |
182 | pub fn into_matrix(self) -> ProjectionMatrix { |
183 | let mut ret = if self.pivot_before == (0, 0, 0) { |
184 | ProjectionMatrix::default() |
185 | } else { |
186 | let (x, y, z) = self.pivot_before; |
187 | ProjectionMatrix::shift(-x as f64, -y as f64, -z as f64) * ProjectionMatrix::default() |
188 | }; |
189 | |
190 | if self.yaw.abs() > 1e-20 { |
191 | ret = ret * ProjectionMatrix::rotate(0.0, self.yaw, 0.0); |
192 | } |
193 | |
194 | if self.pitch.abs() > 1e-20 { |
195 | ret = ret * ProjectionMatrix::rotate(self.pitch, 0.0, 0.0); |
196 | } |
197 | |
198 | if (self.scale - 1.0).abs() > 1e-20 { |
199 | ret = ret * ProjectionMatrix::scale(self.scale); |
200 | } |
201 | |
202 | if self.pivot_after != (0, 0) { |
203 | let (x, y) = self.pivot_after; |
204 | ret = ret * ProjectionMatrix::shift(x as f64, y as f64, 0.0); |
205 | } |
206 | |
207 | ret |
208 | } |
209 | } |
210 | |