Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3D Model File Format

Binary, little-endian static (single-frame) model format.

.3D files are static (single-frame) models. For the animated multi-frame variant, see 3DC.md.

Versions

Four versions exist: v2.6, v2.7, v4.0, v5.0. See GOG vs Original CD Differences for which directories contain which versions.

  • v2.6 / v2.7 — Different header semantics and face data encoding from v4.0/v5.0.
  • v5.0 — Adds Section4 (SubObject BVH).

Header (64 bytes)

OffsetSizeTypeNameDescription
0x004[u8; 4]versionASCII version string, e.g. v4.0
0x044u32num_verticesVertex count
0x084u32num_facesFace count
0x0C4u32radiusBounding radius in 1/256 engine units
0x104u32num_framesAnimation frame count. 1 for v4.0/v5.0 .3D files; 0 in the v2.7 static files.
0x144u32offset_frame_dataOffset to frame data array
0x184u32total_face_verticesSum of all face vertex counts. Also the entry count for the vertex-normal indirection table.
0x1C4u32offset_section4Offset to Section4 (SubObject BVH). 0 when absent. Engine header copier zeroes this field at load time.
0x204u32section4_countNumber of Section4 entries.
0x244u32unused_24Unused; the engine bulk-copies the 64-byte header but never reads this field. Always 0 in v4.0/v5.0. Non-zero in 5 v2.6 files in /3dart (values 4490, 6434). Parsers should ignore it.
0x284u32offset_normal_indicesOffset to vertex-normal indirection table. Each entry is a u32 file offset into the vertex-normal data section. Count = total_face_vertices.
0x2C4u32offset_vertex_normalsOffset to normal data (f32 × 3 per normal record).
0x304u32offset_vertex_coordsOffset to vertex coordinate data.
0x344u32offset_face_normalsOffset to face normal data.
0x384u32total_face_vertices_dupDuplicate of field at 0x18 in v4.0/v5.0 (engine header copier copies this from file). Different value in v2.6/v2.7 but the Glide engine rejects those versions.
0x3C4u32offset_face_dataOffset to face data. Always 64 (0x40) in v4.0/v5.0. Variable (112–3736) in v2.6/v2.7.

v2.6/v2.7 Header Differences

The header is the same 64 bytes, but many fields have different semantics:

Fieldv4.0/v5.0 meaningv2.6/v2.7 meaning
0x18total_face_vertices (count)Offset to vertex-normal data. Parsers should remap this into the 0x2C slot (vertex-normal offset) and zero this field. The engine also zeroes it during frame construction. Not a vertex count despite sharing the offset with v4.0’s total_face_vertices.
0x1Coffset_section4 (0 in .3DC)Often non-zero but zeroed by the engine header copier regardless of file value. Not Section4 (SubObject BVH does not exist in v2.6/v2.7). Discarded at load time.
0x24Always 0Non-zero in 5 files (RICHA001=4490, GARDA001-004=6434)
0x2Coffset_vertex_normalsUnrelated to vertex normals in v2.x files. Acts as a variant marker: 0 for v2.7 .3D, 1 for .3DC (both v2.6 and v2.7). Not an offset — treat as a flag.
0x38Duplicate of 0x18Does NOT equal 0x18 in v2.7. Copied by the engine header copier. However, the Glide engine rejects v2.6/v2.7 files at load time (version byte < '4'), so this field is only used in practice for v4.0+ where it duplicates 0x18.
0x3CAlways 64Variable (112–3736) — face data does NOT start after header

v2.6/v2.7 Frame Data Differences

The 16-byte frame data record has different field semantics:

Fieldv4.0/v5.0v2.6/v2.7
offset 0x00 (vertex_offset)File offset to vertex dataHeader + frame header size (always 88) — not a useful offset
offset 0x04 (normal_offset)File offset to face normal dataFace data byte count. The total size in bytes of all face records.
offset 0x08 (reserved)Always 0Gap byte count between face data end and vertex data (0 when no gap).
offset 0x0C (frame_type)0, 2, 4, or 8Values like 8209 (0x2011), 16402 (0x4012) — different encoding

v2.6/v2.7 Engine Header Handling

At load time, the software renderer copies the 64-byte header and then zeroes the offset fields 0x18, 0x1C, 0x30, 0x34, 0x38, and 0x3C. It reads the file sequentially using frame_data[0].normal_offset as the face data byte count and frame_data[0].reserved as the gap between face data and vertex data, rebuilding the section layout in memory. The raw offset fields in the file header are not used for data access.

