30
/ 0x001E
This document describes the v30 BeamNG cached Collada (.cdae
) shape file format produced inside the BeamNG codebase. The data is serialized in
MessagePack
format.
Use this information to build or debug your own tools for reading the file.
The .cdae
file stores shape data in a binary format for several reasons:
This consistent and compact method ultimately boosts performance when loading shapes, especially at scale.
Rough Pseudo-Code Table of the .cdae File Content
Step | Content | Notes |
---|---|---|
1 | File header (32-bit integer, version). | Check version & 0xFFFF == 30 to verify it’s v30. |
2 | MsgPack stream begins: | Consists of the following sequence… |
2.1 | String (“Welcome :D …”) | Purely informational. |
2.2 | numObjects (uint32) |
For-loop over objects. |
2.3 | Object names (strings) | One per object. |
2.4 | mSmallestVisibleSize (float) |
Minimum size for object to be visible. |
2.5 | mSmallestVisibleDL (int32) |
Smallest detail level. |
2.6 | radius , tubeRadius (floats), |
Bounding info: center (3 floats), bounds (6 floats). |
center , bounds |
||
2.7 | Multiple pack_vector calls |
nodes , objects , transforms, etc. |
2.8 | Names array | Number of shape names (uint32), then each name as string. |
2.9 | Meshes | Total mesh count (uint32), then each mesh in order. |
2.10 | Sequences | numSequences (uint32), and each sequence’s data (flags, keyframes, etc.). |
2.11 | Material List (optional) | Number of materials, then each material’s fields. |
cdae_dump_info.py
For an additional way to inspect or debug .cdae
files, you can use the
cdae_dump_info.py
script. For instance, run the following command
in your console/terminal:
>python cdae_dump_info.py vehicles\pickup\pickup.cdae
At the start of the file, before the MessagePack data, the code writes a 32-bit integer version
:
version = (smVersion | (mExporterVersion << 16))
smVersion
is set to 30
for this shape format.mExporterVersion
is a separate 16-bit value in the upper bits.Thus, if you read the file’s first 4 bytes, you’ll typically see something like 0x0001_001E
if mExporterVersion
is 1
and smVersion
is 30
(since decimal 30 is 0x001E
). Readers should check (version & 0xFFFF) == 30
to confirm it’s a v30 shape.
Immediately after writing that integer, the code proceeds to write MsgPack data.
Once you skip past the first 32-bit version field, the entire remainder of the file is a concatenated MessagePack stream. The code writes a sequence of MsgPack objects in a specific order:
String:
“Welcome :D - please read the docs at
https://go.beamng.com/shapeMessagepackFileformat"
This is purely informational.
Integer: numObjects
(unsigned 32-bit).
For each object (i
in [0 .. numObjects-1]
), a string with the object’s name.
Float: mSmallestVisibleSize
Integer: mSmallestVisibleDL
(32-bit signed).
Bounds and Center:
float radius
float tubeRadius
Point3F center
(3 floats)Box3F bounds
(6 floats)Multiple Vectors (pack_vector), each stored as:
vector.size * elementSize
.The code writes these in order:
nodes
objects
subShapeFirstNode
subShapeFirstObject
subShapeNumNodes
subShapeNumObjects
defaultRotations
defaultTranslations
nodeRotations
nodeTranslations
nodeUniformScales
nodeAlignedScales
nodeArbitraryScaleFactors
nodeArbitraryScaleRots
groundTranslations
groundRotations
objectStates
triggers
details
Strings Array
uint32
for the number of shape names.Meshes
uint32
“total meshes” count.[0..objects.size()-1]
, the shape writes the data for that object’s numMeshes
.Each mesh:
uint32 meshType
0
→ Null mesh1
→ Standard mesh2
→ Skin mesh (deprecated, not actually supported)3
→ Decal mesh (deprecated)If meshType == 0
, skip. Otherwise, the following are written:
int32 numFrames
int32 numMatFrames
int32 parentMesh
Box3F mBounds
(6 floats)Point3F mCenter
(3 floats)float mRadius
Then additional pack_vector
calls for:
verts
tverts
tverts2
colors
norms
encodedNorms
primitives
indices
tangents
And finally:
int32 vertsPerFrame
uint32 meshFlags
Sequences
uint32 numSequences
.int32 nameIndex
uint32 flags
int32 numKeyframes
float duration
int32 priority
int32 firstGroundFrame
int32 numGroundFrames
int32 baseRotation
int32 baseTranslation
int32 baseScale
int32 baseObjectState
int32 baseDecalState
int32 firstTrigger
int32 numTriggers
float toolBegin
TSIntegerSet rotationMatters
TSIntegerSet translationMatters
TSIntegerSet scaleMatters
TSIntegerSet visMatters
TSIntegerSet frameMatters
TSIntegerSet matFrameMatters
Material List (Optional)
uint32 materialCount
.string name
uint32 flags
uint32 reflectanceMap
uint32 bumpMap
uint32 detailMap
float detailScale
float reflectionAmount
This completes the data. Brief definitions:
Point3F
: 3 floats [x, y, z]
.Box3F
: min & max corners => 6 floats total.TSIntegerSet
: specialized bitset for node/element flags.version
.
version & 0xFFFF == 30
.numObjects
(uint32).o
in [0..numObjects)
: read object name (string).mSmallestVisibleSize
), then an int32 (mSmallestVisibleDL
).radius
, tubeRadius
(floats), then center
(3 floats), then bounds
(6 floats).readPackedVector(...)
.uint32
, then read that many strings.uint32 totalMeshes
, then for each object’s set of numMeshes
, read the meshType
and (if not 0) the mesh fields.uint32
, then parse each sequence in order.If you need to parse or create these files, follow the steps above, confirm the 32-bit version field, then read each MsgPack object in the exact sequence. This yields the full shape data: objects, nodes, transformations, meshes, materials, sequences, etc.
Enjoy building your own shape tools or debugging the .cdae
data!