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
- Write a TypeScript parser that produces
{ chunks: Array<{ tag, positions: Float32Array, uvs: Float32Array, atlasIds: Uint16Array, materialIds: Uint16Array, indices: Uint16Array }> }.
- Render each chunk as a
THREE.Mesh with BufferGeometry. Compute normals with computeVertexNormals() for cheap shading; the file does not provide them.
- Phase 1: solid-color material, just to confirm silhouette.
- 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.
- 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/.
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
Per-vertex attrs (8 bytes, four i16)
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:
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.meshParser consumes the file with zero leftover bytes:
0,-1-1,00,00,-1-1,-1-1,00,0Total ~278k verts / ~140k tris. Bbox is ~20 m × 84 m × 26 m, consistent with a high-rise.
Proposed implementation
{ chunks: Array<{ tag, positions: Float32Array, uvs: Float32Array, atlasIds: Uint16Array, materialIds: Uint16Array, indices: Uint16Array }> }.THREE.MeshwithBufferGeometry. Compute normals withcomputeVertexNormals()for cheap shading; the file does not provide them.ShaderMaterial. The atlas source is out of scope for this issue.src/index/terrain-viewer/).Open questions
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.Data/.