Skip to content

Render distant-LOD POI .mesh files in the browser (format reverse-engineered) #160

Description

@kui

Background

7DTD ships per-POI distant-LOD geometry under Data/Prefabs/POIs/<name>.mesh (e.g. skyscraper_01.mesh, 6.4 MB). These are the low-poly imposters the game uses to draw far-away POIs without expanding the full voxel .tts. Displaying them in the existing terrain-viewer (three.js) would let users preview a POI's silhouette and footprint without launching the game.

No public loader exists for this format (not OGRE .mesh, not Unity AssetBundle). This issue captures the reverse-engineered layout so a parser can be implemented.

File layout

File
  magic "mesh"          (4B, ASCII)
  version               (4B, observed 06 07 00 04)
  Chunks[]              (variable count, consumes the rest of the file)

Chunk
  tag_len               (1B)            // omitted for the very first chunk;
  tag                   (tag_len B)     // its tag sits inline after the file header
  vertexCount           (u32 LE)
  positions             [vertexCount * 3 * float32]   // XYZ in metres
  vertexCount           (u32 LE, same value, redundant)
  attrs                 [vertexCount * 4 * int16]     // see below
  indexCount            (u32 LE)
  indices               [indexCount * u16]            // triangle list

Per-vertex attrs (8 bytes, four i16)

field observed range unique values (skyscraper_01) meaning
0 [0, 606] 86 texture-atlas tile / block ID
1 [0, 259] 8 material or face index
2 [0, 10009] 62 UV.u, divide by 10000
3 [-54, 10000] 78 UV.v, divide by 10000

No normals are stored — these are imposters, so lighting is skipped and shading comes entirely from the atlas lookup.

Chunk tags = world chunk coords

Tags are ASCII strings like "0,-1", "-1,0", "0,0", "-1,-1" — the relative world-chunk coordinate the sub-mesh covers. A tag may appear twice in the same file; the second occurrence has a smaller vertex count and a Y-range biased toward the upper floors, which matches a separate sub-mesh for transparent material (windows) split out from the opaque body.

Index layout

Triangles come in quad pairs with no vertex sharing across quads:

[0,1,2, 0,2,3,  4,5,6, 4,6,7,  8,9,10, 8,10,11, ...]

Each block face is independent, which explains why vertex count is roughly 2× the triangle count (e.g. 62103 verts vs 31732 tris).

Verified parse of skyscraper_01.mesh

Parser consumes the file with zero leftover bytes:

tag vertices triangles bbox
0,-1 62,103 31,732 x[0, 20] y[5, 89] z[-26, 0]
-1,0 60,657 30,492 x[-20, 0] y[5, 107] z[0, 21]
0,0 61,862 31,474 x[0, 20] y[5, 90] z[0, 21]
0,-1 14,032 7,020 x[16, 20] y[35, 85] z[-16, 0]
-1,-1 58,459 29,367 x[-20, 0] y[5, 90] z[-26, 0]
-1,0 14,243 7,209 x[-16, 0] y[35, 104] z[16, 21]
0,0 6,360 3,176 x[0, 20] y[5, 89] z[16, 20]

Total ~278k verts / ~140k tris. Bbox is ~20 m × 84 m × 26 m, consistent with a high-rise.

Proposed implementation

  1. Write a TypeScript parser that produces { chunks: Array<{ tag, positions: Float32Array, uvs: Float32Array, atlasIds: Uint16Array, materialIds: Uint16Array, indices: Uint16Array }> }.
  2. Render each chunk as a THREE.Mesh with BufferGeometry. Compute normals with computeVertexNormals() for cheap shading; the file does not provide them.
  3. Phase 1: solid-color material, just to confirm silhouette.
  4. Phase 2: locate the texture atlas inside 7DTD's Unity assets and wire the atlasId / UV to a ShaderMaterial. The atlas source is out of scope for this issue.
  5. Wire the loader into the existing terrain-viewer flow (src/index/terrain-viewer/).

Open questions

  • Whether attrs.field1 (range 0–259, only 8 unique values) is a face direction (±X/±Y/±Z = 6 values) or a material slot. Decoding more POIs should narrow this down.
  • How second-occurrence sub-meshes per chunk are actually used at runtime (transparent material group vs LOD switch).
  • Where the matching texture atlas lives in Data/.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions