Qubicle Binary Tree (QBT) File Specification

Qubicle Binary Tree (qbt) is the successor of the widespread voxel exchange format Qubicle Binary.

  • Open
    You may import and export qbt files without restrictions
  • Small files
    Voxel data is compressed with zlib
  • Node based
    Qbt enables you to store complex data trees
  • Node parameters
    including name, global/local scale and pivot position
  • Future-proof
    In order to ensure correct parsing of upcoming new node types qbt enables skipping of unknown node types during the import process
  • Optional color map
    Color information can be stored as full rgb per voxel or as indexed color references

Data Structure

Header

  • Magic  4 bytes must be 0x32204251 = "QB 2"
  • VersionMajor 1 byte, currently = 1
  • VersionMinor 1 byte, currently = 0
  • GlobalScale X, Y, Z 3 * 4 bytes, float, normally 1, 1, 1, can be used in case voxels are not cubes (e.g. Lego Bricks)

Color Map

  • SectionCaption 8 bytes = "COLORMAP"
  • ColorCount 4 bytes, uint, if this value is 0 then no color map is used
  • Colors ColorCount * 4 bytes, rgba

Data Tree

  • SectionCaption 8 bytes = "DATATREE"
  • RootNode, can currently either be Model, Compound or Matrix

Model Node

  • TypeID 4 bytes, uint = 1
  • DataSize 4 bytes, uint, number of bytes used for this node and all child nodes (excluding TypeID and DataSize of this node)
  • ChildCount 4 bytes, uint, number of child nodes
  • Children ChildCount nodes currently of type Matrix or Compound

Matrix Node

  • TypeID 4 bytes, uint = 0
  • DataSize 4 bytes, uint, number of bytes used for this node (excluding TypeID and DataSize)
  • NameLength  4 bytes
  • Name NameLength bytes, char
  • Position X, Y, Z 3 * 4 bytes, int, position relative to parent node
  • LocalScale X, Y, Z 3 * 4 bytes, uint
  • Pivot X, Y, Z 3 * 4 bytes, float
  • Size X, Y, Z 3 * 4 bytes, uint
  • VoxelDataSize 4 bytes, uint
  • VoxelData VoxelDataSize bytes, zlib compressed voxel data

Compound Node

  • TypeID 4 bytes, uint = 2
  • DataSize 4 bytes, uint, number of bytes used for this node and all child nodes (excluding TypeID and DataSize of this node)
  • NameLength  4 bytes
  • Name NameLength bytes, char
  • Position X, Y, Z 3 * 4 bytes, int, position relative to parent node
  • LocalScale X, Y, Z 3 * 4 bytes, uint
  • Pivot X, Y, Z 3 * 4 bytes, float
  • Size X, Y, Z 3 * 4 bytes, uint
  • CompoundVoxelDataSize 4 bytes, uint
  • CompoundVoxelData VoxelDataSize bytes, zlib compressed voxel data
  • ChildCount 4 bytes, uint, number of child nodes
  • Children ChildCount nodes currently of type Matrix or Compound

Voxel Data

Voxel data is stored in a 3D grid. The data is compressed using zlib and stored in X, Y, Z with Y running fastest and X running slowest. Each voxel uses 4 bytes: RGBM. RGB stores true color information and M the visibility Mask.

If a color map is included then the R byte references to a color of the color map. In this case the G and B bytes may contain additional secondary data references.

The M byte is used to store visibility of the 6 faces of a voxel and whether as voxel is solid or air. If M is bigger than 0 then the voxel is solid. Even when a voxel is solid is may not be needed to be rendered because it is a core voxel that is surrounded by 6 other voxels and thus invisible. If M = 1 then the voxel is a core voxel.

Parsing

The following pseudo code will help you parsing a qbt file.

function LoadQB2(stream) {
  // Load Header
  magic = stream.readInt;
  major = stream.readByte;
  minor = stream.readByte;

  if  (magic != 0x32204251) 
    return false;

  globalScale.x = stream.readFloat;
  globalScale.y = stream.readFloat;
  globalScale.z = stream.readFloat;

  // Load Color Map
  stream.readString(8); // = COLORMAP
  colorCount = stream.readUInt;
  for (i = 0; i < colorCount; i++)
    color[i] = stream.readRGBA;

  // Load Data Tree
  stream.readString(8); // = DATATREE
  LoadNode(stream);
}

function LoadNode(stream) {
  nodeTypeID = stream.readUInt;
  dataSize = stream.readUInt;

  switch nodeTypeID 
    case 0:
      loadMatrix(stream);
    case 1:
      loadModel(stream);
    case 2:
      loadCompound(stream);
    else
      stream.seek(dataSize) // skip node if unknown
}

function loadModel(stream) {
  childCount = stream.loadUInt;
  for (i = 0; i < childCount; i++)
    loadNode(stream);
}

function loadMatrix(stream) {
  nameLength = stream.readInt;
  name = stream.readString(nameLength); 
  position.x = stream.readInt;
  position.y = stream.readInt;
  position.z = stream.readInt;
  localScale.x = stream.readInt;
  localScale.y = stream.readInt;
  localScale.z = stream.readInt;
  pivot.x = stream.readFloat;
  pivot.y = stream.readFloat;
  pivot.z = stream.readFloat;
  size.x = stream.readUInt;
  size.y = stream.readUInt;
  size.z = stream.readUInt;
  decompressStream = new zlibDecompressStream(stream);
  for (x = 0; x < size.x; x++)
    for (z = 0; z < size.z; z++)
      for (y = 0; y < size.y; y++)
        voxelGrid[x,y,z] = decompressStream.ReadBuffer(4);
}

function loadCompound(stream) {
  nameLength = stream.readInt;
  name = stream.readString(nameLength); 
  position.x = stream.readInt;
  position.y = stream.readInt;
  position.z = stream.readInt;
  localScale.x = stream.readInt;
  localScale.y = stream.readInt;
  localScale.z = stream.readInt;
  pivot.x = stream.readFloat;
  pivot.y = stream.readFloat;
  pivot.z = stream.readFloat;
  size.x = stream.readUInt;
  size.y = stream.readUInt;
  size.z = stream.readUInt;

  decompressStream = new zlibDecompressStream(stream);
  for (x = 0; x < size.x; x++)
    for (z = 0; z < size.z; z++)
      for (y = 0; y < size.y; y++)
        voxelGrid[x,y,z] = decompressStream.ReadBuffer(4);

  childCount = stream.loadUInt;
  if (mergeCompounds) { // if you don't need the datatree you can skip child nodes
    for (i = 0; i < childCount; i++)
      skipNode(stream);
  }
  else {
    for (i = 0; i < childCount; i++)
      LoadNode(stream);
  }
}

function skipNode(stream) {
  stream.readInt; // node type, can be ignored
  dataSize = stream.readUInt;
  stream.seek(dataSize);
}
    Attachments
    There are no attachments for this article.