//! Transform: translation, rotation, scale -> model matrix.
//!
//! Always stored in 3D. The 2D helpers ([`Transform::from_xy`],
//! [`Transform::with_rotation_z`]) are conveniences over the same 3D data, so
//! nothing about a sprite's transform has to change when you move to 3D.

use crate::math::{Mat4, Quat, Vec3};

#[derive(Debug, Clone, Copy)]
pub struct Transform {
    pub translation: Vec3,
    pub rotation: Quat,
    pub scale: Vec3,
}

impl Transform {
    pub const IDENTITY: Transform = Transform {
        translation: Vec3::ZERO,
        rotation: Quat::IDENTITY,
        scale: Vec3::ONE,
    };

    pub fn new(translation: Vec3, rotation: Quat, scale: Vec3) -> Self {
        Self {
            translation,
            rotation,
            scale,
        }
    }

    /// 2D placement at `(x, y)` on the `z = 0` plane, unit scale, no rotation.
    pub fn from_xy(x: f32, y: f32) -> Self {
        Self {
            translation: Vec3::new(x, y, 0.0),
            ..Self::IDENTITY
        }
    }

    /// 2D placement with a size in pixels (scale on x/y).
    pub fn from_xy_size(x: f32, y: f32, w: f32, h: f32) -> Self {
        Self {
            translation: Vec3::new(x, y, 0.0),
            rotation: Quat::IDENTITY,
            scale: Vec3::new(w, h, 1.0),
        }
    }

    /// Rotation about the z axis (the only rotation that matters in 2D), radians.
    pub fn with_rotation_z(mut self, radians: f32) -> Self {
        self.rotation = Quat::from_rotation_z(radians);
        self
    }

    pub fn with_scale(mut self, scale: Vec3) -> Self {
        self.scale = scale;
        self
    }

    /// The model matrix `T * R * S`.
    #[inline]
    pub fn matrix(&self) -> Mat4 {
        Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
    }
}

impl Default for Transform {
    fn default() -> Self {
        Self::IDENTITY
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::math::Vec3;

    #[test]
    fn identity_maps_origin_to_origin() {
        let m = Transform::IDENTITY.matrix();
        let p = m.transform_point3(Vec3::ZERO);
        assert!(p.abs_diff_eq(Vec3::ZERO, 1e-6));
    }

    #[test]
    fn translation_moves_point() {
        let t = Transform::from_xy(10.0, 20.0);
        let p = t.matrix().transform_point3(Vec3::ZERO);
        assert!(p.abs_diff_eq(Vec3::new(10.0, 20.0, 0.0), 1e-6));
    }

    #[test]
    fn scale_then_translate() {
        let t = Transform::from_xy_size(5.0, 0.0, 2.0, 3.0);
        // a unit-quad corner at (1,1) becomes (2,3) then shifts by (5,0)
        let p = t.matrix().transform_point3(Vec3::new(1.0, 1.0, 0.0));
        assert!(p.abs_diff_eq(Vec3::new(7.0, 3.0, 0.0), 1e-6));
    }
}
