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 == 31to 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 numFramesint32 numMatFramesint32 parentMeshBox3F mBounds (6 floats)Point3F mCenter (3 floats)float mRadiusThen additional pack_vector calls for:
vertstvertstverts2colorsnormsencodedNormsprimitivesindicestangentsAnd finally:
int32 vertsPerFrameuint32 meshFlagsFor 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 nameIndexuint32 flagsint32 numKeyframesfloat durationint32 priorityint32 firstGroundFrameint32 numGroundFramesint32 baseRotationint32 baseTranslationint32 baseScaleint32 baseObjectStateint32 baseDecalStateint32 firstTriggerint32 numTriggersfloat toolBeginTSIntegerSet rotationMattersTSIntegerSet translationMattersTSIntegerSet scaleMattersTSIntegerSet visMattersTSIntegerSet frameMattersTSIntegerSet matFrameMattersMaterial List (Optional)
uint32 materialCount.string nameuint32 flagsuint32 reflectanceMapuint32 bumpMapuint32 detailMapfloat detailScalefloat reflectionAmountThis 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.
Was this article helpful?
