1use crate::core::math::{Matrix4, Vec3};
60use crate::ecs::components::transform::{Quat, Transform};
61use crate::ecs::Component;
62use cgmath::{InnerSpace, SquareMatrix};
63use std::fmt;
64
65#[derive(Clone, Copy, PartialEq)]
95pub struct GlobalTransform {
96 matrix: Matrix4<f32>,
100}
101
102impl GlobalTransform {
103 pub const IDENTITY: GlobalTransform = GlobalTransform {
105 matrix: Matrix4::new(
106 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
107 ),
108 };
109
110 #[inline]
126 pub const fn from_matrix(matrix: Matrix4<f32>) -> Self {
127 Self { matrix }
128 }
129
130 #[inline]
145 pub fn from_translation(translation: Vec3) -> Self {
146 let matrix = Matrix4::from_translation(cgmath::Vector3::new(
147 translation.x,
148 translation.y,
149 translation.z,
150 ));
151 Self { matrix }
152 }
153
154 #[inline]
176 pub fn from_translation_rotation_scale(translation: Vec3, rotation: Quat, scale: Vec3) -> Self {
177 let transform = Transform::new(translation, rotation, scale);
179 Self {
180 matrix: transform.matrix(),
181 }
182 }
183
184 #[inline]
197 pub fn matrix(&self) -> Matrix4<f32> {
198 self.matrix
199 }
200
201 #[inline]
203 pub fn matrix_ref(&self) -> &Matrix4<f32> {
204 &self.matrix
205 }
206
207 #[inline]
223 pub fn to_cols_array(&self) -> [f32; 16] {
224 [
225 self.matrix.x.x,
226 self.matrix.x.y,
227 self.matrix.x.z,
228 self.matrix.x.w,
229 self.matrix.y.x,
230 self.matrix.y.y,
231 self.matrix.y.z,
232 self.matrix.y.w,
233 self.matrix.z.x,
234 self.matrix.z.y,
235 self.matrix.z.z,
236 self.matrix.z.w,
237 self.matrix.w.x,
238 self.matrix.w.y,
239 self.matrix.w.z,
240 self.matrix.w.w,
241 ]
242 }
243
244 #[inline]
248 pub fn to_rows_array(&self) -> [f32; 16] {
249 [
250 self.matrix.x.x,
251 self.matrix.y.x,
252 self.matrix.z.x,
253 self.matrix.w.x,
254 self.matrix.x.y,
255 self.matrix.y.y,
256 self.matrix.z.y,
257 self.matrix.w.y,
258 self.matrix.x.z,
259 self.matrix.y.z,
260 self.matrix.z.z,
261 self.matrix.w.z,
262 self.matrix.x.w,
263 self.matrix.y.w,
264 self.matrix.z.w,
265 self.matrix.w.w,
266 ]
267 }
268
269 #[inline]
289 pub fn translation(&self) -> Vec3 {
290 Vec3::new(self.matrix.w.x, self.matrix.w.y, self.matrix.w.z)
291 }
292
293 #[inline]
315 pub fn scale(&self) -> Vec3 {
316 let scale_x =
317 cgmath::Vector3::new(self.matrix.x.x, self.matrix.x.y, self.matrix.x.z).magnitude();
318 let scale_y =
319 cgmath::Vector3::new(self.matrix.y.x, self.matrix.y.y, self.matrix.y.z).magnitude();
320 let scale_z =
321 cgmath::Vector3::new(self.matrix.z.x, self.matrix.z.y, self.matrix.z.z).magnitude();
322 Vec3::new(scale_x, scale_y, scale_z)
323 }
324
325 #[inline]
352 pub fn rotation(&self) -> Quat {
353 let scale = self.scale();
355
356 let m00 = if scale.x != 0.0 {
358 self.matrix.x.x / scale.x
359 } else {
360 1.0
361 };
362 let m01 = if scale.y != 0.0 {
363 self.matrix.y.x / scale.y
364 } else {
365 0.0
366 };
367 let m02 = if scale.z != 0.0 {
368 self.matrix.z.x / scale.z
369 } else {
370 0.0
371 };
372
373 let m10 = if scale.x != 0.0 {
374 self.matrix.x.y / scale.x
375 } else {
376 0.0
377 };
378 let m11 = if scale.y != 0.0 {
379 self.matrix.y.y / scale.y
380 } else {
381 1.0
382 };
383 let m12 = if scale.z != 0.0 {
384 self.matrix.z.y / scale.z
385 } else {
386 0.0
387 };
388
389 let m20 = if scale.x != 0.0 {
390 self.matrix.x.z / scale.x
391 } else {
392 0.0
393 };
394 let m21 = if scale.y != 0.0 {
395 self.matrix.y.z / scale.y
396 } else {
397 0.0
398 };
399 let m22 = if scale.z != 0.0 {
400 self.matrix.z.z / scale.z
401 } else {
402 1.0
403 };
404
405 let trace = m00 + m11 + m22;
407 if trace > 0.0 {
408 let s = (trace + 1.0).sqrt() * 2.0;
409 Quat::new((m21 - m12) / s, (m02 - m20) / s, (m10 - m01) / s, 0.25 * s).normalize()
410 } else if m00 > m11 && m00 > m22 {
411 let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;
412 Quat::new(0.25 * s, (m01 + m10) / s, (m02 + m20) / s, (m21 - m12) / s).normalize()
413 } else if m11 > m22 {
414 let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;
415 Quat::new((m01 + m10) / s, 0.25 * s, (m12 + m21) / s, (m02 - m20) / s).normalize()
416 } else {
417 let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;
418 Quat::new((m02 + m20) / s, (m12 + m21) / s, 0.25 * s, (m10 - m01) / s).normalize()
419 }
420 }
421
422 #[inline]
444 pub fn decompose(&self) -> (Vec3, Quat, Vec3) {
445 (self.translation(), self.rotation(), self.scale())
446 }
447
448 #[inline]
453 pub fn to_transform(&self) -> Transform {
454 let (translation, rotation, scale) = self.decompose();
455 Transform::new(translation, rotation, scale)
456 }
457
458 #[inline]
481 pub fn mul_transform(&self, other: &GlobalTransform) -> GlobalTransform {
482 GlobalTransform {
483 matrix: self.matrix * other.matrix,
484 }
485 }
486
487 #[inline]
505 pub fn transform_by(&self, local: &Transform) -> GlobalTransform {
506 GlobalTransform {
507 matrix: self.matrix * local.matrix(),
508 }
509 }
510
511 #[inline]
526 pub fn transform_point(&self, point: Vec3) -> Vec3 {
527 let p = cgmath::Vector4::new(point.x, point.y, point.z, 1.0);
528 let result = self.matrix * p;
529 Vec3::new(result.x, result.y, result.z)
530 }
531
532 #[inline]
536 pub fn transform_direction(&self, direction: Vec3) -> Vec3 {
537 let d = cgmath::Vector4::new(direction.x, direction.y, direction.z, 0.0);
538 let result = self.matrix * d;
539 Vec3::new(result.x, result.y, result.z)
540 }
541
542 #[inline]
547 pub fn inverse(&self) -> Option<GlobalTransform> {
548 self.matrix.invert().map(|m| GlobalTransform { matrix: m })
549 }
550
551 #[inline]
557 pub fn forward(&self) -> Vec3 {
558 self.transform_direction(Vec3::new(0.0, 0.0, -1.0))
559 .normalize()
560 }
561
562 #[inline]
564 pub fn right(&self) -> Vec3 {
565 self.transform_direction(Vec3::new(1.0, 0.0, 0.0))
566 .normalize()
567 }
568
569 #[inline]
571 pub fn up(&self) -> Vec3 {
572 self.transform_direction(Vec3::new(0.0, 1.0, 0.0))
573 .normalize()
574 }
575
576 #[inline]
578 pub fn back(&self) -> Vec3 {
579 self.transform_direction(Vec3::new(0.0, 0.0, 1.0))
580 .normalize()
581 }
582
583 #[inline]
585 pub fn left(&self) -> Vec3 {
586 self.transform_direction(Vec3::new(-1.0, 0.0, 0.0))
587 .normalize()
588 }
589
590 #[inline]
592 pub fn down(&self) -> Vec3 {
593 self.transform_direction(Vec3::new(0.0, -1.0, 0.0))
594 .normalize()
595 }
596
597 #[inline]
620 pub fn lerp(&self, other: &GlobalTransform, t: f32) -> GlobalTransform {
621 let (t1, r1, s1) = self.decompose();
622 let (t2, r2, s2) = other.decompose();
623
624 GlobalTransform::from_translation_rotation_scale(
625 t1.lerp(t2, t),
626 r1.slerp(r2, t),
627 s1.lerp(s2, t),
628 )
629 }
630}
631
632impl Default for GlobalTransform {
633 #[inline]
634 fn default() -> Self {
635 Self::IDENTITY
636 }
637}
638
639impl fmt::Debug for GlobalTransform {
640 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
641 let (t, r, s) = self.decompose();
642 f.debug_struct("GlobalTransform")
643 .field("translation", &(t.x, t.y, t.z))
644 .field(
645 "rotation",
646 &format!("Quat({:.3}, {:.3}, {:.3}, {:.3})", r.x, r.y, r.z, r.w),
647 )
648 .field("scale", &(s.x, s.y, s.z))
649 .finish()
650 }
651}
652
653impl From<Transform> for GlobalTransform {
654 #[inline]
658 fn from(transform: Transform) -> Self {
659 GlobalTransform {
660 matrix: transform.matrix(),
661 }
662 }
663}
664
665impl From<&Transform> for GlobalTransform {
666 #[inline]
667 fn from(transform: &Transform) -> Self {
668 GlobalTransform {
669 matrix: transform.matrix(),
670 }
671 }
672}
673
674impl From<Matrix4<f32>> for GlobalTransform {
675 #[inline]
676 fn from(matrix: Matrix4<f32>) -> Self {
677 GlobalTransform { matrix }
678 }
679}
680
681impl Component for GlobalTransform {}
683
684impl std::ops::Mul for GlobalTransform {
686 type Output = GlobalTransform;
687
688 #[inline]
689 fn mul(self, rhs: GlobalTransform) -> GlobalTransform {
690 self.mul_transform(&rhs)
691 }
692}
693
694impl std::ops::Mul<&GlobalTransform> for GlobalTransform {
695 type Output = GlobalTransform;
696
697 #[inline]
698 fn mul(self, rhs: &GlobalTransform) -> GlobalTransform {
699 self.mul_transform(rhs)
700 }
701}
702
703impl std::ops::Mul<Transform> for GlobalTransform {
704 type Output = GlobalTransform;
705
706 #[inline]
707 fn mul(self, rhs: Transform) -> GlobalTransform {
708 self.transform_by(&rhs)
709 }
710}
711
712impl std::ops::Mul<&Transform> for GlobalTransform {
713 type Output = GlobalTransform;
714
715 #[inline]
716 fn mul(self, rhs: &Transform) -> GlobalTransform {
717 self.transform_by(rhs)
718 }
719}
720
721#[cfg(test)]
726mod tests {
727 use super::*;
728 use std::f32::consts::FRAC_PI_2;
729 use std::f32::consts::FRAC_PI_4;
730
731 mod construction_tests {
732 use super::*;
733
734 #[test]
735 fn test_identity() {
736 let global = GlobalTransform::IDENTITY;
737 let pos = global.translation();
738 let scale = global.scale();
739
740 assert!((pos.x).abs() < 0.0001);
741 assert!((pos.y).abs() < 0.0001);
742 assert!((pos.z).abs() < 0.0001);
743 assert!((scale.x - 1.0).abs() < 0.0001);
744 assert!((scale.y - 1.0).abs() < 0.0001);
745 assert!((scale.z - 1.0).abs() < 0.0001);
746 }
747
748 #[test]
749 fn test_default() {
750 let global: GlobalTransform = Default::default();
751 assert_eq!(global, GlobalTransform::IDENTITY);
752 }
753
754 #[test]
755 fn test_from_translation() {
756 let global = GlobalTransform::from_translation(Vec3::new(10.0, 20.0, 30.0));
757 let pos = global.translation();
758
759 assert!((pos.x - 10.0).abs() < 0.0001);
760 assert!((pos.y - 20.0).abs() < 0.0001);
761 assert!((pos.z - 30.0).abs() < 0.0001);
762 }
763
764 #[test]
765 fn test_from_translation_rotation_scale() {
766 let rotation = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
767 let global = GlobalTransform::from_translation_rotation_scale(
768 Vec3::new(5.0, 10.0, 15.0),
769 rotation,
770 Vec3::new(2.0, 3.0, 4.0),
771 );
772
773 let pos = global.translation();
774 let scale = global.scale();
775
776 assert!((pos.x - 5.0).abs() < 0.0001);
777 assert!((pos.y - 10.0).abs() < 0.0001);
778 assert!((pos.z - 15.0).abs() < 0.0001);
779 assert!((scale.x - 2.0).abs() < 0.0001);
780 assert!((scale.y - 3.0).abs() < 0.0001);
781 assert!((scale.z - 4.0).abs() < 0.0001);
782 }
783
784 #[test]
785 fn test_from_transform() {
786 let transform = Transform::new(
787 Vec3::new(1.0, 2.0, 3.0),
788 Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4),
789 Vec3::new(2.0, 2.0, 2.0),
790 );
791 let global: GlobalTransform = transform.into();
792
793 let pos = global.translation();
794 assert!((pos.x - 1.0).abs() < 0.0001);
795 assert!((pos.y - 2.0).abs() < 0.0001);
796 assert!((pos.z - 3.0).abs() < 0.0001);
797 }
798
799 #[test]
800 fn test_from_transform_ref() {
801 let transform = Transform::from_position(Vec3::new(5.0, 0.0, 0.0));
802 let global: GlobalTransform = (&transform).into();
803 let pos = global.translation();
804 assert!((pos.x - 5.0).abs() < 0.0001);
805 }
806 }
807
808 mod decomposition_tests {
809 use super::*;
810
811 #[test]
812 fn test_translation_extraction() {
813 let global = GlobalTransform::from_translation(Vec3::new(1.0, 2.0, 3.0));
814 let pos = global.translation();
815 assert!((pos.x - 1.0).abs() < 0.0001);
816 assert!((pos.y - 2.0).abs() < 0.0001);
817 assert!((pos.z - 3.0).abs() < 0.0001);
818 }
819
820 #[test]
821 fn test_scale_extraction() {
822 let global = GlobalTransform::from_translation_rotation_scale(
823 Vec3::zero(),
824 Quat::IDENTITY,
825 Vec3::new(2.0, 3.0, 4.0),
826 );
827 let scale = global.scale();
828 assert!((scale.x - 2.0).abs() < 0.0001);
829 assert!((scale.y - 3.0).abs() < 0.0001);
830 assert!((scale.z - 4.0).abs() < 0.0001);
831 }
832
833 #[test]
834 fn test_rotation_extraction() {
835 let original = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
836 let global = GlobalTransform::from_translation_rotation_scale(
837 Vec3::zero(),
838 original,
839 Vec3::one(),
840 );
841 let extracted = global.rotation();
842
843 let dot = original.x * extracted.x
845 + original.y * extracted.y
846 + original.z * extracted.z
847 + original.w * extracted.w;
848 assert!(dot.abs() > 0.999);
849 }
850
851 #[test]
852 fn test_decompose() {
853 let original_t = Vec3::new(10.0, 5.0, 3.0);
854 let original_r = Quat::from_axis_angle(Vec3::unit_x(), FRAC_PI_4);
855 let original_s = Vec3::new(2.0, 3.0, 4.0);
856
857 let global = GlobalTransform::from_translation_rotation_scale(
858 original_t, original_r, original_s,
859 );
860 let (t, r, s) = global.decompose();
861
862 assert!((t - original_t).length() < 0.001);
863 assert!((s - original_s).length() < 0.001);
864
865 let dot =
866 original_r.x * r.x + original_r.y * r.y + original_r.z * r.z + original_r.w * r.w;
867 assert!(dot.abs() > 0.999);
868 }
869
870 #[test]
871 fn test_to_transform() {
872 let global = GlobalTransform::from_translation_rotation_scale(
873 Vec3::new(5.0, 10.0, 15.0),
874 Quat::IDENTITY,
875 Vec3::new(2.0, 2.0, 2.0),
876 );
877
878 let transform = global.to_transform();
879 assert!((transform.position - Vec3::new(5.0, 10.0, 15.0)).length() < 0.001);
880 assert!((transform.scale - Vec3::new(2.0, 2.0, 2.0)).length() < 0.001);
881 }
882 }
883
884 mod transform_tests {
885 use super::*;
886
887 #[test]
888 fn test_mul_transform_translation() {
889 let a = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
890 let b = GlobalTransform::from_translation(Vec3::new(5.0, 0.0, 0.0));
891 let result = a.mul_transform(&b);
892
893 let pos = result.translation();
894 assert!((pos.x - 15.0).abs() < 0.0001);
895 }
896
897 #[test]
898 fn test_mul_transform_scale() {
899 let a = GlobalTransform::from_translation_rotation_scale(
900 Vec3::zero(),
901 Quat::IDENTITY,
902 Vec3::new(2.0, 2.0, 2.0),
903 );
904 let b = GlobalTransform::from_translation(Vec3::new(5.0, 0.0, 0.0));
905 let result = a.mul_transform(&b);
906
907 let pos = result.translation();
908 assert!((pos.x - 10.0).abs() < 0.0001);
910 }
911
912 #[test]
913 fn test_transform_by() {
914 let parent = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
915 let child = Transform::from_position(Vec3::new(5.0, 0.0, 0.0));
916 let result = parent.transform_by(&child);
917
918 let pos = result.translation();
919 assert!((pos.x - 15.0).abs() < 0.0001);
920 }
921
922 #[test]
923 fn test_transform_point() {
924 let global = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
925 let local_point = Vec3::new(5.0, 3.0, 0.0);
926 let world_point = global.transform_point(local_point);
927
928 assert!((world_point.x - 15.0).abs() < 0.0001);
929 assert!((world_point.y - 3.0).abs() < 0.0001);
930 }
931
932 #[test]
933 fn test_transform_direction() {
934 let global = GlobalTransform::from_translation(Vec3::new(1000.0, 0.0, 0.0));
935 let direction = Vec3::new(1.0, 0.0, 0.0);
936 let world_dir = global.transform_direction(direction);
937
938 assert!((world_dir.x - 1.0).abs() < 0.0001);
940 assert!(world_dir.y.abs() < 0.0001);
941 }
942
943 #[test]
944 fn test_inverse() {
945 let global = GlobalTransform::from_translation_rotation_scale(
946 Vec3::new(5.0, 10.0, 15.0),
947 Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4),
948 Vec3::new(2.0, 2.0, 2.0),
949 );
950
951 let inverse = global.inverse().expect("Should be invertible");
952 let identity = global.mul_transform(&inverse);
953
954 let pos = identity.translation();
956 assert!(pos.length() < 0.001);
957
958 let scale = identity.scale();
959 assert!((scale.x - 1.0).abs() < 0.001);
960 }
961
962 #[test]
963 fn test_mul_operator() {
964 let a = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
965 let b = GlobalTransform::from_translation(Vec3::new(5.0, 0.0, 0.0));
966 let result = a * b;
967
968 let pos = result.translation();
969 assert!((pos.x - 15.0).abs() < 0.0001);
970 }
971
972 #[test]
973 fn test_mul_operator_with_transform() {
974 let parent = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
975 let child = Transform::from_position(Vec3::new(5.0, 0.0, 0.0));
976 let result = parent * child;
977
978 let pos = result.translation();
979 assert!((pos.x - 15.0).abs() < 0.0001);
980 }
981 }
982
983 mod direction_tests {
984 use super::*;
985
986 #[test]
987 fn test_directions_identity() {
988 let global = GlobalTransform::IDENTITY;
989
990 assert!((global.forward() - Vec3::new(0.0, 0.0, -1.0)).length() < 0.0001);
991 assert!((global.back() - Vec3::new(0.0, 0.0, 1.0)).length() < 0.0001);
992 assert!((global.right() - Vec3::new(1.0, 0.0, 0.0)).length() < 0.0001);
993 assert!((global.left() - Vec3::new(-1.0, 0.0, 0.0)).length() < 0.0001);
994 assert!((global.up() - Vec3::new(0.0, 1.0, 0.0)).length() < 0.0001);
995 assert!((global.down() - Vec3::new(0.0, -1.0, 0.0)).length() < 0.0001);
996 }
997
998 #[test]
999 fn test_directions_rotated() {
1000 let global = GlobalTransform::from_translation_rotation_scale(
1001 Vec3::zero(),
1002 Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_2),
1003 Vec3::one(),
1004 );
1005
1006 let fwd = global.forward();
1009 assert!((fwd.x - (-1.0)).abs() < 0.0001);
1010 assert!(fwd.y.abs() < 0.0001);
1011 assert!(fwd.z.abs() < 0.0001);
1012 }
1013 }
1014
1015 mod interpolation_tests {
1016 use super::*;
1017
1018 #[test]
1019 fn test_lerp_translation() {
1020 let a = GlobalTransform::from_translation(Vec3::zero());
1021 let b = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
1022
1023 let mid = a.lerp(&b, 0.5);
1024 let pos = mid.translation();
1025 assert!((pos.x - 5.0).abs() < 0.0001);
1026 }
1027
1028 #[test]
1029 fn test_lerp_endpoints() {
1030 let a = GlobalTransform::from_translation(Vec3::new(0.0, 0.0, 0.0));
1031 let b = GlobalTransform::from_translation(Vec3::new(10.0, 10.0, 10.0));
1032
1033 let start = a.lerp(&b, 0.0);
1034 assert!((start.translation() - a.translation()).length() < 0.0001);
1035
1036 let end = a.lerp(&b, 1.0);
1037 assert!((end.translation() - b.translation()).length() < 0.0001);
1038 }
1039 }
1040
1041 mod array_tests {
1042 use super::*;
1043
1044 #[test]
1045 fn test_to_cols_array() {
1046 let global = GlobalTransform::IDENTITY;
1047 let cols = global.to_cols_array();
1048
1049 assert_eq!(cols[0], 1.0); assert_eq!(cols[5], 1.0); assert_eq!(cols[10], 1.0); assert_eq!(cols[15], 1.0); }
1055
1056 #[test]
1057 fn test_to_rows_array() {
1058 let global = GlobalTransform::from_translation(Vec3::new(10.0, 20.0, 30.0));
1059 let rows = global.to_rows_array();
1060
1061 assert!((rows[3] - 10.0).abs() < 0.0001);
1063 assert!((rows[7] - 20.0).abs() < 0.0001);
1064 assert!((rows[11] - 30.0).abs() < 0.0001);
1065 }
1066 }
1067
1068 mod component_tests {
1069 use super::*;
1070
1071 #[test]
1072 fn test_is_component() {
1073 fn assert_component<T: Component>() {}
1074 assert_component::<GlobalTransform>();
1075 }
1076
1077 #[test]
1078 fn test_is_send() {
1079 fn assert_send<T: Send>() {}
1080 assert_send::<GlobalTransform>();
1081 }
1082
1083 #[test]
1084 fn test_is_sync() {
1085 fn assert_sync<T: Sync>() {}
1086 assert_sync::<GlobalTransform>();
1087 }
1088
1089 #[test]
1090 fn test_clone() {
1091 let global = GlobalTransform::from_translation(Vec3::new(1.0, 2.0, 3.0));
1092 let cloned = global.clone();
1093 assert_eq!(global, cloned);
1094 }
1095
1096 #[test]
1097 fn test_copy() {
1098 let global = GlobalTransform::IDENTITY;
1099 let copied = global;
1100 assert_eq!(global, copied);
1101 }
1102
1103 #[test]
1104 fn test_debug() {
1105 let global = GlobalTransform::from_translation(Vec3::new(10.0, 5.0, 0.0));
1106 let debug = format!("{:?}", global);
1107 assert!(debug.contains("GlobalTransform"));
1108 assert!(debug.contains("translation"));
1109 }
1110 }
1111
1112 mod hierarchy_tests {
1113 use super::*;
1114
1115 #[test]
1116 fn test_parent_child_translation() {
1117 let parent_global = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
1119
1120 let child_local = Transform::from_position(Vec3::new(5.0, 0.0, 0.0));
1122
1123 let child_global = parent_global.transform_by(&child_local);
1125 let pos = child_global.translation();
1126 assert!((pos.x - 15.0).abs() < 0.0001);
1127 }
1128
1129 #[test]
1130 fn test_parent_child_rotation() {
1131 let parent_global = GlobalTransform::from_translation_rotation_scale(
1133 Vec3::zero(),
1134 Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_2),
1135 Vec3::one(),
1136 );
1137
1138 let child_local = Transform::from_position(Vec3::new(0.0, 0.0, -10.0));
1140
1141 let child_global = parent_global.transform_by(&child_local);
1143 let pos = child_global.translation();
1144 assert!((pos.x - (-10.0)).abs() < 0.01);
1145 assert!(pos.y.abs() < 0.01);
1146 assert!(pos.z.abs() < 0.01);
1147 }
1148
1149 #[test]
1150 fn test_parent_child_scale() {
1151 let parent_global = GlobalTransform::from_translation_rotation_scale(
1153 Vec3::zero(),
1154 Quat::IDENTITY,
1155 Vec3::new(2.0, 2.0, 2.0),
1156 );
1157
1158 let child_local = Transform::from_position(Vec3::new(5.0, 0.0, 0.0));
1160
1161 let child_global = parent_global.transform_by(&child_local);
1163 let pos = child_global.translation();
1164 assert!((pos.x - 10.0).abs() < 0.0001);
1165 }
1166
1167 #[test]
1168 fn test_three_level_hierarchy() {
1169 let grandparent = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
1171
1172 let parent_local = Transform::from_position(Vec3::new(5.0, 0.0, 0.0));
1174 let parent_global = grandparent.transform_by(&parent_local);
1175
1176 let child_local = Transform::from_position(Vec3::new(3.0, 0.0, 0.0));
1178 let child_global = parent_global.transform_by(&child_local);
1179
1180 let pos = child_global.translation();
1182 assert!((pos.x - 18.0).abs() < 0.0001);
1183 }
1184 }
1185}