Skip to main content

box3d_rust/math_functions/
matrix.rs

1// Matrix3 operations, Steiner parallel-axis helper, and Mat2 helpers for tests.
2// Part of the math_functions module.
3
4use super::*;
5
6/// A 2x2 matrix stored as columns. Ported from math_internal.h for tests and
7/// internal use (`b3Matrix2`).
8#[derive(Debug, Clone, Copy, PartialEq, Default)]
9pub struct Mat2 {
10    pub cx: Vec2,
11    pub cy: Vec2,
12}
13
14/// Zero 2x2 matrix. (math_internal.h literal)
15pub const MAT2_ZERO: Mat2 = Mat2 {
16    cx: Vec2 { x: 0.0, y: 0.0 },
17    cy: Vec2 { x: 0.0, y: 0.0 },
18};
19
20/// Compute the determinant of a 3-by-3 matrix.
21pub fn det(m: Matrix3) -> f32 {
22    dot(m.cx, cross(m.cy, m.cz))
23}
24
25/// Multiply a matrix times a column vector.
26pub fn mul_mv(m: Matrix3, a: Vec3) -> Vec3 {
27    Vec3 {
28        x: m.cx.x * a.x + m.cy.x * a.y + m.cz.x * a.z,
29        y: m.cx.y * a.x + m.cy.y * a.y + m.cz.y * a.z,
30        z: m.cx.z * a.x + m.cy.z * a.y + m.cz.z * a.z,
31    }
32}
33
34/// Symmetric matrix × vector matching `b3MulMVW` under `B3_SIMD_NONE`.
35///
36/// Uses the upper triangle of a column-major [`Matrix3`] (`cx.x/y/z`, `cy.y/z`,
37/// `cz.z`) and the wide association `cxx*x + (cxy*y + cxz*z)`. The Convex
38/// contact solver stores `invIA`/`invIB`/`rollingMass` this way; using full
39/// [`mul_mv`] diverges bit-for-bit when inertia is not perfectly symmetric.
40pub fn mul_mv_sym(m: Matrix3, a: Vec3) -> Vec3 {
41    let cxx = m.cx.x;
42    let cxy = m.cx.y;
43    let cxz = m.cx.z;
44    let cyy = m.cy.y;
45    let cyz = m.cy.z;
46    let czz = m.cz.z;
47    Vec3 {
48        x: cxx * a.x + (cxy * a.y + cxz * a.z),
49        y: cxy * a.x + (cyy * a.y + cyz * a.z),
50        z: cxz * a.x + (cyz * a.y + czz * a.z),
51    }
52}
53
54/// Negate a matrix.
55pub fn negate_mat3(a: Matrix3) -> Matrix3 {
56    Matrix3 {
57        cx: Vec3 {
58            x: -a.cx.x,
59            y: -a.cx.y,
60            z: -a.cx.z,
61        },
62        cy: Vec3 {
63            x: -a.cy.x,
64            y: -a.cy.y,
65            z: -a.cy.z,
66        },
67        cz: Vec3 {
68            x: -a.cz.x,
69            y: -a.cz.y,
70            z: -a.cz.z,
71        },
72    }
73}
74
75/// Matrix addition.
76/// @return a + b
77pub fn add_mm(a: Matrix3, b: Matrix3) -> Matrix3 {
78    Matrix3 {
79        cx: Vec3 {
80            x: a.cx.x + b.cx.x,
81            y: a.cx.y + b.cx.y,
82            z: a.cx.z + b.cx.z,
83        },
84        cy: Vec3 {
85            x: a.cy.x + b.cy.x,
86            y: a.cy.y + b.cy.y,
87            z: a.cy.z + b.cy.z,
88        },
89        cz: Vec3 {
90            x: a.cz.x + b.cz.x,
91            y: a.cz.y + b.cz.y,
92            z: a.cz.z + b.cz.z,
93        },
94    }
95}
96
97/// Matrix subtraction.
98/// @return a - b
99pub fn sub_mm(a: Matrix3, b: Matrix3) -> Matrix3 {
100    Matrix3 {
101        cx: Vec3 {
102            x: a.cx.x - b.cx.x,
103            y: a.cx.y - b.cx.y,
104            z: a.cx.z - b.cx.z,
105        },
106        cy: Vec3 {
107            x: a.cy.x - b.cy.x,
108            y: a.cy.y - b.cy.y,
109            z: a.cy.z - b.cy.z,
110        },
111        cz: Vec3 {
112            x: a.cz.x - b.cz.x,
113            y: a.cz.y - b.cz.y,
114            z: a.cz.z - b.cz.z,
115        },
116    }
117}
118
119/// Multiply a matrix by a scalar, component-wise.
120pub fn mul_sm(s: f32, a: Matrix3) -> Matrix3 {
121    Matrix3 {
122        cx: Vec3 {
123            x: s * a.cx.x,
124            y: s * a.cx.y,
125            z: s * a.cx.z,
126        },
127        cy: Vec3 {
128            x: s * a.cy.x,
129            y: s * a.cy.y,
130            z: s * a.cy.z,
131        },
132        cz: Vec3 {
133            x: s * a.cz.x,
134            y: s * a.cz.y,
135            z: s * a.cz.z,
136        },
137    }
138}
139
140/// Matrix multiplication.
141/// @return a * b
142pub fn mul_mm(a: Matrix3, b: Matrix3) -> Matrix3 {
143    Matrix3 {
144        cx: mul_mv(a, b.cx),
145        cy: mul_mv(a, b.cy),
146        cz: mul_mv(a, b.cz),
147    }
148}
149
150/// Matrix transpose.
151pub fn transpose(m: Matrix3) -> Matrix3 {
152    Matrix3 {
153        cx: Vec3 {
154            x: m.cx.x,
155            y: m.cy.x,
156            z: m.cz.x,
157        },
158        cy: Vec3 {
159            x: m.cx.y,
160            y: m.cy.y,
161            z: m.cz.y,
162        },
163        cz: Vec3 {
164            x: m.cx.z,
165            y: m.cy.z,
166            z: m.cz.z,
167        },
168    }
169}
170
171/// General matrix inverse.
172pub fn invert_matrix(m: Matrix3) -> Matrix3 {
173    let det = det(m);
174    if abs_float(det) > 1000.0 * f32::MIN_POSITIVE {
175        let inv_det = 1.0 / det;
176        let out = Matrix3 {
177            cx: mul_sv(inv_det, cross(m.cy, m.cz)),
178            cy: mul_sv(inv_det, cross(m.cz, m.cx)),
179            cz: mul_sv(inv_det, cross(m.cx, m.cy)),
180        };
181
182        transpose(out)
183    } else {
184        MAT3_ZERO
185    }
186}
187
188/// Solve a matrix equation.
189/// @return inv(m) * a
190pub fn solve3(m: Matrix3, a: Vec3) -> Vec3 {
191    let det = det(m);
192    if abs_float(det) > 1000.0 * f32::MIN_POSITIVE {
193        let inv_det = 1.0 / det;
194        let s = Matrix3 {
195            cx: cross(m.cy, m.cz),
196            cy: cross(m.cz, m.cx),
197            cz: cross(m.cx, m.cy),
198        };
199
200        Vec3 {
201            x: inv_det * dot(s.cx, a),
202            y: inv_det * dot(s.cy, a),
203            z: inv_det * dot(s.cz, a),
204        }
205    } else {
206        VEC3_ZERO
207    }
208}
209
210/// Invert a matrix (returns the adjugate / det without the final transpose of
211/// [`invert_matrix`]).
212pub fn invert_t(m: Matrix3) -> Matrix3 {
213    let det = det(m);
214    if abs_float(det) > 1000.0 * f32::MIN_POSITIVE {
215        let inv_det = 1.0 / det;
216        Matrix3 {
217            cx: mul_sv(inv_det, cross(m.cy, m.cz)),
218            cy: mul_sv(inv_det, cross(m.cz, m.cx)),
219            cz: mul_sv(inv_det, cross(m.cx, m.cy)),
220        }
221    } else {
222        MAT3_ZERO
223    }
224}
225
226/// Get the component-wise absolute value of a matrix.
227pub fn abs_matrix3(m: Matrix3) -> Matrix3 {
228    Matrix3 {
229        cx: abs(m.cx),
230        cy: abs(m.cy),
231        cz: abs(m.cz),
232    }
233}
234
235/// Make a matrix from a quaternion. This is useful if you need to
236/// rotate many vectors.
237pub fn make_matrix_from_quat(q: Quat) -> Matrix3 {
238    let xx = q.v.x * q.v.x;
239    let yy = q.v.y * q.v.y;
240    let zz = q.v.z * q.v.z;
241    let xy = q.v.x * q.v.y;
242    let xz = q.v.x * q.v.z;
243    let xw = q.v.x * q.s;
244    let yz = q.v.y * q.v.z;
245    let yw = q.v.y * q.s;
246    let zw = q.v.z * q.s;
247
248    Matrix3 {
249        cx: Vec3 {
250            x: 1.0 - 2.0 * (yy + zz),
251            y: 2.0 * (xy + zw),
252            z: 2.0 * (xz - yw),
253        },
254        cy: Vec3 {
255            x: 2.0 * (xy - zw),
256            y: 1.0 - 2.0 * (xx + zz),
257            z: 2.0 * (yz + xw),
258        },
259        cz: Vec3 {
260            x: 2.0 * (xz + yw),
261            y: 2.0 * (yz - xw),
262            z: 1.0 - 2.0 * (xx + yy),
263        },
264    }
265}
266
267/// Get the inertia tensor of an offset point.
268/// <https://en.wikipedia.org/wiki/Parallel_axis_theorem>
269pub fn steiner(mass: f32, origin: Vec3) -> Matrix3 {
270    // Usage: Io = Ic + Is and Ic = Io - Is
271    let ixx = mass * (origin.y * origin.y + origin.z * origin.z);
272    let iyy = mass * (origin.x * origin.x + origin.z * origin.z);
273    let izz = mass * (origin.x * origin.x + origin.y * origin.y);
274    let ixy = -mass * origin.x * origin.y;
275    let ixz = -mass * origin.x * origin.z;
276    let iyz = -mass * origin.y * origin.z;
277
278    // Write
279    let mut out = MAT3_ZERO;
280    out.cx.x = ixx;
281    out.cy.x = ixy;
282    out.cz.x = ixz;
283    out.cx.y = ixy;
284    out.cy.y = iyy;
285    out.cz.y = iyz;
286    out.cx.z = ixz;
287    out.cy.z = iyz;
288    out.cz.z = izz;
289
290    out
291}
292
293fn det2(m: Mat2) -> f32 {
294    m.cx.x * m.cy.y - m.cx.y * m.cy.x
295}
296
297/// Multiply a 2-by-2 matrix times a 2D vector. (math_internal.h: b3MulMV2)
298pub fn mul_mv2(m: Mat2, a: Vec2) -> Vec2 {
299    Vec2 {
300        x: m.cx.x * a.x + m.cy.x * a.y,
301        y: m.cx.y * a.x + m.cy.y * a.y,
302    }
303}
304
305/// Symmetric 2×2 × vector matching `b3MulMV2W` (`cxx=cx.x`, `cxy=cx.y`, `cyy=cy.y`).
306pub fn mul_mv2_sym(m: Mat2, a: Vec2) -> Vec2 {
307    let cxx = m.cx.x;
308    let cxy = m.cx.y;
309    let cyy = m.cy.y;
310    Vec2 {
311        x: cxx * a.x + cxy * a.y,
312        y: cxy * a.x + cyy * a.y,
313    }
314}
315
316/// Multiply two 2-by-2 matrices. (math_internal.h: b3MulMM2)
317pub fn mul_mm2(m1: Mat2, m2: Mat2) -> Mat2 {
318    Mat2 {
319        cx: mul_mv2(m1, m2.cx),
320        cy: mul_mv2(m1, m2.cy),
321    }
322}
323
324/// Invert a 2-by-2 matrix. (math_internal.h: b3Invert2)
325pub fn invert2(m: Mat2) -> Mat2 {
326    let det = det2(m);
327    if abs_float(det) > 1000.0 * f32::MIN_POSITIVE {
328        let inv_det = 1.0 / det;
329        Mat2 {
330            cx: Vec2 {
331                x: inv_det * m.cy.y,
332                y: -inv_det * m.cx.y,
333            },
334            cy: Vec2 {
335                x: -inv_det * m.cy.x,
336                y: inv_det * m.cx.x,
337            },
338        }
339    } else {
340        Mat2 {
341            cx: Vec2 { x: 0.0, y: 0.0 },
342            cy: Vec2 { x: 0.0, y: 0.0 },
343        }
344    }
345}
346
347/// Solve A * x = b for a 2-by-2 matrix. Assumes positive semi-definite.
348/// (math_internal.h: b3Solve2)
349pub fn solve2(m: Mat2, b: Vec2) -> Vec2 {
350    let det = det2(m);
351    if det > 1000.0 * f32::MIN_POSITIVE {
352        let inv_det = 1.0 / det;
353        Vec2 {
354            x: inv_det * m.cy.y * b.x - inv_det * m.cy.x * b.y,
355            y: -inv_det * m.cx.y * b.x + inv_det * m.cx.x * b.y,
356        }
357    } else {
358        Vec2 { x: 0.0, y: 0.0 }
359    }
360}
361
362/// Skew-symmetric cross-product matrix. (math_internal.h: b3Skew)
363pub fn skew(v: Vec3) -> Matrix3 {
364    Matrix3 {
365        cx: Vec3 {
366            x: 0.0,
367            y: v.z,
368            z: -v.y,
369        },
370        cy: Vec3 {
371            x: -v.z,
372            y: 0.0,
373            z: v.x,
374        },
375        cz: Vec3 {
376            x: v.y,
377            y: -v.x,
378            z: 0.0,
379        },
380    }
381}