-
Notifications
You must be signed in to change notification settings - Fork 293
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 format, with the intention of helping developers to write their own
tools for converting models to .xkt
.
The node.js-based xeokit-gltf-to-xkt tool is provided as a means to convert
your glTF
files to .xkt
format, and also as a reference implementation of an .xkt
generator. Use the source code
for that tool in combination with this document.
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.
Element | Type | Description | zlib-Compressed |
---|---|---|---|
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 |
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.
positions
, normals
and indices
are the concatenation of the geometries for all the
meshes in the model.
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.
Both positions
and normals
are in World space.
For a simple reference example of geometry quantization and oct-encoding using JavaScript and WebGL, see the mesh-quantization-example demo by @tsherif.
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 integer values.
The indices
array indexes positions
and normals
, to define the primitives within the model. Currently, these are 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
.
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 ];
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.