1 | // pathfinder/geometry/src/basic/transform3d.rs |
2 | // |
3 | // Copyright © 2019 The Pathfinder Project Developers. |
4 | // |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
8 | // option. This file may not be copied, modified, or distributed |
9 | // except according to those terms. |
10 | |
11 | //! 3D transforms that can be applied to paths. |
12 | |
13 | use crate::rect::RectF; |
14 | use crate::transform2d::Matrix2x2F; |
15 | use crate::vector::{Vector2F, Vector2I, Vector3F, Vector4F}; |
16 | use pathfinder_simd::default::F32x4; |
17 | use std::ops::{Add, Mul, MulAssign, Neg}; |
18 | |
19 | /// An transform, optimized with SIMD. |
20 | /// |
21 | /// In column-major order. |
22 | #[derive (Clone, Copy, Debug, PartialEq)] |
23 | #[repr (C)] |
24 | pub struct Transform4F { |
25 | pub c0: F32x4, |
26 | pub c1: F32x4, |
27 | pub c2: F32x4, |
28 | pub c3: F32x4, |
29 | } |
30 | |
31 | impl Default for Transform4F { |
32 | #[inline ] |
33 | fn default() -> Transform4F { |
34 | Transform4F { |
35 | c0: F32x4::new(a:1.0, b:0.0, c:0.0, d:0.0), |
36 | c1: F32x4::new(a:0.0, b:1.0, c:0.0, d:0.0), |
37 | c2: F32x4::new(a:0.0, b:0.0, c:1.0, d:0.0), |
38 | c3: F32x4::new(a:0.0, b:0.0, c:0.0, d:1.0), |
39 | } |
40 | } |
41 | } |
42 | |
43 | impl Transform4F { |
44 | #[inline ] |
45 | pub fn row_major( |
46 | m00: f32, |
47 | m01: f32, |
48 | m02: f32, |
49 | m03: f32, |
50 | m10: f32, |
51 | m11: f32, |
52 | m12: f32, |
53 | m13: f32, |
54 | m20: f32, |
55 | m21: f32, |
56 | m22: f32, |
57 | m23: f32, |
58 | m30: f32, |
59 | m31: f32, |
60 | m32: f32, |
61 | m33: f32, |
62 | ) -> Transform4F { |
63 | Transform4F { |
64 | c0: F32x4::new(m00, m10, m20, m30), |
65 | c1: F32x4::new(m01, m11, m21, m31), |
66 | c2: F32x4::new(m02, m12, m22, m32), |
67 | c3: F32x4::new(m03, m13, m23, m33), |
68 | } |
69 | } |
70 | |
71 | #[inline ] |
72 | pub fn from_scale(scale: Vector4F) -> Transform4F { |
73 | Transform4F { |
74 | c0: F32x4::new(scale.x(), 0.0, 0.0, 0.0), |
75 | c1: F32x4::new(0.0, scale.y(), 0.0, 0.0), |
76 | c2: F32x4::new(0.0, 0.0, scale.z(), 0.0), |
77 | c3: F32x4::new(0.0, 0.0, 0.0, 1.0), |
78 | } |
79 | } |
80 | |
81 | #[inline ] |
82 | pub fn from_uniform_scale(factor: f32) -> Transform4F { |
83 | Transform4F::from_scale(Vector4F::splat(factor)) |
84 | } |
85 | |
86 | #[inline ] |
87 | pub fn from_translation(mut translation: Vector4F) -> Transform4F { |
88 | translation.set_w(1.0); |
89 | Transform4F { c3: translation.0, ..Transform4F::default() } |
90 | } |
91 | |
92 | // TODO(pcwalton): Optimize. |
93 | pub fn from_rotation(yaw: f32, pitch: f32, roll: f32) -> Transform4F { |
94 | let (cos_b, sin_b) = (yaw.cos(), yaw.sin()); |
95 | let (cos_c, sin_c) = (pitch.cos(), pitch.sin()); |
96 | let (cos_a, sin_a) = (roll.cos(), roll.sin()); |
97 | let m00 = cos_a * cos_b; |
98 | let m01 = cos_a * sin_b * sin_c - sin_a * cos_c; |
99 | let m02 = cos_a * sin_b * cos_c + sin_a * sin_c; |
100 | let m10 = sin_a * cos_b; |
101 | let m11 = sin_a * sin_b * sin_c + cos_a * cos_c; |
102 | let m12 = sin_a * sin_b * cos_c - cos_a * sin_c; |
103 | let m20 = -sin_b; |
104 | let m21 = cos_b * sin_c; |
105 | let m22 = cos_b * cos_c; |
106 | Transform4F::row_major( |
107 | m00, m01, m02, 0.0, m10, m11, m12, 0.0, m20, m21, m22, 0.0, 0.0, 0.0, 0.0, 1.0, |
108 | ) |
109 | } |
110 | |
111 | /// Creates a rotation matrix from the given quaternion. |
112 | /// |
113 | /// The quaternion is expected to be packed into a SIMD type (x, y, z, w) corresponding to |
114 | /// x + yi + zj + wk. |
115 | pub fn from_rotation_quaternion(q: F32x4) -> Transform4F { |
116 | // TODO(pcwalton): Optimize better with more shuffles. |
117 | let (mut sq, mut w, mut xy_xz_yz) = (q * q, q.wwww() * q, q.xxyy() * q.yzzy()); |
118 | sq += sq; |
119 | w += w; |
120 | xy_xz_yz += xy_xz_yz; |
121 | let diag = F32x4::splat(1.0) - (sq.yxxy() + sq.zzyy()); |
122 | let (wx2, wy2, wz2) = (w.x(), w.y(), w.z()); |
123 | let (xy2, xz2, yz2) = (xy_xz_yz.x(), xy_xz_yz.y(), xy_xz_yz.z()); |
124 | Transform4F::row_major( |
125 | diag.x(), |
126 | xy2 - wz2, |
127 | xz2 + wy2, |
128 | 0.0, |
129 | xy2 + wz2, |
130 | diag.y(), |
131 | yz2 - wx2, |
132 | 0.0, |
133 | xz2 - wy2, |
134 | yz2 + wx2, |
135 | diag.z(), |
136 | 0.0, |
137 | 0.0, |
138 | 0.0, |
139 | 0.0, |
140 | 1.0, |
141 | ) |
142 | } |
143 | |
144 | /// Just like `glOrtho()`. |
145 | #[inline ] |
146 | pub fn from_ortho( |
147 | left: f32, |
148 | right: f32, |
149 | bottom: f32, |
150 | top: f32, |
151 | near_val: f32, |
152 | far_val: f32, |
153 | ) -> Transform4F { |
154 | let x_inv = 1.0 / (right - left); |
155 | let y_inv = 1.0 / (top - bottom); |
156 | let z_inv = 1.0 / (far_val - near_val); |
157 | let tx = -(right + left) * x_inv; |
158 | let ty = -(top + bottom) * y_inv; |
159 | let tz = -(far_val + near_val) * z_inv; |
160 | Transform4F::row_major( |
161 | 2.0 * x_inv, |
162 | 0.0, |
163 | 0.0, |
164 | tx, |
165 | 0.0, |
166 | 2.0 * y_inv, |
167 | 0.0, |
168 | ty, |
169 | 0.0, |
170 | 0.0, |
171 | -2.0 * z_inv, |
172 | tz, |
173 | 0.0, |
174 | 0.0, |
175 | 0.0, |
176 | 1.0, |
177 | ) |
178 | } |
179 | |
180 | /// Linearly interpolate between transforms |
181 | pub fn lerp(&self, weight: f32, other: &Transform4F) -> Transform4F { |
182 | let c0 = self.c0 * F32x4::splat(weight) + other.c0 * F32x4::splat(1.0 - weight); |
183 | let c1 = self.c1 * F32x4::splat(weight) + other.c1 * F32x4::splat(1.0 - weight); |
184 | let c2 = self.c2 * F32x4::splat(weight) + other.c2 * F32x4::splat(1.0 - weight); |
185 | let c3 = self.c3 * F32x4::splat(weight) + other.c3 * F32x4::splat(1.0 - weight); |
186 | Transform4F { c0, c1, c2, c3 } |
187 | } |
188 | |
189 | /// Just like `gluPerspective()`. |
190 | #[inline ] |
191 | pub fn from_perspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) -> Transform4F { |
192 | let f = 1.0 / (fov_y * 0.5).tan(); |
193 | let z_denom = 1.0 / (z_near - z_far); |
194 | let m00 = f / aspect; |
195 | let m11 = f; |
196 | let m22 = (z_far + z_near) * z_denom; |
197 | let m23 = 2.0 * z_far * z_near * z_denom; |
198 | let m32 = -1.0; |
199 | Transform4F::row_major( |
200 | m00, 0.0, 0.0, 0.0, 0.0, m11, 0.0, 0.0, 0.0, 0.0, m22, m23, 0.0, 0.0, m32, 0.0, |
201 | ) |
202 | } |
203 | |
204 | /// Just like `gluLookAt()`. |
205 | #[inline ] |
206 | pub fn looking_at(eye: Vector3F, center: Vector3F, mut up: Vector3F) -> Transform4F { |
207 | let f = (center - eye).normalize(); |
208 | up = up.normalize(); |
209 | let s = f.cross(up); |
210 | let u = s.normalize().cross(f); |
211 | let minus_f = -f; |
212 | |
213 | // TODO(pcwalton): Use SIMD. This needs a matrix transpose: |
214 | // https://fgiesen.wordpress.com/2013/07/09/simd-transposes-1/ |
215 | let transform = Transform4F::row_major(s.x(), s.y(), s.z(), 0.0, |
216 | u.x(), u.y(), u.z(), 0.0, |
217 | minus_f.x(), minus_f.y(), minus_f.z(), 0.0, |
218 | 0.0, 0.0, 0.0, 1.0) * |
219 | Transform4F::from_translation((-eye).to_4d()); |
220 | transform |
221 | } |
222 | |
223 | // +- -+ |
224 | // | A B | |
225 | // | C D | |
226 | // +- -+ |
227 | #[inline ] |
228 | pub fn from_submatrices( |
229 | a: Matrix2x2F, |
230 | b: Matrix2x2F, |
231 | c: Matrix2x2F, |
232 | d: Matrix2x2F, |
233 | ) -> Transform4F { |
234 | Transform4F { |
235 | c0: a.0.concat_xy_xy(c.0), |
236 | c1: a.0.concat_zw_zw(c.0), |
237 | c2: b.0.concat_xy_xy(d.0), |
238 | c3: b.0.concat_zw_zw(d.0), |
239 | } |
240 | } |
241 | |
242 | #[inline ] |
243 | pub fn rotate(&self, yaw: f32, pitch: f32, roll: f32) -> Transform4F { |
244 | Transform4F::from_rotation(yaw, pitch, roll) * *self |
245 | } |
246 | |
247 | #[inline ] |
248 | pub fn scale(&self, scale: Vector4F) -> Transform4F { |
249 | Transform4F::from_scale(scale) * *self |
250 | } |
251 | |
252 | #[inline ] |
253 | pub fn uniform_scale(&self, scale: f32) -> Transform4F { |
254 | Transform4F::from_uniform_scale(scale) * *self |
255 | } |
256 | |
257 | #[inline ] |
258 | pub fn translate(&self, translation: Vector4F) -> Transform4F { |
259 | Transform4F::from_translation(translation) * *self |
260 | } |
261 | |
262 | #[inline ] |
263 | pub fn upper_left(&self) -> Matrix2x2F { |
264 | Matrix2x2F(self.c0.concat_xy_xy(self.c1)) |
265 | } |
266 | |
267 | #[inline ] |
268 | pub fn upper_right(&self) -> Matrix2x2F { |
269 | Matrix2x2F(self.c2.concat_xy_xy(self.c3)) |
270 | } |
271 | |
272 | #[inline ] |
273 | pub fn lower_left(&self) -> Matrix2x2F { |
274 | Matrix2x2F(self.c0.concat_zw_zw(self.c1)) |
275 | } |
276 | |
277 | #[inline ] |
278 | pub fn lower_right(&self) -> Matrix2x2F { |
279 | Matrix2x2F(self.c2.concat_zw_zw(self.c3)) |
280 | } |
281 | |
282 | // https://en.wikipedia.org/wiki/Invertible_matrix#Blockwise_inversion |
283 | // |
284 | // If A is the upper left submatrix of this matrix, this method assumes that A and the Schur |
285 | // complement of A are invertible. |
286 | pub fn inverse(&self) -> Transform4F { |
287 | // Extract submatrices. |
288 | let (a, b) = (self.upper_left(), self.upper_right()); |
289 | let (c, d) = (self.lower_left(), self.lower_right()); |
290 | |
291 | // Compute temporary matrices. |
292 | let a_inv = a.inverse(); |
293 | let x = c * a_inv; |
294 | let y = (d - x * b).inverse(); |
295 | let z = a_inv * b; |
296 | |
297 | // Compute new submatrices. |
298 | let (a_new, b_new) = (a_inv + z * y * x, -z * y); |
299 | let (c_new, d_new) = (-y * x, y); |
300 | |
301 | // Construct inverse. |
302 | Transform4F::from_submatrices(a_new, b_new, c_new, d_new) |
303 | } |
304 | |
305 | pub fn approx_eq(&self, other: &Transform4F, epsilon: f32) -> bool { |
306 | self.c0.approx_eq(other.c0, epsilon) |
307 | && self.c1.approx_eq(other.c1, epsilon) |
308 | && self.c2.approx_eq(other.c2, epsilon) |
309 | && self.c3.approx_eq(other.c3, epsilon) |
310 | } |
311 | |
312 | #[inline ] |
313 | pub fn as_ptr(&self) -> *const f32 { |
314 | (&self.c0) as *const F32x4 as *const f32 |
315 | } |
316 | |
317 | #[inline ] |
318 | pub fn to_columns(&self) -> [F32x4; 4] { |
319 | [self.c0, self.c1, self.c2, self.c3] |
320 | } |
321 | } |
322 | |
323 | impl Mul<Transform4F> for Transform4F { |
324 | type Output = Transform4F; |
325 | |
326 | // https://stackoverflow.com/a/18508113 |
327 | #[inline ] |
328 | fn mul(self, other: Transform4F) -> Transform4F { |
329 | return Transform4F { |
330 | c0: mul_col(&self, b_col:other.c0), |
331 | c1: mul_col(&self, b_col:other.c1), |
332 | c2: mul_col(&self, b_col:other.c2), |
333 | c3: mul_col(&self, b_col:other.c3), |
334 | }; |
335 | |
336 | #[inline ] |
337 | fn mul_col(a: &Transform4F, b_col: F32x4) -> F32x4 { |
338 | a.c0 * b_col.xxxx() + a.c1 * b_col.yyyy() + a.c2 * b_col.zzzz() + a.c3 * b_col.wwww() |
339 | } |
340 | } |
341 | } |
342 | |
343 | impl Mul<Vector4F> for Transform4F { |
344 | type Output = Vector4F; |
345 | |
346 | #[inline ] |
347 | fn mul(self, vector: Vector4F) -> Vector4F { |
348 | let term0: F32x4 = self.c0 * F32x4::splat(vector.x()); |
349 | let term1: F32x4 = self.c1 * F32x4::splat(vector.y()); |
350 | let term2: F32x4 = self.c2 * F32x4::splat(vector.z()); |
351 | let term3: F32x4 = self.c3 * F32x4::splat(vector.w()); |
352 | Vector4F(term0 + term1 + term2 + term3) |
353 | } |
354 | } |
355 | |
356 | impl MulAssign<Transform4F> for Transform4F { |
357 | fn mul_assign(&mut self, other: Transform4F) { |
358 | *self = *self * other |
359 | } |
360 | } |
361 | |
362 | impl Add<Matrix2x2F> for Matrix2x2F { |
363 | type Output = Matrix2x2F; |
364 | #[inline ] |
365 | fn add(self, other: Matrix2x2F) -> Matrix2x2F { |
366 | Matrix2x2F(self.0 + other.0) |
367 | } |
368 | } |
369 | |
370 | impl Neg for Matrix2x2F { |
371 | type Output = Matrix2x2F; |
372 | #[inline ] |
373 | fn neg(self) -> Matrix2x2F { |
374 | Matrix2x2F(-self.0) |
375 | } |
376 | } |
377 | |
378 | #[derive (Clone, Copy, Debug)] |
379 | pub struct Perspective { |
380 | pub transform: Transform4F, |
381 | pub window_size: Vector2I, |
382 | } |
383 | |
384 | impl Perspective { |
385 | #[inline ] |
386 | pub fn new(transform: &Transform4F, window_size: Vector2I) -> Perspective { |
387 | Perspective { |
388 | transform: *transform, |
389 | window_size, |
390 | } |
391 | } |
392 | } |
393 | |
394 | impl Mul<Transform4F> for Perspective { |
395 | type Output = Perspective; |
396 | #[inline ] |
397 | fn mul(self, other: Transform4F) -> Perspective { |
398 | Perspective { |
399 | transform: self.transform * other, |
400 | window_size: self.window_size, |
401 | } |
402 | } |
403 | } |
404 | |
405 | impl Mul<Vector2F> for Perspective { |
406 | type Output = Vector2F; |
407 | #[inline ] |
408 | fn mul(self, vector: Vector2F) -> Vector2F { |
409 | let point: Vector2F = (self.transform * vector.to_4d()).to_2d() * Vector2F::new(x:1.0, y:-1.0); |
410 | (point + 1.0) * self.window_size.to_f32() * 0.5 |
411 | } |
412 | } |
413 | |
414 | impl Mul<RectF> for Perspective { |
415 | type Output = RectF; |
416 | #[inline ] |
417 | fn mul(self, rect: RectF) -> RectF { |
418 | let (upper_left: Vector2F, upper_right: Vector2F) = (self * rect.origin(), self * rect.upper_right()); |
419 | let (lower_left: Vector2F, lower_right: Vector2F) = (self * rect.lower_left(), self * rect.lower_right()); |
420 | let min_point: Vector2F = upper_left.min(upper_right).min(lower_left).min(lower_right); |
421 | let max_point: Vector2F = upper_left.max(upper_right).max(lower_left).max(lower_right); |
422 | RectF::from_points(origin:min_point, lower_right:max_point) |
423 | } |
424 | } |
425 | |
426 | #[cfg (test)] |
427 | mod test { |
428 | use crate::vector::Vector4F; |
429 | use crate::transform3d::Transform4F; |
430 | |
431 | #[test ] |
432 | fn test_post_mul() { |
433 | let a = Transform4F::row_major( |
434 | 3.0, 1.0, 4.0, 5.0, 9.0, 2.0, 6.0, 5.0, 3.0, 5.0, 8.0, 9.0, 7.0, 9.0, 3.0, 2.0, |
435 | ); |
436 | let b = Transform4F::row_major( |
437 | 3.0, 8.0, 4.0, 6.0, 2.0, 6.0, 4.0, 3.0, 3.0, 8.0, 3.0, 2.0, 7.0, 9.0, 5.0, 0.0, |
438 | ); |
439 | let c = Transform4F::row_major( |
440 | 58.0, 107.0, 53.0, 29.0, 84.0, 177.0, 87.0, 72.0, 106.0, 199.0, 101.0, 49.0, 62.0, |
441 | 152.0, 83.0, 75.0, |
442 | ); |
443 | assert_eq!(a * b, c); |
444 | } |
445 | |
446 | #[test ] |
447 | fn test_pre_mul() { |
448 | let a = Transform4F::row_major( |
449 | 3.0, 1.0, 4.0, 5.0, 9.0, 2.0, 6.0, 5.0, 3.0, 5.0, 8.0, 9.0, 7.0, 9.0, 3.0, 2.0, |
450 | ); |
451 | let b = Transform4F::row_major( |
452 | 3.0, 8.0, 4.0, 6.0, 2.0, 6.0, 4.0, 3.0, 3.0, 8.0, 3.0, 2.0, 7.0, 9.0, 5.0, 0.0, |
453 | ); |
454 | let c = Transform4F::row_major( |
455 | 135.0, 93.0, 110.0, 103.0, 93.0, 61.0, 85.0, 82.0, 104.0, 52.0, 90.0, 86.0, 117.0, |
456 | 50.0, 122.0, 125.0, |
457 | ); |
458 | assert_eq!(b * a, c); |
459 | } |
460 | |
461 | #[test ] |
462 | fn test_transform_point() { |
463 | let a = Transform4F::row_major( |
464 | 3.0, 1.0, 4.0, 5.0, 9.0, 2.0, 6.0, 5.0, 3.0, 5.0, 8.0, 9.0, 7.0, 9.0, 3.0, 2.0, |
465 | ); |
466 | let p = Vector4F::new(3.0, 8.0, 4.0, 6.0); |
467 | let q = Vector4F::new(63.0, 97.0, 135.0, 117.0); |
468 | assert_eq!(a * p, q); |
469 | } |
470 | |
471 | #[test ] |
472 | fn test_inverse() { |
473 | // Random matrix. |
474 | let m = Transform4F::row_major( |
475 | 0.86277982, 0.15986552, 0.90739898, 0.60066808, 0.17386167, 0.016353, 0.8535783, |
476 | 0.12969608, 0.0946466, 0.43248631, 0.63480505, 0.08154603, 0.50305436, 0.48359687, |
477 | 0.51057162, 0.24812012, |
478 | ); |
479 | let p0 = Vector4F::new(0.95536648, 0.80633691, 0.16357357, 0.5477598); |
480 | let p1 = m * p0; |
481 | let m_inv = m.inverse(); |
482 | let m_inv_exp = Transform4F::row_major( |
483 | -2.47290136, |
484 | 3.48865688, |
485 | -6.12298336, |
486 | 6.17536696, |
487 | 0.00124033357, |
488 | -1.72561993, |
489 | 2.16876606, |
490 | 0.186227748, |
491 | -0.375021729, |
492 | 1.53883017, |
493 | -0.0558194403, |
494 | 0.121857058, |
495 | 5.78300323, |
496 | -6.87635769, |
497 | 8.30196620, |
498 | -9.10374060, |
499 | ); |
500 | assert!(m_inv.approx_eq(&m_inv_exp, 0.0001)); |
501 | let p2 = m_inv * p1; |
502 | assert!(p0.approx_eq(p2, 0.0001)); |
503 | } |
504 | } |
505 | |