diff --git a/Source/Scene/B3dmParser.js b/Source/Scene/B3dmParser.js new file mode 100644 index 000000000000..34c664ceb9e2 --- /dev/null +++ b/Source/Scene/B3dmParser.js @@ -0,0 +1,176 @@ +import Check from "../Core/Check.js"; +import defaultValue from "../Core/defaultValue.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; +import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js"; +import RuntimeError from "../Core/RuntimeError.js"; + +/** + * Handles parsing of a Batched 3D Model. + * + * @namespace B3dmParser + * @private + */ +var B3dmParser = {}; +B3dmParser._deprecationWarning = deprecationWarning; + +var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + +/** + * Parses the contents of a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel|Batched 3D Model}. + * + * @private + * + * @param {ArrayBuffer} arrayBuffer The array buffer containing the B3DM. + * @param {Number} [byteOffset=0] The byte offset of the beginning of the B3DM in the array buffer. + * @returns {Object} Returns an object with the batch length, feature table (binary and json), batch table (binary and json) and glTF parts of the B3DM. + */ +B3dmParser.parse = function (arrayBuffer, byteOffset) { + var byteStart = defaultValue(byteOffset, 0); + //>>includeStart('debug', pragmas.debug); + Check.defined("arrayBuffer", arrayBuffer); + //>>includeEnd('debug'); + + byteOffset = byteStart; + + var uint8Array = new Uint8Array(arrayBuffer); + var view = new DataView(arrayBuffer); + byteOffset += sizeOfUint32; // Skip magic + + var version = view.getUint32(byteOffset, true); + if (version !== 1) { + throw new RuntimeError( + "Only Batched 3D Model version 1 is supported. Version " + + version + + " is not." + ); + } + byteOffset += sizeOfUint32; + + var byteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var featureTableJsonByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var featureTableBinaryByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchTableJsonByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchTableBinaryByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchLength; + + // Legacy header #1: [batchLength] [batchTableByteLength] + // Legacy header #2: [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength] + // Current header: [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] + // If the header is in the first legacy format 'batchTableJsonByteLength' will be the start of the JSON string (a quotation mark) or the glTF magic. + // Accordingly its first byte will be either 0x22 or 0x67, and so the minimum uint32 expected is 0x22000000 = 570425344 = 570MB. It is unlikely that the feature table JSON will exceed this length. + // The check for the second legacy format is similar, except it checks 'batchTableBinaryByteLength' instead + if (batchTableJsonByteLength >= 570425344) { + // First legacy check + byteOffset -= sizeOfUint32 * 2; + batchLength = featureTableJsonByteLength; + batchTableJsonByteLength = featureTableBinaryByteLength; + batchTableBinaryByteLength = 0; + featureTableJsonByteLength = 0; + featureTableBinaryByteLength = 0; + B3dmParser._deprecationWarning( + "b3dm-legacy-header", + "This b3dm header is using the legacy format [batchLength] [batchTableByteLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel." + ); + } else if (batchTableBinaryByteLength >= 570425344) { + // Second legacy check + byteOffset -= sizeOfUint32; + batchLength = batchTableJsonByteLength; + batchTableJsonByteLength = featureTableJsonByteLength; + batchTableBinaryByteLength = featureTableBinaryByteLength; + featureTableJsonByteLength = 0; + featureTableBinaryByteLength = 0; + B3dmParser._deprecationWarning( + "b3dm-legacy-header", + "This b3dm header is using the legacy format [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel." + ); + } + + var featureTableJson; + if (featureTableJsonByteLength === 0) { + featureTableJson = { + BATCH_LENGTH: defaultValue(batchLength, 0), + }; + } else { + featureTableJson = getJsonFromTypedArray( + uint8Array, + byteOffset, + featureTableJsonByteLength + ); + byteOffset += featureTableJsonByteLength; + } + + var featureTableBinary = new Uint8Array( + arrayBuffer, + byteOffset, + featureTableBinaryByteLength + ); + byteOffset += featureTableBinaryByteLength; + + var batchTableJson; + var batchTableBinary; + if (batchTableJsonByteLength > 0) { + // PERFORMANCE_IDEA: is it possible to allocate this on-demand? Perhaps keep the + // arraybuffer/string compressed in memory and then decompress it when it is first accessed. + // + // We could also make another request for it, but that would make the property set/get + // API async, and would double the number of numbers in some cases. + batchTableJson = getJsonFromTypedArray( + uint8Array, + byteOffset, + batchTableJsonByteLength + ); + byteOffset += batchTableJsonByteLength; + + if (batchTableBinaryByteLength > 0) { + // Has a batch table binary + batchTableBinary = new Uint8Array( + arrayBuffer, + byteOffset, + batchTableBinaryByteLength + ); + // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed + batchTableBinary = new Uint8Array(batchTableBinary); + byteOffset += batchTableBinaryByteLength; + } + } + + var gltfByteLength = byteStart + byteLength - byteOffset; + if (gltfByteLength === 0) { + throw new RuntimeError("glTF byte length must be greater than 0."); + } + + var gltfView; + if (byteOffset % 4 === 0) { + gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength); + } else { + // Create a copy of the glb so that it is 4-byte aligned + B3dmParser._deprecationWarning( + "b3dm-glb-unaligned", + "The embedded glb is not aligned to a 4-byte boundary." + ); + gltfView = new Uint8Array( + uint8Array.subarray(byteOffset, byteOffset + gltfByteLength) + ); + } + + return { + batchLength: batchLength, + featureTableJson: featureTableJson, + featureTableBinary: featureTableBinary, + batchTableJson: batchTableJson, + batchTableBinary: batchTableBinary, + gltf: gltfView, + }; +}; + +export default B3dmParser; diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index b481980e5750..15b962067aec 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -1,28 +1,22 @@ import Cartesian3 from "../Core/Cartesian3.js"; import Color from "../Core/Color.js"; -import combine from "../Core/combine.js"; import ComponentDatatype from "../Core/ComponentDatatype.js"; -import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; -import ExperimentalFeatures from "../Core/ExperimentalFeatures.js"; -import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js"; import Matrix4 from "../Core/Matrix4.js"; import RequestType from "../Core/RequestType.js"; -import RuntimeError from "../Core/RuntimeError.js"; import Pass from "../Renderer/Pass.js"; import Axis from "./Axis.js"; +import B3dmParser from "./B3dmParser.js"; import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js"; import Cesium3DTileFeature from "./Cesium3DTileFeature.js"; import Cesium3DTileFeatureTable from "./Cesium3DTileFeatureTable.js"; import ClassificationModel from "./ClassificationModel.js"; import Model from "./Model.js"; import ModelAnimationLoop from "./ModelAnimationLoop.js"; -import ModelExperimental from "./ModelExperimental/ModelExperimental.js"; import ModelUtility from "./ModelUtility.js"; -import parseBatchTable from "./parseBatchTable.js"; /** * Represents the contents of a @@ -65,11 +59,6 @@ function Batched3DModel3DTileContent( this.featurePropertiesDirty = false; this._groupMetadata = undefined; - this._featureMetadata = undefined; - this._featureTables = []; - this._featureTableId = undefined; - this._featureTable = undefined; - initialize(this, arrayBuffer, byteOffset); } @@ -79,7 +68,7 @@ Batched3DModel3DTileContent._deprecationWarning = deprecationWarning; Object.defineProperties(Batched3DModel3DTileContent.prototype, { featuresLength: { get: function () { - return defined(this.batchTable) ? this.batchTable.featuresLength : 0; + return this.batchTable.featuresLength; }, }, @@ -109,7 +98,7 @@ Object.defineProperties(Batched3DModel3DTileContent.prototype, { batchTableByteLength: { get: function () { - return defined(this.batchTable) ? this.batchTable.memorySizeInBytes : 0; + return this.batchTable.memorySizeInBytes; }, }, @@ -145,43 +134,7 @@ Object.defineProperties(Batched3DModel3DTileContent.prototype, { batchTable: { get: function () { - return ExperimentalFeatures.enableModelExperimental - ? this._featureTable - : this._batchTable; - }, - }, - - /** - * @private - */ - featureMetadata: { - get: function () { - return this._featureMetadata; - }, - }, - - /** - * @private - */ - featureTables: { - get: function () { - return this._featureTables; - }, - set: function (value) { - this._featureTables = value; - }, - }, - - /** - * @private - */ - featureTableId: { - get: function () { - return this._featureTableId; - }, - set: function (value) { - this._featureTableId = value; - this._featureTable = this._featureTables[value]; + return this._batchTable; }, }, @@ -195,8 +148,6 @@ Object.defineProperties(Batched3DModel3DTileContent.prototype, { }, }); -var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - function getBatchIdAttributeName(gltf) { var batchIdAttributeName = ModelUtility.getAttributeOrUniformBySemantic( gltf, @@ -284,93 +235,12 @@ function initialize(content, arrayBuffer, byteOffset) { var tile = content._tile; var resource = content._resource; - var byteStart = defaultValue(byteOffset, 0); - byteOffset = byteStart; - - var uint8Array = new Uint8Array(arrayBuffer); - var view = new DataView(arrayBuffer); - byteOffset += sizeOfUint32; // Skip magic - - var version = view.getUint32(byteOffset, true); - if (version !== 1) { - throw new RuntimeError( - "Only Batched 3D Model version 1 is supported. Version " + - version + - " is not." - ); - } - byteOffset += sizeOfUint32; - - var byteLength = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - - var featureTableJsonByteLength = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - - var featureTableBinaryByteLength = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - - var batchTableJsonByteLength = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - - var batchTableBinaryByteLength = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - - var batchLength; - - // Legacy header #1: [batchLength] [batchTableByteLength] - // Legacy header #2: [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength] - // Current header: [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] - // If the header is in the first legacy format 'batchTableJsonByteLength' will be the start of the JSON string (a quotation mark) or the glTF magic. - // Accordingly its first byte will be either 0x22 or 0x67, and so the minimum uint32 expected is 0x22000000 = 570425344 = 570MB. It is unlikely that the feature table JSON will exceed this length. - // The check for the second legacy format is similar, except it checks 'batchTableBinaryByteLength' instead - if (batchTableJsonByteLength >= 570425344) { - // First legacy check - byteOffset -= sizeOfUint32 * 2; - batchLength = featureTableJsonByteLength; - batchTableJsonByteLength = featureTableBinaryByteLength; - batchTableBinaryByteLength = 0; - featureTableJsonByteLength = 0; - featureTableBinaryByteLength = 0; - Batched3DModel3DTileContent._deprecationWarning( - "b3dm-legacy-header", - "This b3dm header is using the legacy format [batchLength] [batchTableByteLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel." - ); - } else if (batchTableBinaryByteLength >= 570425344) { - // Second legacy check - byteOffset -= sizeOfUint32; - batchLength = batchTableJsonByteLength; - batchTableJsonByteLength = featureTableJsonByteLength; - batchTableBinaryByteLength = featureTableBinaryByteLength; - featureTableJsonByteLength = 0; - featureTableBinaryByteLength = 0; - Batched3DModel3DTileContent._deprecationWarning( - "b3dm-legacy-header", - "This b3dm header is using the legacy format [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel." - ); - } - - var featureTableJson; - if (featureTableJsonByteLength === 0) { - featureTableJson = { - BATCH_LENGTH: defaultValue(batchLength, 0), - }; - } else { - featureTableJson = getJsonFromTypedArray( - uint8Array, - byteOffset, - featureTableJsonByteLength - ); - byteOffset += featureTableJsonByteLength; - } + var b3dm = B3dmParser.parse(arrayBuffer, byteOffset); - var featureTableBinary = new Uint8Array( - arrayBuffer, - byteOffset, - featureTableBinaryByteLength - ); - byteOffset += featureTableBinaryByteLength; + var batchLength = b3dm.batchLength; + var featureTableJson = b3dm.featureTableJson; + var featureTableBinary = b3dm.featureTableBinary; var featureTable = new Cesium3DTileFeatureTable( featureTableJson, featureTableBinary @@ -379,80 +249,24 @@ function initialize(content, arrayBuffer, byteOffset) { batchLength = featureTable.getGlobalProperty("BATCH_LENGTH"); featureTable.featuresLength = batchLength; - var batchTableJson; - var batchTableBinary; - if (batchTableJsonByteLength > 0) { - // PERFORMANCE_IDEA: is it possible to allocate this on-demand? Perhaps keep the - // arraybuffer/string compressed in memory and then decompress it when it is first accessed. - // - // We could also make another request for it, but that would make the property set/get - // API async, and would double the number of numbers in some cases. - batchTableJson = getJsonFromTypedArray( - uint8Array, - byteOffset, - batchTableJsonByteLength - ); - byteOffset += batchTableJsonByteLength; - - if (batchTableBinaryByteLength > 0) { - // Has a batch table binary - batchTableBinary = new Uint8Array( - arrayBuffer, - byteOffset, - batchTableBinaryByteLength - ); - // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed - batchTableBinary = new Uint8Array(batchTableBinary); - byteOffset += batchTableBinaryByteLength; - } - } + var batchTableJson = b3dm.batchTableJson; + var batchTableBinary = b3dm.batchTableBinary; var colorChangedCallback; if (defined(content._classificationType)) { colorChangedCallback = createColorChangedCallback(content); } - var batchTable; - if ( - ExperimentalFeatures.enableModelExperimental && - batchLength > 0 && - defined(batchTableJson) - ) { - var featureMetadata = parseBatchTable({ - count: batchLength, - batchTable: batchTableJson, - binaryBody: batchTableBinary, - }); - content._featureMetadata = featureMetadata; - } else { - batchTable = new Cesium3DTileBatchTable( - content, - batchLength, - batchTableJson, - batchTableBinary, - colorChangedCallback - ); - content._batchTable = batchTable; - } - - var gltfByteLength = byteStart + byteLength - byteOffset; - if (gltfByteLength === 0) { - throw new RuntimeError("glTF byte length must be greater than 0."); - } + var batchTable = new Cesium3DTileBatchTable( + content, + batchLength, + batchTableJson, + batchTableBinary, + colorChangedCallback + ); + content._batchTable = batchTable; - var gltfView; - if (byteOffset % 4 === 0) { - gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength); - } else { - // Create a copy of the glb so that it is 4-byte aligned - Batched3DModel3DTileContent._deprecationWarning( - "b3dm-glb-unaligned", - "The embedded glb is not aligned to a 4-byte boundary." - ); - gltfView = new Uint8Array( - uint8Array.subarray(byteOffset, byteOffset + gltfByteLength) - ); - } + var gltfView = b3dm.gltf; var pickObject = { content: content, @@ -478,53 +292,39 @@ function initialize(content, arrayBuffer, byteOffset) { ); if (!defined(content._classificationType)) { - var modelOptions = { + // PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes. + // The pick shader still needs to be patched. + content._model = new Model({ gltf: gltfView, cull: false, // The model is already culled by 3D Tiles releaseGltfJson: true, // Models are unique and will not benefit from caching so save memory opaquePass: Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass basePath: resource, + requestType: RequestType.TILES3D, modelMatrix: content._contentModelMatrix, upAxis: tileset._gltfUpAxis, forwardAxis: Axis.X, + shadows: tileset.shadows, + debugWireframe: tileset.debugWireframe, incrementallyLoadTextures: false, - }; - - if (ExperimentalFeatures.enableModelExperimental) { - modelOptions.content = content; - modelOptions.customShader = tileset.customShader; - modelOptions.content = content; - content._model = ModelExperimental.fromGltf(modelOptions); - } else { - modelOptions = combine(modelOptions, { - requestType: RequestType.TILES3D, - shadows: tileset.shadows, - debugWireframe: tileset.debugWireframe, - vertexShaderLoaded: getVertexShaderCallback(content), - fragmentShaderLoaded: getFragmentShaderCallback(content), - uniformMapLoaded: batchTable.getUniformMapCallback(), - pickIdLoaded: getPickIdCallback(content), - addBatchIdToGeneratedShaders: batchLength > 0, // If the batch table has values in it, generated shaders will need a batchId attribute - pickObject: pickObject, - imageBasedLightingFactor: tileset.imageBasedLightingFactor, - lightColor: tileset.lightColor, - luminanceAtZenith: tileset.luminanceAtZenith, - sphericalHarmonicCoefficients: tileset.sphericalHarmonicCoefficients, - specularEnvironmentMaps: tileset.specularEnvironmentMaps, - backFaceCulling: tileset.backFaceCulling, - showOutline: tileset.showOutline, - }); - // PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes. - // The pick shader still needs to be patched. - content._model = new Model(modelOptions); - } - + vertexShaderLoaded: getVertexShaderCallback(content), + fragmentShaderLoaded: getFragmentShaderCallback(content), + uniformMapLoaded: batchTable.getUniformMapCallback(), + pickIdLoaded: getPickIdCallback(content), + addBatchIdToGeneratedShaders: batchLength > 0, // If the batch table has values in it, generated shaders will need a batchId attribute + pickObject: pickObject, + imageBasedLightingFactor: tileset.imageBasedLightingFactor, + lightColor: tileset.lightColor, + luminanceAtZenith: tileset.luminanceAtZenith, + sphericalHarmonicCoefficients: tileset.sphericalHarmonicCoefficients, + specularEnvironmentMaps: tileset.specularEnvironmentMaps, + backFaceCulling: tileset.backFaceCulling, + showOutline: tileset.showOutline, + }); content._model.readyPromise.then(function (model) { - if (defined(model.activeAnimations)) { - model.activeAnimations.addAll({ - loop: ModelAnimationLoop.REPEAT, - }); - } + model.activeAnimations.addAll({ + loop: ModelAnimationLoop.REPEAT, + }); }); } else { // This transcodes glTF to an internal representation for geometry so we can take advantage of the re-batching of vector data. @@ -562,17 +362,10 @@ function createFeatures(content) { } Batched3DModel3DTileContent.prototype.hasProperty = function (batchId, name) { - return this.batchTable.hasProperty(batchId, name); + return this._batchTable.hasProperty(batchId, name); }; Batched3DModel3DTileContent.prototype.getFeature = function (batchId) { - if ( - ExperimentalFeatures.enableModelExperimental && - defined(this.batchTable) - ) { - return this.batchTable.getFeature(batchId); - } - //>>includeStart('debug', pragmas.debug); var featuresLength = this.featuresLength; if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) { @@ -618,14 +411,12 @@ Batched3DModel3DTileContent.prototype.update = function (tileset, frameState) { var model = this._model; var tile = this._tile; - var batchTable = this.batchTable; + var batchTable = this._batchTable; // In the PROCESSING state we may be calling update() to move forward // the content's resource loading. In the READY state, it will // actually generate commands. - if (defined(batchTable)) { - batchTable.update(tileset, frameState); - } + batchTable.update(tileset, frameState); this._contentModelMatrix = Matrix4.multiply( tile.computedTransform, @@ -668,16 +459,14 @@ Batched3DModel3DTileContent.prototype.update = function (tileset, frameState) { model.update(frameState); - if (!ExperimentalFeatures.enableModelExperimental) { - // If any commands were pushed, add derived commands - var commandEnd = frameState.commandList.length; - if ( - commandStart < commandEnd && - (frameState.passes.render || frameState.passes.pick) && - !defined(this._classificationType) - ) { - batchTable.addDerivedCommands(frameState, commandStart); - } + // If any commands were pushed, add derived commands + var commandEnd = frameState.commandList.length; + if ( + commandStart < commandEnd && + (frameState.passes.render || frameState.passes.pick) && + !defined(this._classificationType) + ) { + batchTable.addDerivedCommands(frameState, commandStart); } }; diff --git a/Source/Scene/Cesium3DTileContentFactory.js b/Source/Scene/Cesium3DTileContentFactory.js index e8761d2a365f..9f37dae5f466 100644 --- a/Source/Scene/Cesium3DTileContentFactory.js +++ b/Source/Scene/Cesium3DTileContentFactory.js @@ -8,6 +8,8 @@ import PointCloud3DTileContent from "./PointCloud3DTileContent.js"; import Tileset3DTileContent from "./Tileset3DTileContent.js"; import Vector3DTileContent from "./Vector3DTileContent.js"; import RuntimeError from "../Core/RuntimeError.js"; +import ExperimentalFeatures from "../Core/ExperimentalFeatures.js"; +import ModelExperimental3DTileContent from "./ModelExperimental/ModelExperimental3DTileContent.js"; /** * Maps a tile's magic field in its header to a new content object for the tile's payload. @@ -16,6 +18,15 @@ import RuntimeError from "../Core/RuntimeError.js"; */ var Cesium3DTileContentFactory = { b3dm: function (tileset, tile, resource, arrayBuffer, byteOffset) { + if (ExperimentalFeatures.enableModelExperimental) { + return ModelExperimental3DTileContent.fromB3dm( + tileset, + tile, + resource, + arrayBuffer, + byteOffset + ); + } return new Batched3DModel3DTileContent( tileset, tile, @@ -91,10 +102,25 @@ var Cesium3DTileContentFactory = { var dataView = new DataView(arrayBuffer, byteOffset); var byteLength = dataView.getUint32(8, true); var glb = new Uint8Array(arrayBuffer, byteOffset, byteLength); - + if (ExperimentalFeatures.enableModelExperimental) { + return ModelExperimental3DTileContent.fromGltf( + tileset, + tile, + resource, + glb + ); + } return new Gltf3DTileContent(tileset, tile, resource, glb); }, gltf: function (tileset, tile, resource, json) { + if (ExperimentalFeatures.enableModelExperimental) { + return ModelExperimental3DTileContent.fromGltf( + tileset, + tile, + resource, + json + ); + } return new Gltf3DTileContent(tileset, tile, resource, json); }, }; diff --git a/Source/Scene/Gltf3DTileContent.js b/Source/Scene/Gltf3DTileContent.js index ff7cc5ce5fa1..9054ea6caa22 100644 --- a/Source/Scene/Gltf3DTileContent.js +++ b/Source/Scene/Gltf3DTileContent.js @@ -6,15 +6,9 @@ import Pass from "../Renderer/Pass.js"; import Axis from "./Axis.js"; import Model from "./Model.js"; import ModelAnimationLoop from "./ModelAnimationLoop.js"; -import ExperimentalFeatures from "../Core/ExperimentalFeatures.js"; -import ModelExperimental from "./ModelExperimental/ModelExperimental.js"; -import combine from "../Core/combine.js"; /** - * Represents the contents of a glTF or glb tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset using the {@link https://github.com/CesiumGS/3d-tiles/tree/3d-tiles-next/extensions/3DTILES_content_gltf|3DTILES_content_gltf} extension. - *

- * This class does not yet support the {@link https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_feature_metadata|EXT_feature_metadata Extension}. - *

+ * Represents the contents of a glTF or glb tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset using the {@link https://github.com/CesiumGS/3d-tiles/tree/3d-tiles-next/extensions/3DTILES_content_gltf/|3DTILES_content_gltf} extension. *

* Implements the {@link Cesium3DTileContent} interface. *

@@ -34,10 +28,6 @@ function Gltf3DTileContent(tileset, tile, resource, gltf) { this.featurePropertiesDirty = false; this._groupMetadata = undefined; - this._featureTable = undefined; - this._featureTables = undefined; - this._featureTableId = undefined; - initialize(this, gltf); } @@ -110,7 +100,7 @@ Object.defineProperties(Gltf3DTileContent.prototype, { batchTable: { get: function () { - return this._featureTable; + return undefined; }, }, @@ -122,30 +112,6 @@ Object.defineProperties(Gltf3DTileContent.prototype, { this._groupMetadata = value; }, }, - /** - * @private - */ - featureTables: { - get: function () { - return this._featureTables; - }, - set: function (value) { - this._featureTables = value; - }, - }, - - /** - * @private - */ - featureTableId: { - get: function () { - return this._featureTableId; - }, - set: function (value) { - this._featureTableId = value; - this._featureTable = this._featureTables[value]; - }, - }, }); function initialize(content, gltf) { @@ -158,61 +124,42 @@ function initialize(content, gltf) { primitive: tileset, }; - var modelOptions = { + content._model = new Model({ gltf: gltf, cull: false, // The model is already culled by 3D Tiles releaseGltfJson: true, // Models are unique and will not benefit from caching so save memory opaquePass: Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass basePath: resource, + requestType: RequestType.TILES3D, modelMatrix: tile.computedTransform, upAxis: tileset._gltfUpAxis, forwardAxis: Axis.X, + shadows: tileset.shadows, + debugWireframe: tileset.debugWireframe, incrementallyLoadTextures: false, - }; - - if (ExperimentalFeatures.enableModelExperimental) { - modelOptions.customShader = tileset.customShader; - modelOptions.content = content; - content._model = ModelExperimental.fromGltf(modelOptions); - } else { - modelOptions = combine(modelOptions, { - requestType: RequestType.TILES3D, - shadows: tileset.shadows, - debugWireframe: tileset.debugWireframe, - addBatchIdToGeneratedShaders: false, - pickObject: pickObject, - imageBasedLightingFactor: tileset.imageBasedLightingFactor, - lightColor: tileset.lightColor, - luminanceAtZenith: tileset.luminanceAtZenith, - sphericalHarmonicCoefficients: tileset.sphericalHarmonicCoefficients, - specularEnvironmentMaps: tileset.specularEnvironmentMaps, - backFaceCulling: tileset.backFaceCulling, - showOutline: tileset.showOutline, - }); - content._model = new Model(modelOptions); - } - + addBatchIdToGeneratedShaders: false, + pickObject: pickObject, + imageBasedLightingFactor: tileset.imageBasedLightingFactor, + lightColor: tileset.lightColor, + luminanceAtZenith: tileset.luminanceAtZenith, + sphericalHarmonicCoefficients: tileset.sphericalHarmonicCoefficients, + specularEnvironmentMaps: tileset.specularEnvironmentMaps, + backFaceCulling: tileset.backFaceCulling, + showOutline: tileset.showOutline, + }); content._model.readyPromise.then(function (model) { - if (defined(model.activeAnimations)) { - model.activeAnimations.addAll({ - loop: ModelAnimationLoop.REPEAT, - }); - } + model.activeAnimations.addAll({ + loop: ModelAnimationLoop.REPEAT, + }); }); } -Gltf3DTileContent.prototype.getFeature = function (featureId) { - if (defined(this.batchTable)) { - return this.batchTable.getFeature(featureId); - } - return undefined; +Gltf3DTileContent.prototype.hasProperty = function (batchId, name) { + return false; }; -Gltf3DTileContent.prototype.hasProperty = function (featureId, name) { - if (defined(this.batchTable)) { - return this.batchTable.hasProperty(featureId, name); - } - return false; +Gltf3DTileContent.prototype.getFeature = function (batchId) { + return undefined; }; Gltf3DTileContent.prototype.applyDebugSettings = function (enabled, color) { @@ -266,16 +213,6 @@ Gltf3DTileContent.prototype.update = function (tileset, frameState) { model._clippingPlanes = tilesetClippingPlanes; } - var featureTables = this._featureTables; - if (defined(featureTables)) { - for (var featureTableId in featureTables) { - if (featureTables.hasOwnProperty(featureTableId)) { - var featureTable = featureTables[featureTableId]; - featureTable.update(tileset, frameState); - } - } - } - model.update(frameState); }; @@ -284,16 +221,6 @@ Gltf3DTileContent.prototype.isDestroyed = function () { }; Gltf3DTileContent.prototype.destroy = function () { - var featureTables = this._featureTables; - if (defined(featureTables)) { - for (var featureTableId in featureTables) { - if (featureTables.hasOwnProperty(featureTableId)) { - var featureTable = featureTables[featureTableId]; - featureTable.destroy(); - } - } - } - this._model = this._model && this._model.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/GltfLoader.js b/Source/Scene/GltfLoader.js index bbb503ba14bd..910ece9471d6 100644 --- a/Source/Scene/GltfLoader.js +++ b/Source/Scene/GltfLoader.js @@ -113,6 +113,7 @@ export default function GltfLoader(options) { this._textureState = GltfLoaderState.UNLOADED; this._promise = when.defer(); this._texturesLoadedPromise = when.defer(); + this._transform = Matrix4.IDENTITY; // Loaders that need to be processed before the glTF becomes ready this._textureLoaders = []; @@ -189,6 +190,22 @@ Object.defineProperties(GltfLoader.prototype, { return this._texturesLoadedPromise.promise; }, }, + + /** + * A world-space transform to apply to the primitives. + * + * @memberof GltfLoader.prototype + * + * @type {Matrix4} + * @default {@link Matrix4.IDENTITY} + * @readonly + * @private + */ + transform: { + get: function () { + return this._transform; + }, + }, }); /** diff --git a/Source/Scene/ModelExperimental/B3dmLoader.js b/Source/Scene/ModelExperimental/B3dmLoader.js new file mode 100644 index 000000000000..fb8763a617c3 --- /dev/null +++ b/Source/Scene/ModelExperimental/B3dmLoader.js @@ -0,0 +1,351 @@ +import Axis from "../Axis.js"; +import B3dmParser from "../B3dmParser.js"; +import Cartesian3 from "../../Core/Cartesian3.js"; +import Cesium3DTileFeatureTable from "../Cesium3DTileFeatureTable.js"; +import Check from "../../Core/Check.js"; +import ComponentDatatype from "../../Core/ComponentDatatype.js"; +import defaultValue from "../../Core/defaultValue.js"; +import defined from "../../Core/defined.js"; +import FeatureMetadata from "../FeatureMetadata.js"; +import GltfLoader from "../GltfLoader.js"; +import Matrix4 from "../../Core/Matrix4.js"; +import MetadataClass from "../MetadataClass.js"; +import ModelComponents from "../ModelComponents.js"; +import ModelExperimentalUtility from "./ModelExperimentalUtility.js"; +import parseBatchTable from "../parseBatchTable.js"; +import PropertyTable from "../PropertyTable.js"; +import ResourceLoader from "../ResourceLoader.js"; +import when from "../../ThirdParty/when.js"; +import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; + +var B3dmLoaderState = { + UNLOADED: 0, + LOADING: 1, + PROCESSING: 2, + READY: 3, + FAILED: 4, +}; + +var FeatureIdAttribute = ModelComponents.FeatureIdAttribute; + +/** + * Loads a Batched 3D Model. + *

+ * Implements the {@link ResourceLoader} interface. + *

+ * + * @alias B3dmLoader + * @constructor + * @augments ResourceLoader + * @private + * + * @param {Object} options Object with the following properties: + * @param {Resource} options.b3dmResource The {@link Resource} containing the B3DM. + * @param {ArrayBuffer} options.arrayBuffer The array buffer of the B3DM contents. + * @param {Number} [options.byteOffset] The byte offset to the beginning of the B3DM contents in the array buffer. + * @param {Resource} [options.baseResource] The {@link Resource} that paths in the glTF JSON are relative to. + * @param {Boolean} [options.releaseGltfJson=false] When true, the glTF JSON is released once the glTF is loaded. This is is especially useful for cases like 3D Tiles, where each .gltf model is unique and caching the glTF JSON is not effective. + * @param {Boolean} [options.asynchronous=true] Determines if WebGL resource creation will be spread out over several frames or block until all WebGL resources are created. + * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the glTF is loaded. + * @param {Axis} [options.upAxis=Axis.Y] The up-axis of the glTF model. + * @param {Axis} [options.forwardAxis=Axis.X] The forward-axis of the glTF model. + */ +function B3dmLoader(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var b3dmResource = options.b3dmResource; + var baseResource = options.baseResource; + var arrayBuffer = options.arrayBuffer; + var byteOffset = defaultValue(options.byteOffset, 0); + var releaseGltfJson = defaultValue(options.releaseGltfJson, false); + var asynchronous = defaultValue(options.asynchronous, true); + var incrementallyLoadTextures = defaultValue( + options.incrementallyLoadTextures, + true + ); + var upAxis = defaultValue(options.upAxis, Axis.Y); + var forwardAxis = defaultValue(options.forwardAxis, Axis.X); + + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("options.b3dmResource", b3dmResource); + Check.typeOf.object("options.arrayBuffer", arrayBuffer); + //>>includeEnd('debug'); + + baseResource = defined(baseResource) ? baseResource : b3dmResource.clone(); + + this._b3dmResource = b3dmResource; + this._baseResource = baseResource; + this._arrayBuffer = arrayBuffer; + this._byteOffset = byteOffset; + this._releaseGltfJson = releaseGltfJson; + this._asynchronous = asynchronous; + this._incrementallyLoadTextures = incrementallyLoadTextures; + this._upAxis = upAxis; + this._forwardAxis = forwardAxis; + + this._state = B3dmLoaderState.UNLOADED; + + this._promise = when.defer(); + + this._gltfLoader = undefined; + + // Loaded results. + this._batchLength = 0; + this._propertyTable = undefined; + + // The batch table object contains a json and a binary component access using keys of the same name. + this._batchTable = undefined; + this._components = undefined; + this._transform = Matrix4.IDENTITY; +} + +if (defined(Object.create)) { + B3dmLoader.prototype = Object.create(ResourceLoader.prototype); + B3dmLoader.prototype.constructor = B3dmLoader; +} + +Object.defineProperties(B3dmLoader.prototype, { + /** + * A promise that resolves to the resource when the resource is ready. + * + * @memberof B3dmLoader.prototype + * + * @type {Promise.} + * @readonly + * @private + */ + promise: { + get: function () { + return this._promise.promise; + }, + }, + + /** + * A promise that resolves when all textures are loaded. + * When incrementallyLoadTextures is true this may resolve after + * promise resolves. + * + * @memberof B3dmLoader.prototype + * + * @type {Promise} + * @readonly + * @private + */ + texturesLoadedPromise: { + get: function () { + return this._gltfLoader.texturesLoadedPromise; + }, + }, + /** + * The cache key of the resource + * + * @memberof B3dmLoader.prototype + * + * @type {String} + * @readonly + * @private + */ + cacheKey: { + get: function () { + return undefined; + }, + }, + + /** + * The loaded components. + * + * @memberof B3dmLoader.prototype + * + * @type {ModelComponents.Components} + * @default {@link Matrix4.IDENTITY} + * @readonly + * @private + */ + components: { + get: function () { + return this._components; + }, + }, + + /** + * A world-space transform to apply to the primitives. + * See {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel#global-semantics} + * + * @memberof B3dmLoader.prototype + * + * @type {Matrix4} + * @readonly + * @private + */ + transform: { + get: function () { + return this._transform; + }, + }, +}); + +/** + * Loads the resource. + * @private + */ +B3dmLoader.prototype.load = function () { + var b3dm = B3dmParser.parse(this._arrayBuffer, this._byteOffset); + + var batchLength = b3dm.batchLength; + var featureTableJson = b3dm.featureTableJson; + var featureTableBinary = b3dm.featureTableBinary; + var batchTableJson = b3dm.batchTableJson; + var batchTableBinary = b3dm.batchTableBinary; + + var featureTable = new Cesium3DTileFeatureTable( + featureTableJson, + featureTableBinary + ); + batchLength = featureTable.getGlobalProperty("BATCH_LENGTH"); + // Set batch length. + this._batchLength = batchLength; + // Set the RTC Center transform, if present. + var rtcCenter = featureTable.getGlobalProperty( + "RTC_CENTER", + ComponentDatatype.FLOAT, + 3 + ); + if (defined(rtcCenter)) { + this._transform = Matrix4.fromTranslation(Cartesian3.fromArray(rtcCenter)); + } + + this._batchTable = { + json: batchTableJson, + binary: batchTableBinary, + }; + + var gltfLoader = new GltfLoader({ + upAxis: this._upAxis, + typedArray: b3dm.gltf, + forwardAxis: this._forwardAxis, + gltfResource: this._b3dmResource, + baseResource: this._baseResource, + releaseGltfJson: this._releaseGltfJson, + incrementallyLoadTextures: this._incrementallyLoadTextures, + }); + + this._gltfLoader = gltfLoader; + this._state = B3dmLoaderState.LOADING; + + var that = this; + gltfLoader.load(); + gltfLoader.promise + .then(function () { + if (that.isDestroyed()) { + return; + } + + var components = gltfLoader.components; + createFeatureMetadata(that, components); + that._components = components; + + that._state = B3dmLoaderState.READY; + that._promise.resolve(that); + }) + .otherwise(function (error) { + if (that.isDestroyed()) { + return; + } + handleError(that, error); + }); +}; + +function handleError(b3dmLoader, error) { + b3dmLoader.unload(); + b3dmLoader._state = B3dmLoaderState.FAILED; + var errorMessage = "Failed to load B3DM"; + error = b3dmLoader.getError(errorMessage, error); + b3dmLoader._promise.reject(error); +} + +B3dmLoader.prototype.process = function (frameState) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("frameState", frameState); + //>>includeEnd('debug'); + + if (this._state === B3dmLoaderState.LOADING) { + this._state = B3dmLoaderState.PROCESSING; + } + + if (this._state === B3dmLoaderState.PROCESSING) { + this._gltfLoader.process(frameState); + } +}; + +function createFeatureMetadata(loader, components) { + var batchTable = loader._batchTable; + var batchLength = loader._batchLength; + + var featureMetadata; + if (defined(batchTable.json)) { + // Add the feature metadata from the batch table to the model components. + featureMetadata = parseBatchTable({ + count: batchLength, + batchTable: batchTable.json, + binaryBody: batchTable.binary, + }); + } else { + // If batch table is not defined, create a property table without any properties. + var emptyPropertyTable = new PropertyTable({ + name: MetadataClass.BATCH_TABLE_CLASS_NAME, + count: batchLength, + }); + featureMetadata = new FeatureMetadata({ + schema: {}, + propertyTables: [emptyPropertyTable], + }); + } + + // Add the feature ID attribute to the primitives. + var nodes = components.scene.nodes; + for (var i = 0; i < nodes.length; i++) { + processNode(nodes[i]); + } + components.featureMetadata = featureMetadata; +} + +// Recursive function to add the feature ID attribute to all primitives that have a feature ID vertex attribute. +function processNode(node) { + if (!defined(node.children) && !defined(node.primitives)) { + return; + } + + var i; + if (defined(node.children)) { + for (i = 0; i < node.children.length; i++) { + processNode(node.children[i]); + } + } + + if (defined(node.primitives)) { + for (i = 0; i < node.primitives.length; i++) { + var primitive = node.primitives[i]; + var featureIdVertexAttribute = ModelExperimentalUtility.getAttributeBySemantic( + primitive, + VertexAttributeSemantic.FEATURE_ID + ); + if (defined(featureIdVertexAttribute)) { + featureIdVertexAttribute.setIndex = 0; + var featureIdAttribute = new FeatureIdAttribute(); + featureIdAttribute.propertyTableId = 0; + featureIdAttribute.setIndex = 0; + primitive.featureIdAttributes.push(featureIdAttribute); + } + } + } +} + +B3dmLoader.prototype.unload = function () { + if (defined(this._gltfLoader)) { + this._gltfLoader.unload(); + } + + this._components = undefined; +}; + +export default B3dmLoader; diff --git a/Source/Scene/ModelExperimental/BatchTexturePipelineStage.js b/Source/Scene/ModelExperimental/BatchTexturePipelineStage.js index ac61c33ddcb3..d68ff35b7c0a 100644 --- a/Source/Scene/ModelExperimental/BatchTexturePipelineStage.js +++ b/Source/Scene/ModelExperimental/BatchTexturePipelineStage.js @@ -1,6 +1,5 @@ import combine from "../../Core/combine.js"; import defaultValue from "../../Core/defaultValue.js"; -import defined from "../../Core/defined.js"; /** * The batch texture stage is responsible for setting up the batch texture for the primitive. @@ -31,14 +30,7 @@ BatchTexturePipelineStage.process = function ( var batchTextureUniforms = {}; var model = renderResources.model; - var featureTable; - - var content = model.content; - if (defined(content)) { - featureTable = content.featureTables[renderResources.featureTableId]; - } else { - featureTable = model.featureTables[renderResources.featureTableId]; - } + var featureTable = model.featureTables[model.featureTableId]; // Number of features in the feature table. var featuresLength = featureTable.featuresLength; diff --git a/Source/Scene/ModelExperimental/Cesium3DTileContentFeatureTable.js b/Source/Scene/ModelExperimental/Cesium3DTileContentFeatureTable.js deleted file mode 100644 index e5d9b6a05fc9..000000000000 --- a/Source/Scene/ModelExperimental/Cesium3DTileContentFeatureTable.js +++ /dev/null @@ -1,182 +0,0 @@ -import BatchTexture from "../BatchTexture.js"; -import Cesium3DTileFeature from "../Cesium3DTileFeature.js"; -import Check from "../../Core/Check.js"; -import destroyObject from "../../Core/destroyObject.js"; - -/** - * Manages the {@link Cesium3DTileFeature}s that belong to a {@link Cesium3DTileContent}. - * The properties for a feature are extracted from a {@link PropertyTable}. - * - * @param {Object} options An object containing the following options: - * @param {Cesium3DTileContent} options.content The tile content the features in this table belong to. - * @param {PropertyTable} options.propertyTable The feature table from the model belonging to the content. - * - * @alias Cesium3DTileContentFeatureTable - * @constructor - * @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. - */ -export default function Cesium3DTileContentFeatureTable(options) { - var content = options.content; - var propertyTable = options.propertyTable; - - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("options.content", content); - Check.typeOf.object("options.propertyTable", propertyTable); - //>>includeEnd('debug'); - - this._content = content; - this._propertyTable = propertyTable; - - this._featuresLength = 0; - this._features = undefined; - - this._batchTexture = undefined; - - initialize(this); -} - -Object.defineProperties(Cesium3DTileContentFeatureTable.prototype, { - /** - * The batch texture generated for the features in this table. - * - * @memberof Cesium3DTileContentFeatureTable.prototype - * @type {BatchTexture} - * @readonly - * @private - */ - batchTexture: { - get: function () { - return this._batchTexture; - }, - }, - /** - * The number of features in this table. - * - * @memberof Cesium3DTileContentFeatureTable.prototype - * @type {Number} - * @readonly - * @private - */ - featuresLength: { - get: function () { - return this._featuresLength; - }, - }, - memorySizeInBytes: { - get: function () { - return this._batchTexture.memorySizeInBytes; - }, - }, -}); - -function initialize(contentFeatureTable) { - var featuresLength = contentFeatureTable._propertyTable.count; - if (featuresLength === 0) { - return; - } - - var content = contentFeatureTable._content; - - var features = new Array(featuresLength); - for (var i = 0; i < featuresLength; i++) { - features[i] = new Cesium3DTileFeature(content, i); - } - - contentFeatureTable._featuresLength = featuresLength; - contentFeatureTable._features = features; - - contentFeatureTable._batchTexture = new BatchTexture({ - featuresLength: featuresLength, - owner: content, - statistics: content.tileset.statistics, - }); -} - -Cesium3DTileContentFeatureTable.prototype.getFeature = function (featureId) { - return this._features[featureId]; -}; - -Cesium3DTileContentFeatureTable.prototype.hasProperty = function ( - featureId, - propertyName -) { - return this._propertyTable.hasProperty(featureId, propertyName); -}; - -Cesium3DTileContentFeatureTable.prototype.getProperty = function ( - featureId, - name -) { - return this._propertyTable.getProperty(featureId, name); -}; - -Cesium3DTileContentFeatureTable.prototype.getPropertyInherited = function ( - featureId, - name -) { - return this._propertyTable.getProperty(featureId, name); -}; - -Cesium3DTileContentFeatureTable.prototype.getPropertyNames = function ( - results -) { - return this._propertyTable.getPropertyIds(results); -}; - -Cesium3DTileContentFeatureTable.prototype.setProperty = function ( - featureId, - name, - value -) { - return this._propertyTable.setProperty(featureId, name, value); -}; - -Cesium3DTileContentFeatureTable.prototype.update = function ( - tileset, - frameState -) { - this._batchTexture.update(tileset, frameState); -}; - -Cesium3DTileContentFeatureTable.prototype.getPickColor = function (featureId) { - return this._batchTexture.getPickColor(featureId); -}; - -/** - * Returns true if this object was destroyed; otherwise, false. - *

- * If this object was destroyed, it should not be used; calling any function other than - * isDestroyed will result in a {@link DeveloperError} exception. - *

- * - * @returns {Boolean} true if this object was destroyed; otherwise, false. - * - * @see Cesium3DTileContentFeatureTable#destroy - * @private - */ -Cesium3DTileContentFeatureTable.prototype.isDestroyed = function () { - return false; -}; - -/** - * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic - * release of WebGL resources, instead of relying on the garbage collector to destroy this object. - *

- * Once an object is destroyed, it should not be used; calling any function other than - * isDestroyed will result in a {@link DeveloperError} exception. Therefore, - * assign the return value (undefined) to the object as done in the example. - *

- * - * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. - * - * @example - * e = e && e.destroy(); - * - * @see Cesium3DTileContentFeatureTable#isDestroyed - * @private - */ -Cesium3DTileContentFeatureTable.prototype.destroy = function (frameState) { - this._batchTexture.destroy(); - destroyObject(this); -}; diff --git a/Source/Scene/ModelExperimental/FeatureIdPipelineStage.js b/Source/Scene/ModelExperimental/FeatureIdPipelineStage.js index 8565a3b55ec3..0f1122621dd7 100644 --- a/Source/Scene/ModelExperimental/FeatureIdPipelineStage.js +++ b/Source/Scene/ModelExperimental/FeatureIdPipelineStage.js @@ -98,44 +98,34 @@ function getFeatureIdAttributeInfo( function processFeatureIdAttributes(renderResources, frameState, primitive) { var shaderBuilder = renderResources.shaderBuilder; var model = renderResources.model; + var instances = renderResources.runtimeNode.node.instances; - var featureIdAttributePrefix; + var featureIdAttributeIndex = model.featureIdAttributeIndex; + + var featureIdAttributeInfo = getFeatureIdAttributeInfo( + featureIdAttributeIndex, + primitive, + instances + ); + + var featureIdAttribute = featureIdAttributeInfo.attribute; + var featureIdAttributePrefix = featureIdAttributeInfo.prefix; var featureIdAttributeSetIndex; - // For 3D Tiles 1.0, the FEATURE_ID vertex attribute is present but the Feature ID attribute is not. - // The featureMetadata is owned by the Cesium3DTileContent for the legacy formats. - var content = model.content; - if (defined(content) && defined(content.featureMetadata)) { - featureIdAttributePrefix = "a_featureId"; - featureIdAttributeSetIndex = ""; + // Check if the Feature ID attribute references an existing vertex attribute. + if (defined(featureIdAttribute.setIndex)) { + featureIdAttributeSetIndex = featureIdAttribute.setIndex; } else { - var featureIdAttributeIndex = model.featureIdAttributeIndex; - var instances = renderResources.runtimeNode.node.instances; - - var featureIdAttributeInfo = getFeatureIdAttributeInfo( - featureIdAttributeIndex, - primitive, - instances + // Ensure that the new Feature ID vertex attribute generated does not have any conflicts with + // Feature ID vertex attributes already provided in the model. The featureIdVertexAttributeSetIndex + // is incremented every time a Feature ID vertex attribute is added. + featureIdAttributeSetIndex = renderResources.featureIdVertexAttributeSetIndex++; + generateFeatureIdAttribute( + featureIdAttributeInfo, + featureIdAttributeSetIndex, + frameState, + renderResources ); - - var featureIdAttribute = featureIdAttributeInfo.attribute; - featureIdAttributePrefix = featureIdAttributeInfo.prefix; - - // Check if the Feature ID attribute references an existing vertex attribute. - if (defined(featureIdAttribute.setIndex)) { - featureIdAttributeSetIndex = featureIdAttribute.setIndex; - } else { - // Ensure that the new Feature ID vertex attribute generated does not have any conflicts with - // Feature ID vertex attributes already provided in the model. The featureIdVertexAttributeSetIndex - // is incremented every time a Feature ID vertex attribute is added. - featureIdAttributeSetIndex = renderResources.featureIdVertexAttributeSetIndex++; - generateFeatureIdAttribute( - featureIdAttributeInfo, - featureIdAttributeSetIndex, - frameState, - renderResources - ); - } } shaderBuilder.addDefine( diff --git a/Source/Scene/ModelExperimental/ModelExperimental.js b/Source/Scene/ModelExperimental/ModelExperimental.js index c9a42896fde4..36454e5fe3b0 100644 --- a/Source/Scene/ModelExperimental/ModelExperimental.js +++ b/Source/Scene/ModelExperimental/ModelExperimental.js @@ -11,7 +11,7 @@ import when from "../../ThirdParty/when.js"; import destroyObject from "../../Core/destroyObject.js"; import Matrix4 from "../../Core/Matrix4.js"; import ModelFeatureTable from "./ModelFeatureTable.js"; -import Cesium3DTileContentFeatureTable from "./Cesium3DTileContentFeatureTable.js"; +import B3dmLoader from "./B3dmLoader.js"; /** * A 3D model. This is a new architecture that is more decoupled than the older {@link Model}. This class is still experimental. @@ -56,7 +56,9 @@ export default function ModelExperimental(options) { this._loader = options.loader; this._resource = options.resource; - this._modelMatrix = defaultValue(options.modelMatrix, Matrix4.IDENTITY); + this._modelMatrix = Matrix4.clone( + defaultValue(options.modelMatrix, Matrix4.IDENTITY) + ); this._resourcesLoaded = false; this._drawCommandsBuilt = false; @@ -95,27 +97,6 @@ export default function ModelExperimental(options) { initialize(this); } -function createContentFeatureTables(content, featureMetadata) { - var contentFeatureTables = []; - - var propertyTables = featureMetadata.propertyTables; - for (var i = 0; i < propertyTables.length; i++) { - { - var propertyTable = propertyTables[i]; - var contentFeatureTable = new Cesium3DTileContentFeatureTable({ - content: content, - propertyTable: propertyTable, - }); - - if (contentFeatureTable.featuresLength > 0) { - contentFeatureTables.push(contentFeatureTable); - } - } - } - - return contentFeatureTables; -} - function createModelFeatureTables(model, featureMetadata) { var modelFeatureTables = []; @@ -136,12 +117,7 @@ function createModelFeatureTables(model, featureMetadata) { return modelFeatureTables; } -function selectFeatureTableId(components, model, content) { - // For 3D Tiles 1.0 formats, the feature table is always the first table - if (defined(content) && defined(content.featureMetadata)) { - return 0; - } - +function selectFeatureTableId(components, model) { var featureIdAttributeIndex = model._featureIdAttributeIndex; var featureIdTextureIndex = model._featureIdTextureIndex; @@ -185,39 +161,30 @@ function selectFeatureTableId(components, model, content) { function initialize(model) { var loader = model._loader; var resource = model._resource; - var modelMatrix = model._modelMatrix; loader.load(); loader.promise .then(function (loader) { - var components = loader.components; - var content = model._content; + Matrix4.multiply( + model._modelMatrix, + loader.transform, + model._modelMatrix + ); - // For 3D Tiles 1.0 formats, the feature metadata is owned by the Cesium3DTileContent classes. - // Otherwise, the metadata is owned by ModelExperimental. - var hasContent = defined(content); - var propertyTableOwner = hasContent ? content : model; - var featureMetadata = defined(propertyTableOwner.featureMetadata) - ? content.featureMetadata - : components.featureMetadata; + var components = loader.components; + var featureMetadata = components.featureMetadata; if (defined(featureMetadata) && featureMetadata.propertyTableCount > 0) { - var featureTableId = selectFeatureTableId(components, model, content); - var featureTables; - if (hasContent) { - featureTables = createContentFeatureTables(content, featureMetadata); - } else { - featureTables = createModelFeatureTables(model, featureMetadata); - } - propertyTableOwner.featureTables = featureTables; - propertyTableOwner.featureTableId = featureTableId; + var featureTableId = selectFeatureTableId(components, model); + var featureTables = createModelFeatureTables(model, featureMetadata); + model.featureTables = featureTables; + model.featureTableId = featureTableId; } model._sceneGraph = new ModelExperimentalSceneGraph({ model: model, modelComponents: components, - modelMatrix: modelMatrix, }); model._resourcesLoaded = true; }) @@ -337,7 +304,7 @@ Object.defineProperties(ModelExperimental.prototype, { * * @memberof ModelExperimental.prototype * - * @type {String} + * @type {Number} * @readonly * * @private @@ -356,7 +323,7 @@ Object.defineProperties(ModelExperimental.prototype, { * * @memberof ModelExperimental.prototype * - * @type {Object.} + * @type {Array} * @readonly * * @private @@ -692,6 +659,40 @@ ModelExperimental.fromGltf = function (options) { return model; }; +/* + * @private + */ +ModelExperimental.fromB3dm = function (options) { + var loaderOptions = { + b3dmResource: options.resource, + arrayBuffer: options.arrayBuffer, + byteOffset: options.byteOffset, + releaseGltfJson: options.releaseGltfJson, + incrementallyLoadTextures: options.incrementallyLoadTextures, + upAxis: options.upAxis, + forwardAxis: options.forwardAxis, + }; + + var loader = new B3dmLoader(loaderOptions); + + var modelOptions = { + loader: loader, + resource: loaderOptions.b3dmResource, + modelMatrix: options.modelMatrix, + debugShowBoundingVolume: options.debugShowBoundingVolume, + cull: options.cull, + opaquePass: options.opaquePass, + allowPicking: options.allowPicking, + customShader: options.customShader, + content: options.content, + show: options.show, + featureIdAttributeIndex: options.featureIdAttributeIndex, + featureIdTextureIndex: options.featureIdTextureIndex, + }; + var model = new ModelExperimental(modelOptions); + return model; +}; + function updateShowBoundingVolume(sceneGraph, debugShowBoundingVolume) { var drawCommands = sceneGraph._drawCommands; for (var i = 0; i < drawCommands.length; i++) { diff --git a/Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js b/Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js new file mode 100644 index 000000000000..fca02da38385 --- /dev/null +++ b/Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js @@ -0,0 +1,237 @@ +import Axis from "../Axis.js"; +import defined from "../../Core/defined.js"; +import destroyObject from "../../Core/destroyObject.js"; +import ModelExperimental from "./ModelExperimental.js"; +import Pass from "../../Renderer/Pass.js"; + +/** + * Represents the contents of a glTF, glb or + * {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel|Batched 3D Model} + * tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset. + *

+ * Implements the {@link Cesium3DTileContent} interface. + *

+ * @alias ModelExperimental3DTileContent + * @constructor + * @private + */ +export default function ModelExperimental3DTileContent( + tileset, + tile, + resource +) { + this._tileset = tileset; + this._tile = tile; + this._resource = resource; + + this._model = undefined; + this._groupMetadata = undefined; +} + +Object.defineProperties(ModelExperimental3DTileContent.prototype, { + featuresLength: { + get: function () { + var model = this._model; + var featureTables = model.featureTables; + var featureTableId = model.featureTableId; + + if (defined(featureTables) && defined(featureTables[featureTableId])) { + return featureTables[featureTableId].featuresLength; + } + + return 0; + }, + }, + + pointsLength: { + get: function () { + return 0; + }, + }, + + trianglesLength: { + get: function () { + return 0; + }, + }, + + geometryByteLength: { + get: function () { + return 0; + }, + }, + + texturesByteLength: { + get: function () { + return 0; + }, + }, + + batchTableByteLength: { + get: function () { + return 0; + }, + }, + + innerContents: { + get: function () { + return undefined; + }, + }, + + readyPromise: { + get: function () { + return this._model.readyPromise; + }, + }, + + tileset: { + get: function () { + return this._tileset; + }, + }, + + tile: { + get: function () { + return this._tile; + }, + }, + + url: { + get: function () { + return this._resource.getUrlComponent(true); + }, + }, + + batchTable: { + get: function () { + var model = this._model; + var featureTables = model.featureTables; + var featureTableId = model.featureTableId; + + if (defined(featureTables) && defined(featureTables[featureTableId])) { + return featureTables[featureTableId]; + } + + return undefined; + }, + }, + + groupMetadata: { + get: function () { + return this._groupMetadata; + }, + set: function (value) { + this._groupMetadata = value; + }, + }, +}); + +ModelExperimental3DTileContent.prototype.getFeature = function (featureId) { + var model = this._model; + var featureTableId = model.featureTableId; + if (!defined(featureTableId)) { + return undefined; + } + + var featureTable = model.featureTables[featureTableId]; + return featureTable.getFeature(featureId); +}; + +ModelExperimental3DTileContent.prototype.hasProperty = function ( + featureId, + name +) { + var model = this._model; + var featureTableId = model.featureTableId; + if (!defined(featureTableId)) { + return false; + } + + var featureTable = model.featureTables[featureTableId]; + return featureTable.hasProperty(featureId, name); +}; + +ModelExperimental3DTileContent.prototype.applyDebugSettings = function ( + enabled, + color +) { + return; +}; + +ModelExperimental3DTileContent.prototype.applyStyle = function (style) { + return; +}; + +ModelExperimental3DTileContent.prototype.update = function ( + tileset, + frameState +) { + var model = this._model; + var tile = this._tile; + + model.modelMatrix = tile.computedTransform; + + model.update(frameState); +}; + +ModelExperimental3DTileContent.prototype.isDestroyed = function () { + return false; +}; + +ModelExperimental3DTileContent.prototype.destroy = function () { + this._model = this._model && this._model.destroy(); + return destroyObject(this); +}; + +ModelExperimental3DTileContent.fromGltf = function ( + tileset, + tile, + resource, + gltf +) { + var content = new ModelExperimental3DTileContent(tileset, tile, resource); + + var modelOptions = { + gltf: gltf, + cull: false, // The model is already culled by 3D Tiles + releaseGltfJson: true, // Models are unique and will not benefit from caching so save memory + opaquePass: Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass + basePath: resource, + modelMatrix: tile.computedTransform, + upAxis: tileset._gltfUpAxis, + forwardAxis: Axis.X, + incrementallyLoadTextures: false, + customShader: tileset.customShader, + content: content, + }; + content._model = ModelExperimental.fromGltf(modelOptions); + return content; +}; + +ModelExperimental3DTileContent.fromB3dm = function ( + tileset, + tile, + resource, + arrayBuffer, + byteOffset +) { + var content = new ModelExperimental3DTileContent(tileset, tile, resource); + + var modelOptions = { + arrayBuffer: arrayBuffer, + byteOffset: byteOffset, + resource: resource, + cull: false, // The model is already culled by 3D Tiles + releaseGltfJson: true, // Models are unique and will not benefit from caching so save memory + opaquePass: Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass + modelMatrix: tile.computedTransform, + upAxis: tileset._gltfUpAxis, + forwardAxis: Axis.X, + incrementallyLoadTextures: false, + customShader: tileset.customShader, + content: content, + }; + content._model = ModelExperimental.fromB3dm(modelOptions); + return content; +}; diff --git a/Source/Scene/ModelExperimental/ModelExperimentalPrimitive.js b/Source/Scene/ModelExperimental/ModelExperimentalPrimitive.js index ddd79e6cca91..2bd74663584c 100644 --- a/Source/Scene/ModelExperimental/ModelExperimentalPrimitive.js +++ b/Source/Scene/ModelExperimental/ModelExperimentalPrimitive.js @@ -12,6 +12,7 @@ import LightingPipelineStage from "./LightingPipelineStage.js"; import MaterialPipelineStage from "./MaterialPipelineStage.js"; import ModelExperimentalUtility from "./ModelExperimentalUtility.js"; import PickingPipelineStage from "./PickingPipelineStage.js"; +import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; /** * In memory representation of a single primitive, that is, a primitive @@ -82,7 +83,6 @@ function initialize(runtimePrimitive) { var primitive = runtimePrimitive.primitive; var node = runtimePrimitive.node; var model = runtimePrimitive.model; - var content = model.content; var customShader = model.customShader; var hasCustomShader = defined(customShader); @@ -111,26 +111,38 @@ function initialize(runtimePrimitive) { pipelineStages.push(LightingPipelineStage); + // Add the FeatureIdPipelineStage and BatchTexturePipelineStage when the primitive has features, i.e. when at least one of the following conditions exists: + // - the node is instanced and has feature ID attributes + // - the primitive has a feature ID vertex attributes + // - the primitive has a feature ID texture + // It must be noted that we check for the presence of feature ID vertex attributes, and not feature ID attributes, because it is possible to have features in a model + // without a feature table (for example, in 3D Tiles 1.0, where batch length > 0 but a batch table is not defined.) var featureIdAttributeIndex = model.featureIdAttributeIndex; var featureIdTextureIndex = model.featureIdTextureIndex; - - var hasInstancedFeatureIds; + var hasInstancedFeatureIdAttribute; if ( defined(node.instances) && node.instances.featureIdAttributes.length > 0 ) { var featureIdAttributes = node.instances.featureIdAttributes; if (defined(featureIdAttributes[featureIdAttributeIndex])) { - hasInstancedFeatureIds = true; + hasInstancedFeatureIdAttribute = true; } } - - var hasContentMetadata = defined(content) && defined(content.featureMetadata); + var hasFeatureIdVertexAttribute = defined( + ModelExperimentalUtility.getAttributeBySemantic( + primitive, + VertexAttributeSemantic.FEATURE_ID + ) + ); + var hasFeatureIdTexture = defined( + primitive.featureIdTextures[featureIdTextureIndex] + ); var hasFeatureIds = - defined(primitive.featureIdAttributes[featureIdAttributeIndex]) || - defined(primitive.featureIdTextures[featureIdTextureIndex]) || - hasContentMetadata; - if (hasInstancedFeatureIds || hasFeatureIds) { + hasInstancedFeatureIdAttribute || + hasFeatureIdVertexAttribute || + hasFeatureIdTexture; + if (hasInstancedFeatureIdAttribute || hasFeatureIds) { pipelineStages.push(FeatureIdPipelineStage); pipelineStages.push(BatchTexturePipelineStage); } diff --git a/Source/Scene/ModelExperimental/ModelFeatureTable.js b/Source/Scene/ModelExperimental/ModelFeatureTable.js index 43a734c9e1fd..e5f5deede960 100644 --- a/Source/Scene/ModelExperimental/ModelFeatureTable.js +++ b/Source/Scene/ModelExperimental/ModelFeatureTable.js @@ -1,4 +1,7 @@ import BatchTexture from "../BatchTexture.js"; +import Cesium3DTileFeature from "../Cesium3DTileFeature.js"; +import Check from "../../Core/Check.js"; +import defined from "../../Core/defined.js"; import destroyObject from "../../Core/destroyObject.js"; import ModelFeature from "./ModelFeature.js"; @@ -8,7 +11,7 @@ import ModelFeature from "./ModelFeature.js"; * * @param {Object} options An object containing the following options: * @param {ModelExperimental} options.model The model that owns this feature table. - * @param {PropertyTable} options.propertyTable The feature table from the model used to initialize the model. + * @param {PropertyTable} options.propertyTable The property table from the model used to initialize the model. * * @alias ModelFeatureTable * @constructor @@ -17,8 +20,17 @@ import ModelFeature from "./ModelFeature.js"; * @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. */ export default function ModelFeatureTable(options) { - this._propertyTable = options.propertyTable; - this._model = options.model; + var model = options.model; + var propertyTable = options.propertyTable; + + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("propertyTable", propertyTable); + Check.typeOf.object("model", model); + //>>includeEnd('debug'); + + this._propertyTable = propertyTable; + this._model = model; + this._features = undefined; this._featuresLength = 0; @@ -62,6 +74,9 @@ Object.defineProperties(ModelFeatureTable.prototype, { }); function initialize(modelFeatureTable) { + var content = modelFeatureTable._model.content; + var hasContent = defined(content); + var featuresLength = modelFeatureTable._propertyTable.count; if (featuresLength === 0) { return; @@ -69,19 +84,26 @@ function initialize(modelFeatureTable) { var features = new Array(featuresLength); for (var i = 0; i < featuresLength; i++) { - features[i] = new ModelFeature({ - model: modelFeatureTable._model, - featureId: i, - featureTable: modelFeatureTable, - }); + if (hasContent) { + features[i] = new Cesium3DTileFeature(content, i); + } else { + features[i] = new ModelFeature({ + model: modelFeatureTable._model, + featureId: i, + featureTable: modelFeatureTable, + }); + } } modelFeatureTable._features = features; modelFeatureTable._featuresLength = featuresLength; + modelFeatureTable._batchTexture = new BatchTexture({ featuresLength: featuresLength, owner: modelFeatureTable, - statistics: modelFeatureTable._statistics, + statistics: hasContent + ? content.tileset.statistics + : modelFeatureTable._statistics, }); } @@ -96,6 +118,34 @@ ModelFeatureTable.prototype.update = function (frameState) { this._batchTexture.update(undefined, frameState); }; +ModelFeatureTable.prototype.setShow = function (featureId, show) { + this._batchTexture.setShow(featureId, show); +}; + +ModelFeatureTable.prototype.setAllShow = function (show) { + this._batchTexture.setAllShow(show); +}; + +ModelFeatureTable.prototype.getShow = function (featureId) { + return this._batchTexture.getShow(featureId); +}; + +ModelFeatureTable.prototype.setColor = function (featureId, color) { + this._batchTexture.setColor(featureId, color); +}; + +ModelFeatureTable.prototype.setAllColor = function (color) { + this._batchTexture.setAllColor(color); +}; + +ModelFeatureTable.prototype.getColor = function (featureId, result) { + return this._batchTexture.getColor(featureId, result); +}; + +ModelFeatureTable.prototype.getPickColor = function (featureId) { + return this._batchTexture.getPickColor(featureId); +}; + ModelFeatureTable.prototype.getFeature = function (featureId) { return this._features[featureId]; }; @@ -108,10 +158,6 @@ ModelFeatureTable.prototype.getProperty = function (featureId, name) { return this._propertyTable.getProperty(featureId, name); }; -ModelFeatureTable.prototype.getPropertyInherited = function (featureId, name) { - return this._propertyTable.getProperty(featureId, name); -}; - ModelFeatureTable.prototype.getPropertyNames = function (results) { return this._propertyTable.getPropertyIds(results); }; diff --git a/Source/Scene/ModelExperimental/ModelRenderResources.js b/Source/Scene/ModelExperimental/ModelRenderResources.js index b6be4082aac1..c4a06fb8dcad 100644 --- a/Source/Scene/ModelExperimental/ModelRenderResources.js +++ b/Source/Scene/ModelExperimental/ModelRenderResources.js @@ -1,5 +1,4 @@ import Check from "../../Core/Check.js"; -import defined from "../../Core/defined.js"; import ShaderBuilder from "../../Renderer/ShaderBuilder.js"; /** @@ -35,15 +34,4 @@ export default function ModelRenderResources(model) { * @private */ this.model = model; - /** - * The feature table ID to use for determining features within the model. - * - * @type {String} - * @readonly - * - * @private - */ - this.featureTableId = defined(model.content) - ? model.content.featureTableId - : model.featureTableId; } diff --git a/Source/Scene/ModelExperimental/NodeRenderResources.js b/Source/Scene/ModelExperimental/NodeRenderResources.js index e69490802fe4..ae3843f8d7bc 100644 --- a/Source/Scene/ModelExperimental/NodeRenderResources.js +++ b/Source/Scene/ModelExperimental/NodeRenderResources.js @@ -40,17 +40,6 @@ export default function NodeRenderResources(modelRenderResources, runtimeNode) { */ this.shaderBuilder = modelRenderResources.shaderBuilder.clone(); - /** - * The ID of the feature table to use for picking and styling. Inherited from the model - * render resources. - * - * @type {String} - * @readonly - * - * @private - */ - this.featureTableId = modelRenderResources.featureTableId; - // other properties /** * A reference to the runtime node diff --git a/Source/Scene/ModelExperimental/PickingPipelineStage.js b/Source/Scene/ModelExperimental/PickingPipelineStage.js index be6189fb09c3..7ad25f0e35cc 100644 --- a/Source/Scene/ModelExperimental/PickingPipelineStage.js +++ b/Source/Scene/ModelExperimental/PickingPipelineStage.js @@ -106,14 +106,13 @@ function buildPickObject(renderResources, instanceId) { function processPickTexture(renderResources, primitive, instances) { var model = renderResources.model; - var content = model.content; var featureTableId; var featureIdAttribute; var featureIdAttributeIndex = model.featureIdAttributeIndex; - if (defined(content)) { + if (defined(model.featureTableId)) { // Extract the Feature Table ID from the Cesium3DTileContent. - featureTableId = content.featureTableId; + featureTableId = model.featureTableId; } else if (defined(instances)) { // Extract the Feature Table ID from the instanced Feature ID attributes. featureIdAttribute = instances.featureIdAttributes[featureIdAttributeIndex]; @@ -129,12 +128,7 @@ function processPickTexture(renderResources, primitive, instances) { featureTableId = featureIdAttribute.propertyTableId; } - var featureTable; - if (defined(content)) { - featureTable = content.featureTables[featureTableId]; - } else { - featureTable = model.featureTables[featureTableId]; - } + var featureTable = model.featureTables[featureTableId]; var shaderBuilder = renderResources.shaderBuilder; shaderBuilder.addUniform( diff --git a/Source/Scene/ModelExperimental/PrimitiveRenderResources.js b/Source/Scene/ModelExperimental/PrimitiveRenderResources.js index 1d4e2e9a7bfa..eee3496d9867 100644 --- a/Source/Scene/ModelExperimental/PrimitiveRenderResources.js +++ b/Source/Scene/ModelExperimental/PrimitiveRenderResources.js @@ -86,17 +86,6 @@ export default function PrimitiveRenderResources( */ this.hasFeatureIds = false; - /** - * The ID of the feature table to use for picking and styling. Inherited from the node - * render resources. - * - * @type {String} - * @readonly - * - * @private - */ - this.featureTableId = nodeRenderResources.featureTableId; - /** * The computed model matrix for this primitive. This is cloned from the * node render resources as the primitive may further modify it diff --git a/Specs/Scene/B3dmParserSpec.js b/Specs/Scene/B3dmParserSpec.js new file mode 100644 index 000000000000..25b7665b817d --- /dev/null +++ b/Specs/Scene/B3dmParserSpec.js @@ -0,0 +1,97 @@ +import { + B3dmParser, + Cartesian3, + HeadingPitchRange, +} from "../../Source/Cesium.js"; +import Cesium3DTilesTester from "../Cesium3DTilesTester.js"; +import createScene from "../createScene.js"; + +describe( + "Scene/B3dmParser", + function () { + var scene; + var centerLongitude = -1.31968; + var centerLatitude = 0.698874; + + var deprecated1Url = + "./Data/Cesium3DTiles/Batched/BatchedDeprecated1/tileset.json"; + var deprecated2Url = + "./Data/Cesium3DTiles/Batched/BatchedDeprecated2/tileset.json"; + + function setCamera(longitude, latitude) { + // One feature is located at the center, point the camera there + var center = Cartesian3.fromRadians(longitude, latitude); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 15.0)); + } + + beforeAll(function () { + scene = createScene(); + + // Keep the error from logging to the console when running tests + spyOn(B3dmParser, "_deprecationWarning"); + }); + + afterAll(function () { + scene.destroyForSpecs(); + }); + + beforeEach(function () { + setCamera(centerLongitude, centerLatitude); + }); + + afterEach(function () { + scene.primitives.removeAll(); + }); + + it("throws with invalid version", function () { + var arrayBuffer = Cesium3DTilesTester.generateBatchedTileBuffer({ + version: 2, + }); + Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "b3dm"); + }); + + it("throws with empty gltf", function () { + // Expect to throw DeveloperError in Model due to invalid gltf magic + var arrayBuffer = Cesium3DTilesTester.generateBatchedTileBuffer(); + Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "b3dm"); + }); + + it("throws on undefined arrayBuffer", function () { + expect(function () { + B3dmParser.parse(undefined); + }).toThrowDeveloperError(); + }); + + it("recognizes the legacy 20-byte header", function () { + return Cesium3DTilesTester.loadTileset(scene, deprecated1Url).then( + function (tileset) { + expect(B3dmParser._deprecationWarning).toHaveBeenCalled(); + Cesium3DTilesTester.expectRenderTileset(scene, tileset); + var batchTable = tileset.root.content.batchTable; + expect(batchTable._properties).toBeDefined(); + } + ); + }); + + it("recognizes the legacy 24-byte header", function () { + return Cesium3DTilesTester.loadTileset(scene, deprecated2Url).then( + function (tileset) { + expect(B3dmParser._deprecationWarning).toHaveBeenCalled(); + Cesium3DTilesTester.expectRenderTileset(scene, tileset); + var batchTable = tileset.root.content.batchTable; + expect(batchTable._properties).toBeDefined(); + } + ); + }); + + it("logs deprecation warning for use of BATCHID without prefixed underscore", function () { + return Cesium3DTilesTester.loadTileset(scene, deprecated1Url).then( + function (tileset) { + expect(B3dmParser._deprecationWarning).toHaveBeenCalled(); + Cesium3DTilesTester.expectRenderTileset(scene, tileset); + } + ); + }); + }, + "WebGL" +); diff --git a/Specs/Scene/Batched3DModel3DTileContentSpec.js b/Specs/Scene/Batched3DModel3DTileContentSpec.js index 38bdc127d6b7..85b117c1fdeb 100644 --- a/Specs/Scene/Batched3DModel3DTileContentSpec.js +++ b/Specs/Scene/Batched3DModel3DTileContentSpec.js @@ -1,17 +1,14 @@ import { + B3dmParser, Cartesian3, Color, - ExperimentalFeatures, HeadingPitchRange, HeadingPitchRoll, Matrix4, Transforms, - Batched3DModel3DTileContent, Cesium3DTilePass, ClippingPlane, ClippingPlaneCollection, - Cesium3DTileFeature, - Cesium3DTileContentFeatureTable, MetadataClass, GroupMetadata, Model, @@ -44,10 +41,6 @@ describe( "./Data/Cesium3DTiles/Batched/BatchedWithTransformRegion/tileset.json"; var texturedUrl = "./Data/Cesium3DTiles/Batched/BatchedTextured/tileset.json"; - var deprecated1Url = - "./Data/Cesium3DTiles/Batched/BatchedDeprecated1/tileset.json"; - var deprecated2Url = - "./Data/Cesium3DTiles/Batched/BatchedDeprecated2/tileset.json"; var withRtcCenterUrl = "./Data/Cesium3DTiles/Batched/BatchedWithRtcCenter/tileset.json"; @@ -61,7 +54,7 @@ describe( scene = createScene(); // Keep the error from logging to the console when running tests - spyOn(Batched3DModel3DTileContent, "_deprecationWarning"); + spyOn(B3dmParser, "_deprecationWarning"); }); afterAll(function () { @@ -76,56 +69,6 @@ describe( scene.primitives.removeAll(); }); - it("throws with invalid version", function () { - var arrayBuffer = Cesium3DTilesTester.generateBatchedTileBuffer({ - version: 2, - }); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "b3dm"); - }); - - it("recognizes the legacy 20-byte header", function () { - return Cesium3DTilesTester.loadTileset(scene, deprecated1Url).then( - function (tileset) { - expect( - Batched3DModel3DTileContent._deprecationWarning - ).toHaveBeenCalled(); - Cesium3DTilesTester.expectRenderTileset(scene, tileset); - var batchTable = tileset.root.content.batchTable; - expect(batchTable._properties).toBeDefined(); - } - ); - }); - - it("recognizes the legacy 24-byte header", function () { - return Cesium3DTilesTester.loadTileset(scene, deprecated2Url).then( - function (tileset) { - expect( - Batched3DModel3DTileContent._deprecationWarning - ).toHaveBeenCalled(); - Cesium3DTilesTester.expectRenderTileset(scene, tileset); - var batchTable = tileset.root.content.batchTable; - expect(batchTable._properties).toBeDefined(); - } - ); - }); - - it("logs deprecation warning for use of BATCHID without prefixed underscore", function () { - return Cesium3DTilesTester.loadTileset(scene, deprecated1Url).then( - function (tileset) { - expect( - Batched3DModel3DTileContent._deprecationWarning - ).toHaveBeenCalled(); - Cesium3DTilesTester.expectRenderTileset(scene, tileset); - } - ); - }); - - it("throws with empty gltf", function () { - // Expect to throw DeveloperError in Model due to invalid gltf magic - var arrayBuffer = Cesium3DTilesTester.generateBatchedTileBuffer(); - Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "b3dm"); - }); - it("resolves readyPromise", function () { return Cesium3DTilesTester.resolvesReadyPromise( scene, @@ -461,73 +404,6 @@ describe( return Cesium3DTilesTester.tileDestroys(scene, withoutBatchTableUrl); }); - describe("ModelExperimental", function () { - beforeEach(function () { - ExperimentalFeatures.enableModelExperimental = true; - }); - - afterEach(function () { - ExperimentalFeatures.enableModelExperimental = false; - }); - - it("renders B3DM content with batch table", function () { - return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( - function (tileset) { - Cesium3DTilesTester.expectRender(scene, tileset); - } - ); - }); - - it("renders B3DM content without batch table", function () { - return Cesium3DTilesTester.loadTileset( - scene, - withoutBatchTableUrl - ).then(function (tileset) { - Cesium3DTilesTester.expectRender(scene, tileset); - }); - }); - - it("assigns feature table as batch table", function () { - return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( - function (tileset) { - var content = tileset.root.content; - expect(content.batchTable).toBeDefined(); - expect(content.batchTable).toBeInstanceOf( - Cesium3DTileContentFeatureTable - ); - } - ); - }); - - it("hasProperty works", function () { - return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( - function (tileset) { - var content = tileset.root.content; - var featureTable = content.batchTable; - expect(featureTable).toBeDefined(); - for (var i = 0; i < featureTable.featuresLength; i++) { - expect(content.hasProperty(i, "Height")).toEqual(true); - expect(content.hasProperty(i, "Width")).toEqual(false); - } - } - ); - }); - - it("getFeature works", function () { - return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( - function (tileset) { - var content = tileset.root.content; - var featureTable = content.batchTable; - expect(featureTable).toBeDefined(); - for (var i = 0; i < featureTable.featuresLength; i++) { - var feature = content.getFeature(i); - expect(feature).toBeInstanceOf(Cesium3DTileFeature); - } - } - ); - }); - }); - describe("3DTILES_metadata", function () { var metadataClass = new MetadataClass({ id: "test", diff --git a/Specs/Scene/Gltf3DTileContentSpec.js b/Specs/Scene/Gltf3DTileContentSpec.js index bb2e3c3c4a9b..1396e5aa9f76 100644 --- a/Specs/Scene/Gltf3DTileContentSpec.js +++ b/Specs/Scene/Gltf3DTileContentSpec.js @@ -1,9 +1,6 @@ import { Cartesian3, - Cesium3DTileContentFeatureTable, - Cesium3DTileFeature, Cesium3DTilePass, - ExperimentalFeatures, ClippingPlane, ClippingPlaneCollection, HeadingPitchRange, @@ -23,8 +20,6 @@ describe( var gltfContentUrl = "./Data/Cesium3DTiles/GltfContent/glTF/tileset.json"; var glbContentUrl = "./Data/Cesium3DTiles/GltfContent/glb/tileset.json"; - var buildingsMetadataUrl = - "./Data/Cesium3DTiles/Metadata/FeatureMetadata/tileset.json"; function setCamera(longitude, latitude) { // One feature is located at the center, point the camera there @@ -201,76 +196,6 @@ describe( return Cesium3DTilesTester.tileDestroys(scene, gltfContentUrl); }); - describe("ModelExperimental", function () { - beforeEach(function () { - ExperimentalFeatures.enableModelExperimental = true; - }); - - afterEach(function () { - ExperimentalFeatures.enableModelExperimental = false; - }); - - it("renders glTF content with metadata", function () { - return Cesium3DTilesTester.loadTileset( - scene, - buildingsMetadataUrl - ).then(function (tileset) { - Cesium3DTilesTester.expectRender(scene, tileset); - }); - }); - - it("renders glTF content without metadata", function () { - return Cesium3DTilesTester.loadTileset(scene, glbContentUrl).then( - function (tileset) { - Cesium3DTilesTester.expectRender(scene, tileset); - } - ); - }); - - it("assigns feature table as batch table", function () { - return Cesium3DTilesTester.loadTileset( - scene, - buildingsMetadataUrl - ).then(function (tileset) { - var content = tileset.root.content; - expect(content.batchTable).toBeDefined(); - expect(content.batchTable).toBeInstanceOf( - Cesium3DTileContentFeatureTable - ); - }); - }); - - it("hasProperty works", function () { - return Cesium3DTilesTester.loadTileset( - scene, - buildingsMetadataUrl - ).then(function (tileset) { - var content = tileset.root.content; - var featureTable = content.batchTable; - expect(featureTable).toBeDefined(); - for (var i = 0; i < featureTable.featuresLength; i++) { - expect(content.hasProperty(i, "height")).toEqual(true); - expect(content.hasProperty(i, "width")).toEqual(false); - } - }); - }); - - it("getFeature works", function () { - return Cesium3DTilesTester.loadTileset( - scene, - buildingsMetadataUrl - ).then(function (tileset) { - var content = tileset.root.content; - var featureTable = content.batchTable; - expect(featureTable).toBeDefined(); - for (var i = 0; i < featureTable.featuresLength; i++) { - var feature = content.getFeature(i); - expect(feature).toBeInstanceOf(Cesium3DTileFeature); - } - }); - }); - }); - describe("3DTILES_metadata", function () { var metadataClass = new MetadataClass({ id: "test", diff --git a/Specs/Scene/GltfLoaderSpec.js b/Specs/Scene/GltfLoaderSpec.js index fcbd19657684..61b67f3ea17c 100644 --- a/Specs/Scene/GltfLoaderSpec.js +++ b/Specs/Scene/GltfLoaderSpec.js @@ -2227,6 +2227,12 @@ describe( }); }); + it("sets default transform", function () { + return loadGltf(microcosm).then(function (gltfLoader) { + expect(gltfLoader.transform).toEqual(Matrix4.IDENTITY); + }); + }); + it("destroys glTF loader", function () { var destroyFeatureMetadataLoader = spyOn( GltfFeatureMetadataLoader.prototype, diff --git a/Specs/Scene/ModelExperimental/B3dmLoaderSpec.js b/Specs/Scene/ModelExperimental/B3dmLoaderSpec.js new file mode 100644 index 000000000000..48c7192a75ce --- /dev/null +++ b/Specs/Scene/ModelExperimental/B3dmLoaderSpec.js @@ -0,0 +1,155 @@ +import { + B3dmLoader, + B3dmParser, + Cartesian3, + GltfLoader, + Matrix4, + Resource, + ResourceCache, +} from "../../../Source/Cesium.js"; +import Cesium3DTilesTester from "../../Cesium3DTilesTester.js"; +import createScene from "../../createScene.js"; +import waitForLoaderProcess from "../../waitForLoaderProcess.js"; + +describe("Scene/ModelExperimental/B3dmLoader", function () { + var withBatchTableUrl = + "./Data/Cesium3DTiles/Batched/BatchedWithBatchTable/batchedWithBatchTable.b3dm"; + var withBatchTableBinaryUrl = + "./Data/Cesium3DTiles/Batched/BatchedWithBatchTableBinary/batchedWithBatchTableBinary.b3dm"; + var withoutBatchTableUrl = + "./Data/Cesium3DTiles/Batched/BatchedWithoutBatchTable/batchedWithoutBatchTable.b3dm"; + var withRtcCenterUrl = + "./Data/Cesium3DTiles/Batched/BatchedWithRtcCenter/batchedWithRtcCenter.b3dm"; + var withBatchTableHierarchy = + "./Data/Cesium3DTiles/Hierarchy/BatchTableHierarchy/tile.b3dm"; + var scene; + var b3dmLoaders = []; + + beforeAll(function () { + scene = createScene(); + // Keep the error from logging to the console when running tests + spyOn(B3dmParser, "_deprecationWarning"); + }); + + afterAll(function () { + scene.destroyForSpecs(); + }); + + afterEach(function () { + for (var i = 0; i < b3dmLoaders.length; i++) { + var loader = b3dmLoaders[i]; + if (!loader.isDestroyed()) { + loader.destroy(); + } + } + b3dmLoaders.length = 0; + ResourceCache.clearForSpecs(); + }); + + function loadB3dm(b3dmPath) { + var resource = Resource.createIfNeeded(b3dmPath); + + return Resource.fetchArrayBuffer({ + url: b3dmPath, + }).then(function (arrayBuffer) { + var loader = new B3dmLoader({ + b3dmResource: resource, + arrayBuffer: arrayBuffer, + }); + b3dmLoaders.push(loader); + loader.load(); + + return waitForLoaderProcess(loader, scene); + }); + } + + it("loads BatchedWithBatchTable", function () { + return loadB3dm(withBatchTableUrl).then(function (loader) { + var components = loader.components; + var featureMetadata = components.featureMetadata; + var propertyTable = featureMetadata.getPropertyTable(0); + expect(propertyTable).toBeDefined(); + expect(propertyTable.count).toEqual(10); + expect(propertyTable.class).toBeDefined(); + }); + }); + + it("loads BatchedWithBatchTableBinary", function () { + return loadB3dm(withBatchTableBinaryUrl).then(function (loader) { + var components = loader.components; + var featureMetadata = components.featureMetadata; + var propertyTable = featureMetadata.getPropertyTable(0); + expect(propertyTable).toBeDefined(); + expect(propertyTable.count).toEqual(10); + expect(propertyTable.class).toBeDefined(); + }); + }); + + it("loads BatchedWithoutBatchTableUrl", function () { + return loadB3dm(withoutBatchTableUrl).then(function (loader) { + var components = loader.components; + var featureMetadata = components.featureMetadata; + var propertyTable = featureMetadata.getPropertyTable(0); + expect(propertyTable).toBeDefined(); + expect(propertyTable.count).toEqual(10); + expect(propertyTable.class).toBeUndefined(); + }); + }); + + it("loads BatchedWithRtcCenterUrl", function () { + return loadB3dm(withRtcCenterUrl).then(function (loader) { + var components = loader.components; + var featureMetadata = components.featureMetadata; + var propertyTable = featureMetadata.getPropertyTable(0); + expect(propertyTable).toBeDefined(); + expect(propertyTable.count).toEqual(10); + + expect(loader.transform).toEqual( + Matrix4.fromTranslation(new Cartesian3(0.1, 0.2, 0.3)) + ); + }); + }); + + it("loads BatchTableHierarchy", function () { + return loadB3dm(withBatchTableHierarchy).then(function (loader) { + var components = loader.components; + var featureMetadata = components.featureMetadata; + var propertyTable = featureMetadata.getPropertyTable(0); + expect(propertyTable).toBeDefined(); + expect(propertyTable.count).toEqual(30); + expect(propertyTable._batchTableHierarchy).toBeDefined(); + }); + }); + + it("throws with invalid version", function () { + var arrayBuffer = Cesium3DTilesTester.generateBatchedTileBuffer({ + version: 2, + }); + Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "b3dm"); + }); + + it("throws with empty gltf", function () { + // Expect to throw DeveloperError in Model due to invalid gltf magic + var arrayBuffer = Cesium3DTilesTester.generateBatchedTileBuffer(); + Cesium3DTilesTester.loadTileExpectError(scene, arrayBuffer, "b3dm"); + }); + + it("destroys B3DM loader", function () { + var unloadGltfLoader = spyOn( + GltfLoader.prototype, + "unload" + ).and.callThrough(); + + return loadB3dm(withBatchTableUrl).then(function (loader) { + expect(loader.components).toBeDefined(); + expect(loader.isDestroyed()).toBe(false); + + loader.destroy(); + + expect(loader.components).toBeUndefined(); + expect(loader.isDestroyed()).toBe(true); + + expect(unloadGltfLoader.calls.count()).toBe(1); + }); + }); +}); diff --git a/Specs/Scene/ModelExperimental/BatchTexturePipelineStageSpec.js b/Specs/Scene/ModelExperimental/BatchTexturePipelineStageSpec.js index 60555660febe..0e1fb6cb3a71 100644 --- a/Specs/Scene/ModelExperimental/BatchTexturePipelineStageSpec.js +++ b/Specs/Scene/ModelExperimental/BatchTexturePipelineStageSpec.js @@ -54,8 +54,8 @@ describe("Scene/ModelExperimental/BatchTexturePipelineStage", function () { it("sets up batch textures from ModelExperimental", function () { var renderResources = { shaderBuilder: new ShaderBuilder(), - featureTableId: 0, model: { + featureTableId: 0, featureTables: [ { featuresLength: 10, @@ -78,34 +78,4 @@ describe("Scene/ModelExperimental/BatchTexturePipelineStage", function () { renderResources.uniformMap ); }); - - it("sets up batch textures from Cesium3DTileContent", function () { - var renderResources = { - shaderBuilder: new ShaderBuilder(), - featureTableId: 0, - model: { - content: { - featureTables: [ - { - featuresLength: 10, - batchTexture: { - batchTexture: 0, - textureDimensions: { - y: 2, - }, - textureStep: 2, - }, - }, - ], - }, - }, - }; - - BatchTexturePipelineStage.process(renderResources, {}, {}); - verifyBatchTextureShaders(renderResources.shaderBuilder); - verifyBatchTextureUniforms( - renderResources.model.content.featureTables[0], - renderResources.uniformMap - ); - }); }); diff --git a/Specs/Scene/ModelExperimental/Cesium3DTileContentFeatureTableSpec.js b/Specs/Scene/ModelExperimental/Cesium3DTileContentFeatureTableSpec.js deleted file mode 100644 index 18b6b3a1737c..000000000000 --- a/Specs/Scene/ModelExperimental/Cesium3DTileContentFeatureTableSpec.js +++ /dev/null @@ -1,144 +0,0 @@ -import { - Cesium3DTileContentFeatureTable, - Cesium3DTileFeature, -} from "../../../Source/Cesium.js"; -import MetadataTester from "../../MetadataTester.js"; - -describe("Scene/ModelExperimental/Cesium3DTileContentFeatureTable", function () { - var properties = { - height: { - type: "FLOAT32", - }, - name: { - type: "STRING", - }, - }; - var propertyValues = { - height: [1.0, 2.0], - name: ["A", "B"], - }; - - var mockContent = { - tileset: { - statistics: {}, - }, - }; - var mockPropertyTable = MetadataTester.createPropertyTable({ - properties: properties, - propertyValues: propertyValues, - }); - - it("hasProperty works", function () { - var table = new Cesium3DTileContentFeatureTable({ - content: mockContent, - propertyTable: mockPropertyTable, - }); - mockContent.batchTable = table; - var modelFeatures = table._features; - for (var i = 0; i < modelFeatures.length; i++) { - var feature = modelFeatures[i]; - expect(feature.hasProperty("height")).toEqual(true); - expect(feature.hasProperty("width")).toEqual(false); - } - }); - - it("getFeature works", function () { - var table = new Cesium3DTileContentFeatureTable({ - content: mockContent, - propertyTable: mockPropertyTable, - }); - mockContent.batchTable = table; - expect(table._featuresLength).toEqual(mockPropertyTable.count); - var modelFeatures = table._features; - for (var i = 0; i < modelFeatures.length; i++) { - var feature = table.getFeature(i); - expect(feature).toEqual(modelFeatures[i]); - expect(feature).toBeInstanceOf(Cesium3DTileFeature); - } - }); - - it("getProperty works", function () { - var table = new Cesium3DTileContentFeatureTable({ - content: mockContent, - propertyTable: mockPropertyTable, - }); - mockContent.batchTable = table; - expect(table._featuresLength).toEqual(mockPropertyTable.count); - var modelFeatures = table._features; - - for (var propertyName in properties) { - if (properties.hasOwnProperty(propertyName)) { - for (var i = 0; i < modelFeatures.length; i++) { - var feature = modelFeatures[i]; - expect(feature.getProperty(propertyName)).toEqual( - propertyValues[propertyName][i] - ); - } - } - } - }); - - it("getPropertyInherited works", function () { - var table = new Cesium3DTileContentFeatureTable({ - content: mockContent, - propertyTable: mockPropertyTable, - }); - mockContent.batchTable = table; - expect(table._featuresLength).toEqual(mockPropertyTable.count); - var modelFeatures = table._features; - - for (var propertyName in properties) { - if (properties.hasOwnProperty(propertyName)) { - for (var i = 0; i < modelFeatures.length; i++) { - var feature = modelFeatures[i]; - expect(feature.getPropertyInherited(propertyName)).toEqual( - propertyValues[propertyName][i] - ); - } - } - } - }); - - it("getPropertyNames works", function () { - var table = new Cesium3DTileContentFeatureTable({ - content: mockContent, - propertyTable: mockPropertyTable, - }); - mockContent.batchTable = table; - var modelFeatures = table._features; - var results; - for (var i = 0; i < modelFeatures.length; i++) { - results = []; - var feature = modelFeatures[i]; - expect(feature.getPropertyNames(results)).toEqual(["height", "name"]); - } - }); - - it("setProperty works", function () { - var table = new Cesium3DTileContentFeatureTable({ - content: mockContent, - propertyTable: mockPropertyTable, - }); - mockContent.batchTable = table; - var feature = table._features[0]; - expect(feature.getProperty("height")).toEqual(1.0); - feature.setProperty("height", 3.0); - expect(feature.getProperty("height")).toEqual(3.0); - }); - - it("destroy works", function () { - var table = new Cesium3DTileContentFeatureTable({ - content: mockContent, - propertyTable: mockPropertyTable, - }); - mockContent.batchTable = table; - var batchTexture = table._batchTexture; - expect(batchTexture.isDestroyed()).toEqual(false); - expect(table.isDestroyed()).toEqual(false); - - table.destroy(); - - expect(batchTexture.isDestroyed()).toEqual(true); - expect(table.isDestroyed()).toEqual(true); - }); -}); diff --git a/Specs/Scene/ModelExperimental/ModelExperimental3DTileContentSpec.js b/Specs/Scene/ModelExperimental/ModelExperimental3DTileContentSpec.js new file mode 100644 index 000000000000..3c15052c8b5d --- /dev/null +++ b/Specs/Scene/ModelExperimental/ModelExperimental3DTileContentSpec.js @@ -0,0 +1,199 @@ +import { + Cartesian3, + defined, + ExperimentalFeatures, + GroupMetadata, + HeadingPitchRange, + MetadataClass, +} from "../../../Source/Cesium.js"; +import Cesium3DTilesTester from "../../Cesium3DTilesTester.js"; +import createScene from "../../createScene.js"; + +describe("Scene/ModelExperimental/ModelExperimental3DTileContentSpec", function () { + var gltfContentUrl = "./Data/Cesium3DTiles/GltfContent/glTF/tileset.json"; + var glbContentUrl = "./Data/Cesium3DTiles/GltfContent/glb/tileset.json"; + var buildingsMetadataUrl = + "./Data/Cesium3DTiles/Metadata/FeatureMetadata/tileset.json"; + var withBatchTableUrl = + "./Data/Cesium3DTiles/Batched/BatchedWithBatchTable/tileset.json"; + var withoutBatchTableUrl = + "./Data/Cesium3DTiles/Batched/BatchedWithoutBatchTable/tileset.json"; + + var scene; + var centerLongitude = -1.31968; + var centerLatitude = 0.698874; + + function setCamera(longitude, latitude, height) { + // One feature is located at the center, point the camera there + var center = Cartesian3.fromRadians(longitude, latitude); + scene.camera.lookAt( + center, + new HeadingPitchRange(0.0, -1.57, defined(height) ? height : 100.0) + ); + } + + beforeAll(function () { + ExperimentalFeatures.enableModelExperimental = true; + scene = createScene(); + }); + + afterAll(function () { + ExperimentalFeatures.enableModelExperimental = false; + scene.destroyForSpecs(); + }); + + beforeEach(function () { + setCamera(centerLongitude, centerLatitude); + }); + + afterEach(function () { + scene.primitives.removeAll(); + }); + + it("resolves readyPromise with glb", function () { + return Cesium3DTilesTester.resolvesReadyPromise(scene, glbContentUrl); + }); + + it("resolves readyPromise with glTF", function () { + return Cesium3DTilesTester.resolvesReadyPromise(scene, gltfContentUrl); + }); + + it("resolves readyPromise with B3DM", function () { + setCamera(centerLongitude, centerLatitude, 15.0); + return Cesium3DTilesTester.resolvesReadyPromise(scene, withBatchTableUrl); + }); + + it("renders glTF content", function () { + return Cesium3DTilesTester.loadTileset(scene, buildingsMetadataUrl).then( + function (tileset) { + Cesium3DTilesTester.expectRender(scene, tileset); + } + ); + }); + + it("renders B3DM content", function () { + setCamera(centerLongitude, centerLatitude, 15.0); + return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( + function (tileset) { + Cesium3DTilesTester.expectRender(scene, tileset); + } + ); + }); + + it("picks from glTF", function () { + return Cesium3DTilesTester.loadTileset(scene, gltfContentUrl).then( + function (tileset) { + var content = tileset.root.content; + tileset.show = false; + expect(scene).toPickPrimitive(undefined); + tileset.show = true; + expect(scene).toPickAndCall(function (result) { + expect(result).toBeDefined(); + expect(result.primitive).toBe(tileset); + expect(result.content).toBe(content); + expect(content.hasProperty(0, "id")).toBe(false); + expect(content.getFeature(0)).toBeUndefined(); + }); + } + ); + }); + + it("picks from B3DM", function () { + setCamera(centerLongitude, centerLatitude, 15.0); + return Cesium3DTilesTester.loadTileset(scene, withoutBatchTableUrl).then( + function (tileset) { + var content = tileset.root.content; + tileset.show = false; + expect(scene).toPickPrimitive(undefined); + tileset.show = true; + expect(scene).toPickAndCall(function (result) { + expect(result).toBeDefined(); + expect(result.primitive).toBe(tileset); + expect(result.content).toBe(content); + expect(content.hasProperty(0, "id")).toBe(false); + expect(content.getFeature(0)).toBeDefined(); + }); + } + ); + }); + + it("picks from glTF feature table", function () { + return Cesium3DTilesTester.loadTileset(scene, buildingsMetadataUrl).then( + function (tileset) { + var content = tileset.root.content; + tileset.show = false; + expect(scene).toPickPrimitive(undefined); + tileset.show = true; + expect(scene).toPickAndCall(function (result) { + expect(result).toBeDefined(); + expect(result.primitive).toBe(tileset); + expect(result.content).toBe(content); + expect(content.batchTable).toBeDefined(); + expect(content.hasProperty(0, "id")).toBe(true); + expect(content.getFeature(0)).toBeDefined(); + }); + } + ); + }); + + it("picks from B3DM batch table", function () { + setCamera(centerLongitude, centerLatitude, 15.0); + return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then( + function (tileset) { + var content = tileset.root.content; + tileset.show = false; + expect(scene).toPickPrimitive(undefined); + tileset.show = true; + expect(scene).toPickAndCall(function (result) { + expect(result).toBeDefined(); + expect(result.primitive).toBe(tileset); + expect(result.content).toBe(content); + expect(content.batchTable).toBeDefined(); + expect(content.hasProperty(0, "id")).toBe(true); + expect(content.getFeature(0)).toBeDefined(); + }); + } + ); + }); + + it("destroys", function () { + return Cesium3DTilesTester.tileDestroys(scene, buildingsMetadataUrl); + }); + + describe("3DTILES_metadata", function () { + var metadataClass = new MetadataClass({ + id: "test", + class: { + properties: { + name: { + type: "STRING", + }, + height: { + type: "FLOAT32", + }, + }, + }, + }); + var groupMetadata = new GroupMetadata({ + id: "testGroup", + group: { + properties: { + name: "Test Group", + height: 35.6, + }, + }, + class: metadataClass, + }); + + it("assigns groupMetadata", function () { + setCamera(centerLongitude, centerLatitude, 15.0); + return Cesium3DTilesTester.loadTileset(scene, withoutBatchTableUrl).then( + function (tileset) { + var content = tileset.root.content; + content.groupMetadata = groupMetadata; + expect(content.groupMetadata).toBe(groupMetadata); + } + ); + }); + }); +}); diff --git a/Specs/Scene/ModelExperimental/ModelExperimentalPrimitiveSpec.js b/Specs/Scene/ModelExperimental/ModelExperimentalPrimitiveSpec.js index 74addb95e34c..4428945cc376 100644 --- a/Specs/Scene/ModelExperimental/ModelExperimentalPrimitiveSpec.js +++ b/Specs/Scene/ModelExperimental/ModelExperimentalPrimitiveSpec.js @@ -9,18 +9,19 @@ import { LightingPipelineStage, MaterialPipelineStage, PickingPipelineStage, + VertexAttributeSemantic, + BatchTexturePipelineStage, ModelExperimentalPrimitive, } from "../../../Source/Cesium.js"; -import BatchTexturePipelineStage from "../../../Source/Scene/ModelExperimental/BatchTexturePipelineStage.js"; describe("Scene/ModelExperimental/ModelExperimentalPrimitive", function () { var mockPrimitive = { featureIdAttributes: [], featureIdTextures: [], + attributes: [], }; var mockNode = {}; var mockModel = { - content: {}, allowPicking: true, featureIdAttributeIndex: 0, }; @@ -144,6 +145,11 @@ describe("Scene/ModelExperimental/ModelExperimentalPrimitive", function () { primitive: { featureIdAttributes: [{}, {}], featureIdTextures: [], + attributes: [ + { + semantic: VertexAttributeSemantic.FEATURE_ID, + }, + ], }, node: {}, model: { @@ -169,6 +175,7 @@ describe("Scene/ModelExperimental/ModelExperimentalPrimitive", function () { primitive: { featureIdAttributes: [], featureIdTextures: [{}, {}], + attributes: [], }, node: {}, model: { diff --git a/Specs/Scene/ModelExperimental/ModelFeatureTableSpec.js b/Specs/Scene/ModelExperimental/ModelFeatureTableSpec.js index 28427c33e320..b569476c8f7a 100644 --- a/Specs/Scene/ModelExperimental/ModelFeatureTableSpec.js +++ b/Specs/Scene/ModelExperimental/ModelFeatureTableSpec.js @@ -1,4 +1,8 @@ -import { ModelFeatureTable, ModelFeature } from "../../../Source/Cesium.js"; +import { + Cesium3DTileFeature, + ModelFeatureTable, + ModelFeature, +} from "../../../Source/Cesium.js"; import MetadataTester from "../../MetadataTester.js"; describe("Scene/ModelExperimental/ModelFeatureTable", function () { @@ -20,8 +24,39 @@ describe("Scene/ModelExperimental/ModelFeatureTable", function () { propertyValues: propertyValues, }); + it("creates ModelFeatures when model does not have content", function () { + var table = new ModelFeatureTable({ + propertyTable: mockPropertyTable, + model: {}, + }); + expect(table._featuresLength).toEqual(mockPropertyTable.count); + var modelFeatures = table._features; + for (var i = 0; i < modelFeatures.length; i++) { + var feature = table.getFeature(i); + expect(feature).toBeInstanceOf(ModelFeature); + } + }); + + it("creates ModelFeatures when model has content", function () { + var table = new ModelFeatureTable({ + propertyTable: mockPropertyTable, + model: { + content: { + tileset: {}, + }, + }, + }); + expect(table._featuresLength).toEqual(mockPropertyTable.count); + var modelFeatures = table._features; + for (var i = 0; i < modelFeatures.length; i++) { + var feature = table.getFeature(i); + expect(feature).toBeInstanceOf(Cesium3DTileFeature); + } + }); + it("hasProperty works", function () { var table = new ModelFeatureTable({ + model: {}, propertyTable: mockPropertyTable, }); var modelFeatures = table._features; @@ -34,6 +69,7 @@ describe("Scene/ModelExperimental/ModelFeatureTable", function () { it("getFeature works", function () { var table = new ModelFeatureTable({ + model: {}, propertyTable: mockPropertyTable, }); expect(table._featuresLength).toEqual(mockPropertyTable.count); @@ -47,6 +83,7 @@ describe("Scene/ModelExperimental/ModelFeatureTable", function () { it("getProperty works", function () { var table = new ModelFeatureTable({ + model: {}, propertyTable: mockPropertyTable, }); expect(table._featuresLength).toEqual(mockPropertyTable.count); @@ -66,6 +103,7 @@ describe("Scene/ModelExperimental/ModelFeatureTable", function () { it("getPropertyNames works", function () { var table = new ModelFeatureTable({ + model: {}, propertyTable: mockPropertyTable, }); var modelFeatures = table._features; @@ -79,6 +117,7 @@ describe("Scene/ModelExperimental/ModelFeatureTable", function () { it("setProperty works", function () { var table = new ModelFeatureTable({ + model: {}, propertyTable: mockPropertyTable, }); var feature = table._features[0]; @@ -89,6 +128,7 @@ describe("Scene/ModelExperimental/ModelFeatureTable", function () { it("destroy works", function () { var table = new ModelFeatureTable({ + model: {}, propertyTable: mockPropertyTable, }); var batchTexture = table._batchTexture;