1use crate::core::math::{Matrix4, Quaternion, Vec3};
51use crate::ecs::Component;
52use cgmath::InnerSpace;
53use std::f32::consts::PI;
54
55#[repr(C)]
88#[derive(Clone, Copy, Debug, PartialEq)]
89pub struct Transform {
90 pub position: Vec3,
92 pub rotation: Quat,
97 pub scale: Vec3,
102}
103
104#[repr(C)]
112#[derive(Clone, Copy, Debug, PartialEq)]
113pub struct Quat {
114 pub x: f32,
116 pub y: f32,
118 pub z: f32,
120 pub w: f32,
122}
123
124impl Quat {
125 pub const IDENTITY: Quat = Quat {
127 x: 0.0,
128 y: 0.0,
129 z: 0.0,
130 w: 1.0,
131 };
132
133 #[inline]
135 pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
136 Self { x, y, z, w }
137 }
138
139 #[inline]
157 pub fn from_axis_angle(axis: Vec3, angle: f32) -> Self {
158 let half_angle = angle * 0.5;
159 let s = half_angle.sin();
160 let c = half_angle.cos();
161 Self {
162 x: axis.x * s,
163 y: axis.y * s,
164 z: axis.z * s,
165 w: c,
166 }
167 }
168
169 #[inline]
186 pub fn from_euler(pitch: f32, yaw: f32, roll: f32) -> Self {
187 let (sx, cx) = (pitch * 0.5).sin_cos();
189 let (sy, cy) = (yaw * 0.5).sin_cos();
190 let (sz, cz) = (roll * 0.5).sin_cos();
191
192 Self {
193 x: sx * cy * cz + cx * sy * sz,
194 y: cx * sy * cz - sx * cy * sz,
195 z: cx * cy * sz + sx * sy * cz,
196 w: cx * cy * cz - sx * sy * sz,
197 }
198 }
199
200 #[inline]
204 pub fn from_rotation_arc(from: Vec3, to: Vec3) -> Self {
205 let from_cg: cgmath::Vector3<f32> = from.into();
206 let to_cg: cgmath::Vector3<f32> = to.into();
207
208 let dot = from_cg.dot(to_cg);
210 if dot < -0.999999 {
211 let mut axis = cgmath::Vector3::unit_x().cross(from_cg);
213 if axis.magnitude2() < 0.000001 {
214 axis = cgmath::Vector3::unit_y().cross(from_cg);
215 }
216 axis = axis.normalize();
217 return Self::from_axis_angle(Vec3::from(axis), PI);
218 }
219
220 let cross = from_cg.cross(to_cg);
221 Self {
222 x: cross.x,
223 y: cross.y,
224 z: cross.z,
225 w: 1.0 + dot,
226 }
227 .normalize()
228 }
229
230 #[inline]
232 pub fn length_squared(self) -> f32 {
233 self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
234 }
235
236 #[inline]
238 pub fn length(self) -> f32 {
239 self.length_squared().sqrt()
240 }
241
242 #[inline]
244 pub fn normalize(self) -> Self {
245 let len = self.length();
246 if len == 0.0 {
247 Self::IDENTITY
248 } else {
249 Self {
250 x: self.x / len,
251 y: self.y / len,
252 z: self.z / len,
253 w: self.w / len,
254 }
255 }
256 }
257
258 #[inline]
262 pub fn conjugate(self) -> Self {
263 Self {
264 x: -self.x,
265 y: -self.y,
266 z: -self.z,
267 w: self.w,
268 }
269 }
270
271 #[inline]
275 pub fn inverse(self) -> Self {
276 let len_sq = self.length_squared();
277 if len_sq == 0.0 {
278 Self::IDENTITY
279 } else {
280 let conj = self.conjugate();
281 Self {
282 x: conj.x / len_sq,
283 y: conj.y / len_sq,
284 z: conj.z / len_sq,
285 w: conj.w / len_sq,
286 }
287 }
288 }
289
290 #[inline]
295 pub fn multiply(self, other: Self) -> Self {
296 self * other
297 }
298
299 #[inline]
301 pub fn rotate_vector(self, v: Vec3) -> Vec3 {
302 let qv = Vec3::new(self.x, self.y, self.z);
304 let uv = qv.cross(v);
305 let uuv = qv.cross(uv);
306 v + (uv * self.w + uuv) * 2.0
307 }
308
309 #[inline]
313 pub fn slerp(self, other: Self, t: f32) -> Self {
314 let dot = self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
315
316 let (other, dot) = if dot < 0.0 {
318 (
319 Self {
320 x: -other.x,
321 y: -other.y,
322 z: -other.z,
323 w: -other.w,
324 },
325 -dot,
326 )
327 } else {
328 (other, dot)
329 };
330
331 if dot > 0.9995 {
333 return Self {
334 x: self.x + t * (other.x - self.x),
335 y: self.y + t * (other.y - self.y),
336 z: self.z + t * (other.z - self.z),
337 w: self.w + t * (other.w - self.w),
338 }
339 .normalize();
340 }
341
342 let theta_0 = dot.acos();
343 let theta = theta_0 * t;
344 let sin_theta = theta.sin();
345 let sin_theta_0 = theta_0.sin();
346
347 let s0 = (theta_0 - theta).cos() - dot * sin_theta / sin_theta_0;
348 let s1 = sin_theta / sin_theta_0;
349
350 Self {
351 x: self.x * s0 + other.x * s1,
352 y: self.y * s0 + other.y * s1,
353 z: self.z * s0 + other.z * s1,
354 w: self.w * s0 + other.w * s1,
355 }
356 }
357
358 #[inline]
362 pub fn to_euler(self) -> (f32, f32, f32) {
363 let sinr_cosp = 2.0 * (self.w * self.x + self.y * self.z);
365 let cosr_cosp = 1.0 - 2.0 * (self.x * self.x + self.y * self.y);
366 let roll = sinr_cosp.atan2(cosr_cosp);
367
368 let sinp = 2.0 * (self.w * self.y - self.z * self.x);
370 let pitch = if sinp.abs() >= 1.0 {
371 (PI / 2.0).copysign(sinp)
372 } else {
373 sinp.asin()
374 };
375
376 let siny_cosp = 2.0 * (self.w * self.z + self.x * self.y);
378 let cosy_cosp = 1.0 - 2.0 * (self.y * self.y + self.z * self.z);
379 let yaw = siny_cosp.atan2(cosy_cosp);
380
381 (pitch, yaw, roll)
382 }
383
384 #[inline]
386 pub fn forward(self) -> Vec3 {
387 self.rotate_vector(Vec3::new(0.0, 0.0, -1.0))
388 }
389
390 #[inline]
392 pub fn right(self) -> Vec3 {
393 self.rotate_vector(Vec3::new(1.0, 0.0, 0.0))
394 }
395
396 #[inline]
398 pub fn up(self) -> Vec3 {
399 self.rotate_vector(Vec3::new(0.0, 1.0, 0.0))
400 }
401}
402
403impl Default for Quat {
404 #[inline]
405 fn default() -> Self {
406 Self::IDENTITY
407 }
408}
409
410impl std::ops::Mul for Quat {
412 type Output = Self;
413
414 #[inline]
418 fn mul(self, other: Self) -> Self {
419 Self {
420 x: self.w * other.x + self.x * other.w + self.y * other.z - self.z * other.y,
421 y: self.w * other.y - self.x * other.z + self.y * other.w + self.z * other.x,
422 z: self.w * other.z + self.x * other.y - self.y * other.x + self.z * other.w,
423 w: self.w * other.w - self.x * other.x - self.y * other.y - self.z * other.z,
424 }
425 }
426}
427
428impl From<Quaternion<f32>> for Quat {
430 #[inline]
431 fn from(q: Quaternion<f32>) -> Self {
432 Self {
433 x: q.v.x,
434 y: q.v.y,
435 z: q.v.z,
436 w: q.s,
437 }
438 }
439}
440
441impl From<Quat> for Quaternion<f32> {
442 #[inline]
443 fn from(q: Quat) -> Self {
444 Quaternion::new(q.w, q.x, q.y, q.z)
445 }
446}
447
448impl Transform {
449 pub const IDENTITY_ROTATION: Quat = Quat::IDENTITY;
451
452 #[inline]
454 pub const fn new(position: Vec3, rotation: Quat, scale: Vec3) -> Self {
455 Self {
456 position,
457 rotation,
458 scale,
459 }
460 }
461
462 #[inline]
464 pub fn from_position(position: Vec3) -> Self {
465 Self {
466 position,
467 rotation: Quat::IDENTITY,
468 scale: Vec3::one(),
469 }
470 }
471
472 #[inline]
474 pub fn from_rotation(rotation: Quat) -> Self {
475 Self {
476 position: Vec3::zero(),
477 rotation,
478 scale: Vec3::one(),
479 }
480 }
481
482 #[inline]
484 pub fn from_scale(scale: Vec3) -> Self {
485 Self {
486 position: Vec3::zero(),
487 rotation: Quat::IDENTITY,
488 scale,
489 }
490 }
491
492 #[inline]
494 pub fn from_scale_uniform(scale: f32) -> Self {
495 Self::from_scale(Vec3::new(scale, scale, scale))
496 }
497
498 #[inline]
500 pub fn from_position_rotation(position: Vec3, rotation: Quat) -> Self {
501 Self {
502 position,
503 rotation,
504 scale: Vec3::one(),
505 }
506 }
507
508 #[inline]
529 pub fn look_at(eye: Vec3, target: Vec3, up: Vec3) -> Self {
530 let forward = (target - eye).normalize();
532 let right = up.cross(forward).normalize();
533 let up = forward.cross(right);
534
535 let neg_forward = Vec3::new(-forward.x, -forward.y, -forward.z);
549
550 let m00 = right.x;
552 let m01 = up.x;
553 let m02 = neg_forward.x;
554 let m10 = right.y;
555 let m11 = up.y;
556 let m12 = neg_forward.y;
557 let m20 = right.z;
558 let m21 = up.z;
559 let m22 = neg_forward.z;
560
561 let trace = m00 + m11 + m22;
562 let rotation = if trace > 0.0 {
563 let s = (trace + 1.0).sqrt() * 2.0;
564 Quat::new((m21 - m12) / s, (m02 - m20) / s, (m10 - m01) / s, 0.25 * s)
565 } else if m00 > m11 && m00 > m22 {
566 let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;
567 Quat::new(0.25 * s, (m01 + m10) / s, (m02 + m20) / s, (m21 - m12) / s)
568 } else if m11 > m22 {
569 let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;
570 Quat::new((m01 + m10) / s, 0.25 * s, (m12 + m21) / s, (m02 - m20) / s)
571 } else {
572 let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;
573 Quat::new((m02 + m20) / s, (m12 + m21) / s, 0.25 * s, (m10 - m01) / s)
574 };
575
576 Self {
577 position: eye,
578 rotation: rotation.normalize(),
579 scale: Vec3::one(),
580 }
581 }
582
583 #[inline]
589 pub fn translate(&mut self, offset: Vec3) {
590 self.position = self.position + offset;
591 }
592
593 #[inline]
597 pub fn translate_local(&mut self, offset: Vec3) {
598 let world_offset = self.rotation.rotate_vector(offset);
599 self.position = self.position + world_offset;
600 }
601
602 #[inline]
604 pub fn set_position(&mut self, position: Vec3) {
605 self.position = position;
606 }
607
608 #[inline]
616 pub fn rotate(&mut self, rotation: Quat) {
617 self.rotation = (self.rotation * rotation).normalize();
618 }
619
620 #[inline]
622 pub fn rotate_x(&mut self, angle: f32) {
623 self.rotate(Quat::from_axis_angle(Vec3::unit_x(), angle));
624 }
625
626 #[inline]
628 pub fn rotate_y(&mut self, angle: f32) {
629 self.rotate(Quat::from_axis_angle(Vec3::unit_y(), angle));
630 }
631
632 #[inline]
634 pub fn rotate_z(&mut self, angle: f32) {
635 self.rotate(Quat::from_axis_angle(Vec3::unit_z(), angle));
636 }
637
638 #[inline]
640 pub fn rotate_axis(&mut self, axis: Vec3, angle: f32) {
641 self.rotate(Quat::from_axis_angle(axis.normalize(), angle));
642 }
643
644 #[inline]
646 pub fn rotate_local_x(&mut self, angle: f32) {
647 let local_rotation = Quat::from_axis_angle(Vec3::unit_x(), angle);
648 self.rotation = (self.rotation * local_rotation).normalize();
649 }
650
651 #[inline]
653 pub fn rotate_local_y(&mut self, angle: f32) {
654 let local_rotation = Quat::from_axis_angle(Vec3::unit_y(), angle);
655 self.rotation = (self.rotation * local_rotation).normalize();
656 }
657
658 #[inline]
660 pub fn rotate_local_z(&mut self, angle: f32) {
661 let local_rotation = Quat::from_axis_angle(Vec3::unit_z(), angle);
662 self.rotation = (self.rotation * local_rotation).normalize();
663 }
664
665 #[inline]
667 pub fn set_rotation_euler(&mut self, pitch: f32, yaw: f32, roll: f32) {
668 self.rotation = Quat::from_euler(pitch, yaw, roll);
669 }
670
671 #[inline]
673 pub fn set_rotation(&mut self, rotation: Quat) {
674 self.rotation = rotation.normalize();
675 }
676
677 #[inline]
679 pub fn look_at_target(&mut self, target: Vec3, up: Vec3) {
680 let looking = Transform::look_at(self.position, target, up);
681 self.rotation = looking.rotation;
682 }
683
684 #[inline]
690 pub fn set_scale(&mut self, scale: Vec3) {
691 self.scale = scale;
692 }
693
694 #[inline]
696 pub fn set_scale_uniform(&mut self, scale: f32) {
697 self.scale = Vec3::new(scale, scale, scale);
698 }
699
700 #[inline]
702 pub fn scale_by(&mut self, factors: Vec3) {
703 self.scale = Vec3::new(
704 self.scale.x * factors.x,
705 self.scale.y * factors.y,
706 self.scale.z * factors.z,
707 );
708 }
709
710 #[inline]
716 pub fn forward(&self) -> Vec3 {
717 self.rotation.forward()
718 }
719
720 #[inline]
722 pub fn right(&self) -> Vec3 {
723 self.rotation.right()
724 }
725
726 #[inline]
728 pub fn up(&self) -> Vec3 {
729 self.rotation.up()
730 }
731
732 #[inline]
734 pub fn back(&self) -> Vec3 {
735 self.rotation.rotate_vector(Vec3::new(0.0, 0.0, 1.0))
736 }
737
738 #[inline]
740 pub fn left(&self) -> Vec3 {
741 self.rotation.rotate_vector(Vec3::new(-1.0, 0.0, 0.0))
742 }
743
744 #[inline]
746 pub fn down(&self) -> Vec3 {
747 self.rotation.rotate_vector(Vec3::new(0.0, -1.0, 0.0))
748 }
749
750 #[inline]
759 pub fn matrix(&self) -> Matrix4<f32> {
760 let rotation: Quaternion<f32> = self.rotation.into();
761 let rotation_matrix: cgmath::Matrix3<f32> = rotation.into();
762
763 let sx = self.scale.x;
766 let sy = self.scale.y;
767 let sz = self.scale.z;
768
769 let r = rotation_matrix;
771
772 Matrix4::new(
773 r.x.x * sx,
774 r.x.y * sx,
775 r.x.z * sx,
776 0.0,
777 r.y.x * sy,
778 r.y.y * sy,
779 r.y.z * sy,
780 0.0,
781 r.z.x * sz,
782 r.z.y * sz,
783 r.z.z * sz,
784 0.0,
785 self.position.x,
786 self.position.y,
787 self.position.z,
788 1.0,
789 )
790 }
791
792 #[inline]
796 pub fn matrix_inverse(&self) -> Matrix4<f32> {
797 let inv_rotation = self.rotation.inverse();
799 let inv_rotation_cg: Quaternion<f32> = inv_rotation.into();
800 let inv_rotation_matrix: cgmath::Matrix3<f32> = inv_rotation_cg.into();
801
802 let inv_scale = Vec3::new(1.0 / self.scale.x, 1.0 / self.scale.y, 1.0 / self.scale.z);
803
804 let inv_pos = inv_rotation.rotate_vector(-self.position);
806 let inv_pos_scaled = Vec3::new(
807 inv_pos.x * inv_scale.x,
808 inv_pos.y * inv_scale.y,
809 inv_pos.z * inv_scale.z,
810 );
811
812 let r = inv_rotation_matrix;
813
814 Matrix4::new(
815 r.x.x * inv_scale.x,
816 r.x.y * inv_scale.x,
817 r.x.z * inv_scale.x,
818 0.0,
819 r.y.x * inv_scale.y,
820 r.y.y * inv_scale.y,
821 r.y.z * inv_scale.y,
822 0.0,
823 r.z.x * inv_scale.z,
824 r.z.y * inv_scale.z,
825 r.z.z * inv_scale.z,
826 0.0,
827 inv_pos_scaled.x,
828 inv_pos_scaled.y,
829 inv_pos_scaled.z,
830 1.0,
831 )
832 }
833
834 #[inline]
840 pub fn transform_point(&self, point: Vec3) -> Vec3 {
841 let scaled = Vec3::new(
842 point.x * self.scale.x,
843 point.y * self.scale.y,
844 point.z * self.scale.z,
845 );
846 let rotated = self.rotation.rotate_vector(scaled);
847 rotated + self.position
848 }
849
850 #[inline]
854 pub fn transform_direction(&self, direction: Vec3) -> Vec3 {
855 self.rotation.rotate_vector(direction)
856 }
857
858 #[inline]
860 pub fn inverse_transform_point(&self, point: Vec3) -> Vec3 {
861 let translated = point - self.position;
862 let inv_rotation = self.rotation.inverse();
863 let rotated = inv_rotation.rotate_vector(translated);
864 Vec3::new(
865 rotated.x / self.scale.x,
866 rotated.y / self.scale.y,
867 rotated.z / self.scale.z,
868 )
869 }
870
871 #[inline]
873 pub fn inverse_transform_direction(&self, direction: Vec3) -> Vec3 {
874 let inv_rotation = self.rotation.inverse();
875 inv_rotation.rotate_vector(direction)
876 }
877
878 #[inline]
886 pub fn lerp(self, other: Self, t: f32) -> Self {
887 Self {
888 position: self.position.lerp(other.position, t),
889 rotation: self.rotation.slerp(other.rotation, t),
890 scale: self.scale.lerp(other.scale, t),
891 }
892 }
893}
894
895impl Default for Transform {
896 #[inline]
898 fn default() -> Self {
899 Self {
900 position: Vec3::zero(),
901 rotation: Quat::IDENTITY,
902 scale: Vec3::one(),
903 }
904 }
905}
906
907impl Component for Transform {}
909
910#[cfg(test)]
911mod tests {
912 use super::*;
913 use std::f32::consts::FRAC_PI_2;
914 use std::f32::consts::FRAC_PI_4;
915
916 mod quat_tests {
921 use super::*;
922
923 #[test]
924 fn test_quat_identity() {
925 let q = Quat::IDENTITY;
926 assert_eq!(q.x, 0.0);
927 assert_eq!(q.y, 0.0);
928 assert_eq!(q.z, 0.0);
929 assert_eq!(q.w, 1.0);
930 }
931
932 #[test]
933 fn test_quat_new() {
934 let q = Quat::new(1.0, 2.0, 3.0, 4.0);
935 assert_eq!(q.x, 1.0);
936 assert_eq!(q.y, 2.0);
937 assert_eq!(q.z, 3.0);
938 assert_eq!(q.w, 4.0);
939 }
940
941 #[test]
942 fn test_quat_from_axis_angle() {
943 let q = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_2);
944 assert!((q.length() - 1.0).abs() < 0.0001);
945
946 let rotated = q.rotate_vector(Vec3::unit_z());
948 assert!((rotated.x - 1.0).abs() < 0.0001);
949 assert!(rotated.y.abs() < 0.0001);
950 assert!(rotated.z.abs() < 0.0001);
951 }
952
953 #[test]
954 fn test_quat_from_euler() {
955 let q = Quat::from_euler(0.0, FRAC_PI_2, 0.0);
957 assert!((q.length() - 1.0).abs() < 0.0001);
958 }
959
960 #[test]
961 fn test_quat_normalize() {
962 let q = Quat::new(1.0, 2.0, 3.0, 4.0);
963 let n = q.normalize();
964 assert!((n.length() - 1.0).abs() < 0.0001);
965 }
966
967 #[test]
968 fn test_quat_conjugate() {
969 let q = Quat::new(1.0, 2.0, 3.0, 4.0);
970 let c = q.conjugate();
971 assert_eq!(c.x, -1.0);
972 assert_eq!(c.y, -2.0);
973 assert_eq!(c.z, -3.0);
974 assert_eq!(c.w, 4.0);
975 }
976
977 #[test]
978 fn test_quat_mul_identity() {
979 let q = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
980 let result = q * Quat::IDENTITY;
981 assert!((result.x - q.x).abs() < 0.0001);
982 assert!((result.y - q.y).abs() < 0.0001);
983 assert!((result.z - q.z).abs() < 0.0001);
984 assert!((result.w - q.w).abs() < 0.0001);
985 }
986
987 #[test]
988 fn test_quat_rotate_vector() {
989 let q = Quat::from_axis_angle(Vec3::unit_y(), PI);
990 let v = Vec3::new(1.0, 0.0, 0.0);
991 let rotated = q.rotate_vector(v);
992 assert!((rotated.x - (-1.0)).abs() < 0.0001);
994 assert!(rotated.y.abs() < 0.0001);
995 assert!(rotated.z.abs() < 0.0001);
996 }
997
998 #[test]
999 fn test_quat_slerp() {
1000 let a = Quat::IDENTITY;
1002 let b = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_2);
1003 let mid = a.slerp(b, 0.5);
1004 let expected = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
1006 let dot =
1008 mid.x * expected.x + mid.y * expected.y + mid.z * expected.z + mid.w * expected.w;
1009 assert!(
1010 dot.abs() > 0.999,
1011 "slerp midpoint should represent same rotation"
1012 );
1013 }
1014
1015 #[test]
1016 fn test_quat_directions() {
1017 let q = Quat::IDENTITY;
1018 let fwd = q.forward();
1019 let right = q.right();
1020 let up = q.up();
1021
1022 assert!((fwd - Vec3::new(0.0, 0.0, -1.0)).length() < 0.0001);
1023 assert!((right - Vec3::new(1.0, 0.0, 0.0)).length() < 0.0001);
1024 assert!((up - Vec3::new(0.0, 1.0, 0.0)).length() < 0.0001);
1025 }
1026
1027 #[test]
1028 fn test_quat_default() {
1029 let q = Quat::default();
1030 assert_eq!(q, Quat::IDENTITY);
1031 }
1032
1033 #[test]
1034 fn test_quat_cgmath_conversion() {
1035 let our_quat = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
1036 let cg_quat: Quaternion<f32> = our_quat.into();
1037 let back: Quat = cg_quat.into();
1038
1039 assert!((back.x - our_quat.x).abs() < 0.0001);
1040 assert!((back.y - our_quat.y).abs() < 0.0001);
1041 assert!((back.z - our_quat.z).abs() < 0.0001);
1042 assert!((back.w - our_quat.w).abs() < 0.0001);
1043 }
1044
1045 #[test]
1046 fn test_quat_inverse() {
1047 let q = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4).normalize();
1048 let inv = q.inverse();
1049 let result = q * inv;
1050 assert!((result.x).abs() < 0.0001);
1052 assert!((result.y).abs() < 0.0001);
1053 assert!((result.z).abs() < 0.0001);
1054 assert!((result.w - 1.0).abs() < 0.0001);
1055 }
1056 }
1057
1058 mod construction_tests {
1063 use super::*;
1064
1065 #[test]
1066 fn test_transform_default() {
1067 let t = Transform::default();
1068 assert_eq!(t.position, Vec3::zero());
1069 assert_eq!(t.rotation, Quat::IDENTITY);
1070 assert_eq!(t.scale, Vec3::one());
1071 }
1072
1073 #[test]
1074 fn test_transform_new() {
1075 let pos = Vec3::new(1.0, 2.0, 3.0);
1076 let rot = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
1077 let scale = Vec3::new(2.0, 2.0, 2.0);
1078
1079 let t = Transform::new(pos, rot, scale);
1080 assert_eq!(t.position, pos);
1081 assert_eq!(t.rotation, rot);
1082 assert_eq!(t.scale, scale);
1083 }
1084
1085 #[test]
1086 fn test_transform_from_position() {
1087 let pos = Vec3::new(10.0, 20.0, 30.0);
1088 let t = Transform::from_position(pos);
1089 assert_eq!(t.position, pos);
1090 assert_eq!(t.rotation, Quat::IDENTITY);
1091 assert_eq!(t.scale, Vec3::one());
1092 }
1093
1094 #[test]
1095 fn test_transform_from_rotation() {
1096 let rot = Quat::from_axis_angle(Vec3::unit_x(), FRAC_PI_2);
1097 let t = Transform::from_rotation(rot);
1098 assert_eq!(t.position, Vec3::zero());
1099 assert_eq!(t.rotation, rot);
1100 assert_eq!(t.scale, Vec3::one());
1101 }
1102
1103 #[test]
1104 fn test_transform_from_scale() {
1105 let scale = Vec3::new(2.0, 3.0, 4.0);
1106 let t = Transform::from_scale(scale);
1107 assert_eq!(t.position, Vec3::zero());
1108 assert_eq!(t.rotation, Quat::IDENTITY);
1109 assert_eq!(t.scale, scale);
1110 }
1111
1112 #[test]
1113 fn test_transform_from_scale_uniform() {
1114 let t = Transform::from_scale_uniform(5.0);
1115 assert_eq!(t.scale, Vec3::new(5.0, 5.0, 5.0));
1116 }
1117
1118 #[test]
1119 fn test_transform_from_position_rotation() {
1120 let pos = Vec3::new(1.0, 2.0, 3.0);
1121 let rot = Quat::from_axis_angle(Vec3::unit_z(), FRAC_PI_4);
1122 let t = Transform::from_position_rotation(pos, rot);
1123 assert_eq!(t.position, pos);
1124 assert_eq!(t.rotation, rot);
1125 assert_eq!(t.scale, Vec3::one());
1126 }
1127
1128 #[test]
1129 fn test_transform_look_at() {
1130 let eye = Vec3::new(0.0, 0.0, 10.0);
1131 let target = Vec3::zero();
1132 let up = Vec3::unit_y();
1133
1134 let t = Transform::look_at(eye, target, up);
1135 assert_eq!(t.position, eye);
1136
1137 let fwd = t.forward();
1139 let expected_fwd = (target - eye).normalize();
1140 assert!((fwd - expected_fwd).length() < 0.01);
1141 }
1142 }
1143
1144 mod mutation_tests {
1149 use super::*;
1150
1151 #[test]
1152 fn test_translate() {
1153 let mut t = Transform::default();
1154 t.translate(Vec3::new(5.0, 0.0, 0.0));
1155 assert_eq!(t.position, Vec3::new(5.0, 0.0, 0.0));
1156
1157 t.translate(Vec3::new(0.0, 3.0, 0.0));
1158 assert_eq!(t.position, Vec3::new(5.0, 3.0, 0.0));
1159 }
1160
1161 #[test]
1162 fn test_translate_local() {
1163 let mut t = Transform::default();
1164 t.rotate_y(FRAC_PI_2);
1166 t.translate_local(Vec3::new(1.0, 0.0, 0.0));
1167
1168 assert!(t.position.x.abs() < 0.0001);
1170 assert!(t.position.y.abs() < 0.0001);
1171 assert!((t.position.z - (-1.0)).abs() < 0.0001);
1172 }
1173
1174 #[test]
1175 fn test_set_position() {
1176 let mut t = Transform::from_position(Vec3::new(1.0, 2.0, 3.0));
1177 t.set_position(Vec3::new(10.0, 20.0, 30.0));
1178 assert_eq!(t.position, Vec3::new(10.0, 20.0, 30.0));
1179 }
1180
1181 #[test]
1182 fn test_rotate_x() {
1183 let mut t = Transform::default();
1184 t.rotate_x(FRAC_PI_2);
1185
1186 let up = t.up();
1189 assert!(up.x.abs() < 0.0001);
1190 assert!(up.y.abs() < 0.0001);
1191 assert!((up.z - 1.0).abs() < 0.0001);
1192 }
1193
1194 #[test]
1195 fn test_rotate_y() {
1196 let mut t = Transform::default();
1197 t.rotate_y(FRAC_PI_2);
1198
1199 let fwd = t.forward();
1202 assert!((fwd.x - (-1.0)).abs() < 0.0001);
1203 assert!(fwd.y.abs() < 0.0001);
1204 assert!(fwd.z.abs() < 0.0001);
1205 }
1206
1207 #[test]
1208 fn test_rotate_z() {
1209 let mut t = Transform::default();
1210 t.rotate_z(FRAC_PI_2);
1211
1212 let right = t.right();
1214 assert!(right.x.abs() < 0.0001);
1215 assert!((right.y - 1.0).abs() < 0.0001);
1216 assert!(right.z.abs() < 0.0001);
1217 }
1218
1219 #[test]
1220 fn test_set_rotation() {
1221 let mut t = Transform::default();
1222 let rot = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
1223 t.set_rotation(rot);
1224 assert!((t.rotation.x - rot.x).abs() < 0.0001);
1225 assert!((t.rotation.y - rot.y).abs() < 0.0001);
1226 assert!((t.rotation.z - rot.z).abs() < 0.0001);
1227 assert!((t.rotation.w - rot.w).abs() < 0.0001);
1228 }
1229
1230 #[test]
1231 fn test_set_scale() {
1232 let mut t = Transform::default();
1233 t.set_scale(Vec3::new(2.0, 3.0, 4.0));
1234 assert_eq!(t.scale, Vec3::new(2.0, 3.0, 4.0));
1235 }
1236
1237 #[test]
1238 fn test_set_scale_uniform() {
1239 let mut t = Transform::default();
1240 t.set_scale_uniform(3.0);
1241 assert_eq!(t.scale, Vec3::new(3.0, 3.0, 3.0));
1242 }
1243
1244 #[test]
1245 fn test_scale_by() {
1246 let mut t = Transform::from_scale(Vec3::new(2.0, 2.0, 2.0));
1247 t.scale_by(Vec3::new(3.0, 4.0, 5.0));
1248 assert_eq!(t.scale, Vec3::new(6.0, 8.0, 10.0));
1249 }
1250
1251 #[test]
1252 fn test_look_at_target() {
1253 let mut t = Transform::from_position(Vec3::new(0.0, 0.0, 10.0));
1254 t.look_at_target(Vec3::zero(), Vec3::unit_y());
1255
1256 let fwd = t.forward();
1257 let expected = Vec3::new(0.0, 0.0, -1.0);
1258 assert!((fwd - expected).length() < 0.01);
1259 }
1260 }
1261
1262 mod direction_tests {
1267 use super::*;
1268
1269 #[test]
1270 fn test_directions_identity() {
1271 let t = Transform::default();
1272
1273 assert!((t.forward() - Vec3::new(0.0, 0.0, -1.0)).length() < 0.0001);
1274 assert!((t.back() - Vec3::new(0.0, 0.0, 1.0)).length() < 0.0001);
1275 assert!((t.right() - Vec3::new(1.0, 0.0, 0.0)).length() < 0.0001);
1276 assert!((t.left() - Vec3::new(-1.0, 0.0, 0.0)).length() < 0.0001);
1277 assert!((t.up() - Vec3::new(0.0, 1.0, 0.0)).length() < 0.0001);
1278 assert!((t.down() - Vec3::new(0.0, -1.0, 0.0)).length() < 0.0001);
1279 }
1280
1281 #[test]
1282 fn test_directions_rotated() {
1283 let mut t = Transform::default();
1284 t.rotate_y(FRAC_PI_2);
1285
1286 let fwd = t.forward();
1290 assert!((fwd.x - (-1.0)).abs() < 0.0001);
1291
1292 let right = t.right();
1293 assert!((right.z - (-1.0)).abs() < 0.0001);
1294 }
1295 }
1296
1297 mod matrix_tests {
1302 use super::*;
1303
1304 #[test]
1305 fn test_matrix_identity() {
1306 let t = Transform::default();
1307 let m = t.matrix();
1308
1309 assert!((m.x.x - 1.0).abs() < 0.0001);
1311 assert!((m.y.y - 1.0).abs() < 0.0001);
1312 assert!((m.z.z - 1.0).abs() < 0.0001);
1313 assert!((m.w.w - 1.0).abs() < 0.0001);
1314 }
1315
1316 #[test]
1317 fn test_matrix_translation() {
1318 let t = Transform::from_position(Vec3::new(10.0, 20.0, 30.0));
1319 let m = t.matrix();
1320
1321 assert!((m.w.x - 10.0).abs() < 0.0001);
1323 assert!((m.w.y - 20.0).abs() < 0.0001);
1324 assert!((m.w.z - 30.0).abs() < 0.0001);
1325 }
1326
1327 #[test]
1328 fn test_matrix_scale() {
1329 let t = Transform::from_scale(Vec3::new(2.0, 3.0, 4.0));
1330 let m = t.matrix();
1331
1332 assert!((m.x.x - 2.0).abs() < 0.0001);
1334 assert!((m.y.y - 3.0).abs() < 0.0001);
1335 assert!((m.z.z - 4.0).abs() < 0.0001);
1336 }
1337
1338 #[test]
1339 fn test_matrix_inverse() {
1340 let t = Transform::new(
1341 Vec3::new(5.0, 10.0, 15.0),
1342 Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4),
1343 Vec3::new(2.0, 2.0, 2.0),
1344 );
1345
1346 let m = t.matrix();
1347 let m_inv = t.matrix_inverse();
1348
1349 let identity = m * m_inv;
1351
1352 assert!((identity.x.x - 1.0).abs() < 0.001);
1353 assert!((identity.y.y - 1.0).abs() < 0.001);
1354 assert!((identity.z.z - 1.0).abs() < 0.001);
1355 assert!((identity.w.w - 1.0).abs() < 0.001);
1356 }
1357 }
1358
1359 mod point_transform_tests {
1364 use super::*;
1365
1366 #[test]
1367 fn test_transform_point_translation() {
1368 let t = Transform::from_position(Vec3::new(10.0, 0.0, 0.0));
1369 let p = Vec3::zero();
1370 let transformed = t.transform_point(p);
1371 assert_eq!(transformed, Vec3::new(10.0, 0.0, 0.0));
1372 }
1373
1374 #[test]
1375 fn test_transform_point_scale() {
1376 let t = Transform::from_scale(Vec3::new(2.0, 2.0, 2.0));
1377 let p = Vec3::new(5.0, 5.0, 5.0);
1378 let transformed = t.transform_point(p);
1379 assert_eq!(transformed, Vec3::new(10.0, 10.0, 10.0));
1380 }
1381
1382 #[test]
1383 fn test_transform_point_rotation() {
1384 let t = Transform::from_rotation(Quat::from_axis_angle(Vec3::unit_y(), PI));
1385 let p = Vec3::new(1.0, 0.0, 0.0);
1386 let transformed = t.transform_point(p);
1387 assert!((transformed.x - (-1.0)).abs() < 0.0001);
1389 assert!(transformed.y.abs() < 0.0001);
1390 assert!(transformed.z.abs() < 0.0001);
1391 }
1392
1393 #[test]
1394 fn test_transform_direction() {
1395 let t = Transform::new(
1396 Vec3::new(100.0, 0.0, 0.0), Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_2),
1398 Vec3::one(),
1399 );
1400
1401 let dir = Vec3::new(0.0, 0.0, 1.0);
1402 let transformed = t.transform_direction(dir);
1403
1404 assert!((transformed.x - 1.0).abs() < 0.0001);
1407 assert!(transformed.y.abs() < 0.0001);
1408 assert!(transformed.z.abs() < 0.0001);
1409 }
1410
1411 #[test]
1412 fn test_inverse_transform_point() {
1413 let t = Transform::new(
1414 Vec3::new(10.0, 20.0, 30.0),
1415 Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4),
1416 Vec3::new(2.0, 2.0, 2.0),
1417 );
1418
1419 let world_point = Vec3::new(5.0, 5.0, 5.0);
1420 let local = t.inverse_transform_point(world_point);
1421 let back_to_world = t.transform_point(local);
1422
1423 assert!((back_to_world - world_point).length() < 0.001);
1424 }
1425
1426 #[test]
1427 fn test_inverse_transform_direction() {
1428 let t = Transform::from_rotation(Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_2));
1429
1430 let world_dir = Vec3::new(1.0, 0.0, 0.0);
1431 let local = t.inverse_transform_direction(world_dir);
1432 let back = t.transform_direction(local);
1433
1434 assert!((back - world_dir).length() < 0.0001);
1435 }
1436 }
1437
1438 mod interpolation_tests {
1443 use super::*;
1444
1445 #[test]
1446 fn test_lerp_position() {
1447 let a = Transform::from_position(Vec3::zero());
1448 let b = Transform::from_position(Vec3::new(10.0, 0.0, 0.0));
1449
1450 let mid = a.lerp(b, 0.5);
1451 assert_eq!(mid.position, Vec3::new(5.0, 0.0, 0.0));
1452 }
1453
1454 #[test]
1455 fn test_lerp_scale() {
1456 let a = Transform::from_scale(Vec3::one());
1457 let b = Transform::from_scale(Vec3::new(3.0, 3.0, 3.0));
1458
1459 let mid = a.lerp(b, 0.5);
1460 assert_eq!(mid.scale, Vec3::new(2.0, 2.0, 2.0));
1461 }
1462
1463 #[test]
1464 fn test_lerp_rotation() {
1465 let a = Transform::default();
1467 let b = Transform::from_rotation(Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_2));
1468
1469 let mid = a.lerp(b, 0.5);
1470 let expected = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
1472 let dot = mid.rotation.x * expected.x
1474 + mid.rotation.y * expected.y
1475 + mid.rotation.z * expected.z
1476 + mid.rotation.w * expected.w;
1477 assert!(
1478 dot.abs() > 0.999,
1479 "lerp midpoint rotation should match expected"
1480 );
1481 }
1482
1483 #[test]
1484 fn test_lerp_endpoints() {
1485 let a = Transform::new(
1486 Vec3::new(0.0, 0.0, 0.0),
1487 Quat::IDENTITY,
1488 Vec3::new(1.0, 1.0, 1.0),
1489 );
1490 let b = Transform::new(
1491 Vec3::new(10.0, 10.0, 10.0),
1492 Quat::from_axis_angle(Vec3::unit_y(), PI),
1493 Vec3::new(2.0, 2.0, 2.0),
1494 );
1495
1496 let start = a.lerp(b, 0.0);
1497 assert_eq!(start.position, a.position);
1498 assert_eq!(start.scale, a.scale);
1499
1500 let end = a.lerp(b, 1.0);
1501 assert_eq!(end.position, b.position);
1502 assert_eq!(end.scale, b.scale);
1503 }
1504 }
1505
1506 mod component_tests {
1511 use super::*;
1512
1513 #[test]
1514 fn test_transform_is_component() {
1515 fn assert_component<T: Component>() {}
1516 assert_component::<Transform>();
1517 }
1518
1519 #[test]
1520 fn test_transform_is_send() {
1521 fn assert_send<T: Send>() {}
1522 assert_send::<Transform>();
1523 }
1524
1525 #[test]
1526 fn test_transform_is_sync() {
1527 fn assert_sync<T: Sync>() {}
1528 assert_sync::<Transform>();
1529 }
1530
1531 #[test]
1532 fn test_transform_clone() {
1533 let t = Transform::new(
1534 Vec3::new(1.0, 2.0, 3.0),
1535 Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4),
1536 Vec3::new(2.0, 2.0, 2.0),
1537 );
1538 let cloned = t.clone();
1539 assert_eq!(t, cloned);
1540 }
1541
1542 #[test]
1543 fn test_transform_copy() {
1544 let t = Transform::default();
1545 let copied = t;
1546 assert_eq!(t, copied);
1547 }
1548 }
1549
1550 mod ffi_tests {
1555 use super::*;
1556 use std::mem::{align_of, size_of};
1557
1558 #[test]
1559 fn test_quat_size() {
1560 assert_eq!(size_of::<Quat>(), 16); }
1562
1563 #[test]
1564 fn test_quat_align() {
1565 assert_eq!(align_of::<Quat>(), 4); }
1567
1568 #[test]
1569 fn test_transform_size() {
1570 assert_eq!(size_of::<Transform>(), 40);
1572 }
1573
1574 #[test]
1575 fn test_transform_align() {
1576 assert_eq!(align_of::<Transform>(), 4); }
1578
1579 #[test]
1580 fn test_quat_field_layout() {
1581 let q = Quat::new(1.0, 2.0, 3.0, 4.0);
1582 let ptr = &q as *const Quat as *const f32;
1583 unsafe {
1584 assert_eq!(*ptr, 1.0);
1585 assert_eq!(*ptr.add(1), 2.0);
1586 assert_eq!(*ptr.add(2), 3.0);
1587 assert_eq!(*ptr.add(3), 4.0);
1588 }
1589 }
1590 }
1591}