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