whiteoutlib 0.1.6

Read and write Blizzard game assets from Rust: models (MDX, M2, M3), textures (BLP, DDS, PNG, JPEG, BMP, TGA, TIFF, GIF) and archives (CASC, MPQ).
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 Fernando Sahmkow

#pragma once

/**
 * @file physics.h
 * @brief Physics structures — forces, warps, shapes, rigid bodies, joints, cloth
 *
 * Defines all M3 physics chunk types: Force (FOR_) and Warp (WRP_) for
 * particle/ribbon influences; PhysicsShape (PHSH) with box/sphere/capsule/
 * cylinder/convex-hull/mesh variants; RigidBody (PHRB) for Havok rigid body
 * configuration; PhysicsJoint (PHYJ) and PhysicsConstraint (PHCT) for articulated
 * connections; and ClothPhysics (PHCL) with ClothCollider (PHCC) and ClothProxy
 * (PHAC) for cloth simulation.
 *
 * @see M3_FILE_FORMAT_SPECIFICATION.md §13 Physics
 */

#include "base.h"

namespace whiteout {
namespace m3 {

// ============================================================================
// Physics
// ============================================================================

/**
 * @brief FOR_ — Force field (v0–v2, 104 bytes)
 *
 * Applies radial, wind, or explosion forces to particles and ribbons
 * within an influence volume shape (sphere, cylinder, box, hemisphere).
 */
struct Force {
    ForceType forceType;               ///< Force influence type (radial/wind/explosion)
    ForceShape forceShape;             ///< Influence volume shape
    u32 unknown;                       ///< Unknown field
    u32 boneIndex;                     ///< Index into BONE array
    ForceFlag flags = ForceFlag::None; ///< Force flags (falloff, height gradient, unbounded)
    u32 localChannels;                 ///< Local channel bitmask
    AnimRef<f32> strength;             ///< Animated force strength
    AnimRef<f32> width;                ///< Animated influence width
    AnimRef<f32> height;               ///< Animated influence height
    AnimRef<f32> length;               ///< Animated influence length
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief WRP_ — Warp field (v0–v1, 132 bytes)
 *
 * Warps particle/ribbon trajectories with animated radius, height,
 * and angular/axial/radial strength components.
 */
struct Warp {
    u32 warpType;          ///< Warp type
    u32 boneIndex;         ///< Index into BONE array
    u32 unknown;           ///< Unknown field
    AnimRef<f32> radius;   ///< Animated warp radius
    AnimRef<f32> height;   ///< Animated warp height
    AnimRef<f32> strength; ///< Animated warp strength
    AnimRef<f32> angular;  ///< Animated angular component
    AnimRef<f32> axial;    ///< Animated axial component
    AnimRef<f32> radial;   ///< Animated radial component
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief DMSE — Convex hull half-edge (v0, 4 bytes)
 *
 * Half-edge connectivity for PHSH convex hull shapes (shapeType = 4).
 * Entries are stored in consecutive twin pairs (forward 0x01 / reverse 0xFF).
 * The nextAroundVertex field chains half-edges into closed per-vertex rings.
 */
struct ConvexHullHalfEdge {
    u8 type;             ///< 0x01 = forward, 0xFF = reverse (twin)
    u8 faceIndex;        ///< Face this half-edge borders
    u8 vertexIndex;      ///< Target vertex of this half-edge
    u8 nextAroundVertex; ///< Next half-edge around the same vertex
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief DMMN — Physics mesh BVH node (v0: 12 bytes, v1: 8 bytes)
 *
 * DMMN entries form a linearized k-DOP Bounding Volume Hierarchy (BVH) tree
 * for concave mesh collision. The entry count is always odd: n = 2*n_leaves - 1.
 *
 * **Tree structure** — right-skewed binary tree stored in DFS preorder:
 *   - Array layout: (INT_0, LEAF_1), (INT_2, LEAF_3), ..., LEAF_{n-1}
 *   - Even indices 0..n-3: internal nodes
 *   - Odd indices 1..n-2: leaf nodes
 *   - Last index n-1: leaf node
 *   - Each internal node 2k: left child = leaf 2k+1, right child = node 2k+2
 *
 * **v0** (Havok-era, 12 bytes per node) — stores only the slab normal direction
 * as a plain Vector3f. No quantized slab bounds are present; the tree topology
 * and bounding-slab directions are identical to v1, but distance culling relies
 * on the runtime computing slab projections against meshBoundsCenter/Extent.
 * Only 3 files in the corpus use v0 (all with PHSH v2).
 *
 * **v1** (Domino physics, 8 bytes per node) — octahedral-encoded normal +
 * quantized slab bounds:
 *   - i16 octX, octY: octahedral-mapped slab normal (snorm16 pair)
 *   - u16 slabMin, slabMax: quantized bounding-slab distances along the normal
 *   - Internal nodes: slabMax != 0; leaf sentinel: slabMax == 0
 *     (except the last node, which may have slabMax != 0 despite being a leaf)
 *
 * **Quantization** (v1, universally confirmed across 468 corpus files):
 *   - Per-axis step: tol_i = extent_i / 32767
 *   - Projected step: tol_proj = dot(tolerance, |normal|)
 *   - Slab values quantized as: q = round(projection / tol_proj)
 *   - Root node slab range approaches [-32767, +32767] (full AABB)
 *
 * Internal nodes use one slab direction; their paired leaf uses a DIFFERENT
 * slab direction, forming a 2-DOP bound per primitive group. Most trees (391/468)
 * use multiple slab normals across internal levels for tighter culling.
 *
 * PHSH meshTreeDepth gives the tree height (longest root-to-leaf path in nodes).
 */
struct PhysicsMeshBvhNode {
    /// BVH node: octahedral-encoded slab normal + quantized slab bounds (v1, 8 bytes).
    struct Octahedral {
        i16 octX;    ///< Octahedral-encoded X (snorm16)
        i16 octY;    ///< Octahedral-encoded Y (snorm16)
        u16 slabMin; ///< Quantized bounding-slab min distance
        u16 slabMax; ///< Quantized bounding-slab max distance (0 = leaf sentinel)