v2.6/v2.7 Face Data Differences

Aspectv4.0/v5.0v2.6/v2.7
Face texture header5 bytes (u8 + u32)3 bytes (u8 + u16)
tex_hi values61–91, 2550, 6, 15, 18, 20, 30, 36, 44, 50, 60, 63
Solid-color facestex_hi = 0xFFtexture_id < 2 indicates solid color
Texture ID decodingBCD-like: (raw >> 8) - 4000000Simple: texture_id = raw >> 7
Vertex indexDirect indexByte offset (divide by 12)

Parsers must remap header fields before use. See External References for the remap logic.

Glide version gate: The Glide loader rejects model versions below v4.0. This gate belongs to RGFX.EXE; it does not describe the software engine’s v2.6/v2.7 asset path. External parsers support both model generations.

Section Layout

Sections appear in this order (offsets from header):

  1. Face Data — at 0x40, num_faces variable-size records
  2. Vertex Coordinates — num_vertices × 12 bytes
  3. Face Normals — num_faces × 12 bytes
  4. Frame Data — num_frames × 16-byte records
  5. Section4 — SubObject BVH entries (when present)
  6. Vertex-Normal Indirection Table — total_face_vertices × u32
  7. Vertex Normals — num_vertices × 12 bytes

Frame Data

For v4.0/v5.0, located at offset_frame_data as an array of num_frames × 16-byte records. Software animations instead use the v2.6/v2.7 animation directory.

For v4.0/v5.0 .3D files this is a single entry pointing to the base geometry. The v2.7 static files store a zero frame count; that value does not make the static geometry empty.

OffsetSizeTypeNameDescription
0x004u32vertex_offsetFile offset to vertex position data
0x044u32normal_offsetFile offset to face normal data
0x084u32reservedAlways 0 in v4.0/v5.0.
0x0C4u32frame_type0 for static .3D models in v4.0. See 3DC.md for animated frame types.

vertex_offset and normal_offset point to the base vertex coordinate and face normal sections (same values as header fields 0x30 and 0x34).

Face Data

Located at offset_face_data (0x40 in v4.0/v5.0; variable in v2.6/v2.7). Array of num_faces variable-size records.

v4.0 / v5.0 Face Record

OffsetSizeTypeNameDescription
0x001u8vertex_countNumber of vertices in this face (3–10)
0x011u8tex_hiPer-face flags/high byte field (often 0xFF for solid-color faces)
0x024u32texture_data_rawPacked texture encoding value
0x064u32unused_04Unused — always 0x00000000. Parsers should skip.
0x0A8×NFaceVertex[N]verticesN = vertex_count

Texture Decoding (v4.0 / v5.0)

Solid color (if texture_data_raw >> 20 == 0x0FFF):

  • color_index = (texture_data_raw >> 8) & 0xFF
  • tex_hi is always 0xFF for solid-color faces.

Textured (otherwise):

  • Texture file ID (BCD-like encoding):

    tmp = (texture_data_raw >> 8) - 4000000
    ones     = (tmp / 250) % 40
    tens     = ((tmp - ones*250) / 1000) % 100
    hundreds = (tmp - ones*250 - tens*1000) / 4000
    texture_id = ones + tens + hundreds
    

    This yields the TEXBSI.### file number.

  • Image sub-ID within that TEXBSI file:

    ones = (texture_data_raw & 0xFF) % 10
    tens = ((texture_data_raw & 0xFF) / 40) * 10
    image_id = ones + tens
    

v2.6 / v2.7 Face Record

OffsetSizeTypeNameDescription
0x001u8vertex_countNumber of vertices
0x011u8u1Separate byte field (not part of texture encoding)
0x022u16texture_dataPacked texture encoding
0x044u32unused_04Unused — always 0x00000000. Parsers should skip.
0x088×NFaceVertex[N]verticesN = vertex_count

Texture decode from texture_data (the u16 at offset 0x02):

  • Solid color if (texture_data >> 7) < 2: color_index = texture_data & 0xFF
  • Textured: texture_id = texture_data >> 7, image_id = texture_data & 0x7F

FaceVertex

OffsetSizeTypeNameDescription
0x004u32vertex_indexIndex into vertex coordinate array. For v2.6/v2.7: byte offset, divide by 12. For v4.0+: direct index.
0x042i16u_deltaU texture coordinate delta
0x062i16v_deltaV texture coordinate delta

