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 { as_ref(&self) -> &[[f64; 4]; 4]9 fn as_ref(&self) -> &[[f64; 4]; 4] { 10 &self.0 11 } 12 } 13 14 impl AsMut<[[f64; 4]; 4]> for ProjectionMatrix { as_mut(&mut self) -> &mut [[f64; 4]; 4]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 { from(data: [[f64; 4]; 4]) -> Self21 fn from(data: [[f64; 4]; 4]) -> Self { 22 ProjectionMatrix(data) 23 } 24 } 25 26 impl Default for ProjectionMatrix { default() -> Self27 fn default() -> Self { 28 ProjectionMatrix::rotate(PI, 0.0, 0.0) 29 } 30 } 31 32 impl Mul<ProjectionMatrix> for ProjectionMatrix { 33 type Output = ProjectionMatrix; mul(self, other: ProjectionMatrix) -> ProjectionMatrix34 fn mul(self, other: ProjectionMatrix) -> ProjectionMatrix { 35 let mut ret = ProjectionMatrix::zero(); 36 for r in 0..4 { 37 for c in 0..4 { 38 for k 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); mul(self, (x, y, z): (i32, i32, i32)) -> (i32, i32)50 fn mul(self, (x, y, z): (i32, i32, i32)) -> (i32, i32) { 51 let (x, y, z) = (x as f64, y as f64, z as f64); 52 let m = 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); mul(self, (x, y, z): (f64, f64, f64)) -> (i32, i32)62 fn mul(self, (x, y, z): (f64, f64, f64)) -> (i32, i32) { 63 let m = 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 one() -> Self73 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 zero() -> Self82 pub fn zero() -> Self { 83 ProjectionMatrix([[0.0; 4]; 4]) 84 } 85 /// Returns the matrix which shift the coordinate shift(x: f64, y: f64, z: f64) -> Self86 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 rotate(x: f64, y: f64, z: f64) -> Self95 pub fn rotate(x: f64, y: f64, z: f64) -> Self { 96 let (c, b, a) = (x, y, z); 97 ProjectionMatrix([ 98 [ 99 a.cos() * b.cos(), 100 a.cos() * b.sin() * c.sin() - a.sin() * c.cos(), 101 a.cos() * b.sin() * c.cos() + a.sin() * c.sin(), 102 0.0, 103 ], 104 [ 105 a.sin() * b.cos(), 106 a.sin() * b.sin() * c.sin() + a.cos() * c.cos(), 107 a.sin() * b.sin() * c.cos() - a.cos() * c.sin(), 108 0.0, 109 ], 110 [-b.sin(), b.cos() * c.sin(), b.cos() * c.cos(), 0.0], 111 [0.0, 0.0, 0.0, 1.0], 112 ]) 113 } 114 /// Returns the matrix that applies a scale factor scale(factor: f64) -> Self115 pub fn scale(factor: f64) -> Self { 116 ProjectionMatrix([ 117 [1.0, 0.0, 0.0, 0.0], 118 [0.0, 1.0, 0.0, 0.0], 119 [0.0, 0.0, 1.0, 0.0], 120 [0.0, 0.0, 0.0, 1.0 / factor], 121 ]) 122 } 123 /// Normalize the matrix, this will make the metric unit to 1 normalize(&mut self)124 pub fn normalize(&mut self) { 125 if self.0[3][3] > 1e-20 { 126 for r in 0..4 { 127 for c in 0..4 { 128 self.0[r][c] /= self.0[3][3]; 129 } 130 } 131 } 132 } 133 134 /// Get the distance of the point in guest coordinate from the screen in pixels projected_depth(&self, (x, y, z): (i32, i32, i32)) -> i32135 pub fn projected_depth(&self, (x, y, z): (i32, i32, i32)) -> i32 { 136 let r = &self.0[2]; 137 (r[0] * x as f64 + r[1] * y as f64 + r[2] * z as f64 + r[3]) as i32 138 } 139 } 140 141 /// The helper struct to build a projection matrix 142 #[derive(Copy, Clone)] 143 pub struct ProjectionMatrixBuilder { 144 pub yaw: f64, 145 pub pitch: f64, 146 pub scale: f64, 147 pivot_before: (i32, i32, i32), 148 pivot_after: (i32, i32), 149 } 150 151 impl ProjectionMatrixBuilder { new() -> Self152 pub fn new() -> Self { 153 Self { 154 yaw: 0.5, 155 pitch: 0.15, 156 scale: 1.0, 157 pivot_after: (0, 0), 158 pivot_before: (0, 0, 0), 159 } 160 } 161 162 /// Set the pivot point, which means the 3D coordinate "before" should be mapped into 163 /// the 2D coordinatet "after" set_pivot(&mut self, before: (i32, i32, i32), after: (i32, i32)) -> &mut Self164 pub fn set_pivot(&mut self, before: (i32, i32, i32), after: (i32, i32)) -> &mut Self { 165 self.pivot_before = before; 166 self.pivot_after = after; 167 self 168 } 169 170 /// Build the matrix based on the configuration into_matrix(self) -> ProjectionMatrix171 pub fn into_matrix(self) -> ProjectionMatrix { 172 let mut ret = if self.pivot_before == (0, 0, 0) { 173 ProjectionMatrix::default() 174 } else { 175 let (x, y, z) = self.pivot_before; 176 ProjectionMatrix::shift(-x as f64, -y as f64, -z as f64) * ProjectionMatrix::default() 177 }; 178 179 if self.yaw.abs() > 1e-20 { 180 ret = ret * ProjectionMatrix::rotate(0.0, self.yaw, 0.0); 181 } 182 183 if self.pitch.abs() > 1e-20 { 184 ret = ret * ProjectionMatrix::rotate(self.pitch, 0.0, 0.0); 185 } 186 187 if (self.scale - 1.0).abs() > 1e-20 { 188 ret = ret * ProjectionMatrix::scale(self.scale); 189 } 190 191 if self.pivot_after != (0, 0) { 192 let (x, y) = self.pivot_after; 193 ret = ret * ProjectionMatrix::shift(x as f64, y as f64, 0.0); 194 } 195 196 ret 197 } 198 } 199