Skip to content

XKT Format

xeolabs edited this page Jul 17, 2019 · 21 revisions

See also:

Introduction

The .xkt format is xeokit's native binary format, which may be loaded using the XKTLoaderPlugin.

This page describes the .xkt format, with the intention of helping developers to write their own tools for exporting models to .xkt.

The xeokit-gltf-to-xkt tool provides a means to convert our glTF files to .xkt. It's also a reference implementation intended to help developers create their own .xkt exporters.

Contents

XKT V1.0

Overview

The table below lists the elements within V1.0 of the .xkt file format.

For convence we're using a symbolic name, eg. index_size, for each element.

Some elements are deflated using zlib. These are flagged in the fourth column.

Element Type Description zlib-Deflated
version Uint32 The .xkt file format version. This is the first four bytes in the file.
index_size Uint32 Byte size of the index. The index is is the following block, which provides a table of the sizes of certain subsequent elements within the file.
positions_size Uint32 Byte size of positions. This is the start of the index.
normals_size Uint32 Byte size of normals.
indices_size Uint32 Byte size of indices.
mesh_positions_size Uint32 Byte size of mesh_positions.
mesh_normals_size Uint32 Byte size of mesh_normals.
mesh indices size Uint32 Byte size of mesh_indices.
mesh_colors_size Uint32 Byte size of mesh_colors.
entity_ids_size Uint32 Byte size of entity_ids.
entity_meshes_size Uint32 Byte size of entity_meshes.
entity_is_objects_size Uint32 Byte size ofentity_is_objects.
positions_decode_matrix_size Uint32 Byte size of positions_decode_matrix. This is the end of the index.
positions Uint16[] Quantized positions for all meshes. Yes
normals Uint8[] Oct-encoded normals for all meshes. Yes
indices Uint32[] Geometry indices for all meshes. Yes
mesh_positions Uint32[] For each mesh, base index of a portion in positions. Yes
mesh_normals Uint32[] For each mesh, base index of a portion in normals. Yes
mesh_indices Uint32[] For each mesh, base index of a portion in indices Yes
mesh_colors Uint8[] For each mesh, an RGBA color (four elements) Yes
entity_ids String ID for each entity, as a string-encoded JSON array of strings Yes
entity_meshes Uint32[] For each entity, base index of a portion in mesh_positions, mesh_normals, mesh_indices and mesh_colors. Yes
entity_is_objects_size Uint8[] For each entity, a flag indicating whether or not it represents an object Yes
positions_decode_matrix Float32[] De-quantization matrix to decompress positions Yes

zlib Deflation

Note the last column in the table above, which indicates that some of the elements are deflated using zlib. The xeokit-gltf-to-xkt tool and the XKTLoaderPlugin plugin both use pako.js, which is a JavaScript port of zlib, to deflate and inflate.

When loading .xkt, XKTLoaderPlugin inflates those elements before parsing them.

Geometry Arrays

positions, normals and indices are the concatenation of the geometries for all the meshes in the model.

Both positions and normals are in World space.

The positions array is quantized to 16-bit integers, and will be dequantized in xeokit's shaders using positions_decode_matrix. The normals array is oct-encoded to 8-bit integers, and will be also decoded in xeokit's shaders.

For an example of geometry quantization and oct-encoding using JavaScript and WebGL, see the mesh-quantization-example demo by @tsherif. You can also find an example within the source code of xeokit-gltf-to-xkt.

Implicit Mesh Order

There is an implicit order in which meshes appear in the geometry arrays, and mesh_positions, mesh_normals and mesh_indices indicate which portion of the geometry arrays is used for each mesh. These rely on the implicit mesh order.

For example, the first position used by mesh meshIdx would be:

let i = mesh_positions[ meshIdx ];
let x = positions[ i + 0 ];
let y = positions[ i + 1 ];
let z = positions[ i + 2 ];

while the last position used by mesh meshIdx would be:

let i2 = mesh_positions[ meshIdx + 1 ] - 1;
let x2 = positions[ i2 + 0 ];
let y2 = positions[ i2 + 1 ];
let z2 = positions[ i2 + 2 ];

Recall that the position is quantized to 16-bit integers. To de-quantize it, we would need to multiply it by positions_decode_matrix.

Indices

The indices array indexes positions and normals, to define the geometry primitives, which are currently assumed to be triangles.

In the snippet below, we'll obtain the quantized World-space 3D positions of the vertices of the first triangle for mesh meshIdx:

let indicesBaseIdx = mesh_indices[ meshIdx ];
let positionsBaseIdx = mesh_positions[ meshIdx ];

let a = indices[ indicesBaseIdx + 0 ];
let b = indices[ indicesBaseIdx + 1 ];
let c = indices[ indicesBaseIdx + 2 ];

let ax = positions[ positionsBaseIdx + (a * 3) + 0];
let ay = positions[ positionsBaseIdx + (a * 3) + 1];
let az = positions[ positionsBaseIdx + (a * 3) + 2];

let bx = positions[ positionsBaseIdx + (b * 3) + 0];
let by = positions[ positionsBaseIdx + (b * 3) + 1];
let bz = positions[ positionsBaseIdx + (b * 3) + 2];

let cx = positions[ positionsBaseIdx + (c * 3) + 0];
let cy = positions[ positionsBaseIdx + (c * 3) + 1];
let cz = positions[ positionsBaseIdx + (c * 3) + 2];

Note how mesh_indices contains a base index for each mesh to indicate its portion of indices, and mesh_positions contains a base index for each mesh to indicate its portion of positions. We use mesh_positions to offset each index to align it with the meshes portion in positions.

Meshes

In xeokit, an entity can have multiple meshes. For example, an entity representing a window could have a mesh representing the frame, another representing the pane, another for the handle, and so on.

The entity_meshes array contains a base index into mesh_positions, mesh_normals, mesh_indices and mesh_colors for each entity.

Let's extend the previous snippet to obtain the quantized World-space 3D positions of the vertices of the first triangle within the first mesh belonging to the entity at entityIdx:

let meshBaseIdx = entity_meshes[ entityIdx ];

let indicesBaseIdx = mesh_indices[ meshBaseIdx ];
let positionsBaseIdx = mesh_positions[ meshBaseIdx ];

let a = indices[ indicesBaseIdx + 0 ];
let b = indices[ indicesBaseIdx + 1 ];
let c = indices[ indicesBaseIdx + 2 ];

let ax = positions[ positionsBaseIdx + (a * 3) + 0];
let ay = positions[ positionsBaseIdx + (a * 3) + 1];
let az = positions[ positionsBaseIdx + (a * 3) + 2];

let bx = positions[ positionsBaseIdx + (b * 3) + 0];
let by = positions[ positionsBaseIdx + (b * 3) + 1];
let bz = positions[ positionsBaseIdx + (b * 3) + 2];

let cx = positions[ positionsBaseIdx + (c * 3) + 0];
let cy = positions[ positionsBaseIdx + (c * 3) + 1];
let cz = positions[ positionsBaseIdx + (c * 3) + 2];

Entity IDs

Each entity has a string ID, which we can get like so:

let entityId = entity_ids[ entityIdx ];

Improvements Needed

Geometry reuse

Currently there is no geometry reuse in .xkt. Each geometry instance is transformed into World-space and concatenated to the geometry arrays, as if it was a separate geometry. This will be addressed in the next version of the .xkt format.

Clone this wiki locally