Skip to main content

dreamwell_engine/
transform.rs

1//! Scene transform types for hierarchy propagation.
2//!
3//! Uses raw `[f32; N]` arrays for zero-dependency purity. The existing
4//! `game_object::Transform` handles TRS with quaternion rotation.
5//! This module adds `GlobalTransform` (world-space 4x4 matrix) for
6//! the hierarchy propagation system.
7
8use serde::{Deserialize, Serialize};
9
10/// World-space 4x4 column-major transform matrix.
11/// Computed by propagating local transforms through the scene hierarchy.
12#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct GlobalTransform(pub [f32; 16]);
14
15impl GlobalTransform {
16    /// Identity matrix.
17    pub fn identity() -> Self {
18        #[rustfmt::skip]
19        let m = [
20            1.0, 0.0, 0.0, 0.0,
21            0.0, 1.0, 0.0, 0.0,
22            0.0, 0.0, 1.0, 0.0,
23            0.0, 0.0, 0.0, 1.0,
24        ];
25        Self(m)
26    }
27
28    /// Multiply two 4x4 column-major matrices: self * rhs.
29    pub fn mul(&self, rhs: &GlobalTransform) -> GlobalTransform {
30        let a = &self.0;
31        let b = &rhs.0;
32        let mut out = [0.0f32; 16];
33        for col in 0..4 {
34            for row in 0..4 {
35                out[col * 4 + row] = a[row] * b[col * 4]
36                    + a[4 + row] * b[col * 4 + 1]
37                    + a[8 + row] * b[col * 4 + 2]
38                    + a[12 + row] * b[col * 4 + 3];
39            }
40        }
41        GlobalTransform(out)
42    }
43
44    /// Extract translation (column 3).
45    pub fn translation(&self) -> [f32; 3] {
46        [self.0[12], self.0[13], self.0[14]]
47    }
48}
49
50impl Default for GlobalTransform {
51    fn default() -> Self {
52        Self::identity()
53    }
54}
55
56/// 4x4 column-major matrix multiply. Canonical implementation — use this
57/// instead of duplicating in GPU/editor crates.
58pub fn mat4_mul(a: &[f32; 16], b: &[f32; 16]) -> [f32; 16] {
59    let mut out = [0.0f32; 16];
60    for col in 0..4 {
61        for row in 0..4 {
62            out[col * 4 + row] = a[row] * b[col * 4]
63                + a[4 + row] * b[col * 4 + 1]
64                + a[8 + row] * b[col * 4 + 2]
65                + a[12 + row] * b[col * 4 + 3];
66        }
67    }
68    out
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn identity_default() {
77        let g = GlobalTransform::default();
78        assert_eq!(g, GlobalTransform::identity());
79    }
80
81    #[test]
82    fn identity_mul_identity() {
83        let id = GlobalTransform::identity();
84        let result = id.mul(&id);
85        for i in 0..16 {
86            assert!((result.0[i] - id.0[i]).abs() < 1e-6);
87        }
88    }
89
90    #[test]
91    fn translation_extraction() {
92        let mut g = GlobalTransform::identity();
93        g.0[12] = 5.0;
94        g.0[13] = 10.0;
95        g.0[14] = -3.0;
96        assert_eq!(g.translation(), [5.0, 10.0, -3.0]);
97    }
98
99    #[test]
100    fn mul_with_translation() {
101        let mut a = GlobalTransform::identity();
102        a.0[12] = 1.0; // translate x+1
103        let mut b = GlobalTransform::identity();
104        b.0[12] = 2.0; // translate x+2
105        let c = a.mul(&b);
106        assert!((c.0[12] - 3.0).abs() < 1e-6); // x+3
107    }
108
109    #[test]
110    fn mat4_mul_identity() {
111        #[rustfmt::skip]
112        let id = [
113            1.0, 0.0, 0.0, 0.0,
114            0.0, 1.0, 0.0, 0.0,
115            0.0, 0.0, 1.0, 0.0,
116            0.0, 0.0, 0.0, 1.0,
117        ];
118        let result = mat4_mul(&id, &id);
119        for i in 0..16 {
120            assert!((result[i] - id[i]).abs() < 1e-6);
121        }
122    }
123
124    #[test]
125    fn mat4_mul_translation_compose() {
126        #[rustfmt::skip]
127        let a = [
128            1.0, 0.0, 0.0, 0.0,
129            0.0, 1.0, 0.0, 0.0,
130            0.0, 0.0, 1.0, 0.0,
131            10.0, 0.0, 0.0, 1.0,
132        ];
133        #[rustfmt::skip]
134        let b = [
135            1.0, 0.0, 0.0, 0.0,
136            0.0, 1.0, 0.0, 0.0,
137            0.0, 0.0, 1.0, 0.0,
138            5.0, 0.0, 0.0, 1.0,
139        ];
140        let c = mat4_mul(&a, &b);
141        assert!((c[12] - 15.0).abs() < 1e-6);
142    }
143}