-
Notifications
You must be signed in to change notification settings - Fork 286
XKT Format
See also:
- XKTLoaderPlugin - loading models from
.xkt
format
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.
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 |
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.
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).
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
.
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
.
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];
Each entity has a string ID, which we can get like so:
let entityId = entity_ids[ entityIdx ];
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.