Skip to main content

yog_body/
math.rs

1//! The little linear algebra bodies need: vectors and 4×4 matrices
2//! (column-major, as JOML and the hosts' `PoseStack` keep them).
3
4use std::ops::{Add, Mul, Neg, Sub};
5
6#[derive(Debug, Clone, Copy, PartialEq, Default)]
7pub struct Vec3 {
8    pub x: f32,
9    pub y: f32,
10    pub z: f32,
11}
12
13impl Vec3 {
14    pub const ZERO: Self = Self::new(0.0, 0.0, 0.0);
15    pub const ONE: Self = Self::new(1.0, 1.0, 1.0);
16
17    pub const fn new(x: f32, y: f32, z: f32) -> Self {
18        Self { x, y, z }
19    }
20
21    pub fn from_array(a: [f32; 3]) -> Self {
22        Self::new(a[0], a[1], a[2])
23    }
24
25    pub fn to_array(self) -> [f32; 3] {
26        [self.x, self.y, self.z]
27    }
28
29    pub fn dot(self, o: Self) -> f32 {
30        self.x * o.x + self.y * o.y + self.z * o.z
31    }
32
33    pub fn cross(self, o: Self) -> Self {
34        Self::new(self.y * o.z - self.z * o.y, self.z * o.x - self.x * o.z, self.x * o.y - self.y * o.x)
35    }
36
37    pub fn length(self) -> f32 {
38        self.dot(self).sqrt()
39    }
40
41    /// This one scaled to length 1 (zero stays zero).
42    pub fn normalize(self) -> Self {
43        let l = self.length();
44        if l < 1e-6 { self } else { self * (1.0 / l) }
45    }
46
47    pub fn lerp(self, to: Self, t: f32) -> Self {
48        self + (to - self) * t
49    }
50
51    /// Component-wise product.
52    pub fn scale(self, o: Self) -> Self {
53        Self::new(self.x * o.x, self.y * o.y, self.z * o.z)
54    }
55}
56
57impl Add for Vec3 {
58    type Output = Self;
59    fn add(self, o: Self) -> Self {
60        Self::new(self.x + o.x, self.y + o.y, self.z + o.z)
61    }
62}
63
64impl Sub for Vec3 {
65    type Output = Self;
66    fn sub(self, o: Self) -> Self {
67        Self::new(self.x - o.x, self.y - o.y, self.z - o.z)
68    }
69}
70
71impl Mul<f32> for Vec3 {
72    type Output = Self;
73    fn mul(self, k: f32) -> Self {
74        Self::new(self.x * k, self.y * k, self.z * k)
75    }
76}
77
78impl Neg for Vec3 {
79    type Output = Self;
80    fn neg(self) -> Self {
81        Self::new(-self.x, -self.y, -self.z)
82    }
83}
84
85/// A 4×4 matrix, column-major: `m[col * 4 + row]`.
86#[derive(Debug, Clone, Copy, PartialEq)]
87pub struct Mat4(pub [f32; 16]);
88
89impl Default for Mat4 {
90    fn default() -> Self {
91        Self::IDENTITY
92    }
93}
94
95impl Mat4 {
96    pub const IDENTITY: Self = Self([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]);
97
98    fn at(&self, row: usize, col: usize) -> f32 {
99        self.0[col * 4 + row]
100    }
101
102    pub fn translation(v: Vec3) -> Self {
103        let mut m = Self::IDENTITY;
104        m.0[12] = v.x;
105        m.0[13] = v.y;
106        m.0[14] = v.z;
107        m
108    }
109
110    pub fn scaling(v: Vec3) -> Self {
111        let mut m = Self::IDENTITY;
112        m.0[0] = v.x;
113        m.0[5] = v.y;
114        m.0[10] = v.z;
115        m
116    }
117
118    /// Turned `a` radians about x (right-handed, as `Axis.XP`).
119    pub fn rotation_x(a: f32) -> Self {
120        let (s, c) = a.sin_cos();
121        Self([1.0, 0.0, 0.0, 0.0, 0.0, c, s, 0.0, 0.0, -s, c, 0.0, 0.0, 0.0, 0.0, 1.0])
122    }
123
124    pub fn rotation_y(a: f32) -> Self {
125        let (s, c) = a.sin_cos();
126        Self([c, 0.0, -s, 0.0, 0.0, 1.0, 0.0, 0.0, s, 0.0, c, 0.0, 0.0, 0.0, 0.0, 1.0])
127    }
128
129    pub fn rotation_z(a: f32) -> Self {
130        let (s, c) = a.sin_cos();
131        Self([c, s, 0.0, 0.0, -s, c, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])
132    }
133
134    /// `ModelPart`'s turn: JOML `rotationZYX(z, y, x)` — z, then y, then x
135    /// applied to the vector last.
136    pub fn rotation_zyx(r: Vec3) -> Self {
137        Self::rotation_z(r.z) * Self::rotation_y(r.y) * Self::rotation_x(r.x)
138    }
139
140    /// `ModelPart.translateAndRotate`: to the pivot, turned, scaled.
141    pub fn part(pivot: Vec3, rotation: Vec3, scale: Vec3) -> Self {
142        Self::translation(pivot) * Self::rotation_zyx(rotation) * Self::scaling(scale)
143    }
144
145    pub fn transform_point(&self, p: Vec3) -> Vec3 {
146        Vec3::new(
147            self.at(0, 0) * p.x + self.at(0, 1) * p.y + self.at(0, 2) * p.z + self.at(0, 3),
148            self.at(1, 0) * p.x + self.at(1, 1) * p.y + self.at(1, 2) * p.z + self.at(1, 3),
149            self.at(2, 0) * p.x + self.at(2, 1) * p.y + self.at(2, 2) * p.z + self.at(2, 3),
150        )
151    }
152
153    /// A direction: turned and scaled, not moved.
154    pub fn transform_dir(&self, d: Vec3) -> Vec3 {
155        Vec3::new(
156            self.at(0, 0) * d.x + self.at(0, 1) * d.y + self.at(0, 2) * d.z,
157            self.at(1, 0) * d.x + self.at(1, 1) * d.y + self.at(1, 2) * d.z,
158            self.at(2, 0) * d.x + self.at(2, 1) * d.y + self.at(2, 2) * d.z,
159        )
160    }
161
162    /// A normal, kept a unit vector (good for rotations and uniform scales,
163    /// which is what bodies use).
164    pub fn transform_normal(&self, n: Vec3) -> Vec3 {
165        self.transform_dir(n).normalize()
166    }
167}
168
169impl Mul for Mat4 {
170    type Output = Self;
171    fn mul(self, o: Self) -> Self {
172        let mut r = [0.0; 16];
173        for col in 0..4 {
174            for row in 0..4 {
175                r[col * 4 + row] = (0..4).map(|k| self.at(row, k) * o.at(k, col)).sum();
176            }
177        }
178        Self(r)
179    }
180}
181
182/// Degrees to radians, for the formats (which are in degrees).
183pub fn rad(deg: Vec3) -> Vec3 {
184    deg * (std::f32::consts::PI / 180.0)
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    fn close(a: Vec3, b: Vec3) -> bool {
192        (a - b).length() < 1e-5
193    }
194
195    #[test]
196    fn rotations_are_right_handed() {
197        let half = std::f32::consts::FRAC_PI_2;
198        assert!(close(Mat4::rotation_x(half).transform_point(Vec3::new(0.0, 1.0, 0.0)), Vec3::new(0.0, 0.0, 1.0)));
199        assert!(close(Mat4::rotation_y(half).transform_point(Vec3::new(0.0, 0.0, 1.0)), Vec3::new(1.0, 0.0, 0.0)));
200        assert!(close(Mat4::rotation_z(half).transform_point(Vec3::new(1.0, 0.0, 0.0)), Vec3::new(0.0, 1.0, 0.0)));
201    }
202
203    #[test]
204    fn a_part_moves_after_turning() {
205        let m = Mat4::part(Vec3::new(5.0, 0.0, 0.0), Vec3::new(0.0, 0.0, std::f32::consts::FRAC_PI_2), Vec3::ONE);
206        assert!(close(m.transform_point(Vec3::new(1.0, 0.0, 0.0)), Vec3::new(5.0, 1.0, 0.0)));
207    }
208}