31
/ 0x001F
This document describes the v31 BeamNG cached Collada (.cdae
) shape file format produced inside the BeamNG codebase. The data is serialized in
MessagePack
format, with possible
Zstandard
compression.
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.
The v31 format introduces a more structured approach with a separate header and body, along with optional compression:
Rough Pseudo-Code Table of the v31 .cdae File Content
Step | Content | Notes |
---|---|---|
1 | File header (32-bit integer, version). | Check version & 0xFF == 31 to verify it’s v31. |
2 | Header size (32-bit unsigned integer). | Size of the following header data in bytes. |
3 | MsgPack header (dictionary with metadata) | Contains info like compression flag, body size, etc. |
4 | Body data (possibly compressed) | If compressed, use Zstandard to decompress. Contains the shape data. |
The header is a MsgPack dictionary containing metadata about the file, including whether the body is compressed. The body contains the actual shape data, also in MsgPack format.
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, the format begins with:
32-bit integer version
:
version = (smVersion | (mExporterVersion << 16))
where smVersion
is set to 31
for this format and exporter_version
is a separate 16-bit value in the upper bits.
Readers should check (version & 0xFF) == 31
to confirm it’s a v31 shape.
32-bit unsigned integer headerSize
:
This indicates the size of the MsgPack header data that follows.
MsgPack header: A dictionary containing metadata about the file, such as:
compression
: Boolean indicating if the body is compressed with Zstandardbodysize
: Size of the body data (in bytes)Body data:
compression
is true, the body is compressed with Zstandard and needs to be decompressedAfter reading and (if necessary) decompressing the body data, you can parse the MsgPack objects in a specific order:
Shape Information:
mSmallestVisibleSize
(float)mSmallestVisibleDL
(int32)radius
(float)tubeRadius
(float)center
(Point3F - 3 floats)bounds
(Box3F - 6 floats)Multiple Vectors (pack_vector), each stored as:
vector.size * elementSize
.The code reads 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.uint32 meshType
:
0
→ StandardMesh1
→ SkinMesh2
→ DecalMesh (deprecated)3
→ SortedMesh4
→ NullMeshFor non-null meshes:
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
For SkinMesh types (type 1), additional data is read:
parentMesh < 0
)skinMesh.initialVerts
vectorskinMesh.initialNorms
vectorskinMesh.initialTransforms
vectorskinMesh.vertexIndex
vectorskinMesh.boneIndex
vectorskinMesh.weight
vectorskinMesh.nodeIndex
vectorSequences
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.Read the first 4 bytes (32-bit integer) → version
.
version & 0xFF == 31
.exporter_version = version >> 16
.Read the next 4 bytes (32-bit unsigned integer) → headerSize
.
Read headerSize
bytes and parse as MsgPack → header_info
.
header_info['compression']
is true.bodysize
value.Read the remaining bytes → body_data
.
Pass the decompressed body into a MsgPack parser.
Read shape information:
mSmallestVisibleSize
(float)mSmallestVisibleDL
(int32)radius
(float)tubeRadius
(float)center
(3 floats)bounds
(6 floats)Read multiple vectors in the exact order outlined above.
Read names array: count (uint32) followed by that many strings.
Read meshes: total count (uint32), then each mesh’s data.
Read sequences: count (uint32), then each sequence’s data.
Read materials list (if present): count (uint32), then each material’s data.