        /// Decode octahedral (octX, octY) to a unit-length slab normal.
        Vector3f decodeNormal() const {
            f32 x = static_cast<f32>(snorm16::from_raw(octX));
            f32 y = static_cast<f32>(snorm16::from_raw(octY));
            f32 z = 1.0f - std::abs(x) - std::abs(y);
            if (z < 0.0f) {
                f32 ox = x;
                x = (1.0f - std::abs(y)) * (ox >= 0.0f ? 1.0f : -1.0f);
                y = (1.0f - std::abs(ox)) * (y >= 0.0f ? 1.0f : -1.0f);
            }
            return Vector3f{x, y, z}.normalized();
        }

        /// Encode a unit-length normal into octahedral (octX, octY).
        /// slabMin and slabMax are set from the provided arguments.
        static Octahedral fromNormal(const Vector3f& n, u16 sMin = 0, u16 sMax = 0) {
            f32 invL1 = 1.0f / (std::abs(n.x) + std::abs(n.y) + std::abs(n.z));
            f32 ox = n.x * invL1;
            f32 oy = n.y * invL1;
            if (n.z < 0.0f) {
                f32 tmpX = ox;
                ox = (1.0f - std::abs(oy)) * (tmpX >= 0.0f ? 1.0f : -1.0f);
                oy = (1.0f - std::abs(tmpX)) * (oy >= 0.0f ? 1.0f : -1.0f);
            }
            Octahedral result;
            result.octX = snorm16::from_float(ox).value;
            result.octY = snorm16::from_float(oy).value;
            result.slabMin = sMin;
            result.slabMax = sMax;
            return result;
        }

        /// Compute the projected quantization step for this node's normal.
        /// @param tolerance  PHSH meshTolerance (= meshBoundsExtent / 32767).
        f32 projectedStep(const Vector3f& tolerance) const {
            Vector3f n = decodeNormal();
            return std::abs(n.x) * tolerance.x + std::abs(n.y) * tolerance.y +
                   std::abs(n.z) * tolerance.z;
        }

        /// Dequantize slabMin to a signed slab distance in model-space units.
        /// @param tolerance  PHSH meshTolerance (= meshBoundsExtent / 32767).
        f32 decodeSlabMin(const Vector3f& tolerance) const {
            return static_cast<f32>(static_cast<i16>(slabMin)) * projectedStep(tolerance);
        }