UV coordinates are cumulative deltas in 4-bit fixed-point format: each vertex’s absolute U/V = previous vertex’s U/V + this delta. First vertex in the face starts from 0. The accumulated coordinate does not wrap to 16 bits. The renderer converts each signed delta to pixel-space float before accumulation, then applies the texture descriptor scale. A signed 32-bit fixed-point sum represents every possible face exactly.

The raw i16 values are in 1/16th pixel precision. To convert to pixel-space texture coordinates, multiply by 1/16 (or divide by 16). To normalize to 0–1 UV range, divide by 16 × texture_width (U) and 16 × texture_height (V). This fixed-point scale applies identically to first-vertex values and subsequent deltas.

Vertex Coordinates

Located at offset_vertex_coords. Array of num_vertices × 12 bytes.

v2.6/v2.7 animated .3DC files: Read positions from the software animation directory, not from a guessed offset after the faces. Compressed frames add shifted deltas to the directory’s reference frame. See software animation data. Static v2.6/v2.7 .3D files use offset_vertex_coords directly.

Face Normals

Located at offset_face_normals. Array of num_faces × 12 bytes.

Each normal is 3 × i32, scaled by 1/256.0. Per-face normals (one per face, not per vertex).

Vertex Normals

Located at offset_vertex_normals (header offset 0x2C). The indirection table determines the required normal records; its largest referenced index can exceed num_vertices - 1. Each record occupies 12 bytes. Without an indirection table, normals use direct vertex indexing and there are num_vertices records.

Each entry is 3 × f32 (IEEE 754 single-precision). Per-vertex normals (unit vectors). Some entries may be NaN (bit pattern 0xFFC00000 for all three components), indicating no normal was computed for that vertex.

Vertex-Normal Indirection Table

Located at offset_normal_indices (header offset 0x28). Array of total_face_vertices × u32.

Each entry is a file offset pointing into the vertex-normal data section. Maps each face-vertex to its vertex normal, allowing faces that share a vertex to use different normals (for hard edges).

Shading Model Selection

Per-vertex shading is determined by the indirection table and vertex normal validity:

ConditionShadingNormal used
Indirection table present, referenced vertex normal is validSmooth (Gouraud)Vertex normal from indirection lookup
Indirection table absent, vertex_normals[vertex_index] is validSmooth (Gouraud)Vertex normal by direct index
Vertex normal is NaN (0xFFC00000) or unavailableFlatFace normal

For glTF export, vertex normals whose squared length is zero, subnormal or non-finite also fall back to the authored face normal. This prevents invalid normalization in renderers.

.3D files typically have the indirection table (per-face-vertex normal control). .3DC files always omit it (offset_normal_indices = 0) and fall back to direct vertex-index lookup.

Section4: SubObject BVH

Located at offset_section4 (header offset 0x1C). Contains section4_count variable-size entries. Present when offset and count are non-zero.

Each entry is a bounding-volume node followed by an ordered face-reference list. The fixed header is 18 bytes in v4.0 and 30 bytes in v5.0. The three extent floats exist only in v5.0; reading them in v4.0 consumes face references instead.

OffsetSizeTypeNameDescription
0x0012i32[3]centerModel-local center in 1/256 engine units.
0x0C4u32radiusBounding radius in 1/256 engine units.
0x102u16face_countNumber of references following this header.
0x1212f32[3]extentsPresent only in v5.0.

Runtime centers and radii are floating-point values obtained by dividing the stored integers by 256. The v5.0 extent factors remain unchanged. Runtime face indices are the stored indices divided by four, in the same order. The five nodes of ISLAND.ROB model HBBLD04 contain 26 ordered face references.

Each six-byte reference contains a u32 absolute file offset to the face record and a u16 face index multiplied by four. Both identify the same face in the model’s original face table. The next node begins immediately after the last reference; there is no padding between nodes.

The installed ROB archives contain 3,820 embedded models with these lists: 260 v4.0 and 3,560 v5.0 models, with 22,931 nodes and 222,322 references. Every reference’s file offset agrees with its divided-by-four face index. Lists can repeat a face across nodes: 105 models have repeated references. They need not cover every rendered face: nine palm-tree models omit faces. Preserve the lists and their order rather than reconstructing them from all rendered faces.

External References