@kognifai/cogsengine
    Preparing search index...

    OGC 3DTiles Extension

    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.

    Testing

    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>

    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.

    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.

    How it works

    • 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.
      NOTE: The use of binary-subtrees is only relevant for v1.1+ of the 3DTiles spec and only for IMPLICIT trees.
    • OGC3DTilesSystem.cpp|h: The heart of the system. Handles all updates and processing.
    • Trees can be of type OCT or QUAD which specifiles the shape of the tree.
    • Trees can be of type IMPLICIT or EXPLICIT. The former is the most common type.
    • A "tile" (aka. "tree-node") can, according to the spec, contain multiple "models". Usually glTF models.
    • A tile can also refer to another tileset rather than a model. This is refered to as a "sub-tileset". This tileset will be scheduled for download when encountered and only traversed/processed when within the view. This is a special case and only relevant for EXPLICIT trees.
    • Subtree-files are loaded async whenever encountered during the tree-traversal. These files are only relevant for IMPLICIT trees. See the "The Subtree mechanism"-chapter for more info.

    The 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()"
    • Only the 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.
    • The difference in handling OCT-trees vs. QUAD-trees is not described. The system will consider all coordinates with a TreeCoord with Z=-1 as a QUAD-tree coordinate.

    This 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.

    This 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.