        /// Dequantize slabMax to a signed slab distance in model-space units.
        /// @param tolerance  PHSH meshTolerance (= meshBoundsExtent / 32767).
        f32 decodeSlabMax(const Vector3f& tolerance) const {
            return static_cast<f32>(static_cast<i16>(slabMax)) * projectedStep(tolerance);
        }

        /// Dequantize both slab bounds as a (min, max) pair.
        /// @param tolerance  PHSH meshTolerance (= meshBoundsExtent / 32767).
        /// @return {min distance, max distance} in model-space units.
        std::pair<f32, f32> decodeSlabRange(const Vector3f& tolerance) const {
            f32 step = projectedStep(tolerance);
            return {static_cast<f32>(static_cast<i16>(slabMin)) * step,
                    static_cast<f32>(static_cast<i16>(slabMax)) * step};
        }
    };
    union {
        Vector3f normal{};     ///< Slab normal direction (v0, 12 bytes)
        Octahedral octahedral; ///< Packed octahedral normal + slab bounds (v1, 8 bytes)
    };
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief DMMT — Physics mesh triangle (v0, 28 bytes)
 */
struct PhysicsMeshTriangle {
    u32 vertexIndex0; ///< First vertex index
    u32 vertexIndex1; ///< Second vertex index
    u32 vertexIndex2; ///< Third vertex index
    u32 edgeIndex0;   ///< First edge index
    u32 edgeIndex1;   ///< Second edge index
    u32 edgeIndex2;   ///< Third edge index
    u16 reserved;     ///< Reserved
    u16 flags;        ///< Triangle flags
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief DMME — Physics mesh edge (v0, 20 bytes)
 */
struct PhysicsMeshEdge {
    u32 edgeType; ///< Edge type
    u32 vertexA;  ///< First vertex index
    u32 vertexB;  ///< Second vertex index
    u32 faceA;    ///< First adjacent face
    u32 faceB;    ///< Second adjacent face
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief PHSH — Physics shape (v0–v3, 132–300 bytes)
 *
 * The 300-byte v3 layout is a three-part union. Bytes 0–79 are the common
 * header. Bytes 80–103 hold shape dimensions for simple shapes (0–3) or
 * are zero for complex shapes. Bytes 80–183 form the convex hull section
 * (shapeType 4); bytes 184–299 form the mesh section (shapeType 5).
 */
struct PhysicsShape {
    Matrix44f transform; ///< 4×4 shape transform matrix

    // v1: collisionMargin + shapeType at offsets 64-71
    // v2+: shapeType at offset 64
    f32 collisionMargin;        ///< Havok convex radius (v1 only, ≈ 0.019685)
    PhysicsShapeType shapeType; ///< Shape type (box/sphere/capsule/cylinder/hull/mesh)
    // Note: 3 bytes alignment padding follow shapeType in the binary layout
    Vector3f oldSizes;        ///< Legacy sizes (v1 only, zero for shapeType 4–5)
    Reference reserved0;      ///< Reserved reference
    Vector3f shapeDimensions; ///< Shape dimensions (v2+, zero for complex shapes)

    // --- Convex Hull section (binary offsets 80–183, shapeType = 4 only) ---
    std::vector<Vector3f> hullFaceNormals;         ///< Per-face unit normals (VEC3)
    std::vector<Vector4f> hullVertexPositions;     ///< Vertex positions, w=0 (VEC4)
    std::vector<ConvexHullHalfEdge> hullHalfEdges; ///< Half-edge table (DMSE)
    std::vector<u8> hullVertexFaceIndices;         ///< One face index per vertex (U8__)
    Vector3f hullCenter;                           ///< Hull centroid
    u32 hullFaceNormalCount;                       ///< Number of face normals
    u32 hullVertexCount;                           ///< Number of vertices
    u32 hullHalfEdgeCount;                         ///< Number of half-edges
    f32 hullUnknown0;                              ///< Unknown hull parameter 0
    f32 hullUnknown1;                              ///< Unknown hull parameter 1

