pathfinder_geometry/
transform3d.rs

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
13use crate::rect::RectF;
14use crate::transform2d::Matrix2x2F;
15use crate::vector::{Vector2F, Vector2I, Vector3F, Vector4F};
16use pathfinder_simd::default::F32x4;
17use 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)]
24pub struct Transform4F {
25    pub c0: F32x4,
26    pub c1: F32x4,
27    pub c2: F32x4,
28    pub c3: F32x4,
29}
30
31impl Default for Transform4F {
32    #[inline]
33    fn default() -> Transform4F {
34        Transform4F {
35            c0: F32x4::new(1.0, 0.0, 0.0, 0.0),
36            c1: F32x4::new(0.0, 1.0, 0.0, 0.0),
37            c2: F32x4::new(0.0, 0.0, 1.0, 0.0),
38            c3: F32x4::new(0.0, 0.0, 0.0, 1.0),
39        }
40    }
41}
42
43impl 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
323impl 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, other.c0),
331            c1: mul_col(&self, other.c1),
332            c2: mul_col(&self, other.c2),
333            c3: mul_col(&self, 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
343impl Mul<Vector4F> for Transform4F {
344    type Output = Vector4F;
345
346    #[inline]
347    fn mul(self, vector: Vector4F) -> Vector4F {
348        let term0 = self.c0 * F32x4::splat(vector.x());
349        let term1 = self.c1 * F32x4::splat(vector.y());
350        let term2 = self.c2 * F32x4::splat(vector.z());
351        let term3 = self.c3 * F32x4::splat(vector.w());
352        Vector4F(term0 + term1 + term2 + term3)
353    }
354}
355
356impl MulAssign<Transform4F> for Transform4F {
357    fn mul_assign(&mut self, other: Transform4F) {
358        *self = *self * other
359    }
360}
361
362impl 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
370impl 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)]
379pub struct Perspective {
380    pub transform: Transform4F,
381    pub window_size: Vector2I,
382}
383
384impl 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
394impl 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
405impl Mul<Vector2F> for Perspective {
406    type Output = Vector2F;
407    #[inline]
408    fn mul(self, vector: Vector2F) -> Vector2F {
409        let point = (self.transform * vector.to_4d()).to_2d() * Vector2F::new(1.0, -1.0);
410        (point + 1.0) * self.window_size.to_f32() * 0.5
411    }
412}
413
414impl Mul<RectF> for Perspective {
415    type Output = RectF;
416    #[inline]
417    fn mul(self, rect: RectF) -> RectF {
418        let (upper_left, upper_right) = (self * rect.origin(),     self * rect.upper_right());
419        let (lower_left, lower_right) = (self * rect.lower_left(), self * rect.lower_right());
420        let min_point = upper_left.min(upper_right).min(lower_left).min(lower_right);
421        let max_point = upper_left.max(upper_right).max(lower_left).max(lower_right);
422        RectF::from_points(min_point, max_point)
423    }
424}
425
426#[cfg(test)]
427mod 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}