Expand description
HMD8: a merged model container carrying mesh, bone ranges and every clip’s composed bone palettes in one blob.
Lifted from hl-psx, which developed it to run the whole Half-Life campaign (103 maps, 237 transitions) inside a 223.7 KB model pool covering mesh AND animation. It lives here so both games share one format rather than each carrying a private reader.
What it does that Model + Animation do not:
- one blob per model, with a clip table, so a model’s whole animation set is a single asset rather than one file per clip;
- vertices stored bone-local and grouped into contiguous RANGES, so each range pays one matrix load and feeds the static stream straight to RTPT;
- a 20-byte composed affine per bone per frame (
HMD8_AFFINE_BYTES), the same packed Q11 encoding.psxanimv3 already uses; - optional sections gated by flags (body masks, mouth transforms, studio hitboxes, packed or i8 normals, frame times, vertex SoA), so a consumer that wants none of the GoldSrc specifics simply does not emit them.
Extraction note: the only coupling to its old home was a vertex-count guard
reading a game-local budget constant. That is now
DEFAULT_MAX_VERTICES with Model::from_bytes_with_vertex_cap for
callers that police their own arena.
Structs§
- Bone
Range - A contiguous run of vertices sharing one bone.
- Bone
Transform - A bone’s composed pose, unpacked from the 20-byte record into GTE registers.
- GteVertex
Words - One HMD8 vertex in the exact two-register layout consumed by the GTE.
- Interpolated
Model Frame - Two baked bone palettes prepared for fixed-point affine interpolation.
- Jaw
- The cooked mouth controller: one bone’s local rotation, fully open.
- Model
- A parsed HMD8 blob: mesh, bone ranges, clip table and every clip’s composed
bone palettes, all borrowed from one
&'staticslice with no copying. - Model
Frame - One frame’s bone palette, read lazily: a bone’s affine is unpacked from the blob when asked for, not eagerly into a scratch array.
- Render
Face Payload - Cold UV payload for a projected model face. Vertex indices live in a separate packed-u32 stream so near/backface rejects touch only four uncached bytes. Texture ids live once per ordered face run, not once per face; the streamed actor set keeps those ids out of every face payload.
- Studio
Hitbox - A GoldSrc studio hitbox, present only when the cook set the hitbox flag.
- Tracks
- A model’s HMA1 section (see
Model::tracks). - Triangle
- A decoded triangle. The blob keeps these packed; this is the unpacked view.
- Vertex
Words - A model’s vertex stream as GTE register words, bounds-checked once.
Enums§
- Pose
- One animation sample’s bone transforms (see
Model::pose).
Constants§
- DEFAULT_
MAX_ VERTICES - Vertex-count guard used by
Model::from_bytes. - RENDER_
FACE_ INDEX_ MASK - Three ten-bit indices fit one hot word exactly (with the top two bits
unused), so a model whose faces are packed this way can have at most
RENDER_FACE_INDEX_MASK + 1(1024) vertices.DEFAULT_MAX_VERTICESis that bound, soModel::from_bytesnever admits a model the packing would mangle; a caller that passes a larger cap toModel::from_bytes_with_vertex_capmust not pack its faces.