    // --- Mesh section (binary offsets 184–299, shapeType = 5 only) ---
    std::vector<PhysicsMeshBvhNode> meshBvhNodes;      ///< BVH tree nodes (DMMN)
    std::vector<Vector4f> meshVertexPositions;         ///< Vertex positions, w=0 (VEC4)
    std::vector<std::array<u16, 7>> meshFaceIndices16; ///< 16-bit face data (MT16, or empty)
    std::vector<std::array<u32, 7>> meshFaceIndices32; ///< 32-bit face data (MT32, or empty)
    // MT32/MT16 per-entry layout: {v0, v1, v2, adj0, adj1, adj2, flags}
    Vector3f meshBoundsCenter; ///< AABB center in model space (quantization grid origin)
    Vector3f
        meshBoundsExtent;   ///< AABB half-extents (quantization range: tolerance = extent / 32767)
    Vector3f meshTolerance; ///< Per-axis quantization step (= extent / 32767)
    u32 meshNormalCount;    ///< Number of mesh normals
    u32 meshVertexCount;    ///< Number of mesh vertices
    u32 meshFaceIndex16Count; ///< MT16 face count (0 when MT32)
    u32 meshFaceIndex32Count; ///< MT32 face count (0 when MT16)
    u32 meshUnknown1;         ///< Unknown mesh parameter
    u32 meshReserved;         ///< Reserved (always 0)
    u32 meshTreeDepth;        ///< BVH tree height (root-to-leaf path length, 1–12)
    f32 meshCollisionMargin;  ///< Collision margin (MT16: small float; MT32: 0.0)

    /// Deprecated shape data from older versions
    struct {
        struct {
            std::vector<PhysicsMeshBvhNode> meshBvhNodes; ///< v2 only: BVH tree nodes
            std::vector<Vector3f> meshVertexPositions;    ///< v2 only: vertex positions
            std::vector<PhysicsMeshTriangle> unknown;     ///< v2 only: triangles
            std::vector<PhysicsMeshEdge> unknown2;        ///< v2 only: edges
        } v2;
        struct {
            std::vector<Vector3f> legacyVertices; ///< v1 only: convex hull / mesh vertices
            std::vector<u8> unknown0;             ///< v1 only: convex hull unknown data
            std::vector<u16> faceIndices;         ///< v1 only: mesh face indices
            std::vector<Vector4f> planeEquations; ///< v1 only: convex hull plane equations
            Vector3f halfExtents;                 ///< v1 only: shape bounding half-extents
        } v1;
    } deprecated;
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief PHRB — Rigid body (v2–v4, 56–104 bytes)
 *
 * Havok rigid body with density, friction, restitution, damping,
 * gravity scale, and collision shape references.
 */
struct RigidBody {
    u16 simulationType;                        ///< Simulation mode (v3+)
    u16 parentBoneIndex;                       ///< Parent bone index
    u32 physicsType;                           ///< Engine-specific body type (v3+)
    f32 density;                               ///< Body density
    f32 friction;                              ///< Surface friction
    f32 restitution;                           ///< Elasticity / bounciness
    f32 linearDamping;                         ///< Linear velocity damping
    f32 angularDamping;                        ///< Angular velocity damping
    f32 gravityScale;                          ///< Gravity influence scale
    AnimRef<u32> dynamicState;                 ///< Animated dynamic state (v4+)
    f32 dynamicBlendOut;                       ///< Dynamic blend-out duration (v4+)
    std::vector<PhysicsShape> rigidBodyShape;  ///< Collision shapes (PHSH)
    RigidBodyFlag flags = RigidBodyFlag::None; ///< Rigid body flags
    u16 localForces;                           ///< Local force channel bitmask
    u16 worldForces;                           ///< World force channel bitmask
    u32 priority;                              ///< Simulation priority

