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(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
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, 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
343impl 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
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: 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
414impl 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)]
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}
505