mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
use bytemuck::{Pod, Zeroable};

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

/// A draw's position, turn, and size.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Transform(Mat4);

impl Transform {
    /// No position, turn, or size change.
    pub const IDENTITY: Self = Self(Mat4::IDENTITY);

    /// At `translation`, in meters.
    pub fn from_translation(translation: Vec3) -> Self {
        Self(Mat4::from_translation(translation))
    }

    /// Turned about the origin.
    pub fn from_rotation(rotation: Quat) -> Self {
        Self(Mat4::from_quat(rotation))
    }

    /// Sized about the origin, per axis.
    pub fn from_scale(scale: Vec3) -> Self {
        Self(Mat4::from_scale(scale))
    }

    /// Turned about the origin, then moved.
    pub fn from_rotation_translation(rotation: Quat, translation: Vec3) -> Self {
        Self(Mat4::from_rotation_translation(rotation, translation))
    }

    /// Sized, then turned, then moved.
    pub fn from_scale_rotation_translation(scale: Vec3, rotation: Quat, translation: Vec3) -> Self {
        Self(Mat4::from_scale_rotation_translation(
            scale,
            rotation,
            translation,
        ))
    }

    /// The matrix that takes mesh space to world space.
    pub const fn matrix(self) -> Mat4 {
        self.0
    }

    /// True where every value of the matrix is finite, so the transform
    /// places a draw.
    pub(crate) fn is_finite(self) -> bool {
        self.0.is_finite()
    }
}

impl core::ops::Mul for Transform {
    type Output = Self;

    /// This transform applied over `after` — matrix order, so
    /// `base * track.at(elapsed)` places the animation within `base`.
    fn mul(self, after: Self) -> Self {
        Self(self.0 * after.0)
    }
}

impl Default for Transform {
    /// [`Transform::IDENTITY`].
    fn default() -> Self {
        Self::IDENTITY
    }
}

impl From<Vec3> for Transform {
    /// A position.
    fn from(translation: Vec3) -> Self {
        Self::from_translation(translation)
    }
}

impl From<Mat4> for Transform {
    /// A matrix a game built itself; only its affine part is kept.
    fn from(matrix: Mat4) -> Self {
        Self(matrix)
    }
}

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

    #[test]
    fn a_rotation_and_a_translation_turn_a_point_before_they_move_it() {
        let quarter = Quat::from_rotation_y(core::f32::consts::FRAC_PI_2);
        let placed = Transform::from_rotation_translation(quarter, Vec3::X);

        let point = placed.matrix().transform_point3(Vec3::X);

        assert!(
            point.abs_diff_eq(Vec3::new(1.0, 0.0, -1.0), 1e-6),
            "a quarter turn takes {} onto {}, which the meter out then places at {point}",
            Vec3::X,
            Vec3::NEG_Z
        );
        assert!(
            !point.abs_diff_eq(Vec3::new(0.0, 0.0, -2.0), 1e-6),
            "and the meter is not moved before the turn"
        );
    }
}