    /// Deprecated rigid body data from v2
    struct {
        std::array<std::array<f32, 3>, 3> inertiaTensor =
            {};        ///< v2 only: 3×3 inertia tensor (36 bytes)
        u16 boneIndex; ///< v2 only: bone index (typically same as parentBoneIndex)
        std::array<u32, 4> reserved = {}; ///< v2 only: reserved (always 0)
    } deprecated;
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief PHYJ — Physics joint (v0, 180 bytes)
 *
 * Connects two rigid bodies with limit, friction, and break-threshold parameters.
 */
struct PhysicsJoint {
    u32 jointType;         ///< Joint type
    u32 boneIndex1;        ///< First bone index
    u32 boneIndex2;        ///< Second bone index
    Matrix44f matrixBody1; ///< Transform for body 1
    Matrix44f matrixBody2; ///< Transform for body 2
    u32 enableLimits;      ///< Enable angular limits
    f32 limitMin;          ///< Minimum limit angle
    f32 limitMax;          ///< Maximum limit angle
    f32 coneAngle;         ///< Cone constraint angle
    u32 enableFriction;    ///< Enable joint friction
    f32 friction;          ///< Friction coefficient
    f32 dampingRatio;      ///< Damping ratio
    f32 angularFrequency;  ///< Angular frequency
    f32 breakThreshold;    ///< Force threshold to break joint
    u8 enableShape;        ///< Enable shape constraint
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief PHCT — Physics constraint (v0, 24 bytes)
 *
 * Constrains two rigid bodies with break-force threshold.
 */
struct PhysicsConstraint {
    std::vector<u16> dependents; ///< Dependent bone indices (U16_)
    u16 rigidBody1;              ///< First rigid body index
    u16 rigidBody2;              ///< Second rigid body index
    Flag flags;                  ///< Constraint flags
    f32 breakForce;              ///< Force required to break constraint
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief PHCC — Cloth collider (v0, 76 bytes)
 *
 * Capsule-shaped collider used by cloth simulation.
 */
struct ClothCollider {
    Matrix44f transform; ///< 4×4 collider transform
    f32 radius;          ///< Capsule radius
    f32 height;          ///< Capsule height
    u32 padding;         ///< Alignment padding
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief PHAC — Cloth proxy (v0, 32 bytes)
 *
 * Maps cloth vertices to proxy geometry for collision.
 */
struct ClothProxy {
    u32 proxyIndex;                 ///< Proxy mesh index
    u32 clothIndex;                 ///< Cloth mesh index
    std::vector<u64> proxyVertices; ///< Proxy vertex data (U64_)
    std::vector<u32> proxyWeights;  ///< Proxy blend weights (U32_)
    M3_DEFINE_VERSION_ACCESSORS()
};

/**
 * @brief PHCL — Cloth physics (v0–v4, 192 bytes)
 *
 * Full cloth simulation configuration: skin bone binding, stiffness
 * parameters, damping, wind/explosion/gravity scales, colliders, and proxies.
 * Added in MODL v28.
 */
struct ClothPhysics {
    u32 clothMeshCount;                   ///< Number of cloth mesh sections
    u32 skinBoneCount;                    ///< Number of skin bones
    std::vector<u16> skinBones;           ///< Skin bone indices (U16_)
    std::vector<u8> simEnabled;           ///< Per-vertex simulation enable flags (U8__)
    std::vector<u32> vertexBones;         ///< Per-vertex bone indices (U32_)
    std::vector<u32> vertexWeights;       ///< Per-vertex bone weights (U32_)
    std::vector<ClothCollider> colliders; ///< Cloth colliders (PHCC)
    std::vector<ClothProxy> proxies;      ///< Cloth proxies (PHAC)
    f32 density;                          ///< Cloth density
    f32 tracking;                         ///< Tracking factor
    f32 stretchStiffness;                 ///< Stretch stiffness
    f32 horizontalStiffness;              ///< Horizontal stiffness
    f32 bendingStiffness;                 ///< Bending stiffness
    f32 damping;                          ///< Damping coefficient
    f32 friction;                         ///< Friction coefficient
    f32 gravity;                          ///< Gravity influence
    f32 explosionScale;                   ///< Explosion force scale
    f32 windScale;                        ///< Wind force scale
    f32 shearStiffness;                   ///< Shear stiffness
    f32 dragFactor;                       ///< Drag factor
    f32 liftFactor;                       ///< Lift factor (v4+)
    f32 sphereStiffness;                  ///< Sphere collider stiffness (v4+)
    u32 flatten;                          ///< Flatten mode (v4+)
    AnimRef<u32> active;                  ///< Animated active state
    u32 useSkinCollision;                 ///< Use skin mesh for collision
    f32 skinOffset;                       ///< Skin collision offset
    f32 skinExponent;                     ///< Skin collision exponent
    f32 skinStiffness;                    ///< Skin collision stiffness
    u32 localChannels;                    ///< Local force channel bitmask
    Vector3f localWind;                   ///< Local wind direction and magnitude
    M3_DEFINE_VERSION_ACCESSORS()
};

} // namespace m3
} // namespace whiteout