Skip to main content

goud_engine/ecs/components/
transform.rs

1//! Transform component for 3D spatial transformations.
2//!
3//! The [`Transform`] component represents an entity's position, rotation, and scale
4//! in 3D space. It is one of the most commonly used components in game development,
5//! essential for positioning and orienting objects in the world.
6//!
7//! # Design Philosophy
8//!
9//! Transform stores position, rotation (as quaternion), and scale separately rather
10//! than as a combined matrix. This provides:
11//!
12//! - **Intuitive manipulation**: Modify position/rotation/scale independently
13//! - **Numerical stability**: Quaternions avoid gimbal lock and numerical drift
14//! - **Memory efficiency**: 10 floats vs 16 for a full matrix
15//! - **Interpolation support**: Easy lerp/slerp for animations
16//!
17//! # Usage
18//!
19//! ```
20//! use goud_engine::ecs::components::Transform;
21//! use goud_engine::core::math::Vec3;
22//!
23//! // Create a transform at position (10, 5, 0)
24//! let mut transform = Transform::from_position(Vec3::new(10.0, 5.0, 0.0));
25//!
26//! // Modify the transform
27//! transform.translate(Vec3::new(1.0, 0.0, 0.0));
28//! transform.rotate_y(std::f32::consts::PI / 4.0);
29//! transform.set_scale(Vec3::new(2.0, 2.0, 2.0));
30//!
31//! // Get the transformation matrix for rendering
32//! let matrix = transform.matrix();
33//! ```
34//!
35//! # Coordinate System
36//!
37//! GoudEngine uses a right-handed coordinate system:
38//! - X axis: Right
39//! - Y axis: Up
40//! - Z axis: Out of the screen (towards viewer)
41//!
42//! Rotations follow the right-hand rule: positive rotation around an axis
43//! goes counter-clockwise when looking down that axis.
44//!
45//! # FFI Safety
46//!
47//! Transform is `#[repr(C)]` and can be safely passed across FFI boundaries.
48//! The quaternion is stored as (x, y, z, w) to match common conventions.
49
50use crate::core::math::{Matrix4, Quaternion, Vec3};
51use crate::ecs::Component;
52use cgmath::InnerSpace;
53use std::f32::consts::PI;
54
55/// A 3D spatial transformation component.
56///
57/// Represents position, rotation, and scale in 3D space. This is the primary
58/// component for positioning entities in the game world.
59///
60/// # Memory Layout
61///
62/// The component is laid out as:
63/// - `position`: 3 x f32 (12 bytes)
64/// - `rotation`: 4 x f32 (16 bytes) - quaternion (x, y, z, w)
65/// - `scale`: 3 x f32 (12 bytes)
66/// - Total: 40 bytes
67///
68/// # Example
69///
70/// ```
71/// use goud_engine::ecs::components::Transform;
72/// use goud_engine::core::math::Vec3;
73///
74/// // Create at origin with default rotation and scale
75/// let mut transform = Transform::default();
76///
77/// // Or create with specific position
78/// let transform = Transform::from_position(Vec3::new(10.0, 0.0, 0.0));
79///
80/// // Or with full control
81/// let transform = Transform::new(
82///     Vec3::new(10.0, 5.0, 0.0),     // position
83///     Transform::IDENTITY_ROTATION,   // rotation (identity quaternion)
84///     Vec3::one(),                    // scale
85/// );
86/// ```
87#[repr(C)]
88#[derive(Clone, Copy, Debug, PartialEq)]
89pub struct Transform {
90    /// Position in world space (or local space if entity has a parent).
91    pub position: Vec3,
92    /// Rotation as a quaternion (x, y, z, w).
93    ///
94    /// The quaternion should be normalized. Use the rotation methods to ensure
95    /// this invariant is maintained.
96    pub rotation: Quat,
97    /// Scale along each axis.
98    ///
99    /// A scale of (1, 1, 1) means no scaling. Negative values flip the object
100    /// along that axis.
101    pub scale: Vec3,
102}
103
104/// A quaternion for representing rotations.
105///
106/// Stored as (x, y, z, w) where:
107/// - (x, y, z) is the vector part (imaginary components)
108/// - w is the scalar part (real component)
109///
110/// The identity rotation is (0, 0, 0, 1).
111#[repr(C)]
112#[derive(Clone, Copy, Debug, PartialEq)]
113pub struct Quat {
114    /// The x-component of the vector part.
115    pub x: f32,
116    /// The y-component of the vector part.
117    pub y: f32,
118    /// The z-component of the vector part.
119    pub z: f32,
120    /// The scalar (real) part.
121    pub w: f32,
122}
123
124impl Quat {
125    /// Identity quaternion representing no rotation.
126    pub const IDENTITY: Quat = Quat {
127        x: 0.0,
128        y: 0.0,
129        z: 0.0,
130        w: 1.0,
131    };
132
133    /// Creates a new quaternion from components.
134    #[inline]
135    pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
136        Self { x, y, z, w }
137    }
138
139    /// Creates a quaternion from axis-angle representation.
140    ///
141    /// # Arguments
142    ///
143    /// * `axis` - The normalized rotation axis
144    /// * `angle` - The rotation angle in radians
145    ///
146    /// # Example
147    ///
148    /// ```
149    /// use goud_engine::ecs::components::transform::Quat;
150    /// use goud_engine::core::math::Vec3;
151    /// use std::f32::consts::PI;
152    ///
153    /// // Rotate 90 degrees around Y axis
154    /// let q = Quat::from_axis_angle(Vec3::unit_y(), PI / 2.0);
155    /// ```
156    #[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    /// Creates a quaternion from Euler angles (in radians).
170    ///
171    /// Uses XYZ rotation order (pitch, yaw, roll):
172    /// - X: pitch (rotation around right axis)
173    /// - Y: yaw (rotation around up axis)
174    /// - Z: roll (rotation around forward axis)
175    ///
176    /// # Example
177    ///
178    /// ```
179    /// use goud_engine::ecs::components::transform::Quat;
180    /// use std::f32::consts::PI;
181    ///
182    /// // 45 degree yaw rotation
183    /// let q = Quat::from_euler(0.0, PI / 4.0, 0.0);
184    /// ```
185    #[inline]
186    pub fn from_euler(pitch: f32, yaw: f32, roll: f32) -> Self {
187        // Using XYZ convention
188        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    /// Creates a quaternion that rotates from one direction to another.
201    ///
202    /// Both vectors should be normalized.
203    #[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        // Handle edge case where vectors are opposite
209        let dot = from_cg.dot(to_cg);
210        if dot < -0.999999 {
211            // Vectors are opposite, rotate 180 degrees around any perpendicular axis
212            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    /// Returns the squared length of the quaternion.
231    #[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    /// Returns the length of the quaternion.
237    #[inline]
238    pub fn length(self) -> f32 {
239        self.length_squared().sqrt()
240    }
241
242    /// Returns a normalized version of this quaternion.
243    #[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    /// Returns the conjugate of this quaternion.
259    ///
260    /// For a unit quaternion, the conjugate is also the inverse.
261    #[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    /// Returns the inverse of this quaternion.
272    ///
273    /// For unit quaternions (normalized), this is equivalent to the conjugate.
274    #[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    /// Multiplies two quaternions (combines rotations).
291    ///
292    /// This represents combining two rotations, where `self` is applied first,
293    /// then `other`. This is also available via the `*` operator.
294    #[inline]
295    pub fn multiply(self, other: Self) -> Self {
296        self * other
297    }
298
299    /// Rotates a vector by this quaternion.
300    #[inline]
301    pub fn rotate_vector(self, v: Vec3) -> Vec3 {
302        // q * v * q^-1, optimized formula
303        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    /// Spherical linear interpolation between two quaternions.
310    ///
311    /// This provides smooth interpolation between rotations.
312    #[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        // If the dot product is negative, negate one quaternion to take shorter path
317        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 quaternions are very close, use linear interpolation
332        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    /// Converts the quaternion to Euler angles (in radians).
359    ///
360    /// Returns (pitch, yaw, roll) using XYZ convention.
361    #[inline]
362    pub fn to_euler(self) -> (f32, f32, f32) {
363        // Roll (x-axis rotation)
364        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        // Pitch (y-axis rotation)
369        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        // Yaw (z-axis rotation)
377        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    /// Returns the forward direction vector (negative Z axis after rotation).
385    #[inline]
386    pub fn forward(self) -> Vec3 {
387        self.rotate_vector(Vec3::new(0.0, 0.0, -1.0))
388    }
389
390    /// Returns the right direction vector (positive X axis after rotation).
391    #[inline]
392    pub fn right(self) -> Vec3 {
393        self.rotate_vector(Vec3::new(1.0, 0.0, 0.0))
394    }
395
396    /// Returns the up direction vector (positive Y axis after rotation).
397    #[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
410// Implement Mul trait for quaternion multiplication
411impl std::ops::Mul for Quat {
412    type Output = Self;
413
414    /// Multiplies two quaternions, combining their rotations.
415    ///
416    /// `self * other` applies `self` first, then `other`.
417    #[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
428// Conversion to/from cgmath quaternion
429impl 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    /// Identity rotation (no rotation).
450    pub const IDENTITY_ROTATION: Quat = Quat::IDENTITY;
451
452    /// Creates a new Transform with the specified position, rotation, and scale.
453    #[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    /// Creates a Transform at the specified position with default rotation and scale.
463    #[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    /// Creates a Transform with the specified rotation and default position/scale.
473    #[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    /// Creates a Transform with the specified scale and default position/rotation.
483    #[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    /// Creates a Transform with uniform scale.
493    #[inline]
494    pub fn from_scale_uniform(scale: f32) -> Self {
495        Self::from_scale(Vec3::new(scale, scale, scale))
496    }
497
498    /// Creates a Transform with position and rotation.
499    #[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    /// Creates a Transform looking at a target position.
509    ///
510    /// # Arguments
511    ///
512    /// * `eye` - The position of the transform
513    /// * `target` - The point to look at
514    /// * `up` - The up direction (usually Vec3::unit_y())
515    ///
516    /// # Example
517    ///
518    /// ```
519    /// use goud_engine::ecs::components::Transform;
520    /// use goud_engine::core::math::Vec3;
521    ///
522    /// let transform = Transform::look_at(
523    ///     Vec3::new(0.0, 5.0, 10.0),  // eye position
524    ///     Vec3::zero(),               // looking at origin
525    ///     Vec3::unit_y(),             // up direction
526    /// );
527    /// ```
528    #[inline]
529    pub fn look_at(eye: Vec3, target: Vec3, up: Vec3) -> Self {
530        // Calculate the forward direction (from eye to target)
531        let forward = (target - eye).normalize();
532        let right = up.cross(forward).normalize();
533        let up = forward.cross(right);
534
535        // We want the rotation that transforms:
536        //   local -Z -> world forward
537        //   local +X -> world right
538        //   local +Y -> world up
539        //
540        // Since forward() returns rotate_vector(-Z), we need a rotation matrix
541        // where column 2 (Z basis) is -forward (negated), so that when we negate
542        // Z and rotate, we get forward.
543        //
544        // The rotation matrix M has columns [right, up, -forward] so that:
545        //   M * [0,0,-1] = forward
546        //   M * [1,0,0] = right
547        //   M * [0,1,0] = up
548        let neg_forward = Vec3::new(-forward.x, -forward.y, -forward.z);
549
550        // Build rotation matrix columns: [right, up, -forward]
551        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    // =========================================================================
584    // Position Methods
585    // =========================================================================
586
587    /// Translates the transform by the given offset.
588    #[inline]
589    pub fn translate(&mut self, offset: Vec3) {
590        self.position = self.position + offset;
591    }
592
593    /// Translates the transform in local space.
594    ///
595    /// The offset is rotated by the transform's rotation before being applied.
596    #[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    /// Sets the position of the transform.
603    #[inline]
604    pub fn set_position(&mut self, position: Vec3) {
605        self.position = position;
606    }
607
608    // =========================================================================
609    // Rotation Methods
610    // =========================================================================
611
612    /// Rotates the transform by the given quaternion.
613    ///
614    /// The rotation is applied in world space (rotation is applied after the current rotation).
615    #[inline]
616    pub fn rotate(&mut self, rotation: Quat) {
617        self.rotation = (self.rotation * rotation).normalize();
618    }
619
620    /// Rotates around the X axis by the given angle in radians.
621    #[inline]
622    pub fn rotate_x(&mut self, angle: f32) {
623        self.rotate(Quat::from_axis_angle(Vec3::unit_x(), angle));
624    }
625
626    /// Rotates around the Y axis by the given angle in radians.
627    #[inline]
628    pub fn rotate_y(&mut self, angle: f32) {
629        self.rotate(Quat::from_axis_angle(Vec3::unit_y(), angle));
630    }
631
632    /// Rotates around the Z axis by the given angle in radians.
633    #[inline]
634    pub fn rotate_z(&mut self, angle: f32) {
635        self.rotate(Quat::from_axis_angle(Vec3::unit_z(), angle));
636    }
637
638    /// Rotates around an arbitrary axis by the given angle in radians.
639    #[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    /// Rotates in local space around the X axis.
645    #[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    /// Rotates in local space around the Y axis.
652    #[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    /// Rotates in local space around the Z axis.
659    #[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    /// Sets the rotation from Euler angles (in radians).
666    #[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    /// Sets the rotation.
672    #[inline]
673    pub fn set_rotation(&mut self, rotation: Quat) {
674        self.rotation = rotation.normalize();
675    }
676
677    /// Makes the transform look at a target position.
678    #[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    // =========================================================================
685    // Scale Methods
686    // =========================================================================
687
688    /// Sets the scale of the transform.
689    #[inline]
690    pub fn set_scale(&mut self, scale: Vec3) {
691        self.scale = scale;
692    }
693
694    /// Sets uniform scale on all axes.
695    #[inline]
696    pub fn set_scale_uniform(&mut self, scale: f32) {
697        self.scale = Vec3::new(scale, scale, scale);
698    }
699
700    /// Multiplies the current scale by the given factors.
701    #[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    // =========================================================================
711    // Direction Vectors
712    // =========================================================================
713
714    /// Returns the forward direction vector (negative Z in local space).
715    #[inline]
716    pub fn forward(&self) -> Vec3 {
717        self.rotation.forward()
718    }
719
720    /// Returns the right direction vector (positive X in local space).
721    #[inline]
722    pub fn right(&self) -> Vec3 {
723        self.rotation.right()
724    }
725
726    /// Returns the up direction vector (positive Y in local space).
727    #[inline]
728    pub fn up(&self) -> Vec3 {
729        self.rotation.up()
730    }
731
732    /// Returns the back direction vector (positive Z in local space).
733    #[inline]
734    pub fn back(&self) -> Vec3 {
735        self.rotation.rotate_vector(Vec3::new(0.0, 0.0, 1.0))
736    }
737
738    /// Returns the left direction vector (negative X in local space).
739    #[inline]
740    pub fn left(&self) -> Vec3 {
741        self.rotation.rotate_vector(Vec3::new(-1.0, 0.0, 0.0))
742    }
743
744    /// Returns the down direction vector (negative Y in local space).
745    #[inline]
746    pub fn down(&self) -> Vec3 {
747        self.rotation.rotate_vector(Vec3::new(0.0, -1.0, 0.0))
748    }
749
750    // =========================================================================
751    // Matrix Generation
752    // =========================================================================
753
754    /// Computes the 4x4 transformation matrix.
755    ///
756    /// The matrix represents the combined transformation: Scale * Rotation * Translation
757    /// (applied in that order when transforming points).
758    #[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        // Build transformation matrix: T * R * S
764        // Scale
765        let sx = self.scale.x;
766        let sy = self.scale.y;
767        let sz = self.scale.z;
768
769        // Rotation columns scaled
770        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    /// Computes the inverse transformation matrix.
793    ///
794    /// Useful for view matrices or converting world-space to local-space.
795    #[inline]
796    pub fn matrix_inverse(&self) -> Matrix4<f32> {
797        // For a TRS matrix, inverse is: S^-1 * R^-1 * T^-1
798        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        // Rotated and scaled inverse translation
805        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    // =========================================================================
835    // Point Transformation
836    // =========================================================================
837
838    /// Transforms a point from local space to world space.
839    #[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    /// Transforms a direction from local space to world space.
851    ///
852    /// Unlike points, directions are not affected by translation.
853    #[inline]
854    pub fn transform_direction(&self, direction: Vec3) -> Vec3 {
855        self.rotation.rotate_vector(direction)
856    }
857
858    /// Transforms a point from world space to local space.
859    #[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    /// Transforms a direction from world space to local space.
872    #[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    // =========================================================================
879    // Interpolation
880    // =========================================================================
881
882    /// Linearly interpolates between two transforms.
883    ///
884    /// Position and scale are linearly interpolated, rotation uses slerp.
885    #[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    /// Returns a Transform at the origin with no rotation and unit scale.
897    #[inline]
898    fn default() -> Self {
899        Self {
900            position: Vec3::zero(),
901            rotation: Quat::IDENTITY,
902            scale: Vec3::one(),
903        }
904    }
905}
906
907// Implement Component trait for Transform
908impl 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    // =========================================================================
917    // Quat Tests
918    // =========================================================================
919
920    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            // Rotating unit_z by 90 degrees around Y should give unit_x
947            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            // 90 degree yaw rotation
956            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            // 180 degree rotation around Y should negate X
993            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            // Test slerp with a smaller rotation (avoid 180-degree edge case)
1001            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            // Midpoint should be 45 degrees
1005            let expected = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
1006            // Compare quaternion components (accounting for sign flip equivalence)
1007            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            // q * q^-1 should be identity
1051            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    // =========================================================================
1059    // Transform Construction Tests
1060    // =========================================================================
1061
1062    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            // Forward direction should point towards target
1138            let fwd = t.forward();
1139            let expected_fwd = (target - eye).normalize();
1140            assert!((fwd - expected_fwd).length() < 0.01);
1141        }
1142    }
1143
1144    // =========================================================================
1145    // Transform Mutation Tests
1146    // =========================================================================
1147
1148    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            // Rotate 90 degrees around Y, so local X becomes world Z
1165            t.rotate_y(FRAC_PI_2);
1166            t.translate_local(Vec3::new(1.0, 0.0, 0.0));
1167
1168            // Local X (1,0,0) should become world Z direction after 90 degree Y rotation
1169            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            // After +90 degree X rotation (counter-clockwise looking down X),
1187            // up (Y) should rotate towards +Z
1188            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            // After +90 degree Y rotation (counter-clockwise looking down Y),
1200            // forward (-Z) should rotate towards -X
1201            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            // After 90 degree Z rotation, right (X) should become up (Y)
1213            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    // =========================================================================
1263    // Transform Direction Tests
1264    // =========================================================================
1265
1266    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            // After +90 degree Y rotation (counter-clockwise looking down Y):
1287            // forward (-Z) -> -X
1288            // right (X) -> -Z
1289            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    // =========================================================================
1298    // Matrix Tests
1299    // =========================================================================
1300
1301    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            // Identity transform should produce identity matrix
1310            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            // Translation should be in the last column
1322            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            // Scale should affect diagonal elements
1333            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            // M * M^-1 should be identity
1350            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    // =========================================================================
1360    // Point Transformation Tests
1361    // =========================================================================
1362
1363    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            // 180 degree rotation should negate X
1388            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), // Translation should not affect direction
1397                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            // After +90 degree Y rotation (counter-clockwise looking down Y),
1405            // +Z direction should rotate towards +X
1406            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    // =========================================================================
1439    // Interpolation Tests
1440    // =========================================================================
1441
1442    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            // Test lerp with a smaller rotation (avoid 180-degree edge case)
1466            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            // Midpoint should be 45 degrees
1471            let expected = Quat::from_axis_angle(Vec3::unit_y(), FRAC_PI_4);
1472            // Compare using dot product (handles sign flip)
1473            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    // =========================================================================
1507    // Component Trait Tests
1508    // =========================================================================
1509
1510    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    // =========================================================================
1551    // FFI Layout Tests
1552    // =========================================================================
1553
1554    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); // 4 * f32
1561        }
1562
1563        #[test]
1564        fn test_quat_align() {
1565            assert_eq!(align_of::<Quat>(), 4); // f32 alignment
1566        }
1567
1568        #[test]
1569        fn test_transform_size() {
1570            // Vec3 (12) + Quat (16) + Vec3 (12) = 40 bytes
1571            assert_eq!(size_of::<Transform>(), 40);
1572        }
1573
1574        #[test]
1575        fn test_transform_align() {
1576            assert_eq!(align_of::<Transform>(), 4); // f32 alignment
1577        }
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}