123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167 |
- import Check from "../Core/Check.js";
- import defaultValue from "../Core/defaultValue.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import defined from "../Core/defined.js";
- import destroyObject from "../Core/destroyObject.js";
- import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
- import RuntimeError from "../Core/RuntimeError.js";
- import hasExtension from "./hasExtension.js";
- import ImplicitAvailabilityBitstream from "./ImplicitAvailabilityBitstream.js";
- import ImplicitMetadataView from "./ImplicitMetadataView.js";
- import ImplicitSubdivisionScheme from "./ImplicitSubdivisionScheme.js";
- import ImplicitSubtreeMetadata from "./ImplicitSubtreeMetadata.js";
- import MetadataTable from "./MetadataTable.js";
- import ResourceCache from "./ResourceCache.js";
- /**
- * An object representing a single subtree in an implicit tileset
- * including availability.
- * <p>
- * Subtrees handle tile metadata, defined in the subtree JSON in either
- * tileMetadata (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension.
- * Subtrees also handle content metadata and metadata about the subtree itself.
- * </p>
- *
- * This object is normally not instantiated directly, use {@link ImplicitSubtree.fromSubtreeJson}.
- *
- * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_metadata#implicit-tile-properties|Implicit Tile Properties in the 3DTILES_metadata specification}
- * @see ImplicitSubtree.fromSubtreeJson
- *
- * @alias ImplicitSubtree
- * @constructor
- *
- * @param {Resource} resource The resource for this subtree. This is used for fetching external buffers as needed.
- * @param {ImplicitTileset} implicitTileset The implicit tileset. This includes information about the size of subtrees
- * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the subtree's root tile.
- *
- * @private
- * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
- */
- function ImplicitSubtree(resource, implicitTileset, implicitCoordinates) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.object("resource", resource);
- Check.typeOf.object("implicitTileset", implicitTileset);
- Check.typeOf.object("implicitCoordinates", implicitCoordinates);
- //>>includeEnd('debug');
- this._resource = resource;
- this._subtreeJson = undefined;
- this._bufferLoader = undefined;
- this._tileAvailability = undefined;
- this._contentAvailabilityBitstreams = [];
- this._childSubtreeAvailability = undefined;
- this._implicitCoordinates = implicitCoordinates;
- this._subtreeLevels = implicitTileset.subtreeLevels;
- this._subdivisionScheme = implicitTileset.subdivisionScheme;
- this._branchingFactor = implicitTileset.branchingFactor;
- // properties for metadata
- this._metadata = undefined;
- this._tileMetadataTable = undefined;
- this._tilePropertyTableJson = undefined;
- this._contentMetadataTables = [];
- this._contentPropertyTableJsons = [];
- // Jump buffers are maps of availability bit index to entity ID
- this._tileJumpBuffer = undefined;
- this._contentJumpBuffers = [];
- this._ready = false;
- }
- Object.defineProperties(ImplicitSubtree.prototype, {
- /**
- * Returns true once all necessary availability buffers
- * are loaded.
- *
- * @type {boolean}
- * @readonly
- * @private
- */
- ready: {
- get: function () {
- return this._ready;
- },
- },
- /**
- * When subtree metadata is present (3D Tiles 1.1), this property stores an {@link ImplicitSubtreeMetadata} instance
- *
- * @type {ImplicitSubtreeMetadata}
- * @readonly
- * @private
- */
- metadata: {
- get: function () {
- return this._metadata;
- },
- },
- /**
- * When tile metadata is present (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension is used,
- * this property stores a {@link MetadataTable} instance for the tiles in the subtree.
- *
- * @type {MetadataTable}
- * @readonly
- * @private
- */
- tileMetadataTable: {
- get: function () {
- return this._tileMetadataTable;
- },
- },
- /**
- * When tile metadata is present (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension is used,
- * this property stores the JSON from the extension. This is used by {@link TileMetadata}
- * to get the extras and extensions for the tiles in the subtree.
- *
- * @type {object}
- * @readonly
- * @private
- */
- tilePropertyTableJson: {
- get: function () {
- return this._tilePropertyTableJson;
- },
- },
- /**
- * When content metadata is present (3D Tiles 1.1), this property stores
- * an array of {@link MetadataTable} instances for the contents in the subtree.
- *
- * @type {Array}
- * @readonly
- * @private
- */
- contentMetadataTables: {
- get: function () {
- return this._contentMetadataTables;
- },
- },
- /**
- * When content metadata is present (3D Tiles 1.1), this property
- * an array of the JSONs from the extension. This is used to get the extras
- * and extensions for the contents in the subtree.
- *
- * @type {Array}
- * @readonly
- * @private
- */
- contentPropertyTableJsons: {
- get: function () {
- return this._contentPropertyTableJsons;
- },
- },
- /**
- * Gets the implicit tile coordinates for the root of the subtree.
- *
- * @type {ImplicitTileCoordinates}
- * @readonly
- * @private
- */
- implicitCoordinates: {
- get: function () {
- return this._implicitCoordinates;
- },
- },
- });
- /**
- * Check if a specific tile is available at an index of the tile availability bitstream
- *
- * @param {number} index The index of the desired tile
- * @returns {boolean} The value of the i-th bit
- * @private
- */
- ImplicitSubtree.prototype.tileIsAvailableAtIndex = function (index) {
- return this._tileAvailability.getBit(index);
- };
- /**
- * Check if a specific tile is available at an implicit tile coordinate
- *
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
- * @returns {boolean} The value of the i-th bit
- * @private
- */
- ImplicitSubtree.prototype.tileIsAvailableAtCoordinates = function (
- implicitCoordinates
- ) {
- const index = this.getTileIndex(implicitCoordinates);
- return this.tileIsAvailableAtIndex(index);
- };
- /**
- * Check if a specific tile's content is available at an index of the content availability bitstream
- *
- * @param {number} index The index of the desired tile
- * @param {number} [contentIndex=0] The index of the desired content when multiple contents are used.
- * @returns {boolean} The value of the i-th bit
- * @private
- */
- ImplicitSubtree.prototype.contentIsAvailableAtIndex = function (
- index,
- contentIndex
- ) {
- contentIndex = defaultValue(contentIndex, 0);
- //>>includeStart('debug', pragmas.debug);
- if (
- contentIndex < 0 ||
- contentIndex >= this._contentAvailabilityBitstreams.length
- ) {
- throw new DeveloperError("contentIndex out of bounds.");
- }
- //>>includeEnd('debug');
- return this._contentAvailabilityBitstreams[contentIndex].getBit(index);
- };
- /**
- * Check if a specific tile's content is available at an implicit tile coordinate
- *
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
- * @param {number} [contentIndex=0] The index of the desired content when the <code>3DTILES_multiple_contents</code> extension is used.
- * @returns {boolean} The value of the i-th bit
- * @private
- */
- ImplicitSubtree.prototype.contentIsAvailableAtCoordinates = function (
- implicitCoordinates,
- contentIndex
- ) {
- const index = this.getTileIndex(implicitCoordinates);
- return this.contentIsAvailableAtIndex(index, contentIndex);
- };
- /**
- * Check if a child subtree is available at an index of the child subtree availability bitstream
- *
- * @param {number} index The index of the desired child subtree
- * @returns {boolean} The value of the i-th bit
- * @private
- */
- ImplicitSubtree.prototype.childSubtreeIsAvailableAtIndex = function (index) {
- return this._childSubtreeAvailability.getBit(index);
- };
- /**
- * Check if a specific child subtree is available at an implicit tile coordinate
- *
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a child subtree
- * @returns {boolean} The value of the i-th bit
- * @private
- */
- ImplicitSubtree.prototype.childSubtreeIsAvailableAtCoordinates = function (
- implicitCoordinates
- ) {
- const index = this.getChildSubtreeIndex(implicitCoordinates);
- return this.childSubtreeIsAvailableAtIndex(index);
- };
- /**
- * Get the index of the first node at the given level within this subtree.
- * e.g. for a quadtree:
- * <ul>
- * <li>Level 0 starts at index 0</li>
- * <li>Level 1 starts at index 1</li>
- * <li>Level 2 starts at index 5</li>
- * </ul>
- *
- * @param {number} level The 0-indexed level number relative to the root of the subtree
- * @returns {number} The first index at the desired level
- * @private
- */
- ImplicitSubtree.prototype.getLevelOffset = function (level) {
- const branchingFactor = this._branchingFactor;
- return (Math.pow(branchingFactor, level) - 1) / (branchingFactor - 1);
- };
- /**
- * Get the morton index of a tile's parent. This is equivalent to
- * chopping off the last 2 (quadtree) or 3 (octree) bits of the morton
- * index.
- *
- * @param {number} childIndex The morton index of the child tile relative to its parent
- * @returns {number} The index of the child's parent node
- * @private
- */
- ImplicitSubtree.prototype.getParentMortonIndex = function (mortonIndex) {
- let bitsPerLevel = 2;
- if (this._subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
- bitsPerLevel = 3;
- }
- return mortonIndex >> bitsPerLevel;
- };
- /**
- * Parse all relevant information out of the subtree. This fetches any
- * external buffers that are used by the implicit tileset.
- *
- * @param {Resource} resource The resource for this subtree. This is used for fetching external buffers as needed.
- * @param {object} [json] The JSON object for this subtree. If parsing from a binary subtree file, this will be undefined.
- * @param {Uint8Array} [subtreeView] The contents of the subtree binary
- * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to.
- * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the subtree's root tile.
- * @return {Promise<ImplicitSubtree>} The created subtree
- * @private
- *
- * @exception {DeveloperError} One of json and subtreeView must be defined.
- */
- ImplicitSubtree.fromSubtreeJson = async function (
- resource,
- json,
- subtreeView,
- implicitTileset,
- implicitCoordinates
- ) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.object("resource", resource);
- if (defined(json) === defined(subtreeView)) {
- throw new DeveloperError("One of json and subtreeView must be defined.");
- }
- Check.typeOf.object("implicitTileset", implicitTileset);
- Check.typeOf.object("implicitCoordinates", implicitCoordinates);
- //>>includeEnd('debug');
- const subtree = new ImplicitSubtree(
- resource,
- implicitTileset,
- implicitCoordinates
- );
- let chunks;
- if (defined(json)) {
- chunks = {
- json: json,
- binary: undefined,
- };
- } else {
- chunks = parseSubtreeChunks(subtreeView);
- }
- const subtreeJson = chunks.json;
- subtree._subtreeJson = subtreeJson;
- let tilePropertyTableJson;
- if (hasExtension(subtreeJson, "3DTILES_metadata")) {
- tilePropertyTableJson = subtreeJson.extensions["3DTILES_metadata"];
- } else if (defined(subtreeJson.tileMetadata)) {
- const propertyTableIndex = subtreeJson.tileMetadata;
- tilePropertyTableJson = subtreeJson.propertyTables[propertyTableIndex];
- }
- const contentPropertyTableJsons = [];
- if (defined(subtreeJson.contentMetadata)) {
- const length = subtreeJson.contentMetadata.length;
- for (let i = 0; i < length; i++) {
- const propertyTableIndex = subtreeJson.contentMetadata[i];
- contentPropertyTableJsons.push(
- subtreeJson.propertyTables[propertyTableIndex]
- );
- }
- }
- let metadata;
- const schema = implicitTileset.metadataSchema;
- const subtreeMetadata = subtreeJson.subtreeMetadata;
- if (defined(subtreeMetadata)) {
- const metadataClass = subtreeMetadata.class;
- const subtreeMetadataClass = schema.classes[metadataClass];
- metadata = new ImplicitSubtreeMetadata({
- subtreeMetadata: subtreeMetadata,
- class: subtreeMetadataClass,
- });
- }
- subtree._metadata = metadata;
- subtree._tilePropertyTableJson = tilePropertyTableJson;
- subtree._contentPropertyTableJsons = contentPropertyTableJsons;
- // if no contentAvailability is specified, no tile in the subtree has
- // content
- const defaultContentAvailability = {
- constant: 0,
- };
- // In 3D Tiles 1.1, content availability is provided in an array in the subtree JSON
- // regardless of whether or not it contains multiple contents. This differs from previous
- // schemas, where content availability is either a single object in the subtree JSON or
- // as an array in the 3DTILES_multiple_contents extension.
- //
- // After identifying how availability is stored, put the results in this new array for consistent processing later
- subtreeJson.contentAvailabilityHeaders = [];
- if (hasExtension(subtreeJson, "3DTILES_multiple_contents")) {
- subtreeJson.contentAvailabilityHeaders =
- subtreeJson.extensions["3DTILES_multiple_contents"].contentAvailability;
- } else if (Array.isArray(subtreeJson.contentAvailability)) {
- subtreeJson.contentAvailabilityHeaders = subtreeJson.contentAvailability;
- } else {
- subtreeJson.contentAvailabilityHeaders.push(
- defaultValue(subtreeJson.contentAvailability, defaultContentAvailability)
- );
- }
- const bufferHeaders = preprocessBuffers(subtreeJson.buffers);
- const bufferViewHeaders = preprocessBufferViews(
- subtreeJson.bufferViews,
- bufferHeaders
- );
- // Buffers and buffer views are inactive until explicitly marked active.
- // This way we can avoid fetching buffers that will not be used.
- markActiveBufferViews(subtreeJson, bufferViewHeaders);
- if (defined(tilePropertyTableJson)) {
- markActiveMetadataBufferViews(tilePropertyTableJson, bufferViewHeaders);
- }
- for (let i = 0; i < contentPropertyTableJsons.length; i++) {
- const contentPropertyTableJson = contentPropertyTableJsons[i];
- markActiveMetadataBufferViews(contentPropertyTableJson, bufferViewHeaders);
- }
- const buffersU8 = await requestActiveBuffers(
- subtree,
- bufferHeaders,
- chunks.binary
- );
- const bufferViewsU8 = parseActiveBufferViews(bufferViewHeaders, buffersU8);
- parseAvailability(subtree, subtreeJson, implicitTileset, bufferViewsU8);
- if (defined(tilePropertyTableJson)) {
- parseTileMetadataTable(subtree, implicitTileset, bufferViewsU8);
- makeTileJumpBuffer(subtree);
- }
- parseContentMetadataTables(subtree, implicitTileset, bufferViewsU8);
- makeContentJumpBuffers(subtree);
- subtree._ready = true;
- return subtree;
- };
- /**
- * A helper object for storing the two parts of the subtree binary
- *
- * @typedef {object} SubtreeChunks
- * @property {object} json The json chunk of the subtree
- * @property {Uint8Array} binary The binary chunk of the subtree. This represents the internal buffer.
- * @private
- */
- /**
- * Given the binary contents of a subtree, split into JSON and binary chunks
- *
- * @param {Uint8Array} subtreeView The subtree binary
- * @returns {SubtreeChunks} An object containing the JSON and binary chunks.
- * @private
- */
- function parseSubtreeChunks(subtreeView) {
- // Parse the header
- const littleEndian = true;
- const subtreeReader = new DataView(
- subtreeView.buffer,
- subtreeView.byteOffset
- );
- // Skip to the chunk lengths
- let byteOffset = 8;
- // Read the bottom 32 bits of the 64-bit byte length. This is ok for now because:
- // 1) not all browsers have native 64-bit operations
- // 2) the data is well under 4GB
- const jsonByteLength = subtreeReader.getUint32(byteOffset, littleEndian);
- byteOffset += 8;
- const binaryByteLength = subtreeReader.getUint32(byteOffset, littleEndian);
- byteOffset += 8;
- const subtreeJson = getJsonFromTypedArray(
- subtreeView,
- byteOffset,
- jsonByteLength
- );
- byteOffset += jsonByteLength;
- const subtreeBinary = subtreeView.subarray(
- byteOffset,
- byteOffset + binaryByteLength
- );
- return {
- json: subtreeJson,
- binary: subtreeBinary,
- };
- }
- /**
- * A buffer header is the JSON header from the subtree JSON chunk plus
- * a couple extra boolean flags for easy reference.
- *
- * Buffers are assumed inactive until explicitly marked active. This is used
- * to avoid fetching unneeded buffers.
- *
- * @typedef {object} BufferHeader
- * @property {boolean} isExternal True if this is an external buffer
- * @property {boolean} isActive Whether this buffer is currently used.
- * @property {string} [uri] The URI of the buffer (external buffers only)
- * @property {number} byteLength The byte length of the buffer, including any padding contained within.
- * @private
- */
- /**
- * Iterate over the list of buffers from the subtree JSON and add the
- * isExternal and isActive fields for easier parsing later. This modifies
- * the objects in place.
- *
- * @param {Object[]} [bufferHeaders=[]] The JSON from subtreeJson.buffers.
- * @returns {BufferHeader[]} The same array of headers with additional fields.
- * @private
- */
- function preprocessBuffers(bufferHeaders) {
- bufferHeaders = defined(bufferHeaders) ? bufferHeaders : [];
- for (let i = 0; i < bufferHeaders.length; i++) {
- const bufferHeader = bufferHeaders[i];
- bufferHeader.isExternal = defined(bufferHeader.uri);
- bufferHeader.isActive = false;
- }
- return bufferHeaders;
- }
- /**
- * A buffer header is the JSON header from the subtree JSON chunk plus
- * the isActive flag and a reference to the header for the underlying buffer
- *
- * @typedef {object} BufferViewHeader
- * @property {BufferHeader} bufferHeader A reference to the header for the underlying buffer
- * @property {boolean} isActive Whether this bufferView is currently used.
- * @property {number} buffer The index of the underlying buffer.
- * @property {number} byteOffset The start byte of the bufferView within the buffer.
- * @property {number} byteLength The length of the bufferView. No padding is included in this length.
- * @private
- */
- /**
- * Iterate the list of buffer views from the subtree JSON and add the
- * isActive flag. Also save a reference to the bufferHeader
- *
- * @param {Object[]} [bufferViewHeaders=[]] The JSON from subtree.bufferViews
- * @param {BufferHeader[]} bufferHeaders The preprocessed buffer headers
- * @returns {BufferViewHeader[]} The same array of bufferView headers with additional fields
- * @private
- */
- function preprocessBufferViews(bufferViewHeaders, bufferHeaders) {
- bufferViewHeaders = defined(bufferViewHeaders) ? bufferViewHeaders : [];
- for (let i = 0; i < bufferViewHeaders.length; i++) {
- const bufferViewHeader = bufferViewHeaders[i];
- const bufferHeader = bufferHeaders[bufferViewHeader.buffer];
- bufferViewHeader.bufferHeader = bufferHeader;
- bufferViewHeader.isActive = false;
- }
- return bufferViewHeaders;
- }
- /**
- * Determine which buffer views need to be loaded into memory. This includes:
- *
- * <ul>
- * <li>The tile availability bitstream (if a bitstream is defined)</li>
- * <li>The content availability bitstream(s) (if a bitstream is defined)</li>
- * <li>The child subtree availability bitstream (if a bitstream is defined)</li>
- * </ul>
- *
- * <p>
- * This function modifies the buffer view headers' isActive flags in place.
- * </p>
- *
- * @param {Object[]} subtreeJson The JSON chunk from the subtree
- * @param {BufferViewHeader[]} bufferViewHeaders The preprocessed buffer view headers
- * @private
- */
- function markActiveBufferViews(subtreeJson, bufferViewHeaders) {
- let header;
- const tileAvailabilityHeader = subtreeJson.tileAvailability;
- // Check for bitstream first, which is part of the current schema.
- // bufferView is the name of the bitstream from an older schema.
- if (defined(tileAvailabilityHeader.bitstream)) {
- header = bufferViewHeaders[tileAvailabilityHeader.bitstream];
- } else if (defined(tileAvailabilityHeader.bufferView)) {
- header = bufferViewHeaders[tileAvailabilityHeader.bufferView];
- }
- if (defined(header)) {
- header.isActive = true;
- header.bufferHeader.isActive = true;
- }
- const contentAvailabilityHeaders = subtreeJson.contentAvailabilityHeaders;
- for (let i = 0; i < contentAvailabilityHeaders.length; i++) {
- header = undefined;
- if (defined(contentAvailabilityHeaders[i].bitstream)) {
- header = bufferViewHeaders[contentAvailabilityHeaders[i].bitstream];
- } else if (defined(contentAvailabilityHeaders[i].bufferView)) {
- header = bufferViewHeaders[contentAvailabilityHeaders[i].bufferView];
- }
- if (defined(header)) {
- header.isActive = true;
- header.bufferHeader.isActive = true;
- }
- }
- header = undefined;
- const childSubtreeAvailabilityHeader = subtreeJson.childSubtreeAvailability;
- if (defined(childSubtreeAvailabilityHeader.bitstream)) {
- header = bufferViewHeaders[childSubtreeAvailabilityHeader.bitstream];
- } else if (defined(childSubtreeAvailabilityHeader.bufferView)) {
- header = bufferViewHeaders[childSubtreeAvailabilityHeader.bufferView];
- }
- if (defined(header)) {
- header.isActive = true;
- header.bufferHeader.isActive = true;
- }
- }
- /**
- * For handling metadata, look over the tile and content metadata buffers
- * <p>
- * This always loads all of the metadata immediately. Future iterations may
- * allow filtering this to avoid downloading unneeded buffers.
- * </p>
- *
- * @param {object} propertyTableJson The property table JSON for either a tile or some content
- * @param {BufferViewHeader[]} bufferViewHeaders The preprocessed buffer view headers
- * @private
- */
- function markActiveMetadataBufferViews(propertyTableJson, bufferViewHeaders) {
- const properties = propertyTableJson.properties;
- let header;
- for (const key in properties) {
- if (properties.hasOwnProperty(key)) {
- const metadataHeader = properties[key];
- // An older spec used bufferView
- const valuesBufferView = defaultValue(
- metadataHeader.values,
- metadataHeader.bufferView
- );
- header = bufferViewHeaders[valuesBufferView];
- header.isActive = true;
- header.bufferHeader.isActive = true;
- // An older spec used stringOffsetBufferView
- const stringOffsetBufferView = defaultValue(
- metadataHeader.stringOffsets,
- metadataHeader.stringOffsetBufferView
- );
- if (defined(stringOffsetBufferView)) {
- header = bufferViewHeaders[stringOffsetBufferView];
- header.isActive = true;
- header.bufferHeader.isActive = true;
- }
- // an older spec used arrayOffsetBufferView
- const arrayOffsetBufferView = defaultValue(
- metadataHeader.arrayOffsets,
- metadataHeader.arrayOffsetBufferView
- );
- if (defined(arrayOffsetBufferView)) {
- header = bufferViewHeaders[arrayOffsetBufferView];
- header.isActive = true;
- header.bufferHeader.isActive = true;
- }
- }
- }
- }
- /**
- * Go through the list of buffers and gather all the active ones into a
- * a dictionary. Since external buffers are allowed, this sometimes involves
- * fetching separate binary files. Consequently, this method returns a promise.
- * <p>
- * The results are put into a dictionary object. The keys are indices of
- * buffers, and the values are Uint8Arrays of the contents. Only buffers
- * marked with the isActive flag are fetched.
- * </p>
- * <p>
- * The internal buffer (the subtree's binary chunk) is also stored in this
- * dictionary if it is marked active.
- * </p>
- * @param {ImplicitSubtree} subtree The subtree
- * @param {BufferHeader[]} bufferHeaders The preprocessed buffer headers
- * @param {Uint8Array} internalBuffer The binary chunk of the subtree file
- * @returns {Promise<object>} A promise resolving to the dictionary of active buffers
- * @private
- */
- function requestActiveBuffers(subtree, bufferHeaders, internalBuffer) {
- const promises = [];
- for (let i = 0; i < bufferHeaders.length; i++) {
- const bufferHeader = bufferHeaders[i];
- if (!bufferHeader.isActive) {
- promises.push(Promise.resolve(undefined));
- } else if (bufferHeader.isExternal) {
- const promise = requestExternalBuffer(subtree, bufferHeader);
- promises.push(promise);
- } else {
- promises.push(Promise.resolve(internalBuffer));
- }
- }
- return Promise.all(promises).then(function (bufferResults) {
- const buffersU8 = {};
- for (let i = 0; i < bufferResults.length; i++) {
- const result = bufferResults[i];
- if (defined(result)) {
- buffersU8[i] = result;
- }
- }
- return buffersU8;
- });
- }
- async function requestExternalBuffer(subtree, bufferHeader) {
- const baseResource = subtree._resource;
- const bufferResource = baseResource.getDerivedResource({
- url: bufferHeader.uri,
- });
- const bufferLoader = ResourceCache.getExternalBufferLoader({
- resource: bufferResource,
- });
- subtree._bufferLoader = bufferLoader;
- try {
- await bufferLoader.load();
- } catch (error) {
- if (bufferLoader.isDestroyed()) {
- return;
- }
- throw error;
- }
- return bufferLoader.typedArray;
- }
- /**
- * Go through the list of buffer views, and if they are marked as active,
- * extract a subarray from one of the active buffers.
- *
- * @param {BufferViewHeader[]} bufferViewHeaders
- * @param {object} buffersU8 A dictionary of buffer index to a Uint8Array of its contents.
- * @returns {object} A dictionary of buffer view index to a Uint8Array of its contents.
- * @private
- */
- function parseActiveBufferViews(bufferViewHeaders, buffersU8) {
- const bufferViewsU8 = {};
- for (let i = 0; i < bufferViewHeaders.length; i++) {
- const bufferViewHeader = bufferViewHeaders[i];
- if (!bufferViewHeader.isActive) {
- continue;
- }
- const start = bufferViewHeader.byteOffset;
- const end = start + bufferViewHeader.byteLength;
- const buffer = buffersU8[bufferViewHeader.buffer];
- const bufferView = buffer.subarray(start, end);
- bufferViewsU8[i] = bufferView;
- }
- return bufferViewsU8;
- }
- /**
- * Parse the three availability bitstreams and store them in the subtree
- *
- * @param {ImplicitSubtree} subtree The subtree to modify
- * @param {object} subtreeJson The subtree JSON
- * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to
- * @param {object} bufferViewsU8 A dictionary of buffer view index to a Uint8Array of its contents.
- * @private
- */
- function parseAvailability(
- subtree,
- subtreeJson,
- implicitTileset,
- bufferViewsU8
- ) {
- const branchingFactor = implicitTileset.branchingFactor;
- const subtreeLevels = implicitTileset.subtreeLevels;
- const tileAvailabilityBits =
- (Math.pow(branchingFactor, subtreeLevels) - 1) / (branchingFactor - 1);
- const childSubtreeBits = Math.pow(branchingFactor, subtreeLevels);
- // availableCount is only needed for the metadata jump buffer, which
- // corresponds to the tile availability bitstream.
- const hasMetadataExtension = hasExtension(subtreeJson, "3DTILES_metadata");
- const hasTileMetadata = defined(subtree._tilePropertyTableJson);
- let computeAvailableCountEnabled = hasMetadataExtension || hasTileMetadata;
- subtree._tileAvailability = parseAvailabilityBitstream(
- subtreeJson.tileAvailability,
- bufferViewsU8,
- tileAvailabilityBits,
- computeAvailableCountEnabled
- );
- const hasContentMetadata = subtree._contentPropertyTableJsons.length > 0;
- computeAvailableCountEnabled =
- computeAvailableCountEnabled || hasContentMetadata;
- for (let i = 0; i < subtreeJson.contentAvailabilityHeaders.length; i++) {
- const bitstream = parseAvailabilityBitstream(
- subtreeJson.contentAvailabilityHeaders[i],
- bufferViewsU8,
- // content availability has the same length as tile availability.
- tileAvailabilityBits,
- computeAvailableCountEnabled
- );
- subtree._contentAvailabilityBitstreams.push(bitstream);
- }
- subtree._childSubtreeAvailability = parseAvailabilityBitstream(
- subtreeJson.childSubtreeAvailability,
- bufferViewsU8,
- childSubtreeBits
- );
- }
- /**
- * Given the JSON describing an availability bitstream, turn it into an
- * in-memory representation using an {@link ImplicitAvailabilityBitstream}
- * object. This handles both constants and bitstreams from a bufferView.
- *
- * @param {object} availabilityJson A JSON object representing the availability
- * @param {object} bufferViewsU8 A dictionary of bufferView index to its Uint8Array contents.
- * @param {number} lengthBits The length of the availability bitstream in bits
- * @param {boolean} [computeAvailableCountEnabled] If true and availabilityJson.availableCount is undefined, the availableCount will be computed.
- * @returns {ImplicitAvailabilityBitstream} The parsed bitstream object
- * @private
- */
- function parseAvailabilityBitstream(
- availabilityJson,
- bufferViewsU8,
- lengthBits,
- computeAvailableCountEnabled
- ) {
- if (defined(availabilityJson.constant)) {
- return new ImplicitAvailabilityBitstream({
- constant: Boolean(availabilityJson.constant),
- lengthBits: lengthBits,
- availableCount: availabilityJson.availableCount,
- });
- }
- let bufferView;
- // Check for bitstream first, which is part of the current schema.
- // bufferView is the name of the bitstream from an older schema.
- if (defined(availabilityJson.bitstream)) {
- bufferView = bufferViewsU8[availabilityJson.bitstream];
- } else if (defined(availabilityJson.bufferView)) {
- bufferView = bufferViewsU8[availabilityJson.bufferView];
- }
- return new ImplicitAvailabilityBitstream({
- bitstream: bufferView,
- lengthBits: lengthBits,
- availableCount: availabilityJson.availableCount,
- computeAvailableCountEnabled: computeAvailableCountEnabled,
- });
- }
- /**
- * Parse the metadata table for the tile metadata, storing a {@link MetadataTable}
- * in the subtree.
- *
- * @param {ImplicitSubtree} subtree The subtree
- * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to.
- * @param {object} bufferViewsU8 A dictionary of bufferView index to its Uint8Array contents.
- * @private
- */
- function parseTileMetadataTable(subtree, implicitTileset, bufferViewsU8) {
- const tilePropertyTableJson = subtree._tilePropertyTableJson;
- const tileCount = subtree._tileAvailability.availableCount;
- const metadataSchema = implicitTileset.metadataSchema;
- const tileMetadataClassName = tilePropertyTableJson.class;
- const tileMetadataClass = metadataSchema.classes[tileMetadataClassName];
- subtree._tileMetadataTable = new MetadataTable({
- class: tileMetadataClass,
- count: tileCount,
- properties: tilePropertyTableJson.properties,
- bufferViews: bufferViewsU8,
- });
- }
- /**
- * Parse the metadata tables for the content metadata, storing an array of
- * {@link MetadataTable}s in the subtree.
- *
- * @param {ImplicitSubtree} subtree The subtree
- * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to.
- * @param {object} bufferViewsU8 A dictionary of bufferView index to its Uint8Array contents.
- * @private
- */
- function parseContentMetadataTables(subtree, implicitTileset, bufferViewsU8) {
- const contentPropertyTableJsons = subtree._contentPropertyTableJsons;
- const contentAvailabilityBitstreams = subtree._contentAvailabilityBitstreams;
- const metadataSchema = implicitTileset.metadataSchema;
- const contentMetadataTables = subtree._contentMetadataTables;
- for (let i = 0; i < contentPropertyTableJsons.length; i++) {
- const contentPropertyTableJson = contentPropertyTableJsons[i];
- const contentAvailabilityBitsteam = contentAvailabilityBitstreams[i];
- const contentCount = contentAvailabilityBitsteam.availableCount;
- const contentMetadataClassName = contentPropertyTableJson.class;
- const contentMetadataClass =
- metadataSchema.classes[contentMetadataClassName];
- const metadataTable = new MetadataTable({
- class: contentMetadataClass,
- count: contentCount,
- properties: contentPropertyTableJson.properties,
- bufferViews: bufferViewsU8,
- });
- contentMetadataTables.push(metadataTable);
- }
- }
- /**
- * Make a jump buffer, i.e. a map of a bit index to the metadata entity ID.
- * <p>
- * For unavailable tiles and content, the jump buffer entries will be uninitialized.
- * Use the tile and content availability to determine whether a jump buffer value is valid.
- * </p>
- *
- * @param {ImplicitAvailabilityBitstream} availability The availability bitstream to create the jump buffer from.
- * @returns {Array} The resulting jump buffer.
- * @private
- */
- function makeJumpBuffer(availability) {
- let entityId = 0;
- const bufferLength = availability.lengthBits;
- const availableCount = availability.availableCount;
- let jumpBuffer;
- if (availableCount < 256) {
- jumpBuffer = new Uint8Array(bufferLength);
- } else if (availableCount < 65536) {
- jumpBuffer = new Uint16Array(bufferLength);
- } else {
- jumpBuffer = new Uint32Array(bufferLength);
- }
- for (let i = 0; i < availability.lengthBits; i++) {
- if (availability.getBit(i)) {
- jumpBuffer[i] = entityId;
- entityId++;
- }
- }
- return jumpBuffer;
- }
- /**
- * Make the jump buffer, i.e. a map of a bit index to the metadata entity ID,
- * for the content metadata. This is stored in the subtree.
- *
- * @param {ImplicitSubtree} subtree The subtree
- * @private
- */
- function makeTileJumpBuffer(subtree) {
- const tileJumpBuffer = makeJumpBuffer(subtree._tileAvailability);
- subtree._tileJumpBuffer = tileJumpBuffer;
- }
- /**
- * Make the jump buffers, i.e. maps of bit indices to the metadata entity IDs,
- * for the content metadata. This is stored in the subtree.
- *
- * @param {ImplicitSubtree} subtree The subtree
- * @private
- */
- function makeContentJumpBuffers(subtree) {
- const contentJumpBuffers = subtree._contentJumpBuffers;
- const contentAvailabilityBitstreams = subtree._contentAvailabilityBitstreams;
- for (let i = 0; i < contentAvailabilityBitstreams.length; i++) {
- const contentAvailability = contentAvailabilityBitstreams[i];
- const contentJumpBuffer = makeJumpBuffer(contentAvailability);
- contentJumpBuffers.push(contentJumpBuffer);
- }
- }
- /**
- * Given the implicit tiling coordinates for a tile, get the index within the
- * subtree's tile availability bitstream.
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
- * @return {number} The tile's index within the subtree.
- * @private
- */
- ImplicitSubtree.prototype.getTileIndex = function (implicitCoordinates) {
- const localLevel =
- implicitCoordinates.level - this._implicitCoordinates.level;
- if (localLevel < 0 || this._subtreeLevels <= localLevel) {
- throw new RuntimeError("level is out of bounds for this subtree");
- }
- const subtreeCoordinates = implicitCoordinates.getSubtreeCoordinates();
- const offsetCoordinates = subtreeCoordinates.getOffsetCoordinates(
- implicitCoordinates
- );
- const index = offsetCoordinates.tileIndex;
- return index;
- };
- /**
- * Given the implicit tiling coordinates for a child subtree, get the index within the
- * subtree's child subtree availability bitstream.
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a child subtree
- * @return {number} The child subtree's index within the subtree's child subtree availability bitstream.
- * @private
- */
- ImplicitSubtree.prototype.getChildSubtreeIndex = function (
- implicitCoordinates
- ) {
- const localLevel =
- implicitCoordinates.level - this._implicitCoordinates.level;
- if (localLevel !== this._implicitCoordinates.subtreeLevels) {
- throw new RuntimeError("level is out of bounds for this subtree");
- }
- // Call getParentSubtreeCoordinates instead of getSubtreeCoordinates because the
- // child subtree is by definition the root of its own subtree, so we need to find
- // the parent subtree.
- const parentSubtreeCoordinates = implicitCoordinates.getParentSubtreeCoordinates();
- const offsetCoordinates = parentSubtreeCoordinates.getOffsetCoordinates(
- implicitCoordinates
- );
- const index = offsetCoordinates.mortonIndex;
- return index;
- };
- /**
- * Get the entity ID for a tile within this subtree.
- * @param {ImplicitSubtree} subtree The subtree
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
- * @return {number} The entity ID for this tile for accessing tile metadata, or <code>undefined</code> if not applicable.
- *
- * @private
- */
- function getTileEntityId(subtree, implicitCoordinates) {
- if (!defined(subtree._tileMetadataTable)) {
- return undefined;
- }
- const tileIndex = subtree.getTileIndex(implicitCoordinates);
- if (subtree._tileAvailability.getBit(tileIndex)) {
- return subtree._tileJumpBuffer[tileIndex];
- }
- return undefined;
- }
- /**
- * Get the entity ID for a content within this subtree.
- * @param {ImplicitSubtree} subtree The subtree
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a content
- * @param {number} contentIndex The content index, for distinguishing between multiple contents.
- * @return {number} The entity ID for this content for accessing content metadata, or <code>undefined</code> if not applicable.
- *
- * @private
- */
- function getContentEntityId(subtree, implicitCoordinates, contentIndex) {
- const metadataTables = subtree._contentMetadataTables;
- if (!defined(metadataTables)) {
- return undefined;
- }
- const metadataTable = metadataTables[contentIndex];
- if (!defined(metadataTable)) {
- return undefined;
- }
- const availability = subtree._contentAvailabilityBitstreams[contentIndex];
- const tileIndex = subtree.getTileIndex(implicitCoordinates);
- if (availability.getBit(tileIndex)) {
- const contentJumpBuffer = subtree._contentJumpBuffers[contentIndex];
- return contentJumpBuffer[tileIndex];
- }
- return undefined;
- }
- /**
- * Create and return a metadata table view for a tile within this subtree.
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
- * @return {ImplicitMetadataView} The metadata view for this tile, or <code>undefined</code> if not applicable.
- *
- * @private
- */
- ImplicitSubtree.prototype.getTileMetadataView = function (implicitCoordinates) {
- const entityId = getTileEntityId(this, implicitCoordinates);
- if (!defined(entityId)) {
- return undefined;
- }
- const metadataTable = this._tileMetadataTable;
- return new ImplicitMetadataView({
- class: metadataTable.class,
- metadataTable: metadataTable,
- entityId: entityId,
- propertyTableJson: this._tilePropertyTableJson,
- });
- };
- /**
- * Create and return a metadata table view for a content within this subtree.
- * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a content
- * @param {number} contentIndex The index of the content used to distinguish between multiple contents
- * @return {ImplicitMetadataView} The metadata view for this content, or <code>undefined</code> if not applicable.
- *
- * @private
- */
- ImplicitSubtree.prototype.getContentMetadataView = function (
- implicitCoordinates,
- contentIndex
- ) {
- const entityId = getContentEntityId(this, implicitCoordinates, contentIndex);
- if (!defined(entityId)) {
- return undefined;
- }
- const metadataTable = this._contentMetadataTables[contentIndex];
- const propertyTableJson = this._contentPropertyTableJsons[contentIndex];
- return new ImplicitMetadataView({
- class: metadataTable.class,
- metadataTable: metadataTable,
- entityId: entityId,
- contentIndex: contentIndex,
- propertyTableJson: propertyTableJson,
- });
- };
- /**
- * @private
- */
- ImplicitSubtree.prototype.isDestroyed = function () {
- return false;
- };
- /**
- * @private
- */
- ImplicitSubtree.prototype.destroy = function () {
- if (defined(this._bufferLoader)) {
- ResourceCache.unload(this._bufferLoader);
- }
- return destroyObject(this);
- };
- export default ImplicitSubtree;
|