Implementation of the OGC 3DTiles spec; https://docs.ogc.org/cs/22-025r4/22-025r4.html
Demo 1050 is an example of 3DTiles usage in C++
To use the 3DTiles extension simply add an OGC3DTilesComponent
to the scene. The component will start to initialize when the baseURL
-field has
been set. Please note that the initialization process is an async process as several JSON files might be downloaded and parsed.
The rendering will start automatically when the dataset has been properly initialized.
Be aware that the system will not apply any global transformation which might have been specified in the dataset. Doing so will be up to the client.
A ComponentNotification
called TilesetReady
will be sent when the dataset is ready. This notification will contain an array of 16 doubles which
will describe the matrix. See the C++ demo for an example on how catch notifications.
It is up to the user to apply whatever offset or other transforms needed by setting the corresponding fields in the component's
TransformComponent
.
The default up-axis for all 3DTiles datasets are, according to the official spec, the Y-axis. The default up-axis for Cogs is usually
the Z-axis. The user will therefore have to set the rotation
field in the component's TransformComponent
accordingly.
Setting the rotation
quaternion to [-0.707107, 0.0, 0.0, 0.707107]
will get the dataset in an Z-up orientation.
There are currently no automated tests for the 3DTiles extension. The only way to test is to run the 1050 demo and inspect the rendering.
A 3DTiles dataset is needed to be able to test the extension. Here is a list of publicly available datasets which can be downloaded and used for testing with a local server.
3DTiles dataset on 3dvstorage
:
There are a couple of datasets on the 3dvstorage
-Azure blob which can be used for testing. Use the "Microsoft Azure Storage Explorer" to access the datasets.
Thulstrups Gate 3
https://kognifai.visualstudio.com/Cogs/_git/Cogs.Data?path=/OGC3DTiles/v1.1/Thulstrups-gate-3--ver1
This is a simple scan of Thulstrups gate 3 using a phone and the Reality Capture app. The quality is not great but the dataset is useful for testing
basic 3DTiles functionalities. It is located in the Cogs.Data
repository.
The official Cesium/OGC sample dataset
https://github.com/CesiumGS/3d-tiles-samples
This collection of datasets is not optimal as they are mostly not relevant for our use-case. The are also quite small and mostly based on v1.0 of the spec.
The Huldra rig dataset
FIXME:
We need to build this dataset and make it available for download
An in-house dataset used by many in KDI as a test-case for 3D-rendering.
The easiest way to setup a local fileserver is using Python or Node.js.
The http.server
module is a simple way to setup a local file-server. See the official Python docs for more info (https://docs.python.org/3/library/http.server.html)
How to start the server (stand in the directory of the dataset):
$ python3 -m http.server 4321
The live-server
is also an easy way to serve local files and it supports CORS. Be aware that it is somewhat slower that
the python server at startup as it seems to index all the files in the <PATH>
directory.
See the official NPM page for more info (https://www.npmjs.com/package/live-server)
How to start the server:
$ live-server --port=4321 --cors --no-browser <PATH>
Cogs.Core.Native
The OGC3DTiles extension supports streaming data from a 3DTiles server like CesiumION but Cogs.Core.Native does not support GZIP'ed responses. This means that the native demo will only work with a local file-server.
Cogs.js
Testing in Cogs.js is more or less the same as in native mode. The demo/example can be found in the "Examples -> Extensions" menu.
Cogs.js can handle GZIP'ed responses so data can be streamed from a 3DTiles server like CesiumION or a local server. A web-server
which supports CORS (like node.js
mentioned above) is usually needed when testing Cogs.js with a local tile-server.
CesiumION is a cloud-based service for hosting 3DTiles datasets. The service is free to use for datasets up to 5 GB.
An access-token is needed to be able to fetch data from the service. To be granted an access-token one has to register
at their site. This token is set in the OGC3DTilesComponent
's accessToken
field at startup. The 1050-demo will automatically pick
up the CESIUM_ION_ACCESS_TOKEN
environment variable if available.
OGC3DTilesComponent.cpp|h
: The main component which will be used to setup a 3DTiles render scene.OGC3DTilesAssetsParser.cpp|h
: Used to fetch and parse assets and access info when fetching data from a 3DTiles server like CesiumION.
This step is not needed when fetching data from a basic file-server.OGC3DTilesTileset.cpp|h
: Code for fetching and parsing the tileset specification which describes the actual dataset.OGC3DTilesSubtree.cpp|h
: Code for fetching, parsing and processing subtree binary-files. These files are used to describe contents of branches
in the dataset in a highly compressed bitmap-format.IMPLICIT
trees.OGC3DTilesSystem.cpp|h
: The heart of the system. Handles all updates and processing.OCT
or QUAD
which specifiles the shape of the tree.IMPLICIT
or EXPLICIT
. The former is the most common type.glTF
models.EXPLICIT
trees.IMPLICIT
trees.
See the "The Subtree mechanism"-chapter for more info.OGC3DTilesSystem::update()
methodThe update()
method is called by the system on demand. This means that Cogs.Core will not call this every frame unless needed.
The primary task is to initialize the dataset on the first call for an OGC3DTilesComponent
(or if the baseURL
of the component has changed).
Beware that this init-task triggers an async requests to fetch misc JSON files. Rendering will start when ready.
The OGC3DTilesSystem
will keep a state-struct for each OGC3DTilesComponent
in the scene.
This struct will contain info regarding tiles, sub-tilesets, subtrees, what is loaded or in-flight etc.
We are assuming the dataset is initialized and ready.
Traverse tree:
IF is implicit tree:
Build implicit tree based on camera view
ELSE
Build explicit tree based on camera view
COLLECT all tiles from built tree in a list
FOR each tile in collected list:
FOR each model in tile:
IF model is not loaded AND NOT already requested:
IF max number of requests per frame is reached:
Register request as pending
ELSE:
Schedule request for model
ELSE
IF model is loaded:
Update timestamp to prevent pruning.
ELSE:
## Do nothing as model is currently "in flight"
Cancel stale model requests ## A model was requested earlier, hasn't arrived but is no longer needed.
Calculate tile visibilities in tree: ## We have two strategies depending on the 'waitForTileSiblings' flag
IF 'waitForTileSibings':
IF tile has all its children loaded and ready:
Hide the tile and show children instead.
ELSE:
Hide children until all siblings are ready.
ELSE:
IF tile has no children:
Show tile
ELSE:
IF tile has all its children loaded and ready:
Hide tile
Recursively process all tiles to calculate visibility.
Prune tile cache:
FOR each loaded tile:
FOR each model in tile:
IF models's timestamp is too old:
Remove model
Process model requests:
FOR each model request:
IF model's modelHandle->isLoaded():
Add model to scene and register in state as loaded.
Update model's timestamp to prevent pruning.
ELSE IF request failed:
Handle failure and log errors.
Process subtree requests:
FOR each subtree request:
Start the async fetch of the subtree file. Handle incoming data in callback.
Process SubTileset requests: ## NOTE: This is only relevant for EXPLICIT trees
FOR each SubTileset request:
Start the async fetch of the SubTileset file. Handle incoming data in callback.
FOR each SubTileset found in view:
traverseTileset(SubTileset) ## Recursively process all subtilesets in view.
IF has pending requests due to reaching the max request-limit:
Schedule another "update()"
REFINE
-strategy is described, not the ADD
strategy. The ADD-strategy is basically just adding more models as they
are encountered when digging deeper into the tree.OGC3DTilesSystem::buildImplicitTree()
methodThis is a recursive method which will traverse the tree using the subtree binary data. All tiles will be checked against the current
view frustum to determine visibility. The depth of the traversal is controlled by the detailFactor
property of the OGC3DTilesComponent
which
is checked against a combination of the tile's tree-depth and distance from the camera.
Only v1.1+ datasets has support for implicit trees. Most datasets built by Cesium is of type IMPLICIT
.
It is the subtree-bitmap which determines if a tile has children or not, and how many.
OGC3DTilesSystem::buildExplicitTree()
methodThis is a recursive method which will traverse the tree to determine which nodes are visible. All tiles are explicitly defined in the tileset with a bounding-volume. Some tiles might refer to other tilesets, which will be fetched and processed whenever within the view.
Explicit trees are not much in use as the tileset-definitions can get quite large, but they are the only type which allows for sub-tilesets. Encountered SubTilesets within the view will be registered and loaded (if not already loaded) and traversed (if already loaded).
The subtrees are used for describing IMPLICIT
trees and are a binary-format which efficiently maps sections of the tree-structure using a minimal
amount of data. The indexing technique used is called Morton code
. All relevant code is in the OGC3DTilesSubtree.cpp|h
files.
See https://docs.ogc.org/cs/22-025r4/22-025r4.html#toc37 for more info on subtrees.
See https://en.wikipedia.org/wiki/Z-order_curve for info on Morton Codes / Z-Order curves.