1use super::{Px, euclid};
2
3use zng_var::{
4 animation::{Transitionable, easing::EasingStep, is_slerp_enabled, slerp_enabled},
5 impl_from_and_into_var,
6};
7
8use super::{AngleRadian, AngleUnits, Factor, FactorUnits, Layout1d, Length, PxTransform};
9
10#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
26pub struct Transform {
27 parts: Vec<TransformPart>,
28 needs_layout: bool,
29 lerp_to: Vec<(Self, EasingStep, bool)>,
30}
31#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
32enum TransformPart {
33 Computed(PxTransform),
34 Translate(Length, Length),
35 Translate3d(Length, Length, Length),
36 Perspective(Length),
37}
38
39impl Transform {
40 pub fn identity() -> Self {
42 Self::default()
43 }
44
45 pub fn new_rotate<A: Into<AngleRadian>>(angle: A) -> Transform {
47 Transform::identity().rotate(angle)
48 }
49
50 pub fn new_rotate_x<A: Into<AngleRadian>>(angle: A) -> Transform {
52 Transform::identity().rotate_x(angle)
53 }
54
55 pub fn new_rotate_y<A: Into<AngleRadian>>(angle: A) -> Transform {
57 Transform::identity().rotate_y(angle)
58 }
59
60 pub fn new_rotate_z<A: Into<AngleRadian>>(angle: A) -> Transform {
62 Transform::identity().rotate_z(angle)
63 }
64
65 pub fn new_rotate_3d<A: Into<AngleRadian>>(x: f32, y: f32, z: f32, angle: A) -> Transform {
67 Transform::identity().rotate_3d(x, y, z, angle)
68 }
69
70 pub fn new_translate<X: Into<Length>, Y: Into<Length>>(x: X, y: Y) -> Transform {
72 Transform::identity().translate(x, y)
73 }
74
75 pub fn new_translate_3d<X: Into<Length>, Y: Into<Length>, Z: Into<Length>>(x: X, y: Y, z: Z) -> Transform {
77 Transform::identity().translate_3d(x, y, z)
78 }
79
80 pub fn new_translate_x<X: Into<Length>>(x: X) -> Transform {
82 Transform::new_translate(x, 0.0)
83 }
84
85 pub fn new_translate_y<Y: Into<Length>>(y: Y) -> Transform {
87 Transform::new_translate(0.0, y)
88 }
89
90 pub fn new_translate_z<Z: Into<Length>>(z: Z) -> Transform {
92 Transform::new_translate_3d(0.0, 0.0, z)
93 }
94
95 pub fn new_perspective<D: Into<Length>>(d: D) -> Transform {
97 Transform::identity().perspective(d)
98 }
99
100 pub fn new_skew<X: Into<AngleRadian>, Y: Into<AngleRadian>>(x: X, y: Y) -> Transform {
102 Transform::identity().skew(x, y)
103 }
104
105 pub fn new_skew_x<X: Into<AngleRadian>>(x: X) -> Transform {
107 Transform::new_skew(x, 0.rad())
108 }
109
110 pub fn new_skew_y<Y: Into<AngleRadian>>(y: Y) -> Transform {
112 Transform::new_skew(0.rad(), y)
113 }
114
115 pub fn new_scale<S: Into<Factor>>(scale: S) -> Transform {
119 let scale = scale.into();
120 Transform::new_scale_xy(scale, scale)
121 }
122
123 pub fn new_scale_x<X: Into<Factor>>(x: X) -> Transform {
125 Transform::new_scale_xy(x, 1.0)
126 }
127
128 pub fn new_scale_y<Y: Into<Factor>>(y: Y) -> Transform {
130 Transform::new_scale_xy(1.0, y)
131 }
132
133 pub fn new_scale_xy<X: Into<Factor>, Y: Into<Factor>>(x: X, y: Y) -> Transform {
135 Transform::identity().scale_xy(x, y)
136 }
137}
138
139impl Transform {
140 pub fn then(mut self, other: Transform) -> Self {
156 let mut other_parts = other.parts.into_iter();
157 self.needs_layout |= other.needs_layout;
158 if let Some(first) = other_parts.next() {
159 match first {
160 TransformPart::Computed(first) => self.then_transform(first),
161 first => self.parts.push(first),
162 }
163 self.parts.extend(other_parts);
164 }
165 self
166 }
167
168 fn then_transform(&mut self, transform: PxTransform) {
169 if let Some(TransformPart::Computed(last)) = self.parts.last_mut() {
170 *last = last.then(&transform);
171 } else {
172 self.parts.push(TransformPart::Computed(transform));
173 }
174 }
175
176 pub fn rotate<A: Into<AngleRadian>>(mut self, angle: A) -> Self {
178 self.then_transform(PxTransform::rotation(0.0, 0.0, angle.into().into()));
179 self
180 }
181
182 pub fn rotate_x<A: Into<AngleRadian>>(mut self, angle: A) -> Self {
186 self.then_transform(PxTransform::rotation_3d(1.0, 0.0, 0.0, angle.into().into()));
187 self
188 }
189
190 pub fn rotate_y<A: Into<AngleRadian>>(mut self, angle: A) -> Self {
194 self.then_transform(PxTransform::rotation_3d(0.0, 1.0, 0.0, angle.into().into()));
195 self
196 }
197
198 pub fn rotate_z<A: Into<AngleRadian>>(mut self, angle: A) -> Self {
202 self.then_transform(PxTransform::rotation_3d(0.0, 0.0, 1.0, angle.into().into()));
203 self
204 }
205
206 pub fn rotate_3d<A: Into<AngleRadian>>(mut self, x: f32, y: f32, z: f32, angle: A) -> Self {
210 self.then_transform(PxTransform::rotation_3d(x, y, z, angle.into().into()));
211 self
212 }
213
214 pub fn translate<X: Into<Length>, Y: Into<Length>>(mut self, x: X, y: Y) -> Self {
216 self.parts.push(TransformPart::Translate(x.into(), y.into()));
217 self.needs_layout = true;
218 self
219 }
220 pub fn translate_x<X: Into<Length>>(self, x: X) -> Self {
222 self.translate(x, 0.0)
223 }
224 pub fn translate_y<Y: Into<Length>>(self, y: Y) -> Self {
226 self.translate(0.0, y)
227 }
228
229 pub fn translate_z<Z: Into<Length>>(self, z: Z) -> Self {
231 self.translate_3d(0.0, 0.0, z)
232 }
233
234 pub fn translate_3d<X: Into<Length>, Y: Into<Length>, Z: Into<Length>>(mut self, x: X, y: Y, z: Z) -> Self {
238 self.parts.push(TransformPart::Translate3d(x.into(), y.into(), z.into()));
239 self.needs_layout = true;
240 self
241 }
242
243 pub fn skew<X: Into<AngleRadian>, Y: Into<AngleRadian>>(mut self, x: X, y: Y) -> Self {
245 self.then_transform(PxTransform::skew(x.into().into(), y.into().into()));
246 self
247 }
248 pub fn skew_x<X: Into<AngleRadian>>(self, x: X) -> Self {
250 self.skew(x, 0.rad())
251 }
252 pub fn skew_y<Y: Into<AngleRadian>>(self, y: Y) -> Self {
254 self.skew(0.rad(), y)
255 }
256
257 pub fn scale_xy<X: Into<Factor>, Y: Into<Factor>>(mut self, x: X, y: Y) -> Self {
259 self.then_transform(PxTransform::scale(x.into().0, y.into().0));
260 self
261 }
262 pub fn scale_x<X: Into<Factor>>(self, x: X) -> Self {
264 self.scale_xy(x, 1.0)
265 }
266 pub fn scale_y<Y: Into<Factor>>(self, y: Y) -> Self {
268 self.scale_xy(1.0, y)
269 }
270 pub fn scale<S: Into<Factor>>(self, scale: S) -> Self {
272 let s = scale.into();
273 self.scale_xy(s, s)
274 }
275
276 pub fn perspective<D: Into<Length>>(mut self, d: D) -> Self {
278 self.parts.push(TransformPart::Perspective(d.into()));
279 self.needs_layout = true;
280 self
281 }
282}
283impl Transform {
284 pub fn layout(&self) -> PxTransform {
288 let mut r = PxTransform::identity();
289 for step in &self.parts {
290 r = match step {
291 TransformPart::Computed(m) => r.then(m),
292 TransformPart::Translate(x, y) => r.then(&PxTransform::translation(x.layout_f32_x(), y.layout_f32_y())),
293 TransformPart::Translate3d(x, y, z) => {
294 r.then(&PxTransform::translation_3d(x.layout_f32_x(), y.layout_f32_y(), z.layout_f32_z()))
295 }
296 TransformPart::Perspective(d) => r.then(&PxTransform::perspective(d.layout_f32_z())),
297 };
298 }
299
300 for (to, step, slerp) in self.lerp_to.iter() {
301 let to = to.layout();
302 r = slerp_enabled(*slerp, || lerp_px_transform(r, &to, *step));
303 }
304
305 r
306 }
307
308 pub fn try_layout(&self) -> Option<PxTransform> {
310 if self.needs_layout {
311 return None;
312 }
313
314 let mut r = PxTransform::identity();
315 for step in &self.parts {
316 r = match step {
317 TransformPart::Computed(m) => r.then(m),
318 TransformPart::Translate(_, _) | TransformPart::Translate3d(_, _, _) | TransformPart::Perspective(_) => unreachable!(),
319 }
320 }
321 Some(r)
322 }
323
324 pub fn needs_layout(&self) -> bool {
326 self.needs_layout
327 }
328}
329impl super::Layout2d for Transform {
330 type Px = PxTransform;
331
332 fn layout_dft(&self, _: Self::Px) -> Self::Px {
333 self.layout()
334 }
335
336 fn affect_mask(&self) -> super::LayoutMask {
337 let mut mask = super::LayoutMask::empty();
338 for part in &self.parts {
339 match part {
340 TransformPart::Computed(_) => {}
341 TransformPart::Translate(x, y) => {
342 mask |= x.affect_mask();
343 mask |= y.affect_mask();
344 }
345 TransformPart::Translate3d(x, y, z) => {
346 mask |= x.affect_mask();
347 mask |= y.affect_mask();
348 mask |= z.affect_mask();
349 }
350 TransformPart::Perspective(d) => mask |= d.affect_mask(),
351 }
352 }
353 mask
354 }
355}
356
357impl Transitionable for Transform {
358 fn lerp(mut self, to: &Self, step: EasingStep) -> Self {
359 if step == 0.fct() {
360 self
361 } else if step == 1.fct() {
362 to.clone()
363 } else {
364 if self.needs_layout || to.needs_layout {
365 self.needs_layout = true;
366 self.lerp_to.push((to.clone(), step, is_slerp_enabled()));
367 } else {
368 let a = self.layout();
369 let b = to.layout();
370 self = Transform::from(lerp_px_transform(a, &b, step));
371 }
372 self
373 }
374 }
375}
376
377fn lerp_px_transform(s: PxTransform, to: &PxTransform, step: EasingStep) -> PxTransform {
378 if step == 0.fct() {
379 s
380 } else if step == 1.fct() {
381 *to
382 } else {
383 match (s, to) {
384 (PxTransform::Offset(from), PxTransform::Offset(to)) => PxTransform::Offset(from.lerp(*to, step.0)),
385 (from, to) => {
386 match (
387 MatrixDecomposed3D::decompose(from.to_transform()),
388 MatrixDecomposed3D::decompose(to.to_transform()),
389 ) {
390 (Some(from), Some(to)) => {
391 let l = from.lerp(&to, step);
392 PxTransform::Transform(l.recompose())
393 }
394 _ => {
395 if step.0 < 0.5 {
396 s
397 } else {
398 *to
399 }
400 }
401 }
402 }
403 }
404 }
405}
406
407impl_from_and_into_var! {
408 fn from(t: PxTransform) -> Transform {
409 Transform {
410 parts: vec![TransformPart::Computed(t)],
411 needs_layout: false,
412 lerp_to: vec![],
413 }
414 }
415}
416
417type Scale = (f32, f32, f32);
421type Skew = (f32, f32, f32);
422type Perspective = (f32, f32, f32, f32);
423type Quaternion = euclid::Rotation3D<f64, (), ()>;
424
425#[derive(Clone, Copy, Debug, PartialEq)]
427struct MatrixDecomposed3D {
428 pub translate: euclid::Vector3D<f32, Px>,
430 pub scale: Scale,
432 pub skew: Skew,
434 pub perspective: Perspective,
436 pub quaternion: Quaternion,
438}
439impl MatrixDecomposed3D {
440 pub fn decompose(mut matrix: euclid::Transform3D<f32, Px, Px>) -> Option<Self> {
441 let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| {
443 [
444 (ascl * a[0]) + (bscl * b[0]),
445 (ascl * a[1]) + (bscl * b[1]),
446 (ascl * a[2]) + (bscl * b[2]),
447 ]
448 };
449 let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
451 let cross = |row1: [f32; 3], row2: [f32; 3]| {
453 [
454 row1[1] * row2[2] - row1[2] * row2[1],
455 row1[2] * row2[0] - row1[0] * row2[2],
456 row1[0] * row2[1] - row1[1] * row2[0],
457 ]
458 };
459
460 if matrix.m44 == 0.0 {
461 return None;
462 }
463
464 let scaling_factor = matrix.m44;
465
466 matrix_scale_by_factor(&mut matrix, 1.0 / scaling_factor);
468
469 let mut perspective_matrix = matrix;
472
473 perspective_matrix.m14 = 0.0;
474 perspective_matrix.m24 = 0.0;
475 perspective_matrix.m34 = 0.0;
476 perspective_matrix.m44 = 1.0;
477
478 if perspective_matrix.determinant() == 0.0 {
479 return None;
480 }
481
482 let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 {
484 let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44];
485
486 perspective_matrix = matrix_transpose(perspective_matrix.inverse().unwrap());
487 let perspective = matrix_pre_mul_point4(&perspective_matrix, &right_hand_side);
488
489 (perspective[0], perspective[1], perspective[2], perspective[3])
490 } else {
491 (0.0, 0.0, 0.0, 1.0)
492 };
493
494 let translate = euclid::Vector3D::new(matrix.m41, matrix.m42, matrix.m43);
496
497 let mut row = get_matrix_3x3_part(&matrix);
499
500 let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();
502 let mut scale = (row0len, 0.0, 0.0);
503 row[0] = [row[0][0] / row0len, row[0][1] / row0len, row[0][2] / row0len];
504
505 let mut skew = (dot(row[0], row[1]), 0.0, 0.0);
507 row[1] = combine(row[1], row[0], 1.0, -skew.0);
508
509 let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt();
511 scale.1 = row1len;
512 row[1] = [row[1][0] / row1len, row[1][1] / row1len, row[1][2] / row1len];
513 skew.0 /= scale.1;
514
515 skew.1 = dot(row[0], row[2]);
517 row[2] = combine(row[2], row[0], 1.0, -skew.1);
518 skew.2 = dot(row[1], row[2]);
519 row[2] = combine(row[2], row[1], 1.0, -skew.2);
520
521 let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt();
523 scale.2 = row2len;
524 row[2] = [row[2][0] / row2len, row[2][1] / row2len, row[2][2] / row2len];
525 skew.1 /= scale.2;
526 skew.2 /= scale.2;
527
528 if dot(row[0], cross(row[1], row[2])) < 0.0 {
532 scale.0 *= -1.0;
533 scale.1 *= -1.0;
534 scale.2 *= -1.0;
535
536 #[expect(clippy::needless_range_loop)]
537 for i in 0..3 {
538 row[i][0] *= -1.0;
539 row[i][1] *= -1.0;
540 row[i][2] *= -1.0;
541 }
542 }
543
544 let mut quaternion = Quaternion::quaternion(
546 0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
547 0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(),
548 0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
549 0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt(),
550 );
551
552 if row[2][1] > row[1][2] {
553 quaternion.i = -quaternion.i
554 }
555 if row[0][2] > row[2][0] {
556 quaternion.j = -quaternion.j
557 }
558 if row[1][0] > row[0][1] {
559 quaternion.k = -quaternion.k
560 }
561
562 Some(MatrixDecomposed3D {
563 translate,
564 scale,
565 skew,
566 perspective,
567 quaternion,
568 })
569 }
570
571 pub fn recompose(self) -> euclid::Transform3D<f32, Px, Px> {
572 let mut matrix = euclid::Transform3D::identity();
573
574 matrix.m14 = self.perspective.0;
576 matrix.m24 = self.perspective.1;
577 matrix.m34 = self.perspective.2;
578 matrix.m44 = self.perspective.3;
579
580 matrix.m41 += self.translate.x * matrix.m11 + self.translate.y * matrix.m21 + self.translate.z * matrix.m31;
582 matrix.m42 += self.translate.x * matrix.m12 + self.translate.y * matrix.m22 + self.translate.z * matrix.m32;
583 matrix.m43 += self.translate.x * matrix.m13 + self.translate.y * matrix.m23 + self.translate.z * matrix.m33;
584
585 {
587 let x = self.quaternion.i;
588 let y = self.quaternion.j;
589 let z = self.quaternion.k;
590 let w = self.quaternion.r;
591
592 let mut rotation_matrix = euclid::Transform3D::identity();
595 rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32;
596 rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32;
597 rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32;
598 rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32;
599 rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32;
600 rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32;
601 rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32;
602 rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32;
603 rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32;
604
605 matrix = rotation_matrix.then(&matrix);
606 }
607
608 {
610 let mut temp = euclid::Transform3D::identity();
611 if self.skew.2 != 0.0 {
612 temp.m32 = self.skew.2;
613 matrix = temp.then(&matrix);
614 temp.m32 = 0.0;
615 }
616
617 if self.skew.1 != 0.0 {
618 temp.m31 = self.skew.1;
619 matrix = temp.then(&matrix);
620 temp.m31 = 0.0;
621 }
622
623 if self.skew.0 != 0.0 {
624 temp.m21 = self.skew.0;
625 matrix = temp.then(&matrix);
626 }
627 }
628
629 matrix.m11 *= self.scale.0;
631 matrix.m12 *= self.scale.0;
632 matrix.m13 *= self.scale.0;
633 matrix.m21 *= self.scale.1;
634 matrix.m22 *= self.scale.1;
635 matrix.m23 *= self.scale.1;
636 matrix.m31 *= self.scale.2;
637 matrix.m32 *= self.scale.2;
638 matrix.m33 *= self.scale.2;
639
640 matrix
641 }
642
643 pub fn lerp_scale(from: Scale, to: Scale, step: EasingStep) -> Scale {
644 (from.0.lerp(&to.0, step), from.1.lerp(&to.1, step), from.2.lerp(&to.2, step))
645 }
646
647 pub fn lerp_skew(from: Skew, to: Skew, step: EasingStep) -> Skew {
648 (from.0.lerp(&to.0, step), from.1.lerp(&to.1, step), from.2.lerp(&to.2, step))
649 }
650
651 pub fn lerp_perspective(from: Perspective, to: Perspective, step: EasingStep) -> Perspective {
652 (
653 from.0.lerp(&to.0, step),
654 from.1.lerp(&to.1, step),
655 from.2.lerp(&to.2, step),
656 from.3.lerp(&to.3, step),
657 )
658 }
659
660 pub fn lerp_quaternion(from: Quaternion, to: Quaternion, step: EasingStep) -> Quaternion {
661 match is_slerp_enabled() {
662 false => from.lerp(&to, step.0 as _),
663 true => from.slerp(&to, step.0 as _),
664 }
665 }
666}
667impl Transitionable for MatrixDecomposed3D {
668 fn lerp(self, to: &Self, step: EasingStep) -> Self {
669 Self {
670 translate: self.translate.lerp(to.translate, step.0),
671 scale: Self::lerp_scale(self.scale, to.scale, step),
672 skew: Self::lerp_skew(self.skew, to.skew, step),
673 perspective: Self::lerp_perspective(self.perspective, to.perspective, step),
674 quaternion: Self::lerp_quaternion(self.quaternion, to.quaternion, step),
675 }
676 }
677}
678
679fn matrix_scale_by_factor(m: &mut euclid::Transform3D<f32, Px, Px>, scaling_factor: f32) {
680 m.m11 *= scaling_factor;
681 m.m12 *= scaling_factor;
682 m.m13 *= scaling_factor;
683 m.m14 *= scaling_factor;
684 m.m21 *= scaling_factor;
685 m.m22 *= scaling_factor;
686 m.m23 *= scaling_factor;
687 m.m24 *= scaling_factor;
688 m.m31 *= scaling_factor;
689 m.m32 *= scaling_factor;
690 m.m33 *= scaling_factor;
691 m.m34 *= scaling_factor;
692 m.m41 *= scaling_factor;
693 m.m42 *= scaling_factor;
694 m.m43 *= scaling_factor;
695 m.m44 *= scaling_factor;
696}
697
698fn matrix_transpose(m: euclid::Transform3D<f32, Px, Px>) -> euclid::Transform3D<f32, Px, Px> {
699 euclid::Transform3D {
700 m11: m.m11,
701 m12: m.m21,
702 m13: m.m31,
703 m14: m.m41,
704 m21: m.m12,
705 m22: m.m22,
706 m23: m.m32,
707 m24: m.m42,
708 m31: m.m13,
709 m32: m.m23,
710 m33: m.m33,
711 m34: m.m43,
712 m41: m.m14,
713 m42: m.m24,
714 m43: m.m34,
715 m44: m.m44,
716 _unit: std::marker::PhantomData,
717 }
718}
719
720fn matrix_pre_mul_point4(m: &euclid::Transform3D<f32, Px, Px>, pin: &[f32; 4]) -> [f32; 4] {
721 [
722 pin[0] * m.m11 + pin[1] * m.m21 + pin[2] * m.m31 + pin[3] * m.m41,
723 pin[0] * m.m12 + pin[1] * m.m22 + pin[2] * m.m32 + pin[3] * m.m42,
724 pin[0] * m.m13 + pin[1] * m.m23 + pin[2] * m.m33 + pin[3] * m.m43,
725 pin[0] * m.m14 + pin[1] * m.m24 + pin[2] * m.m34 + pin[3] * m.m44,
726 ]
727}
728
729fn get_matrix_3x3_part(&m: &euclid::Transform3D<f32, Px, Px>) -> [[f32; 3]; 3] {
730 [[m.m11, m.m12, m.m13], [m.m21, m.m22, m.m23], [m.m31, m.m32, m.m33]]
731}