import Cartesian3 from "../Core/Cartesian3.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import getMagic from "../Core/getMagic.js"; import RuntimeError from "../Core/RuntimeError.js"; /** * Represents the contents of a * {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Composite|Composite} * tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset. *

* Implements the {@link Cesium3DTileContent} interface. *

* * @implements Cesium3DTileContent * @private */ class Composite3DTileContent { constructor(tileset, tile, resource, contents) { this._tileset = tileset; this._tile = tile; this._resource = resource; if (!defined(contents)) { contents = []; } this._contents = contents; this._metadata = undefined; this._group = undefined; this._ready = false; } get featurePropertiesDirty() { const contents = this._contents; const length = contents.length; for (let i = 0; i < length; ++i) { if (contents[i].featurePropertiesDirty) { return true; } } return false; } set featurePropertiesDirty(value) { const contents = this._contents; const length = contents.length; for (let i = 0; i < length; ++i) { contents[i].featurePropertiesDirty = value; } } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns 0. Instead call featuresLength for a tile in the composite. */ get featuresLength() { return 0; } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns 0. Instead call pointsLength for a tile in the composite. */ get pointsLength() { return 0; } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns 0. Instead call trianglesLength for a tile in the composite. */ get trianglesLength() { return 0; } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns 0. Instead call geometryByteLength for a tile in the composite. */ get geometryByteLength() { return 0; } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns 0. Instead call texturesByteLength for a tile in the composite. */ get texturesByteLength() { return 0; } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns 0. Instead call batchTableByteLength for a tile in the composite. */ get batchTableByteLength() { return 0; } get innerContents() { return this._contents; } /** * Returns true when the tile's content is ready to render; otherwise false * * * @type {boolean} * @readonly * @private */ get ready() { return this._ready; } get tileset() { return this._tileset; } get tile() { return this._tile; } get url() { return this._resource.getUrlComponent(true); } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * both stores the content metadata and propagates the content metadata to all of its children. * @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. */ get metadata() { return this._metadata; } set metadata(value) { this._metadata = value; const contents = this._contents; const length = contents.length; for (let i = 0; i < length; ++i) { contents[i].metadata = value; } } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns undefined. Instead call batchTable for a tile in the composite. */ get batchTable() { return undefined; } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * both stores the group metadata and propagates the group metadata to all of its children. * @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. */ get group() { return this._group; } set group(value) { this._group = value; const contents = this._contents; const length = contents.length; for (let i = 0; i < length; ++i) { contents[i].group = value; } } static async fromTileType( tileset, tile, resource, arrayBuffer, byteOffset, factory, ) { byteOffset = byteOffset ?? 0; const uint8Array = new Uint8Array(arrayBuffer); const view = new DataView(arrayBuffer); byteOffset += sizeOfUint32; // Skip magic const version = view.getUint32(byteOffset, true); if (version !== 1) { throw new RuntimeError( `Only Composite Tile version 1 is supported. Version ${version} is not.`, ); } byteOffset += sizeOfUint32; // Skip byteLength byteOffset += sizeOfUint32; const tilesLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; // For caching purposes, models within the composite tile must be // distinguished. To do this, add a query parameter ?compositeIndex=i. // Since composite tiles may contain other composite tiles, check for an // existing prefix and separate them with underscores. e.g. // ?compositeIndex=0_1_1 let prefix = resource.queryParameters.compositeIndex; if (defined(prefix)) { // We'll be adding another value at the end, so add an underscore. prefix = `${prefix}_`; } else { // no prefix prefix = ""; } const promises = []; promises.length = tilesLength; for (let i = 0; i < tilesLength; ++i) { const tileType = getMagic(uint8Array, byteOffset); // Tile byte length is stored after magic and version const tileByteLength = view.getUint32( byteOffset + sizeOfUint32 * 2, true, ); const contentFactory = factory[tileType]; // Label which content within the composite this is const compositeIndex = `${prefix}${i}`; const childResource = resource.getDerivedResource({ queryParameters: { compositeIndex: compositeIndex, }, }); if (defined(contentFactory)) { promises[i] = Promise.resolve( contentFactory(tileset, tile, childResource, arrayBuffer, byteOffset), ); } else { throw new RuntimeError( `Unknown tile content type, ${tileType}, inside Composite tile`, ); } byteOffset += tileByteLength; } const innerContents = await Promise.all(promises); const content = new Composite3DTileContent( tileset, tile, resource, innerContents, ); return content; } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns false. Instead call hasProperty for a tile in the composite. */ hasProperty(batchId, name) { return false; } /** * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent * always returns undefined. Instead call getFeature for a tile in the composite. */ getFeature(batchId) { return undefined; } applyDebugSettings(enabled, color) { const contents = this._contents; const length = contents.length; for (let i = 0; i < length; ++i) { contents[i].applyDebugSettings(enabled, color); } } applyStyle(style) { const contents = this._contents; const length = contents.length; for (let i = 0; i < length; ++i) { contents[i].applyStyle(style); } } update(tileset, frameState) { const contents = this._contents; const length = contents.length; let ready = true; for (let i = 0; i < length; ++i) { contents[i].update(tileset, frameState); ready = ready && contents[i].ready; } if (!this._ready && ready) { this._ready = true; } } /** * Find an intersection between a ray and the tile content surface that was rendered. The ray must be given in world coordinates. * * @param {Ray} ray The ray to test for intersection. * @param {FrameState} frameState The frame state. * @param {Cartesian3|undefined} [result] The intersection or undefined if none was found. * @returns {Cartesian3|undefined} The intersection or undefined if none was found. * * @private */ pick(ray, frameState, result) { if (!this._ready) { return undefined; } let intersection; let minDistance = Number.POSITIVE_INFINITY; const contents = this._contents; const length = contents.length; for (let i = 0; i < length; ++i) { const candidate = contents[i].pick(ray, frameState, result); if (!defined(candidate)) { continue; } const distance = Cartesian3.distance(ray.origin, candidate); if (distance < minDistance) { intersection = candidate; minDistance = distance; } } if (!defined(intersection)) { return undefined; } return result; } isDestroyed() { return false; } destroy() { const contents = this._contents; const length = contents.length; for (let i = 0; i < length; ++i) { contents[i].destroy(); } return destroyObject(this); } } const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; export default Composite3DTileContent;