dreamwell-engine 1.0.0

Dreamwell pure-logic engine library — transforms, hierarchy, canon pipeline, spatial math, hashing, tile rules, validation, waymark schema, material/lighting descriptors. No SpacetimeDB dependency.
Documentation
//! Scene transform types for hierarchy propagation.
//!
//! Uses raw `[f32; N]` arrays for zero-dependency purity. The existing
//! `game_object::Transform` handles TRS with quaternion rotation.
//! This module adds `GlobalTransform` (world-space 4x4 matrix) for
//! the hierarchy propagation system.

use serde::{Deserialize, Serialize};

/// World-space 4x4 column-major transform matrix.
/// Computed by propagating local transforms through the scene hierarchy.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct GlobalTransform(pub [f32; 16]);

impl GlobalTransform {
    /// Identity matrix.
    pub fn identity() -> Self {
        #[rustfmt::skip]
        let m = [
            1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0,
        ];
        Self(m)
    }

    /// Multiply two 4x4 column-major matrices: self * rhs.
    pub fn mul(&self, rhs: &GlobalTransform) -> GlobalTransform {
        let a = &self.0;
        let b = &rhs.0;
        let mut out = [0.0f32; 16];
        for col in 0..4 {
            for row in 0..4 {
                out[col * 4 + row] = a[row] * b[col * 4]
                    + a[4 + row] * b[col * 4 + 1]
                    + a[8 + row] * b[col * 4 + 2]
                    + a[12 + row] * b[col * 4 + 3];
            }
        }
        GlobalTransform(out)
    }

    /// Extract translation (column 3).
    pub fn translation(&self) -> [f32; 3] {
        [self.0[12], self.0[13], self.0[14]]
    }
}

impl Default for GlobalTransform {
    fn default() -> Self {
        Self::identity()
    }
}

/// 4x4 column-major matrix multiply. Canonical implementation — use this
/// instead of duplicating in GPU/editor crates.
pub fn mat4_mul(a: &[f32; 16], b: &[f32; 16]) -> [f32; 16] {
    let mut out = [0.0f32; 16];
    for col in 0..4 {
        for row in 0..4 {
            out[col * 4 + row] = a[row] * b[col * 4]
                + a[4 + row] * b[col * 4 + 1]
                + a[8 + row] * b[col * 4 + 2]
                + a[12 + row] * b[col * 4 + 3];
        }
    }
    out
}

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

    #[test]
    fn identity_default() {
        let g = GlobalTransform::default();
        assert_eq!(g, GlobalTransform::identity());
    }

    #[test]
    fn identity_mul_identity() {
        let id = GlobalTransform::identity();
        let result = id.mul(&id);
        for i in 0..16 {
            assert!((result.0[i] - id.0[i]).abs() < 1e-6);
        }
    }

    #[test]
    fn translation_extraction() {
        let mut g = GlobalTransform::identity();
        g.0[12] = 5.0;
        g.0[13] = 10.0;
        g.0[14] = -3.0;
        assert_eq!(g.translation(), [5.0, 10.0, -3.0]);
    }

    #[test]
    fn mul_with_translation() {
        let mut a = GlobalTransform::identity();
        a.0[12] = 1.0; // translate x+1
        let mut b = GlobalTransform::identity();
        b.0[12] = 2.0; // translate x+2
        let c = a.mul(&b);
        assert!((c.0[12] - 3.0).abs() < 1e-6); // x+3
    }

    #[test]
    fn mat4_mul_identity() {
        #[rustfmt::skip]
        let id = [
            1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0,
        ];
        let result = mat4_mul(&id, &id);
        for i in 0..16 {
            assert!((result[i] - id[i]).abs() < 1e-6);
        }
    }

    #[test]
    fn mat4_mul_translation_compose() {
        #[rustfmt::skip]
        let a = [
            1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            10.0, 0.0, 0.0, 1.0,
        ];
        #[rustfmt::skip]
        let b = [
            1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            5.0, 0.0, 0.0, 1.0,
        ];
        let c = mat4_mul(&a, &b);
        assert!((c[12] - 15.0).abs() < 1e-6);
    }
}