bMesh - themeldingwars/Documentation GitHub Wiki

bMesh files, which I assume stands for Binary Mesh are used for the majority of the meshes in Firefall, with R5A files holding some UI meshes and the terrain geo being in another.

The .oMesh extension refers to the same format. It is what shows up in pfx <Model> references and in the bMesh refMeshFileName field.

010 Editor Template

Implementation

Tools

Versions

Version 32 is what the later builds ship and is the version documented below. Anything older is out of date and the engine will attempt to update it on load.

Older versions are not documented. An earlier, partly guessed description of a pre-32 layout used to live on this page. It did not match version 32 and has been removed because it was misleading, but the version 32 layout below is still the best starting point for an older file since the order of the sections has been fairly stable.

Format

The whole file is little endian. Every array is preceded by an int32 count, and the sections always appear in the order below. There are no offsets or chunk markers anywhere in the file, so the only way to reach a later section is to walk every preceding one.

Header

Field Type Notes
magic char[5] bMesh
version uint16 32
meshUsage uint8 Not used any more, in practice 0
boundsMin float[3] Local space bounding box
boundsMax float[3] Local space bounding box
tangentType uint8 0 = Melody, 1 = Turtle
tangentSplit uint8 0 = off, 1 = on

Sections

Each row is an int32 count followed by that many elements, in this order:

# Section Element size Element layout
1 vertices 12 float x, y, z
2 vertexLodPositions ? Never seen populated, the count is 0 in every file we have
3 normals 4 Packed byte vector, see Packed normals and tangents
4 texcoord0 8 float u, v, the texture UVs
5 texcoord1 8 float u, v, the normal map UVs
6 tangents 4 Packed byte vector, the 4th byte is the bitangent flag
7 vertexColors 4 uint8 r, g, b, a
8 faceIndices 4 uint32, used to look up the vertices referenced by a face
9 faces 12 uint32 v1, v2, v3
10 bones 200 See Bone
11 links 8 See Link
12 boneWeights 2 uint8 influence, uint8 boneIndex
13 hardpoints 388 See Hardpoint

Then the reference mesh block, which is not an array:

Field Type Notes
refMeshFileNameLen int32 0 when there is no reference mesh
refMeshFileName char[n] Only present when the length is non-zero, points at an .oMesh
refMeshFileTime uint64 File time of the reference mesh
meshType uint8 0 = none, 1 = base variant, 2 = conform mesh

And then three more counted arrays:

# Section Element size Element layout
14 baseVariantDiffs 6 See Base variants and conform meshes
15 conformWeights 12 See Base variants and conform meshes
16 materialSections variable See Material section

Bone

Field Type Notes
name char[64] Null terminated, zero padded
parent int32 Index into the bone array
bindMatrix float[4][4]
inverseBindMatrix float[4][4]
hasSkinnedVertices uint8 1 if any vertex is skinned to this bone
padding uint8[3]

Hardpoint

Field Type Notes
name char[64] Null terminated, zero padded
parent int32 Index of the parent bone
bindMatrix float[4][4] Absolute transform for the hard point
inverseBindMatrix float[4][4] Used for skinning
hpToBoneMatrix float[4][4] Hard point space to bone space, bindMatrix * bone.inverseBindMatrix
normalizedBindMatrix float[4][4]
normalizedHpToBoneMatrix float[4][4]

Link

Two uint32 values per entry. The two known readers disagree on what they mean:

  • FauFau reads them as startWeight and weightCount, where the count is 1 to 4. That makes the array a per-vertex range into boneWeights, which matches how the four influence slots of a skinned vertex are normally stored.
  • The 010 template reads them as weightIndex and vertexIndex.

Both readings consume the same bytes, so either one parses the file. Treat the meaning as unconfirmed until it has been checked against a skinned mesh with known weights.

Material section

Field Type Notes
nameLen uint32
name char[n] Material name, not a file path
faceStart uint32 First face covered by this material
faceCount uint32 Number of faces covered by this material
vertexMin uint32 Lowest vertex index used by those faces
vertexMax uint32 Highest vertex index used by those faces

Packed normals and tangents

Normals and tangents are not compressed, they are quantised. Each component is one unsigned byte covering the range -1.0 to 1.0:

float Decode(byte b)  => b * (2.0f / 255.0f) - 1.0f;
byte  Encode(float f) => (byte)(((255 * (Clamp(f, -1, 1) + 1)) / 2) + 0.5f);

The fourth byte differs between the two:

  • For a normal it is unused padding.
  • For a tangent it is a flag. 255 means the bitangent is reversed/mirrored, 0 means it is not.

Base variants and conform meshes

A "base variant" is for instance a head type: there is a base head and several variants of it. baseVariantDiffs encodes the positional difference from this mesh to that base head, as a uint16 face index followed by two half floats holding barycentric weights.

Conform meshes are meshes such as hair or beards. conformWeights holds three pairs of uint16 geometry index plus half float weight, and is what lets a hair or beard mesh be reshaped to fit any head.

refMeshFileName points at the mesh this one is a variant of or conforms to, and meshType says which of the two roles this file plays.

⚠️ **GitHub.com Fallback** ⚠️