Skip to content

XKT Format

Lindsay Kay edited this page Mar 3, 2020 · 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. Use the source code for that tool to help understand this specification.


Update March 2020 - XKT now has geometry reuse and fixes for distorted geometry, now part of XKT V3. This documentation should be considered redundant. Documentation for V3 is in progress.


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 deflated positions. This is the start of the index.
normals_size Uint32 Byte size of deflated normals.
indices_size Uint32 Byte size of deflated indices.
edge_indices_size Uint32 Byte size of deflated edge_indices.
mesh_positions_size Uint32 Byte size of deflated mesh_positions.
mesh_normals_size Uint32 Byte size of deflated mesh_normals.
mesh_indices_size Uint32 Byte size of deflated mesh_indices.
mesh_edge_indices_size Uint32 Byte size of deflated mesh_edge_indices.
mesh_colors_size Uint32 Byte size of deflated mesh_colors.
entity_ids_size Uint32 Byte size of deflated entity_ids.
entity_meshes_size Uint32 Byte size of deflated entity_meshes.
entity_is_objects_size Uint32 Byte size of deflated entity_is_objects.
positions_decode_matrix_size Uint32 Byte size of deflated positions_decode_matrix. This is the end of the index.
positions Uint16[] Quantized positions for all meshes. Deflated
normals Uint8[] Oct-encoded normals for all meshes. Deflated
indices Uint32[] Geometry triangle indices for all meshes. Has three elements per triangle. Deflated
edge_indices Uint32[] Geometry edge indices for all meshes. Has two elements per edge. Deflated
mesh_positions Uint32[] For each mesh, base index of a portion in positions. Deflated
mesh_normals Uint32[] For each mesh, base index of a portion in normals. Deflated
mesh_indices Uint32[] For each mesh, base index of a portion in indices Deflated
mesh_edge_indices Uint32[] For each mesh, base index of a portion in edge_indices Deflated
mesh_colors Uint8[] For each mesh, an RGBA color. Has four elements per color, each in range [0..255]. The fourth element, alpha, is opacity. Deflated
entity_ids String ID for each entity, as a string-encoded JSON array of strings Deflated
entity_meshes Uint32[] For each entity, base index of a portion in mesh_positions, mesh_normals, mesh_indices and mesh_colors. Deflated
entity_is_objects_size Uint8[] For each entity, a flag indicating whether or not it represents an object Deflated
positions_decode_matrix Float32[] De-quantization matrix to decompress positions Deflated

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

The positions, normals, indices and edge_indices arrays 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.

The indices array defines triangles, with three elements per triangle.

The edge_indices array defines the edges that xeokit draws for wireframe views, with two elements per edge. An .xkt exporter needs to generate those edge indices from the geometries, using the algorithm demonstrated in buildEdgesindices.js (a file within 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.

The first vertex position used by mesh meshIdx is:

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

The last vertex position used by mesh meshIdx is:

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

Recall that positions are quantized to 16-bit integers. To de-quantize them back to floating point values, xeokit will multiply them by positions_decode_matrix.

Indices

The indices array indexes positions and normals to define the geometry primitives, which are (so far) 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

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

Even without geometry reuse, however, the geometry quantization, oct-encoding and zlib deflation still make the .xkt format the efficient option for loading models into xeokit.