1use crate::core::math::Vec2;
52use crate::ecs::Component;
53use std::f32::consts::PI;
54
55#[repr(C)]
88#[derive(Clone, Copy, Debug, PartialEq)]
89pub struct Transform2D {
90 pub position: Vec2,
92 pub rotation: f32,
94 pub scale: Vec2,
99}
100
101#[repr(C)]
113#[derive(Clone, Copy, Debug, PartialEq)]
114pub struct Mat3x3 {
115 pub m: [f32; 9],
117}
118
119impl Mat3x3 {
120 pub const IDENTITY: Mat3x3 = Mat3x3 {
122 m: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
123 };
124
125 #[inline]
127 pub const fn new(m: [f32; 9]) -> Self {
128 Self { m }
129 }
130
131 #[inline]
140 pub const fn from_rows(
141 m00: f32,
142 m01: f32,
143 m02: f32,
144 m10: f32,
145 m11: f32,
146 m12: f32,
147 m20: f32,
148 m21: f32,
149 m22: f32,
150 ) -> Self {
151 Self {
153 m: [m00, m10, m20, m01, m11, m21, m02, m12, m22],
154 }
155 }
156
157 #[inline]
159 pub fn translation(tx: f32, ty: f32) -> Self {
160 Self {
161 m: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, tx, ty, 1.0],
162 }
163 }
164
165 #[inline]
167 pub fn rotation(angle: f32) -> Self {
168 let (sin, cos) = angle.sin_cos();
169 Self {
170 m: [cos, sin, 0.0, -sin, cos, 0.0, 0.0, 0.0, 1.0],
171 }
172 }
173
174 #[inline]
176 pub fn scale(sx: f32, sy: f32) -> Self {
177 Self {
178 m: [sx, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 1.0],
179 }
180 }
181
182 #[inline]
184 pub fn get_translation(&self) -> Vec2 {
185 Vec2::new(self.m[6], self.m[7])
186 }
187
188 #[inline]
190 pub fn multiply(&self, other: &Self) -> Self {
191 let a = &self.m;
192 let b = &other.m;
193
194 Self {
195 m: [
196 a[0] * b[0] + a[3] * b[1] + a[6] * b[2],
197 a[1] * b[0] + a[4] * b[1] + a[7] * b[2],
198 a[2] * b[0] + a[5] * b[1] + a[8] * b[2],
199 a[0] * b[3] + a[3] * b[4] + a[6] * b[5],
200 a[1] * b[3] + a[4] * b[4] + a[7] * b[5],
201 a[2] * b[3] + a[5] * b[4] + a[8] * b[5],
202 a[0] * b[6] + a[3] * b[7] + a[6] * b[8],
203 a[1] * b[6] + a[4] * b[7] + a[7] * b[8],
204 a[2] * b[6] + a[5] * b[7] + a[8] * b[8],
205 ],
206 }
207 }
208
209 #[inline]
211 pub fn transform_point(&self, point: Vec2) -> Vec2 {
212 Vec2::new(
213 self.m[0] * point.x + self.m[3] * point.y + self.m[6],
214 self.m[1] * point.x + self.m[4] * point.y + self.m[7],
215 )
216 }
217
218 #[inline]
220 pub fn transform_direction(&self, direction: Vec2) -> Vec2 {
221 Vec2::new(
222 self.m[0] * direction.x + self.m[3] * direction.y,
223 self.m[1] * direction.x + self.m[4] * direction.y,
224 )
225 }
226
227 #[inline]
229 pub fn determinant(&self) -> f32 {
230 let m = &self.m;
231 m[0] * (m[4] * m[8] - m[7] * m[5]) - m[3] * (m[1] * m[8] - m[7] * m[2])
232 + m[6] * (m[1] * m[5] - m[4] * m[2])
233 }
234
235 pub fn inverse(&self) -> Option<Self> {
239 let det = self.determinant();
240 if det.abs() < f32::EPSILON {
241 return None;
242 }
243
244 let m = &self.m;
245 let inv_det = 1.0 / det;
246
247 Some(Self {
248 m: [
249 (m[4] * m[8] - m[7] * m[5]) * inv_det,
250 (m[7] * m[2] - m[1] * m[8]) * inv_det,
251 (m[1] * m[5] - m[4] * m[2]) * inv_det,
252 (m[6] * m[5] - m[3] * m[8]) * inv_det,
253 (m[0] * m[8] - m[6] * m[2]) * inv_det,
254 (m[3] * m[2] - m[0] * m[5]) * inv_det,
255 (m[3] * m[7] - m[6] * m[4]) * inv_det,
256 (m[6] * m[1] - m[0] * m[7]) * inv_det,
257 (m[0] * m[4] - m[3] * m[1]) * inv_det,
258 ],
259 })
260 }
261
262 #[inline]
272 pub fn to_mat4(&self) -> [f32; 16] {
273 [
274 self.m[0], self.m[1], 0.0, 0.0, self.m[3], self.m[4], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, self.m[6], self.m[7], 0.0, 1.0, ]
279 }
280}
281
282impl Default for Mat3x3 {
283 #[inline]
284 fn default() -> Self {
285 Self::IDENTITY
286 }
287}
288
289impl std::ops::Mul for Mat3x3 {
290 type Output = Self;
291
292 #[inline]
293 fn mul(self, other: Self) -> Self {
294 self.multiply(&other)
295 }
296}
297
298impl Transform2D {
299 #[inline]
301 pub const fn new(position: Vec2, rotation: f32, scale: Vec2) -> Self {
302 Self {
303 position,
304 rotation,
305 scale,
306 }
307 }
308
309 #[inline]
311 pub fn from_position(position: Vec2) -> Self {
312 Self {
313 position,
314 rotation: 0.0,
315 scale: Vec2::one(),
316 }
317 }
318
319 #[inline]
321 pub fn from_rotation(rotation: f32) -> Self {
322 Self {
323 position: Vec2::zero(),
324 rotation,
325 scale: Vec2::one(),
326 }
327 }
328
329 #[inline]
331 pub fn from_rotation_degrees(degrees: f32) -> Self {
332 Self::from_rotation(degrees.to_radians())
333 }
334
335 #[inline]
337 pub fn from_scale(scale: Vec2) -> Self {
338 Self {
339 position: Vec2::zero(),
340 rotation: 0.0,
341 scale,
342 }
343 }
344
345 #[inline]
347 pub fn from_scale_uniform(scale: f32) -> Self {
348 Self::from_scale(Vec2::new(scale, scale))
349 }
350
351 #[inline]
353 pub fn from_position_rotation(position: Vec2, rotation: f32) -> Self {
354 Self {
355 position,
356 rotation,
357 scale: Vec2::one(),
358 }
359 }
360
361 #[inline]
366 pub fn look_at(position: Vec2, target: Vec2) -> Self {
367 let direction = target - position;
368 let rotation = direction.y.atan2(direction.x);
369 Self {
370 position,
371 rotation,
372 scale: Vec2::one(),
373 }
374 }
375
376 #[inline]
382 pub fn translate(&mut self, offset: Vec2) {
383 self.position = self.position + offset;
384 }
385
386 #[inline]
390 pub fn translate_local(&mut self, offset: Vec2) {
391 let (sin, cos) = self.rotation.sin_cos();
392 let rotated = Vec2::new(
393 offset.x * cos - offset.y * sin,
394 offset.x * sin + offset.y * cos,
395 );
396 self.position = self.position + rotated;
397 }
398
399 #[inline]
401 pub fn set_position(&mut self, position: Vec2) {
402 self.position = position;
403 }
404
405 #[inline]
411 pub fn rotate(&mut self, angle: f32) {
412 self.rotation = normalize_angle(self.rotation + angle);
413 }
414
415 #[inline]
417 pub fn rotate_degrees(&mut self, degrees: f32) {
418 self.rotate(degrees.to_radians());
419 }
420
421 #[inline]
423 pub fn set_rotation(&mut self, rotation: f32) {
424 self.rotation = normalize_angle(rotation);
425 }
426
427 #[inline]
429 pub fn set_rotation_degrees(&mut self, degrees: f32) {
430 self.set_rotation(degrees.to_radians());
431 }
432
433 #[inline]
435 pub fn rotation_degrees(&self) -> f32 {
436 self.rotation.to_degrees()
437 }
438
439 #[inline]
441 pub fn look_at_target(&mut self, target: Vec2) {
442 let direction = target - self.position;
443 self.rotation = direction.y.atan2(direction.x);
444 }
445
446 #[inline]
452 pub fn set_scale(&mut self, scale: Vec2) {
453 self.scale = scale;
454 }
455
456 #[inline]
458 pub fn set_scale_uniform(&mut self, scale: f32) {
459 self.scale = Vec2::new(scale, scale);
460 }
461
462 #[inline]
464 pub fn scale_by(&mut self, factors: Vec2) {
465 self.scale = Vec2::new(self.scale.x * factors.x, self.scale.y * factors.y);
466 }
467
468 #[inline]
474 pub fn forward(&self) -> Vec2 {
475 let (sin, cos) = self.rotation.sin_cos();
476 Vec2::new(cos, sin)
477 }
478
479 #[inline]
483 pub fn right(&self) -> Vec2 {
484 let (sin, cos) = self.rotation.sin_cos();
485 Vec2::new(-sin, cos)
486 }
487
488 #[inline]
490 pub fn backward(&self) -> Vec2 {
491 -self.forward()
492 }
493
494 #[inline]
496 pub fn left(&self) -> Vec2 {
497 -self.right()
498 }
499
500 #[inline]
509 pub fn matrix(&self) -> Mat3x3 {
510 let (sin, cos) = self.rotation.sin_cos();
511 let sx = self.scale.x;
512 let sy = self.scale.y;
513
514 Mat3x3 {
519 m: [
520 cos * sx,
521 sin * sx,
522 0.0,
523 -sin * sy,
524 cos * sy,
525 0.0,
526 self.position.x,
527 self.position.y,
528 1.0,
529 ],
530 }
531 }
532
533 #[inline]
537 pub fn matrix_inverse(&self) -> Mat3x3 {
538 let (sin, cos) = self.rotation.sin_cos();
539 let inv_sx = 1.0 / self.scale.x;
540 let inv_sy = 1.0 / self.scale.y;
541
542 let inv_tx = -(cos * self.position.x + sin * self.position.y) * inv_sx;
554 let inv_ty = -(-sin * self.position.x + cos * self.position.y) * inv_sy;
555
556 Mat3x3 {
557 m: [
558 cos * inv_sx, -sin * inv_sy, 0.0, sin * inv_sx, cos * inv_sy, 0.0, inv_tx, inv_ty, 1.0, ],
568 }
569 }
570
571 #[inline]
575 pub fn to_mat4(&self) -> [f32; 16] {
576 self.matrix().to_mat4()
577 }
578
579 #[inline]
585 pub fn transform_point(&self, point: Vec2) -> Vec2 {
586 let (sin, cos) = self.rotation.sin_cos();
587 let scaled = Vec2::new(point.x * self.scale.x, point.y * self.scale.y);
588 let rotated = Vec2::new(
589 scaled.x * cos - scaled.y * sin,
590 scaled.x * sin + scaled.y * cos,
591 );
592 rotated + self.position
593 }
594
595 #[inline]
599 pub fn transform_direction(&self, direction: Vec2) -> Vec2 {
600 let (sin, cos) = self.rotation.sin_cos();
601 Vec2::new(
602 direction.x * cos - direction.y * sin,
603 direction.x * sin + direction.y * cos,
604 )
605 }
606
607 #[inline]
609 pub fn inverse_transform_point(&self, point: Vec2) -> Vec2 {
610 let translated = point - self.position;
611 let (sin, cos) = self.rotation.sin_cos();
612 let rotated = Vec2::new(
613 translated.x * cos + translated.y * sin,
614 -translated.x * sin + translated.y * cos,
615 );
616 Vec2::new(rotated.x / self.scale.x, rotated.y / self.scale.y)
617 }
618
619 #[inline]
621 pub fn inverse_transform_direction(&self, direction: Vec2) -> Vec2 {
622 let (sin, cos) = self.rotation.sin_cos();
623 Vec2::new(
624 direction.x * cos + direction.y * sin,
625 -direction.x * sin + direction.y * cos,
626 )
627 }
628
629 #[inline]
638 pub fn lerp(self, other: Self, t: f32) -> Self {
639 Self {
640 position: self.position.lerp(other.position, t),
641 rotation: lerp_angle(self.rotation, other.rotation, t),
642 scale: self.scale.lerp(other.scale, t),
643 }
644 }
645}
646
647impl Default for Transform2D {
648 #[inline]
650 fn default() -> Self {
651 Self {
652 position: Vec2::zero(),
653 rotation: 0.0,
654 scale: Vec2::one(),
655 }
656 }
657}
658
659impl Component for Transform2D {}
661
662#[inline]
664fn normalize_angle(angle: f32) -> f32 {
665 let mut result = angle % (2.0 * PI);
666 if result >= PI {
667 result -= 2.0 * PI;
668 } else if result < -PI {
669 result += 2.0 * PI;
670 }
671 result
672}
673
674#[inline]
676fn lerp_angle(from: f32, to: f32, t: f32) -> f32 {
677 let mut diff = to - from;
678
679 while diff > PI {
681 diff -= 2.0 * PI;
682 }
683 while diff < -PI {
684 diff += 2.0 * PI;
685 }
686
687 normalize_angle(from + diff * t)
688}
689
690#[cfg(test)]
691mod tests {
692 use super::*;
693 use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI};
694
695 mod mat3x3_tests {
700 use super::*;
701
702 #[test]
703 fn test_identity() {
704 let m = Mat3x3::IDENTITY;
705 assert_eq!(m.m[0], 1.0);
706 assert_eq!(m.m[4], 1.0);
707 assert_eq!(m.m[8], 1.0);
708 }
709
710 #[test]
711 fn test_translation() {
712 let m = Mat3x3::translation(10.0, 20.0);
713 let p = m.transform_point(Vec2::zero());
714 assert!((p.x - 10.0).abs() < 0.0001);
715 assert!((p.y - 20.0).abs() < 0.0001);
716 }
717
718 #[test]
719 fn test_rotation() {
720 let m = Mat3x3::rotation(FRAC_PI_2);
721 let p = m.transform_point(Vec2::unit_x());
722 assert!(p.x.abs() < 0.0001);
724 assert!((p.y - 1.0).abs() < 0.0001);
725 }
726
727 #[test]
728 fn test_scale() {
729 let m = Mat3x3::scale(2.0, 3.0);
730 let p = m.transform_point(Vec2::new(1.0, 1.0));
731 assert!((p.x - 2.0).abs() < 0.0001);
732 assert!((p.y - 3.0).abs() < 0.0001);
733 }
734
735 #[test]
736 fn test_multiply() {
737 let t = Mat3x3::translation(10.0, 0.0);
738 let r = Mat3x3::rotation(FRAC_PI_2);
739 let combined = t * r;
740
741 let p = combined.transform_point(Vec2::unit_x());
742 assert!((p.x - 10.0).abs() < 0.0001);
745 assert!((p.y - 1.0).abs() < 0.0001);
746 }
747
748 #[test]
749 fn test_inverse() {
750 let m = Mat3x3::translation(10.0, 20.0);
751 let inv = m.inverse().unwrap();
752 let result = m * inv;
753
754 assert!((result.m[0] - 1.0).abs() < 0.0001);
756 assert!((result.m[4] - 1.0).abs() < 0.0001);
757 assert!((result.m[8] - 1.0).abs() < 0.0001);
758 }
759
760 #[test]
761 fn test_inverse_rotation() {
762 let m = Mat3x3::rotation(FRAC_PI_4);
763 let inv = m.inverse().unwrap();
764 let result = m * inv;
765
766 assert!((result.m[0] - 1.0).abs() < 0.0001);
767 assert!((result.m[4] - 1.0).abs() < 0.0001);
768 }
769
770 #[test]
771 fn test_determinant() {
772 let m = Mat3x3::IDENTITY;
773 assert!((m.determinant() - 1.0).abs() < 0.0001);
774
775 let s = Mat3x3::scale(2.0, 3.0);
776 assert!((s.determinant() - 6.0).abs() < 0.0001);
777 }
778
779 #[test]
780 fn test_transform_direction() {
781 let m = Mat3x3::translation(100.0, 100.0);
782 let d = m.transform_direction(Vec2::unit_x());
783 assert!((d.x - 1.0).abs() < 0.0001);
785 assert!(d.y.abs() < 0.0001);
786 }
787
788 #[test]
789 fn test_to_mat4() {
790 let m = Mat3x3::translation(10.0, 20.0);
791 let m4 = m.to_mat4();
792
793 assert_eq!(m4[0], 1.0);
795 assert_eq!(m4[5], 1.0);
796 assert_eq!(m4[10], 1.0);
797 assert_eq!(m4[15], 1.0);
798
799 assert_eq!(m4[12], 10.0);
801 assert_eq!(m4[13], 20.0);
802 assert_eq!(m4[14], 0.0);
803 }
804
805 #[test]
806 fn test_default() {
807 assert_eq!(Mat3x3::default(), Mat3x3::IDENTITY);
808 }
809 }
810
811 mod construction_tests {
816 use super::*;
817
818 #[test]
819 fn test_default() {
820 let t = Transform2D::default();
821 assert_eq!(t.position, Vec2::zero());
822 assert_eq!(t.rotation, 0.0);
823 assert_eq!(t.scale, Vec2::one());
824 }
825
826 #[test]
827 fn test_new() {
828 let pos = Vec2::new(10.0, 20.0);
829 let rot = FRAC_PI_4;
830 let scale = Vec2::new(2.0, 3.0);
831
832 let t = Transform2D::new(pos, rot, scale);
833 assert_eq!(t.position, pos);
834 assert_eq!(t.rotation, rot);
835 assert_eq!(t.scale, scale);
836 }
837
838 #[test]
839 fn test_from_position() {
840 let pos = Vec2::new(100.0, 50.0);
841 let t = Transform2D::from_position(pos);
842 assert_eq!(t.position, pos);
843 assert_eq!(t.rotation, 0.0);
844 assert_eq!(t.scale, Vec2::one());
845 }
846
847 #[test]
848 fn test_from_rotation() {
849 let t = Transform2D::from_rotation(FRAC_PI_2);
850 assert_eq!(t.position, Vec2::zero());
851 assert_eq!(t.rotation, FRAC_PI_2);
852 assert_eq!(t.scale, Vec2::one());
853 }
854
855 #[test]
856 fn test_from_rotation_degrees() {
857 let t = Transform2D::from_rotation_degrees(90.0);
858 assert!((t.rotation - FRAC_PI_2).abs() < 0.0001);
859 }
860
861 #[test]
862 fn test_from_scale() {
863 let scale = Vec2::new(2.0, 3.0);
864 let t = Transform2D::from_scale(scale);
865 assert_eq!(t.position, Vec2::zero());
866 assert_eq!(t.rotation, 0.0);
867 assert_eq!(t.scale, scale);
868 }
869
870 #[test]
871 fn test_from_scale_uniform() {
872 let t = Transform2D::from_scale_uniform(2.0);
873 assert_eq!(t.scale, Vec2::new(2.0, 2.0));
874 }
875
876 #[test]
877 fn test_from_position_rotation() {
878 let pos = Vec2::new(10.0, 20.0);
879 let t = Transform2D::from_position_rotation(pos, FRAC_PI_4);
880 assert_eq!(t.position, pos);
881 assert_eq!(t.rotation, FRAC_PI_4);
882 assert_eq!(t.scale, Vec2::one());
883 }
884
885 #[test]
886 fn test_look_at() {
887 let t = Transform2D::look_at(Vec2::zero(), Vec2::new(1.0, 0.0));
888 assert!(t.rotation.abs() < 0.0001); let t2 = Transform2D::look_at(Vec2::zero(), Vec2::new(0.0, 1.0));
891 assert!((t2.rotation - FRAC_PI_2).abs() < 0.0001); }
893 }
894
895 mod mutation_tests {
900 use super::*;
901
902 #[test]
903 fn test_translate() {
904 let mut t = Transform2D::default();
905 t.translate(Vec2::new(5.0, 10.0));
906 assert_eq!(t.position, Vec2::new(5.0, 10.0));
907
908 t.translate(Vec2::new(3.0, 2.0));
909 assert_eq!(t.position, Vec2::new(8.0, 12.0));
910 }
911
912 #[test]
913 fn test_translate_local() {
914 let mut t = Transform2D::from_rotation(FRAC_PI_2);
915 t.translate_local(Vec2::new(1.0, 0.0));
916
917 assert!(t.position.x.abs() < 0.0001);
919 assert!((t.position.y - 1.0).abs() < 0.0001);
920 }
921
922 #[test]
923 fn test_set_position() {
924 let mut t = Transform2D::from_position(Vec2::new(10.0, 20.0));
925 t.set_position(Vec2::new(100.0, 200.0));
926 assert_eq!(t.position, Vec2::new(100.0, 200.0));
927 }
928
929 #[test]
930 fn test_rotate() {
931 let mut t = Transform2D::default();
932 t.rotate(FRAC_PI_4);
933 assert!((t.rotation - FRAC_PI_4).abs() < 0.0001);
934
935 t.rotate(FRAC_PI_4);
936 assert!((t.rotation - FRAC_PI_2).abs() < 0.0001);
937 }
938
939 #[test]
940 fn test_rotate_degrees() {
941 let mut t = Transform2D::default();
942 t.rotate_degrees(45.0);
943 assert!((t.rotation - FRAC_PI_4).abs() < 0.0001);
944 }
945
946 #[test]
947 fn test_set_rotation() {
948 let mut t = Transform2D::default();
949 t.set_rotation(FRAC_PI_2);
950 assert!((t.rotation - FRAC_PI_2).abs() < 0.0001);
951 }
952
953 #[test]
954 fn test_set_rotation_degrees() {
955 let mut t = Transform2D::default();
956 t.set_rotation_degrees(90.0);
957 assert!((t.rotation - FRAC_PI_2).abs() < 0.0001);
958 }
959
960 #[test]
961 fn test_rotation_degrees() {
962 let t = Transform2D::from_rotation(FRAC_PI_2);
963 assert!((t.rotation_degrees() - 90.0).abs() < 0.01);
964 }
965
966 #[test]
967 fn test_look_at_target() {
968 let mut t = Transform2D::from_position(Vec2::new(10.0, 10.0));
969 t.look_at_target(Vec2::new(20.0, 10.0));
970 assert!(t.rotation.abs() < 0.0001); }
972
973 #[test]
974 fn test_set_scale() {
975 let mut t = Transform2D::default();
976 t.set_scale(Vec2::new(2.0, 3.0));
977 assert_eq!(t.scale, Vec2::new(2.0, 3.0));
978 }
979
980 #[test]
981 fn test_set_scale_uniform() {
982 let mut t = Transform2D::default();
983 t.set_scale_uniform(5.0);
984 assert_eq!(t.scale, Vec2::new(5.0, 5.0));
985 }
986
987 #[test]
988 fn test_scale_by() {
989 let mut t = Transform2D::from_scale(Vec2::new(2.0, 3.0));
990 t.scale_by(Vec2::new(2.0, 2.0));
991 assert_eq!(t.scale, Vec2::new(4.0, 6.0));
992 }
993 }
994
995 mod direction_tests {
1000 use super::*;
1001
1002 #[test]
1003 fn test_directions_identity() {
1004 let t = Transform2D::default();
1005
1006 let fwd = t.forward();
1007 assert!((fwd.x - 1.0).abs() < 0.0001);
1008 assert!(fwd.y.abs() < 0.0001);
1009
1010 let right = t.right();
1011 assert!(right.x.abs() < 0.0001);
1012 assert!((right.y - 1.0).abs() < 0.0001);
1013 }
1014
1015 #[test]
1016 fn test_directions_rotated() {
1017 let t = Transform2D::from_rotation(FRAC_PI_2);
1018
1019 let fwd = t.forward();
1022 assert!(fwd.x.abs() < 0.0001);
1023 assert!((fwd.y - 1.0).abs() < 0.0001);
1024
1025 let right = t.right();
1027 assert!((right.x - (-1.0)).abs() < 0.0001);
1028 assert!(right.y.abs() < 0.0001);
1029 }
1030
1031 #[test]
1032 fn test_backward_and_left() {
1033 let t = Transform2D::default();
1034
1035 let back = t.backward();
1036 assert!((back.x - (-1.0)).abs() < 0.0001);
1037
1038 let left = t.left();
1039 assert!((left.y - (-1.0)).abs() < 0.0001);
1040 }
1041 }
1042
1043 mod matrix_tests {
1048 use super::*;
1049
1050 #[test]
1051 fn test_matrix_identity() {
1052 let t = Transform2D::default();
1053 let m = t.matrix();
1054
1055 assert!((m.m[0] - 1.0).abs() < 0.0001);
1057 assert!((m.m[4] - 1.0).abs() < 0.0001);
1058 assert!((m.m[8] - 1.0).abs() < 0.0001);
1059 }
1060
1061 #[test]
1062 fn test_matrix_translation() {
1063 let t = Transform2D::from_position(Vec2::new(10.0, 20.0));
1064 let m = t.matrix();
1065
1066 assert!((m.m[6] - 10.0).abs() < 0.0001);
1067 assert!((m.m[7] - 20.0).abs() < 0.0001);
1068 }
1069
1070 #[test]
1071 fn test_matrix_scale() {
1072 let t = Transform2D::from_scale(Vec2::new(2.0, 3.0));
1073 let m = t.matrix();
1074
1075 assert!((m.m[0] - 2.0).abs() < 0.0001);
1076 assert!((m.m[4] - 3.0).abs() < 0.0001);
1077 }
1078
1079 #[test]
1080 fn test_matrix_rotation() {
1081 let t = Transform2D::from_rotation(FRAC_PI_2);
1082 let m = t.matrix();
1083
1084 let p = m.transform_point(Vec2::new(1.0, 0.0));
1085 assert!(p.x.abs() < 0.0001);
1087 assert!((p.y - 1.0).abs() < 0.0001);
1088 }
1089
1090 #[test]
1091 fn test_matrix_inverse() {
1092 let t = Transform2D::new(Vec2::new(10.0, 20.0), FRAC_PI_4, Vec2::new(2.0, 3.0));
1093
1094 let m = t.matrix();
1095 let m_inv = t.matrix_inverse();
1096
1097 let result = m * m_inv;
1098
1099 assert!((result.m[0] - 1.0).abs() < 0.001);
1101 assert!((result.m[4] - 1.0).abs() < 0.001);
1102 assert!((result.m[8] - 1.0).abs() < 0.001);
1103 assert!(result.m[6].abs() < 0.001);
1104 assert!(result.m[7].abs() < 0.001);
1105 }
1106
1107 #[test]
1108 fn test_to_mat4() {
1109 let t = Transform2D::from_position(Vec2::new(5.0, 10.0));
1110 let m4 = t.to_mat4();
1111
1112 assert_eq!(m4[12], 5.0);
1114 assert_eq!(m4[13], 10.0);
1115 assert_eq!(m4[14], 0.0);
1116
1117 assert_eq!(m4[0], 1.0);
1119 assert_eq!(m4[5], 1.0);
1120 assert_eq!(m4[10], 1.0);
1121 assert_eq!(m4[15], 1.0);
1122 }
1123 }
1124
1125 mod point_transform_tests {
1130 use super::*;
1131
1132 #[test]
1133 fn test_transform_point_translation() {
1134 let t = Transform2D::from_position(Vec2::new(10.0, 20.0));
1135 let p = t.transform_point(Vec2::zero());
1136 assert_eq!(p, Vec2::new(10.0, 20.0));
1137 }
1138
1139 #[test]
1140 fn test_transform_point_scale() {
1141 let t = Transform2D::from_scale(Vec2::new(2.0, 3.0));
1142 let p = t.transform_point(Vec2::new(5.0, 5.0));
1143 assert_eq!(p, Vec2::new(10.0, 15.0));
1144 }
1145
1146 #[test]
1147 fn test_transform_point_rotation() {
1148 let t = Transform2D::from_rotation(FRAC_PI_2);
1149 let p = t.transform_point(Vec2::new(1.0, 0.0));
1150 assert!(p.x.abs() < 0.0001);
1152 assert!((p.y - 1.0).abs() < 0.0001);
1153 }
1154
1155 #[test]
1156 fn test_transform_direction() {
1157 let t = Transform2D::new(
1158 Vec2::new(100.0, 100.0), FRAC_PI_2,
1160 Vec2::one(),
1161 );
1162
1163 let dir = Vec2::new(1.0, 0.0);
1164 let transformed = t.transform_direction(dir);
1165
1166 assert!(transformed.x.abs() < 0.0001);
1168 assert!((transformed.y - 1.0).abs() < 0.0001);
1169 }
1170
1171 #[test]
1172 fn test_inverse_transform_point() {
1173 let t = Transform2D::new(Vec2::new(10.0, 20.0), FRAC_PI_4, Vec2::new(2.0, 2.0));
1174
1175 let world_point = Vec2::new(5.0, 5.0);
1176 let local = t.inverse_transform_point(world_point);
1177 let back_to_world = t.transform_point(local);
1178
1179 assert!((back_to_world.x - world_point.x).abs() < 0.001);
1180 assert!((back_to_world.y - world_point.y).abs() < 0.001);
1181 }
1182
1183 #[test]
1184 fn test_inverse_transform_direction() {
1185 let t = Transform2D::from_rotation(FRAC_PI_2);
1186
1187 let world_dir = Vec2::new(1.0, 0.0);
1188 let local = t.inverse_transform_direction(world_dir);
1189 let back = t.transform_direction(local);
1190
1191 assert!((back.x - world_dir.x).abs() < 0.0001);
1192 assert!((back.y - world_dir.y).abs() < 0.0001);
1193 }
1194 }
1195
1196 mod interpolation_tests {
1201 use super::*;
1202
1203 #[test]
1204 fn test_lerp_position() {
1205 let a = Transform2D::from_position(Vec2::zero());
1206 let b = Transform2D::from_position(Vec2::new(10.0, 20.0));
1207
1208 let mid = a.lerp(b, 0.5);
1209 assert_eq!(mid.position, Vec2::new(5.0, 10.0));
1210 }
1211
1212 #[test]
1213 fn test_lerp_scale() {
1214 let a = Transform2D::from_scale(Vec2::one());
1215 let b = Transform2D::from_scale(Vec2::new(3.0, 3.0));
1216
1217 let mid = a.lerp(b, 0.5);
1218 assert_eq!(mid.scale, Vec2::new(2.0, 2.0));
1219 }
1220
1221 #[test]
1222 fn test_lerp_rotation() {
1223 let a = Transform2D::from_rotation(0.0);
1224 let b = Transform2D::from_rotation(FRAC_PI_2);
1225
1226 let mid = a.lerp(b, 0.5);
1227 assert!((mid.rotation - FRAC_PI_4).abs() < 0.0001);
1228 }
1229
1230 #[test]
1231 fn test_lerp_rotation_shortest_path() {
1232 let a = Transform2D::from_rotation(-170.0_f32.to_radians());
1234 let b = Transform2D::from_rotation(170.0_f32.to_radians());
1235
1236 let mid = a.lerp(b, 0.5);
1237 assert!(mid.rotation.abs() > 3.0); }
1240
1241 #[test]
1242 fn test_lerp_endpoints() {
1243 let a = Transform2D::new(Vec2::zero(), 0.0, Vec2::one());
1244 let b = Transform2D::new(Vec2::new(10.0, 10.0), PI, Vec2::new(2.0, 2.0));
1245
1246 let start = a.lerp(b, 0.0);
1247 assert_eq!(start.position, a.position);
1248 assert_eq!(start.scale, a.scale);
1249
1250 let end = a.lerp(b, 1.0);
1251 assert_eq!(end.position, b.position);
1252 assert_eq!(end.scale, b.scale);
1253 }
1254 }
1255
1256 mod component_tests {
1261 use super::*;
1262
1263 #[test]
1264 fn test_transform2d_is_component() {
1265 fn assert_component<T: Component>() {}
1266 assert_component::<Transform2D>();
1267 }
1268
1269 #[test]
1270 fn test_transform2d_is_send() {
1271 fn assert_send<T: Send>() {}
1272 assert_send::<Transform2D>();
1273 }
1274
1275 #[test]
1276 fn test_transform2d_is_sync() {
1277 fn assert_sync<T: Sync>() {}
1278 assert_sync::<Transform2D>();
1279 }
1280
1281 #[test]
1282 fn test_transform2d_clone() {
1283 let t = Transform2D::new(Vec2::new(1.0, 2.0), FRAC_PI_4, Vec2::new(2.0, 3.0));
1284 let cloned = t.clone();
1285 assert_eq!(t, cloned);
1286 }
1287
1288 #[test]
1289 fn test_transform2d_copy() {
1290 let t = Transform2D::default();
1291 let copied = t;
1292 assert_eq!(t, copied);
1293 }
1294 }
1295
1296 mod ffi_tests {
1301 use super::*;
1302 use std::mem::{align_of, size_of};
1303
1304 #[test]
1305 fn test_transform2d_size() {
1306 assert_eq!(size_of::<Transform2D>(), 20);
1308 }
1309
1310 #[test]
1311 fn test_transform2d_align() {
1312 assert_eq!(align_of::<Transform2D>(), 4); }
1314
1315 #[test]
1316 fn test_mat3x3_size() {
1317 assert_eq!(size_of::<Mat3x3>(), 36);
1319 }
1320
1321 #[test]
1322 fn test_mat3x3_align() {
1323 assert_eq!(align_of::<Mat3x3>(), 4);
1324 }
1325
1326 #[test]
1327 fn test_transform2d_field_layout() {
1328 let t = Transform2D::new(Vec2::new(1.0, 2.0), 3.0, Vec2::new(4.0, 5.0));
1329 let ptr = &t as *const Transform2D as *const f32;
1330 unsafe {
1331 assert_eq!(*ptr, 1.0); assert_eq!(*ptr.add(1), 2.0); assert_eq!(*ptr.add(2), 3.0); assert_eq!(*ptr.add(3), 4.0); assert_eq!(*ptr.add(4), 5.0); }
1337 }
1338 }
1339
1340 mod utility_tests {
1345 use super::*;
1346
1347 #[test]
1348 fn test_normalize_angle() {
1349 assert!((normalize_angle(0.0) - 0.0).abs() < 0.0001);
1351 assert!((normalize_angle(1.0) - 1.0).abs() < 0.0001);
1352
1353 assert!((normalize_angle(PI + 0.5) - (-PI + 0.5)).abs() < 0.0001);
1355
1356 assert!((normalize_angle(-PI - 0.5) - (PI - 0.5)).abs() < 0.0001);
1358
1359 let result = normalize_angle(3.0 * PI);
1361 assert!(result >= -PI && result < PI);
1362
1363 let result = normalize_angle(-3.0 * PI);
1365 assert!(result >= -PI && result < PI);
1366 }
1367
1368 #[test]
1369 fn test_lerp_angle_same_direction() {
1370 let result = lerp_angle(0.0, FRAC_PI_2, 0.5);
1371 assert!((result - FRAC_PI_4).abs() < 0.0001);
1372 }
1373
1374 #[test]
1375 fn test_lerp_angle_across_boundary() {
1376 let from = -170.0_f32.to_radians();
1378 let to = 170.0_f32.to_radians();
1379 let mid = lerp_angle(from, to, 0.5);
1380
1381 assert!(mid.abs() > 3.0);
1383 }
1384
1385 #[test]
1386 fn test_lerp_angle_endpoints() {
1387 let from = 0.5;
1388 let to = 1.5;
1389
1390 assert!((lerp_angle(from, to, 0.0) - from).abs() < 0.0001);
1391 assert!((lerp_angle(from, to, 1.0) - to).abs() < 0.0001);
1392 }
1393 }
1394}