Cached Collada (.cdae)

version 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.

Why Use a Binary Format for Speed and Efficiency

The .cdae file stores shape data in a binary format for several reasons:

  1. Reduced Size: Binary data has less overhead than text-based formats, lowering disk space usage and download times.
  2. Faster Parsing: Tools and the engine can more quickly read integers, floats, and other elements in binary form than by parsing them from text.
  3. Streamlined Workflow: With binary, the data layout (e.g., floats, ints, vectors) maps more closely to memory structures, minimizing conversions.
  4. Consistent Representation: Binary ensures there’s no ambiguity in formatting, making read/write operations more reliable.

This consistent and compact method ultimately boosts performance when loading shapes, especially at scale.


Overview of the .cdae File Content

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.

Using 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

File Header: Version

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.


Overview of the 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:

  1. String:
    “Welcome :D - please read the docs at https://go.beamng.com/shapeMessagepackFileformat" This is purely informational.

  2. Integer: numObjects (unsigned 32-bit).

  3. For each object (i in [0 .. numObjects-1]), a string with the object’s name.

  4. Float: mSmallestVisibleSize

  5. Integer: mSmallestVisibleDL (32-bit signed).

  6. Bounds and Center:

    1. float radius
    2. float tubeRadius
    3. Point3F center (3 floats)
    4. Box3F bounds (6 floats)
  7. Multiple Vectors (pack_vector), each stored as:

    • A 32-bit unsigned integer for the vector length
    • A 32-bit signed integer for the element size in bytes
    • A raw binary block (MsgPack “bin”) of length 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

  8. Strings Array

    • First a uint32 for the number of shape names.
    • Then each name is a MsgPack string.
  9. Meshes

    • A uint32 “total meshes” count.
    • For each object in [0..objects.size()-1], the shape writes the data for that object’s numMeshes.

    Each mesh:

    1. uint32 meshType

      • 0 → Null mesh
      • 1 → Standard mesh
      • 2 → Skin mesh (deprecated, not actually supported)
      • 3 → Decal mesh (deprecated)
    2. 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
  10. Sequences

    • A uint32 numSequences.
    • For each sequence:
      • 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
  11. Material List (Optional)

    • A uint32 materialCount.
    • For each material:
      • 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.

Reading Strategy (Pseudocode)

  1. Read the first 4 bytes (32-bit integer) → version.
    • Check version & 0xFFFF == 30.
  2. Pass the rest of the stream into a MsgPack parser.
  3. Next object is the welcome string (verify if you wish).
  4. Next: read numObjects (uint32).
  5. For o in [0..numObjects): read object name (string).
  6. Next: read a float (mSmallestVisibleSize), then an int32 (mSmallestVisibleDL).
  7. Bounds: read radius, tubeRadius (floats), then center (3 floats), then bounds (6 floats).
  8. Vectors: read them in the exact order, each by readPackedVector(...).
  9. Names: read a uint32, then read that many strings.
  10. Meshes: read uint32 totalMeshes, then for each object’s set of numMeshes, read the meshType and (if not 0) the mesh fields.
  11. Sequences: read uint32, then parse each sequence in order.
  12. MaterialList: if present, read the count, then each material’s fields.

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!

Last modified: February 23, 2025

Any further questions?

Join our discord
Our documentation is currently incomplete and undergoing active development. If you have any questions or feedback, please visit this forum thread.