Math Foundation — Deep Engineering Guide
Every frame pushes millions of floats through vectors, matrices, quaternions, and collision tests. Math is the substrate: correct under 16.6 ms, deterministic for netcode, and SIMD-friendly or it dies. This skill covers the core math library every engine needs.
1. The Canonical Type Set
| Type |
Represents |
Memory |
float2/3/4 |
position, uv, color, dir |
8/12/16 B |
quat |
orientation |
16 B |
mat3/mat4 |
transforms, projections |
36/64 B |
aabb, sphere, plane, frustum |
volume queries |
24/16/16/64 B |
ray |
intersection tests |
32 B |
transform (pos, rot, scale) |
rigid + scale |
48 B (hot tier) |
Rules:
- Value types,
struct (not class), no vtables — SIMD can then reinterpret.
- NaN/Inf guards: default init to 0; assert on non-finite inputs in hot paths.
- Deterministic math when needed (see §6).
2. SIMD: The Practical Diet
2.1 The SIMD Types
float4 maps to __m128 (SSE), float4x4 to rows of __m128. Use intrinsic headers you control (or DirectXMath/Eigen/SIMD eigen) rather than accidental scalar loops.
struct alignas(16) float4 { float x, y, z, w; };
// SSE: _mm_add_ps(a, b) etc.
inline float4 FMA(float4 a, float4 b, float4 c) {
return _mm_fmadd_ps(a, b, c); // a*b + c — 1 instr, no rounding split
}
2.2 Alignment & Layout
alignas(16) on every SIMD type (avoid memcpy warnings, use aligned_alloc(16, …)).
- Hot arrays (vertex pos, transforms) live in SoA SIMD lanes (§ in memory skill).
- Avoid
-ffast-math unless you know: it breaks NaN propagation (netcode determinism).
2.3 When SIMD Matters Most
Matrix×Vector, quaternion×vector, AABB test packs, and any tight loop with uniform ops. At 100k transforms: SIMD mat×vec = ~4–8x over scalar.
3. Quaternions: The Correct Rotation
3.1 Why Not Euler
Gimbal lock (order-dependent), non-unique representations, bad interpolation. Quaternions: 4 floats, no gimbal lock, slerp smooth, compose cheaply.
3.2 The Core Ops
quat mul(q1, q2); // compose: apply q2 first, then q1. ~8 mults via SIMD
vec3 rotate(q, v); // q v q^-1 → t=2 cross(q.xyz,v); v + q.w*t + cross(q.xyz,t)
quat slerp(a, b, t); // shortest arc, accounting for sign (a·b<0 → negate b)
quat nlerp(a, b, t); // cheap approx (normalize lerp); fine for small arcs
quat fromAngleAxis(angle, axis); // normalized
quat fromMat4(m);/mat4 toMat4(q); // round-trip must preserve axis/angle
3.3 The Sign Problem
q and -q represent the same rotation. Correct slerp/nlerp: if dot(a,b) < 0, flip b sign first (else the path follows the long way). Meets every engine bug report: "model spins 360° on t=0..1".
3.4 Normalization Drift
Composing 1k quats drifts norm → normalize periodically (per-frame per-transform), cheap with rsqrt.
4. Transforms & Coordinate Conventions
4.1 Transform Composition
World transform = T(parent) * R * S (translation × rotation × scale, parent-first). Store as Transform { pos, rot, scale }; combine for children.
Convention decision (critical, document it):
- Row vectors × matrices (D3D-style) or column vectors (GL style)? Pick ONE for the whole engine + renderer; document in the codebase header; mismatch = mirrored sprites.
4.2 Handedness
- Engine convention: e.g., right-handed, Z-up (DOOM/Unreal) vs Z-forward left-handed (Unity-ish) — pick and convert at import (see
import-and-cook).
- All camera/projection/shadow conversions flow through one place to avoid 50 copies of the flip.
4.3 Fast Paths
- World→local: combine inverse
T * R^-1 * S^-1 without building a full inverse matrix.
- AABBs re-computed from transform (or a 3-vector math) — cheaper than matrix multiply then recompute box.
- LOD/stream culling: cheap sphere test first, then exact.
5. Numerical Stability
5.1 The Sneaky FP Facts
a+b+c != (a+b)+c (float non-associativity).
0.1f is not 0.1.
- Large + small = the small is lost (catastrophic cancellation near equal values).
5.2 Mitigations
- Use
double for accumulation templates (ray-tracing accumulators, physics) — speed cost acceptable there.
- Long lerps / slerps: interpolate
t relative to start, not absolute.
epsilon comparisons with std::numeric_limits<T>::epsilon() * errorScale, never a == b.
- Retain a "stable normal" path: recompute normals from the transform each frame.
5.3 Determinism (Netcode Critical!)
- No
-ffast-math, no FMA being used in some builds but not others, same float ops per platform.
- Fixed-point integer math for lockstep position where feasible (see
determinism.md).
- Deterministic
sqrt/sin implementations cross-compiler (see multiplayer netcode determinism references).
6. Collision Primitives
6.1 The Primitive Zoo
| Shape |
Store |
Tests |
| Sphere |
center r |
sphere-sphere ( |
| AABB |
min max |
overlap, point-in, ray-slab |
| OBB |
center, axes(3 rot), halfsizes |
SAT |
| Ray |
o, d(t) |
sphere, aabb (slab), triangle (Möller–Trumbore) |
| Plane |
n·p = d |
side, distance |
| Frustum |
6 planes |
tight test: sphere + box + point |
6.2 Prioritize Cheap → Expensive
Collision/streaming queries:
- Sphere vs frustum (cheap) → skip.
- AABB/AABB exact.
- OBB/OBB via SAT only when needed.
- Triangle/ray only on candidate hits.
6.3 Ray-AABB Slab (fastest shape test)
bool RayAABB(Ray r, AABB b, float& tmin, float& tmax) {
float t0 = DBL_MIN, t1 = DBL_MAX;
for (int a = 0; a < 3; ++a) {
float inv = 1.0f / r.d[a];
float tNear = (b.min[a] - r.o[a]) * inv;
float tFar = (b.max[a] - r.o[a]) * inv;
std::swap if inverted...
t0 = max(t0, tNear); t1 = min(t1, tFar);
if (t0 > t1) return false;
}
return true;
}
The 3-iteration slab test beats general AABB intersection on hot paths.
7. Curves & Interpolation
| Curve |
Use |
Notes |
lerp |
positions, colors, alpha |
scalar/vector |
slerp/nlerp |
rotations |
sign-correct |
smoothstep |
easing |
t*t*(3-2t) cheap |
| Catmull-Rom |
camera splines, paths |
C1 continuous through points |
| Cubic Beziers |
UI/easing/curves |
de Casteljau |
7.1 The Easing Family
Linear → use EaseInOut for UI, camera; avoid linear for non-linear feel. Include easeOutBack(s), easeInOutCubic, etc. Table-driven t function + cheap.
8. The Math Library Checklist (Lead-Level)
- Value types, SIMD-aligned, deterministic (no fast-math).
- Quaternions everywhere for rotation; sign-correct slerp; periodic normalization.
- One matrix convention documented end to end.
- Collision tests ranked cheap→expensive; slab/static fast paths.
- Numerical stability patterns (double accumulators, relative epsilon, no
==).
- Easing/curves table.
- Unit tests: properties (round-trips, orthonormality, no drift), fuzz a few.
9. Unit-Testing the Library
- Algebraic identities: quat compose distributes; inverse round-trips; slerp endpoints exact.
- Determinism: run same sim under MSVC+GCC+Clang, compare hashes.
- SIMD vs scalar reference within epsilon.
- Fuzz: random transforms → inverse → reconstruct, assert within tolerance.
- NaN/Inf injection: assert allfinite guards fire.
Ship a math_self_test executable run in CI.
10. References
references/simd.md — SIMD intrinsics, alignment, SoA, FMA/fast-math, specialized mat×vec
references/quaternion.md — algebra, composition, slerp/nlerp, axis-angle, drift fix, matrix round-trip
references/transforms.md — conventions, handedness, T·R·S, world/local inverse, AABB from transform
references/numerical-stability.md — FP facts, cancellation, determinism, epsilon discipline
references/collision-primitives.md — sphere/aabb/obb/ray/plane/frustum tests, slab ray-AABB, SAT
references/curves-and-interp.md — lerp/slerp, easing family, Catmull-Rom, Beziers, frame-rate independent
1---2name: math-foundation3description: Expert game engine math foundation — SIMD vectors/matrices, quaternions, transforms, interpolation, collision primitives, curves and numerical stability under real-time budgets.4---56# Math Foundation — Deep Engineering Guide78Every frame pushes millions of floats through vectors, matrices, quaternions, and collision tests. Math is the substrate: correct under 16.6 ms, deterministic for netcode, and SIMD-friendly or it dies. This skill covers the core math library every engine needs.910## 1. The Canonical Type Set1112| Type | Represents | Memory |13|------|-----------|--------|14| `float2/3/4` | position, uv, color, dir | 8/12/16 B |15| `quat` | orientation | 16 B |16| `mat3/mat4` | transforms, projections | 36/64 B |17| `aabb`, `sphere`, `plane`, `frustum` | volume queries | 24/16/16/64 B |18| `ray` | intersection tests | 32 B |19| `transform` (pos, rot, scale) | rigid + scale | 48 B (hot tier) |2021Rules:22- **Value types**, `struct` (not class), no vtables — SIMD can then reinterpret.23- **NaN/Inf guards**: default init to 0; assert on non-finite inputs in hot paths.24- Deterministic math when needed (see §6).2526## 2. SIMD: The Practical Diet2728### 2.1 The SIMD Types2930`float4` maps to `__m128` (SSE), `float4x4` to rows of `__m128`. Use intrinsic headers you control (or DirectXMath/Eigen/SIMD eigen) rather than accidental scalar loops.3132```cpp33struct alignas(16) float4 { float x, y, z, w; };34// SSE: _mm_add_ps(a, b) etc.35inline float4 FMA(float4 a, float4 b, float4 c) {36 return _mm_fmadd_ps(a, b, c); // a*b + c — 1 instr, no rounding split37}38```3940### 2.2 Alignment & Layout4142- `alignas(16)` on every SIMD type (avoid `memcpy` warnings, use `aligned_alloc(16, …)`).43- Hot arrays (vertex pos, transforms) live in SoA SIMD lanes (§ in memory skill).44- Avoid `-ffast-math` unless you know: it breaks NaN propagation (netcode determinism).4546### 2.3 When SIMD Matters Most4748Matrix×Vector, quaternion×vector, AABB test packs, and any tight loop with uniform ops. At 100k transforms: SIMD mat×vec = ~4–8x over scalar.4950## 3. Quaternions: The Correct Rotation5152### 3.1 Why Not Euler5354Gimbal lock (order-dependent), non-unique representations, bad interpolation. Quaternions: 4 floats, no gimbal lock, `slerp` smooth, compose cheaply.5556### 3.2 The Core Ops5758```cpp59quat mul(q1, q2); // compose: apply q2 first, then q1. ~8 mults via SIMD60vec3 rotate(q, v); // q v q^-1 → t=2 cross(q.xyz,v); v + q.w*t + cross(q.xyz,t)61quat slerp(a, b, t); // shortest arc, accounting for sign (a·b<0 → negate b)62quat nlerp(a, b, t); // cheap approx (normalize lerp); fine for small arcs63quat fromAngleAxis(angle, axis); // normalized64quat fromMat4(m);/mat4 toMat4(q); // round-trip must preserve axis/angle65```6667### 3.3 The Sign Problem6869`q` and `-q` represent the same rotation. Correct **slerp/nlerp**: if `dot(a,b) < 0`, flip `b` sign first (else the path follows the long way). Meets every engine bug report: "model spins 360° on t=0..1".7071### 3.4 Normalization Drift7273Composing 1k quats drifts norm → normalize periodically (per-frame per-transform), cheap with `rsqrt`.7475## 4. Transforms & Coordinate Conventions7677### 4.1 Transform Composition7879World transform = `T(parent) * R * S` (translation × rotation × scale, parent-first). Store as `Transform { pos, rot, scale }`; combine for children.8081Convention decision (critical, document it):82- **Row vectors × matrices** (D3D-style) or **column vectors** (GL style)? Pick ONE for the whole engine + renderer; document in the codebase header; mismatch = mirrored sprites.8384### 4.2 Handedness8586- Engine convention: e.g., **right-handed, Z-up** (DOOM/Unreal) vs **Z-forward left-handed (Unity-ish)** — pick and convert at import (see `import-and-cook`).87- All camera/projection/shadow conversions flow through one place to avoid 50 copies of the flip.8889### 4.3 Fast Paths9091- World→local: combine inverse `T * R^-1 * S^-1` without building a full inverse matrix.92- AABBs re-computed from transform (or a 3-vector math) — cheaper than matrix multiply then recompute box.93- LOD/stream culling: cheap sphere test first, then exact.9495## 5. Numerical Stability9697### 5.1 The Sneaky FP Facts9899- `a+b+c != (a+b)+c` (float non-associativity).100- `0.1f` is not `0.1`.101- Large + small = the small is lost (catastrophic cancellation near equal values).102103### 5.2 Mitigations104105- Use `double` for accumulation templates (ray-tracing accumulators, physics) — speed cost acceptable there.106- Long lerps / slerps: interpolate `t` relative to start, not absolute.107- `epsilon` comparisons with `std::numeric_limits<T>::epsilon() * errorScale`, never `a == b`.108- Retain a "stable normal" path: recompute normals from the transform each frame.109110### 5.3 Determinism (Netcode Critical!)111112- No `-ffast-math`, no FMA being used in some builds but not others, same `float` ops per platform.113- Fixed-point integer math for lockstep position where feasible (see `determinism.md`).114- Deterministic `sqrt`/`sin` implementations cross-compiler (see multiplayer netcode determinism references).115116## 6. Collision Primitives117118### 6.1 The Primitive Zoo119120| Shape | Store | Tests |121|-------|-------|-------|122| Sphere | center r | sphere-sphere (|d|² ≤ (r1+r2)²) |123| AABB | min max | overlap, point-in, ray-slab |124| OBB | center, axes(3 rot), halfsizes | SAT |125| Ray | o, d(t) | sphere, aabb (slab), triangle (Möller–Trumbore) |126| Plane | n·p = d | side, distance |127| Frustum | 6 planes | tight test: sphere + box + point |128129### 6.2 Prioritize Cheap → Expensive130131Collision/streaming queries:1321. Sphere vs frustum (cheap) → skip.1332. AABB/AABB exact.1343. OBB/OBB via SAT only when needed.1354. Triangle/ray only on candidate hits.136137### 6.3 Ray-AABB Slab (fastest shape test)138139```cpp140bool RayAABB(Ray r, AABB b, float& tmin, float& tmax) {141 float t0 = DBL_MIN, t1 = DBL_MAX;142 for (int a = 0; a < 3; ++a) {143 float inv = 1.0f / r.d[a];144 float tNear = (b.min[a] - r.o[a]) * inv;145 float tFar = (b.max[a] - r.o[a]) * inv;146 std::swap if inverted...147 t0 = max(t0, tNear); t1 = min(t1, tFar);148 if (t0 > t1) return false;149 }150 return true;151}152```153154The 3-iteration slab test beats general AABB intersection on hot paths.155156## 7. Curves & Interpolation157158| Curve | Use | Notes |159|-------|-----|-------|160| `lerp` | positions, colors, alpha | scalar/vector |161| `slerp`/`nlerp` | rotations | sign-correct |162| `smoothstep` | easing | `t*t*(3-2t)` cheap |163| Catmull-Rom | camera splines, paths | C1 continuous through points |164| Cubic Beziers | UI/easing/curves | de Casteljau |165166### 7.1 The Easing Family167168Linear → use `EaseInOut` for UI, camera; avoid linear for non-linear feel. Include `easeOutBack(s)`, `easeInOutCubic`, etc. Table-driven `t` function + cheap.169170## 8. The Math Library Checklist (Lead-Level)1711721. Value types, SIMD-aligned, deterministic (no fast-math).1732. Quaternions everywhere for rotation; sign-correct slerp; periodic normalization.1743. One matrix convention documented end to end.1754. Collision tests ranked cheap→expensive; slab/static fast paths.1765. Numerical stability patterns (double accumulators, relative epsilon, no `==`).1776. Easing/curves table.1787. Unit tests: properties (round-trips, orthonormality, no drift), fuzz a few.179180## 9. Unit-Testing the Library181182- Algebraic identities: quat compose distributes; inverse round-trips; slerp endpoints exact.183- Determinism: run same sim under MSVC+GCC+Clang, compare hashes.184- SIMD vs scalar reference within epsilon.185- Fuzz: random transforms → inverse → reconstruct, assert within tolerance.186- NaN/Inf injection: assert allfinite guards fire.187188Ship a `math_self_test` executable run in CI.189190## 10. References191192- `references/simd.md` — SIMD intrinsics, alignment, SoA, FMA/fast-math, specialized mat×vec193- `references/quaternion.md` — algebra, composition, slerp/nlerp, axis-angle, drift fix, matrix round-trip194- `references/transforms.md` — conventions, handedness, T·R·S, world/local inverse, AABB from transform195- `references/numerical-stability.md` — FP facts, cancellation, determinism, epsilon discipline196- `references/collision-primitives.md` — sphere/aabb/obb/ray/plane/frustum tests, slab ray-AABB, SAT197- `references/curves-and-interp.md` — lerp/slerp, easing family, Catmull-Rom, Beziers, frame-rate independent