|
WoWs Model Exported
World of Warships 3D model Exporter
|
How World of Warships assembles a ship mesh for export: param lookup, part files, scene graph (assets.bin), materials, and textures. Raw .geometry byte layout is documented in GEOMETRY.md.
Here is how the game files fit together to construct a ship model:
See draw-call matching for how vertex and index blocs are paired inside a .geometry file.
A ship's geometry is split across multiple .geometry files in the same directory:
The bare base file (same name as the directory) is a low-detail stand-in used at extreme camera distances. The suffixed part files (_Bow, _MidFront, etc.) hold the actual high-detail mesh. All parts share the same ship coordinate space and can be merged directly without any transform.
A corresponding .visual file exists for each .geometry file at the same path. The .visual path is the key used to look up the VisualPrototype record in assets.bin.
Within a single part file, multiple detail levels are expressed as groups of render sets in the VisualPrototype record (see VisualPrototype). Each LOD entry lists which render sets (identified by indices_mapping_id) belong to that detail level:
| LOD level | Description |
|---|---|
| 0 | Highest detail (full geometry + normal maps) |
| 1 | Medium detail |
| 2 | Low detail |
| 3 | Lowest detail / impostor |
A render set's indices_mapping_id matches the mapping_id field in the index bloc mapping table of the .geometry file. This is how LOD filtering maps directly onto primitive selection during export.
Ships split into sections on sinking. Each break point has associated geometry:
| Node name pattern | Role |
|---|---|
Xxx_crack_YYY_DeckHouse | Exterior deckhouse face at break joint (visible at all times) |
Xxx_crack_YYY_Hull | Exterior hull face at break joint (visible at all times) |
Xxx_crack_YYY_in | Inner cross-section face revealed when the ship splits (damage) |
Xxx_crack_YYY | Inner/cross-section face (bare name, no suffix) (damage) |
Xxx_hide | Hidden torn-metal mesh revealed on break (damage) |
Node names are stored in the VisualPrototype and resolved via the strings section of assets.bin. Render sets associated with _hide or _crack_* nodes (except _DeckHouse variants) are considered damage geometry.
Damage materials also identify damage primitives:
*Razlom* — torn-metal texture used at break surfaces*C011_Grid* — grid/alpha overlay used at break pointsassets.bin are world-space, column-major 4×4 float32 matrices.Format: reverse(zlib(pickle(root_dict)))
The bytes of the file are stored in reverse order; reversing and zlib-decompressing yields a Python pickle. The root object is either a dict with an "" key (older builds) or a list whose first element is the param dict (newer builds).
Each ship entry is keyed by its param name (e.g. PJSB007, PASC013). Ship entries have typeinfo.type == "Ship" and contain:
| Field | Description |
|---|---|
ShipUpgradeInfo | Dict of hull/weapon upgrade configs |
<HullCompName> | Hull component dict: model path, visibilityFactor, armor, etc. |
<ArtilleryComp> | Artillery component dict: HP_* mount points with model paths |
<TorpedoComp> | Torpedo component dict: HP_* mount points |
The model field inside a hull component is a path like:
Replace the .model suffix with .geometry to obtain the geometry file path relative to the game root directory.
The armor dict inside a hull component uses integer keys:
model_index — zero-based index into the armor model blocs in the .geometry file.material_id — armor surface material (determines penetration/bounce behaviour).assets.bin is the BigWorld PrototypeDatabase binary format. It stores pre-compiled scene-graph data (visual prototypes) for all game objects.
| Offset | Size | Field | Value / Notes |
|---|---|---|---|
| 0 | 4 | magic | 0x42574442 ("BWDB" LE) |
| 4 | 4 | version | 0x01010000 |
| 8 | 4 | checksum | CRC of the body |
| 12 | 2 | architecture | |
| 14 | 2 | endianness |
Body starts at offset 0x10.
| Base offset | Size | Section |
|---|---|---|
0x10 | 0x28 | Strings section |
0x38 | 0x18 | R2P (record-to-path) map |
0x50 | 0x10 | Path storage |
0x60 | — | Databases array |
All sections use relative pointers (int64_t, signed, from the field's own address).
Open-addressing hashmap mapping uint32_t name IDs to null-terminated UTF-8 strings.
| Offset in section | Field | Description |
|---|---|---|
| 0 | capacity (u32) | Number of buckets |
| 4 | pad | |
| 8 | buckets_relptr | → array of [u32 key, u32 sentinel] pairs |
| 16 | values_relptr | → array of u32 byte offsets into string data |
| 24 | string_data_size | Size of string data blob in bytes |
| 28 | pad | |
| 32 | string_data_relptr | → null-terminated strings |
Lookup: probe (key % capacity + i) % capacity until key matches or bucket is empty (both fields zero).
Maps path self_id (uint64_t) to a packed uint32_t value encoding the blob and record where the prototype data lives.
| Offset in section | Field | Description |
|---|---|---|
| 0 | capacity (u32) | Number of buckets |
| 4 | pad | |
| 8 | buckets_relptr | → array of [u64 key, u64 ] pairs (16 B each) |
| 16 | values_relptr | → array of u32 packed values |
Packed value decoding:
Flat array of path entries (32 bytes each):
| Offset in entry | Field | Description |
|---|---|---|
| 0 | self_id | uint64_t unique ID for this path node |
| 8 | parent_id | uint64_t ID of the parent node (0 = root) |
| 16 | packed string | Node name (BigWorld packed string: u32 char_count, 4 B pad, i64 text_relptr) |
Full paths are reconstructed by walking parent links. A .visual path suffix (e.g. JSB007_Kongo_1942/JSB007_Kongo_1942.visual) is the lookup key.
Array of database entries (0x18 bytes each):
| Offset in entry | Field | Description |
|---|---|---|
| 0 | proto_magic | Prototype type magic (u32) |
| 4 | proto_checksum | (u32) |
| 8 | size | Byte size of the blob (u32) |
| 12 | pad | |
| 16 | data_relptr | i64 relative to entry base → blob |
Each blob starts with a uint64_t record count, followed by fixed-size records. Blob index 1 holds VisualPrototype records (item size = 0x70).
Record format (item size 0x70 = 112 bytes):
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | nodes_count | Number of scene-graph nodes |
| 4 | 4 | pad | |
| 8 | 8 | name_map_name_ids_rp | i64 relptr → u32[] name IDs (length = nodes_count) |
| 16 | 8 | name_map_node_ids_rp | i64 relptr → u16[] node indices |
| 24 | 8 | name_ids_rp | i64 relptr → u32[] name IDs (one per node) |
| 32 | 8 | matrices_rp | i64 relptr → 16×f32 column-major matrices (64 B each) |
| 40 | 8 | parent_ids_rp | i64 relptr → u16[] parent indices (0xFFFF = root) |
| 48 | 8 | merged_geom_path_id | u64 path ID of the merged geometry resource |
| 56 | 1 | underwater_model | u8 |
| 57 | 1 | abovewater_model | u8 |
| 58 | 2 | render_sets_count | u16 |
| 60 | 1 | lods_count | u8 |
| 61 | 3 | pad | |
| 64 | 32 | BoundingBox | min + max as 2 × vec3f32 + 2 × 4 B pad |
| 96 | 8 | render_sets_rp | i64 relptr → RenderSet array |
| 104 | 8 | lods_rp | i64 relptr → LOD entry array |
World-space transform for a node = product of local matrices from root to node (column-major, right-to-left multiplication).
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | name_id | u32 name string ID (render set name) |
| 4 | 4 | mat_name_id | u32 material name string ID |
| 8 | 4 | vertices_mapping_id | u32 matches mapping_id in vertex bloc mapping table (GEOMETRY.md) |
| 12 | 4 | indices_mapping_id | u32 matches mapping_id in index bloc mapping table |
| 16 | 8 | mfm_path_id | u64 path ID of the .mfm material file |
| 24 | 1 | skinned | u8 |
| 25 | 1 | nodes_count | u8 number of bone nodes |
| 26 | 6 | pad | |
| 32 | 8 | node_name_ids_rp | i64 relptr → u32[] node name IDs |
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | dist_threshold | f32 camera distance threshold for this LOD level |
| 4 | 2 | unknown | u16 |
| 6 | 2 | rs_count | u16 number of render sets in this LOD |
| 8 | 8 | rs_names_rp | i64 relptr from this entry's base → u32[] name IDs |
Name IDs reference render sets by their name_id field. Resolved to indices_mapping_id for LOD-based primitive filtering.
Textures live in the same directory as the .mfm file they are referenced from. The stem is derived from the MFM filename (minus the .mfm extension and optional MFM-only suffixes: _skinned, _alpha, _ep).
| Suffix | Channel | glTF slot |
|---|---|---|
_a | Albedo / base color | baseColorTexture |
_n | Normal map | normalTexture |
_mg | Metallic/gloss | metallicRoughnessTexture |
File extensions in priority order: .dd0 (highest resolution MIP set), .dd1, .dds. All are standard DDS containers.
UV orientation: BigWorld stores V with origin at the bottom; DDS textures are stored top-to-bottom. Correct display requires flipping V: v_display = 1.0 - v_stored. In glTF this is expressed with KHR_texture_transform: scale=[1,-1], offset=[0,1].