From a91a15a5117473ff42813c17a9e9113a678da76e Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 19 Dec 2024 18:12:51 -0500 Subject: [PATCH 01/26] Preliminary support for EXT_primitive_voxels --- .../Scene/Cesium3DTilesVoxelProvider.js | 163 ++++++++++++------ .../engine/Source/Scene/Model/ModelUtility.js | 4 +- packages/engine/Source/Scene/VoxelProvider.js | 1 + .../engine/Source/Scene/VoxelTraversal.js | 13 +- 4 files changed, 119 insertions(+), 62 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index f473b19b96e7..9d3db264f42e 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -1,26 +1,28 @@ import Cartesian3 from "../Core/Cartesian3.js"; +import Cesium3DTilesetMetadata from "./Cesium3DTilesetMetadata.js"; import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import Ellipsoid from "../Core/Ellipsoid.js"; -import Matrix4 from "../Core/Matrix4.js"; -import OrientedBoundingBox from "../Core/OrientedBoundingBox.js"; -import Resource from "../Core/Resource.js"; -import RuntimeError from "../Core/RuntimeError.js"; -import Cesium3DTilesetMetadata from "./Cesium3DTilesetMetadata.js"; +import GltfLoader from "./GltfLoader.js"; import hasExtension from "./hasExtension.js"; import ImplicitSubtree from "./ImplicitSubtree.js"; import ImplicitSubtreeCache from "./ImplicitSubtreeCache.js"; import ImplicitTileCoordinates from "./ImplicitTileCoordinates.js"; import ImplicitTileset from "./ImplicitTileset.js"; +import Matrix4 from "../Core/Matrix4.js"; import MetadataSemantic from "./MetadataSemantic.js"; import MetadataType from "./MetadataType.js"; +import OrientedBoundingBox from "../Core/OrientedBoundingBox.js"; import preprocess3DTileContent from "./preprocess3DTileContent.js"; +import Resource from "../Core/Resource.js"; import ResourceCache from "./ResourceCache.js"; +import RuntimeError from "../Core/RuntimeError.js"; import VoxelBoxShape from "./VoxelBoxShape.js"; -import VoxelContent from "./VoxelContent.js"; import VoxelCylinderShape from "./VoxelCylinderShape.js"; import VoxelShapeType from "./VoxelShapeType.js"; +import MetadataComponentType from "./MetadataComponentType.js"; +import ComponentDatatype from "../Core/ComponentDatatype.js"; /** * A {@link VoxelProvider} that fetches voxel data from a 3D Tiles tileset. @@ -89,6 +91,14 @@ function Cesium3DTilesVoxelProvider(options) { /** @inheritdoc */ this.maximumTileCount = undefined; + /** + * glTFs that are in the process of being loaded. + * + * @type {GltfLoader[]} + * @private + */ + this._gltfLoaders = new Array(); + this._implicitTileset = undefined; this._subtreeCache = new ImplicitSubtreeCache(); } @@ -127,14 +137,14 @@ Cesium3DTilesVoxelProvider.fromUrl = async function (url) { : tilesetJson; const metadataSchema = schemaLoader.schema; - const metadata = new Cesium3DTilesetMetadata({ + const tilesetMetadata = new Cesium3DTilesetMetadata({ metadataJson: metadataJson, schema: metadataSchema, }); const provider = new Cesium3DTilesVoxelProvider(); - addAttributeInfo(provider, metadata, className); + addAttributeInfo(provider, tilesetMetadata, className); const implicitTileset = new ImplicitTileset(resource, root, metadataSchema); @@ -147,7 +157,7 @@ Cesium3DTilesVoxelProvider.fromUrl = async function (url) { provider.dimensions = Cartesian3.unpack(voxel.dimensions); provider.shapeTransform = shapeTransform; provider.globalTransform = globalTransform; - provider.maximumTileCount = getTileCount(metadata); + provider.maximumTileCount = getTileCount(tilesetMetadata); let paddingBefore; let paddingAfter; @@ -339,29 +349,7 @@ function copyArray(values, length) { return Array.from({ length }, (v, i) => valuesArray[i]); } -async function getVoxelContent(implicitTileset, tileCoordinates) { - const voxelRelative = - implicitTileset.contentUriTemplates[0].getDerivedResource({ - templateValues: tileCoordinates.getTemplateValues(), - }); - const voxelResource = implicitTileset.baseResource.getDerivedResource({ - url: voxelRelative.url, - }); - - const arrayBuffer = await voxelResource.fetchArrayBuffer(); - const preprocessed = preprocess3DTileContent(arrayBuffer); - - const voxelContent = await VoxelContent.fromJson( - voxelResource, - preprocessed.jsonPayload, - preprocessed.binaryPayload, - implicitTileset.metadataSchema, - ); - - return voxelContent; -} - -async function getSubtreePromise(provider, subtreeCoord) { +async function getSubtree(provider, subtreeCoord) { const implicitTileset = provider._implicitTileset; const subtreeCache = provider._subtreeCache; @@ -403,13 +391,20 @@ async function getSubtreePromise(provider, subtreeCoord) { } /** @inheritdoc */ -Cesium3DTilesVoxelProvider.prototype.requestData = function (options) { +Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - const tileLevel = defaultValue(options.tileLevel, 0); - const tileX = defaultValue(options.tileX, 0); - const tileY = defaultValue(options.tileY, 0); - const tileZ = defaultValue(options.tileZ, 0); - const keyframe = defaultValue(options.keyframe, 0); + const { + tileLevel = 0, + tileX = 0, + tileY = 0, + tileZ = 0, + keyframe = 0, + frameState, + } = options; + + //>>includeStart('debug', pragmas.debug); + Check.typeof.object("options.frameState", frameState); + //>>includeEnd('debug'); // 3D Tiles currently doesn't support time-dynamic data. if (keyframe !== 0) { @@ -420,7 +415,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) { // 2. Load the voxel content if available const implicitTileset = this._implicitTileset; - const names = this.names; + const { names, types, componentTypes } = this; // Can't use a scratch variable here because the object is used inside the promise chain. const tileCoordinates = new ImplicitTileCoordinates({ @@ -444,23 +439,79 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) { const that = this; - return getSubtreePromise(that, subtreeCoord) - .then(function (subtree) { - const available = isSubtreeRoot - ? subtree.childSubtreeIsAvailableAtCoordinates(tileCoordinates) - : subtree.tileIsAvailableAtCoordinates(tileCoordinates); - - if (!available) { - return Promise.reject("Tile is not available"); - } - - return getVoxelContent(implicitTileset, tileCoordinates); - }) - .then(function (voxelContent) { - return names.map(function (name) { - return voxelContent.metadataTable.getPropertyTypedArray(name); - }); - }); + const subtree = await getSubtree(that, subtreeCoord); + // TODO: this looks wrong? We get !available for tiles that are present if we ignore availability + // NOTE: these two subtree methods are ONLY used by voxels! + // NOTE: the errors are coming from the first call, childSubtreeIsAvailableAtCoordinates + const available = isSubtreeRoot + ? subtree.childSubtreeIsAvailableAtCoordinates(tileCoordinates) + : subtree.tileIsAvailableAtCoordinates(tileCoordinates); + + if (!available) { + //console.log(`requestData: not available. isSubtreeRoot = ${isSubtreeRoot}, tileLevel = ${tileLevel}`); + return Promise.reject("Tile is not available"); + } + + const gltfLoader = getGltfLoader(implicitTileset, tileCoordinates); + that._gltfLoaders.push(gltfLoader); + // TODO: try / catch + await gltfLoader.load(true); + gltfLoader.process(frameState); + await gltfLoader._loadResourcesPromise; + gltfLoader.process(frameState); + + const { attributes } = gltfLoader.components.scene.nodes[0].primitives[0]; + + const data = new Array(attributes.length); + for (let i = 0; i < attributes.length; i++) { + // The attributes array from GltfLoader is not in the same order as + // names, types, etc. from the provider. + // Find the appropriate glTF attribute based on its name. + // Note: glTF custom attribute names are prefixed with "_" + const name = `_${names[i]}`; + const attribute = attributes.find((a) => a.name === name); + if (!defined(attribute)) { + continue; + } + + const componentDatatype = MetadataComponentType.toComponentDatatype( + componentTypes[i], + ); + const componentCount = MetadataType.getComponentCount(types[i]); + const totalCount = attribute.count * componentCount; + data[i] = ComponentDatatype.createArrayBufferView( + componentDatatype, + attribute.typedArray.buffer, + attribute.typedArray.byteOffset + attribute.byteOffset, + totalCount, + ); + } + + that._gltfLoaders.splice(that._gltfLoaders.indexOf(gltfLoader), 1); + return data; }; +/** + * Get a loader for a glTF tile from a tileset + * @param {ImplicitTileset} implicitTileset The tileset from which the loader will retrieve a glTF tile + * @param {ImplicitTileCoordinates} tileCoord The coordinates of the desired tile + * @returns {GltfLoader} A loader for the requested tile + * @private + */ +function getGltfLoader(implicitTileset, tileCoord) { + const { contentUriTemplates, baseResource } = implicitTileset; + const gltfRelative = contentUriTemplates[0].getDerivedResource({ + templateValues: tileCoord.getTemplateValues(), + }); + const gltfResource = baseResource.getDerivedResource({ + url: gltfRelative.url, + }); + + return new GltfLoader({ + gltfResource: gltfResource, + releaseGltfJson: false, + loadAttributesAsTypedArray: true, + }); +} + export default Cesium3DTilesVoxelProvider; diff --git a/packages/engine/Source/Scene/Model/ModelUtility.js b/packages/engine/Source/Scene/Model/ModelUtility.js index a117a878b10f..e36c35619b4f 100644 --- a/packages/engine/Source/Scene/Model/ModelUtility.js +++ b/packages/engine/Source/Scene/Model/ModelUtility.js @@ -350,11 +350,12 @@ ModelUtility.supportedExtensions = { EXT_mesh_features: true, EXT_mesh_gpu_instancing: true, EXT_meshopt_compression: true, + EXT_primitive_voxels: true, EXT_structural_metadata: true, EXT_texture_webp: true, KHR_blend: true, KHR_draco_mesh_compression: true, - KHR_techniques_webgl: true, + KHR_implicit_shapes: true, KHR_materials_common: true, KHR_materials_pbrSpecularGlossiness: true, KHR_materials_specular: true, @@ -362,6 +363,7 @@ ModelUtility.supportedExtensions = { KHR_materials_clearcoat: true, KHR_materials_unlit: true, KHR_mesh_quantization: true, + KHR_techniques_webgl: true, KHR_texture_basisu: true, KHR_texture_transform: true, WEB3D_quantized_attributes: true, diff --git a/packages/engine/Source/Scene/VoxelProvider.js b/packages/engine/Source/Scene/VoxelProvider.js index 0b5343d5b4c3..58f59c134b70 100644 --- a/packages/engine/Source/Scene/VoxelProvider.js +++ b/packages/engine/Source/Scene/VoxelProvider.js @@ -223,6 +223,7 @@ Object.defineProperties(VoxelProvider.prototype, { * @param {number} [options.tileX=0] The tile's X coordinate. * @param {number} [options.tileY=0] The tile's Y coordinate. * @param {number} [options.tileZ=0] The tile's Z coordinate. + * @param {FrameState} options.frameState The frame state * @privateparam {number} [options.keyframe=0] The requested keyframe. * @returns {Promise|undefined} A promise to an array of typed arrays containing the requested voxel data or undefined if there was a problem loading the data. */ diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index b7e02d06f1dd..6c46046569f9 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -406,10 +406,11 @@ function recomputeBoundingVolumesRecursive(that, node) { * * @param {VoxelTraversal} that * @param {KeyframeNode} keyframeNode + * @param {FrameState} frameState * * @private */ -function requestData(that, keyframeNode) { +function requestData(that, keyframeNode, frameState) { if ( that._simultaneousRequestCount >= VoxelTraversal.simultaneousRequestCountMaximum @@ -451,19 +452,21 @@ function requestData(that, keyframeNode) { } } - function postRequestFailure() { + function postRequestFailure(error) { that._simultaneousRequestCount--; keyframeNode.state = KeyframeNode.LoadState.FAILED; } const { keyframe, spatialNode } = keyframeNode; - const promise = provider.requestData({ + const requestParameters = { tileLevel: spatialNode.level, tileX: spatialNode.x, tileY: spatialNode.y, tileZ: spatialNode.z, keyframe: keyframe, - }); + frameState: frameState, + }; + const promise = provider.requestData(requestParameters); if (defined(promise)) { that._simultaneousRequestCount++; @@ -635,7 +638,7 @@ function loadAndUnload(that, frameState) { continue; } if (highPriorityKeyframeNode.state === KeyframeNode.LoadState.UNLOADED) { - requestData(that, highPriorityKeyframeNode); + requestData(that, highPriorityKeyframeNode, frameState); } if (highPriorityKeyframeNode.state === KeyframeNode.LoadState.RECEIVED) { let addNodeIndex = 0; From 639f532a5bb4dbc0116d632bc51f700d06c8df81 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 7 Jan 2025 23:18:05 -0500 Subject: [PATCH 02/26] Adjust type info to fix build error --- .../Scene/Cesium3DTilesVoxelProvider.js | 139 +++++++++++++++--- packages/engine/Source/Scene/VoxelProvider.js | 28 ++-- 2 files changed, 130 insertions(+), 37 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index 9d3db264f42e..87123dc62bc6 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -49,46 +49,137 @@ import ComponentDatatype from "../Core/ComponentDatatype.js"; function Cesium3DTilesVoxelProvider(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - /** @inheritdoc */ + /** + * A transform from shape space to local space. If undefined, the identity matrix will be used instead. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {Matrix4|undefined} + * @readonly + */ this.shapeTransform = undefined; - /** @inheritdoc */ + /** + * A transform from local space to global space. If undefined, the identity matrix will be used instead. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {Matrix4|undefined} + * @readonly + */ this.globalTransform = undefined; - /** @inheritdoc */ + /** + * Gets the {@link VoxelShapeType} + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {VoxelShapeType} + * @readonly + */ this.shape = undefined; - /** @inheritdoc */ + /** + * Gets the minimum bounds. + * If undefined, the shape's default minimum bounds will be used instead. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {Cartesian3|undefined} + * @readonly + */ this.minBounds = undefined; - /** @inheritdoc */ + /** + * Gets the maximum bounds. + * If undefined, the shape's default maximum bounds will be used instead. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {Cartesian3|undefined} + * @readonly + */ this.maxBounds = undefined; - /** @inheritdoc */ + /** + * Gets the number of voxels per dimension of a tile. + * This is the same for all tiles in the dataset. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {Cartesian3} + * @readonly + */ this.dimensions = undefined; - /** @inheritdoc */ + /** + * Gets the number of padding voxels before the tile. + * This improves rendering quality when sampling the edge of a tile, but it increases memory usage. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {Cartesian3|undefined} + * @readonly + */ this.paddingBefore = undefined; - /** @inheritdoc */ + /** + * Gets the number of padding voxels after the tile. + * This improves rendering quality when sampling the edge of a tile, but it increases memory usage. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {Cartesian3|undefined} + * @readonly + */ this.paddingAfter = undefined; - /** @inheritdoc */ + /** + * Gets the metadata names. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {string[]} + * @readonly + */ this.names = undefined; - /** @inheritdoc */ + /** + * Gets the metadata types. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {MetadataType[]} + * @readonly + */ this.types = undefined; - /** @inheritdoc */ + /** + * Gets the metadata component types. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {MetadataComponentType[]} + * @readonly + */ this.componentTypes = undefined; - /** @inheritdoc */ + /** + * Gets the metadata minimum values. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {number[][]|undefined} + * @readonly + */ this.minimumValues = undefined; - /** @inheritdoc */ + /** + * Gets the metadata maximum values. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {number[][]|undefined} + * @readonly + */ this.maximumValues = undefined; - /** @inheritdoc */ + /** + * The maximum number of tiles that exist for this provider. + * This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. + * If this value is not known it can be undefined. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {number|undefined} + * @readonly + */ this.maximumTileCount = undefined; /** @@ -104,7 +195,7 @@ function Cesium3DTilesVoxelProvider(options) { } /** - * Creates a {@link VoxelProvider} that fetches voxel data from a 3D Tiles tileset. + * Creates a {@link Cesium3DTilesVoxelProvider} that fetches voxel data from a 3D Tiles tileset. * * @param {Resource|string} url The URL to a tileset JSON file * @returns {Promise} The created provider @@ -390,7 +481,20 @@ async function getSubtree(provider, subtreeCoord) { return subtree; } -/** @inheritdoc */ +/** + * Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z. + * + * @private + * + * @param {object} [options] Object with the following properties: + * @param {number} [options.tileLevel=0] The tile's level. + * @param {number} [options.tileX=0] The tile's X coordinate. + * @param {number} [options.tileY=0] The tile's Y coordinate. + * @param {number} [options.tileZ=0] The tile's Z coordinate. + * @param {FrameState} options.frameState The frame state + * @privateparam {number} [options.keyframe=0] The requested keyframe. + * @returns {Promise|undefined} A promise to an array of typed arrays containing the requested voxel data or undefined if there was a problem loading the data. + */ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const { @@ -448,14 +552,13 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { : subtree.tileIsAvailableAtCoordinates(tileCoordinates); if (!available) { - //console.log(`requestData: not available. isSubtreeRoot = ${isSubtreeRoot}, tileLevel = ${tileLevel}`); return Promise.reject("Tile is not available"); } const gltfLoader = getGltfLoader(implicitTileset, tileCoordinates); that._gltfLoaders.push(gltfLoader); // TODO: try / catch - await gltfLoader.load(true); + await gltfLoader.load(); gltfLoader.process(frameState); await gltfLoader._loadResourcesPromise; gltfLoader.process(frameState); diff --git a/packages/engine/Source/Scene/VoxelProvider.js b/packages/engine/Source/Scene/VoxelProvider.js index 58f59c134b70..8a7c80ea03b6 100644 --- a/packages/engine/Source/Scene/VoxelProvider.js +++ b/packages/engine/Source/Scene/VoxelProvider.js @@ -42,7 +42,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the {@link VoxelShapeType} - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {VoxelShapeType} @@ -55,7 +54,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the minimum bounds. * If undefined, the shape's default minimum bounds will be used instead. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {Cartesian3|undefined} @@ -68,7 +66,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the maximum bounds. * If undefined, the shape's default maximum bounds will be used instead. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {Cartesian3|undefined} @@ -80,7 +77,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {Cartesian3} @@ -92,7 +88,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the number of padding voxels before the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {Cartesian3|undefined} @@ -104,7 +99,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the number of padding voxels after the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {Cartesian3|undefined} @@ -116,7 +110,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the metadata names. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {string[]} @@ -128,7 +121,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the metadata types. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {MetadataType[]} @@ -140,7 +132,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the metadata component types. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {MetadataComponentType[]} @@ -152,7 +143,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the metadata minimum values. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {number[][]|undefined} @@ -164,7 +154,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the metadata maximum values. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {number[][]|undefined} @@ -175,8 +164,9 @@ Object.defineProperties(VoxelProvider.prototype, { }, /** - * The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be undefined. - * This should not be called before {@link VoxelProvider#ready} returns true. + * The maximum number of tiles that exist for this provider. + * This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. + * If this value is not known it can be undefined. * * @memberof VoxelProvider.prototype * @type {number|undefined} @@ -188,7 +178,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Gets the number of keyframes in the dataset. - * This should not be called before {@link VoxelProvider#ready} returns true. * * @memberof VoxelProvider.prototype * @type {number} @@ -200,8 +189,8 @@ Object.defineProperties(VoxelProvider.prototype, { }, /** - * Gets the {@link TimeIntervalCollection} for the dataset, or undefined if it doesn't have timestamps. - * This should not be called before {@link VoxelProvider#ready} returns true. + * Gets the {@link TimeIntervalCollection} for the dataset, + * or undefined if it doesn't have timestamps. * * @memberof VoxelProvider.prototype * @type {TimeIntervalCollection} @@ -214,9 +203,10 @@ Object.defineProperties(VoxelProvider.prototype, { }); /** - * Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z. - * This function should not be called before {@link VoxelProvider#ready} returns true. - * @function + * Requests the data for a given tile. + * The data is a flattened 3D array ordered by X, then Y, then Z. + * + * @private * * @param {object} [options] Object with the following properties: * @param {number} [options.tileLevel=0] The tile's level. From f9306a7b45b4da754d4ce8ce63ad0366be3cb25e Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 9 Jan 2025 20:52:46 -0500 Subject: [PATCH 03/26] Use smaller functions in VoxelTraversal --- .../Scene/Cesium3DTilesVoxelProvider.js | 6 +- .../engine/Source/Scene/VoxelTraversal.js | 186 +++++++++--------- 2 files changed, 101 insertions(+), 91 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index 87123dc62bc6..1ef10b3102a8 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -507,7 +507,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { } = options; //>>includeStart('debug', pragmas.debug); - Check.typeof.object("options.frameState", frameState); + Check.typeOf.object("options.frameState", frameState); //>>includeEnd('debug'); // 3D Tiles currently doesn't support time-dynamic data. @@ -552,7 +552,9 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { : subtree.tileIsAvailableAtCoordinates(tileCoordinates); if (!available) { - return Promise.reject("Tile is not available"); + return Promise.reject( + `Tile is not available at level ${tileLevel}, x ${tileX}, y ${tileY}, z ${tileZ}`, + ); } const gltfLoader = getGltfLoader(implicitTileset, tileCoordinates); diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index 6c46046569f9..e3dc942fcfed 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -499,96 +499,16 @@ function mapInfiniteRangeToZeroOne(x) { */ function loadAndUnload(that, frameState) { const frameNumber = that._frameNumber; - const primitive = that._primitive; - const shape = primitive._shape; - const targetScreenSpaceError = primitive.screenSpaceError; const priorityQueue = that._priorityQueue; - const keyframeCount = that._keyframeCount; - - const previousKeyframe = CesiumMath.clamp( - Math.floor(that._keyframeLocation), - 0, - keyframeCount - 2, - ); - const nextKeyframe = previousKeyframe + 1; - - const { camera, context, pixelRatio } = frameState; - const { positionWC, frustum } = camera; - const screenHeight = context.drawingBufferHeight / pixelRatio; - const screenSpaceErrorMultiplier = screenHeight / frustum.sseDenominator; - - /** - * @ignore - * @param {SpatialNode} spatialNode - * @param {number} visibilityPlaneMask - */ - function addToQueueRecursive(spatialNode, visibilityPlaneMask) { - spatialNode.computeScreenSpaceError(positionWC, screenSpaceErrorMultiplier); - - visibilityPlaneMask = spatialNode.visibility( - frameState, - visibilityPlaneMask, - ); - if (visibilityPlaneMask === CullingVolume.MASK_OUTSIDE) { - return; - } - spatialNode.visitedFrameNumber = frameNumber; - - // Create keyframe nodes at the playhead. - // If they already exist, nothing will be created. - if (keyframeCount === 1) { - spatialNode.createKeyframeNode(0); - } else if (spatialNode.keyframeNodes.length !== keyframeCount) { - for (let k = 0; k < keyframeCount; k++) { - spatialNode.createKeyframeNode(k); - } - } - const { screenSpaceError, keyframeNodes } = spatialNode; - const ssePriority = mapInfiniteRangeToZeroOne(screenSpaceError); - - let hasLoadedKeyframe = false; - for (let i = 0; i < keyframeNodes.length; i++) { - const keyframeNode = keyframeNodes[i]; - - keyframeNode.priority = - 10.0 * ssePriority + - keyframePriority( - previousKeyframe, - keyframeNode.keyframe, - nextKeyframe, - that, - ); - - if ( - keyframeNode.state !== KeyframeNode.LoadState.UNAVAILABLE && - keyframeNode.state !== KeyframeNode.LoadState.FAILED && - keyframeNode.priority !== -Number.MAX_VALUE - ) { - priorityQueue.insert(keyframeNode); - } - if (keyframeNode.state === KeyframeNode.LoadState.LOADED) { - hasLoadedKeyframe = true; - } - } - - if (screenSpaceError < targetScreenSpaceError || !hasLoadedKeyframe) { - // Free up memory - spatialNode.children = undefined; - return; - } - - if (!defined(spatialNode.children)) { - spatialNode.constructChildNodes(shape); - } - for (let childIndex = 0; childIndex < 8; childIndex++) { - const child = spatialNode.children[childIndex]; - addToQueueRecursive(child, visibilityPlaneMask); - } - } // Add all the nodes to the queue, to sort them by priority. priorityQueue.reset(); - addToQueueRecursive(that.rootNode, CullingVolume.MASK_INDETERMINATE); + addToQueueRecursive( + that.rootNode, + CullingVolume.MASK_INDETERMINATE, + that, + frameState, + ); // Move the nodes from the queue to array of high priority nodes. const highPriorityKeyframeNodes = that._highPriorityKeyframeNodes; @@ -665,6 +585,97 @@ function loadAndUnload(that, frameState) { } } +/** + * @param {SpatialNode} spatialNode + * @param {number} visibilityPlaneMask + * @param {VoxelTraversal} that + * @param {FrameState} frameState + * + * @private + */ +function addToQueueRecursive( + spatialNode, + visibilityPlaneMask, + that, + frameState, +) { + const { camera, context, pixelRatio, frameNumber } = frameState; + const { positionWC, frustum } = camera; + const screenHeight = context.drawingBufferHeight / pixelRatio; + const screenSpaceErrorMultiplier = screenHeight / frustum.sseDenominator; + + spatialNode.computeScreenSpaceError(positionWC, screenSpaceErrorMultiplier); + + visibilityPlaneMask = spatialNode.visibility(frameState, visibilityPlaneMask); + if (visibilityPlaneMask === CullingVolume.MASK_OUTSIDE) { + return; + } + spatialNode.visitedFrameNumber = frameNumber; + + const primitive = that._primitive; + const shape = primitive._shape; + const targetScreenSpaceError = primitive.screenSpaceError; + const priorityQueue = that._priorityQueue; + const keyframeCount = that._keyframeCount; + const previousKeyframe = CesiumMath.clamp( + Math.floor(that._keyframeLocation), + 0, + keyframeCount - 2, + ); + const nextKeyframe = previousKeyframe + 1; + + // Create keyframe nodes at the playhead. + // If they already exist, nothing will be created. + if (keyframeCount === 1) { + spatialNode.createKeyframeNode(0); + } else if (spatialNode.keyframeNodes.length !== keyframeCount) { + for (let k = 0; k < keyframeCount; k++) { + spatialNode.createKeyframeNode(k); + } + } + const { screenSpaceError, keyframeNodes } = spatialNode; + const ssePriority = mapInfiniteRangeToZeroOne(screenSpaceError); + + let hasLoadedKeyframe = false; + for (let i = 0; i < keyframeNodes.length; i++) { + const keyframeNode = keyframeNodes[i]; + + keyframeNode.priority = + 10.0 * ssePriority + + keyframePriority( + previousKeyframe, + keyframeNode.keyframe, + nextKeyframe, + that, + ); + + if ( + keyframeNode.state !== KeyframeNode.LoadState.UNAVAILABLE && + keyframeNode.state !== KeyframeNode.LoadState.FAILED && + keyframeNode.priority !== -Number.MAX_VALUE + ) { + priorityQueue.insert(keyframeNode); + } + if (keyframeNode.state === KeyframeNode.LoadState.LOADED) { + hasLoadedKeyframe = true; + } + } + + if (screenSpaceError < targetScreenSpaceError || !hasLoadedKeyframe) { + // Free up memory + spatialNode.children = undefined; + return; + } + + if (!defined(spatialNode.children)) { + spatialNode.constructChildNodes(shape); + } + for (let childIndex = 0; childIndex < 8; childIndex++) { + const child = spatialNode.children[childIndex]; + addToQueueRecursive(child, visibilityPlaneMask, that, frameState); + } +} + /** * Compute a priority for a keyframe node. * @@ -734,7 +745,6 @@ function printDebugInformation( } /** - * @ignore * @param {SpatialNode} node */ function traverseRecursive(node) { @@ -828,7 +838,6 @@ const GpuOctreeFlag = { * @function * * @param {VoxelTraversal} that - * @param {FrameState} frameState * @param {number} sampleCount * @param {number} levelBlendFactor * @private @@ -845,7 +854,6 @@ function generateOctree(that, sampleCount, levelBlendFactor) { const leafNodeOctreeData = []; /** - * @ignore * @param {SpatialNode} node * @param {number} childOctreeIndex * @param {number} childEntryIndex From aee9302824e49ed4f1281d65c9e084590e5e819f Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 10 Jan 2025 23:07:17 -0500 Subject: [PATCH 04/26] Use smaller functions in VoxelTraversal --- .../engine/Source/Scene/VoxelTraversal.js | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index e3dc942fcfed..27f790a96cdd 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -113,6 +113,12 @@ function VoxelTraversal( */ this._highPriorityKeyframeNodes = new Array(maximumTileCount); + /** + * @type {number} + * @private + */ + this._highPriorityKeyframeNodeCount = 0; + /** * @type {KeyframeNode[]} * @private @@ -312,7 +318,8 @@ VoxelTraversal.prototype.update = function ( this._frameNumber = frameState.frameNumber; const timestamp0 = getTimestamp(); - loadAndUnload(this, frameState); + selectKeyframeNodes(this, frameState); + updateKeyframeNodes(this, frameState); const timestamp1 = getTimestamp(); generateOctree(this, sampleCount, levelBlendFactor); const timestamp2 = getTimestamp(); @@ -490,14 +497,12 @@ function mapInfiniteRangeToZeroOne(x) { } /** - * @function - * * @param {VoxelTraversal} that * @param {FrameState} frameState * * @private */ -function loadAndUnload(that, frameState) { +function selectKeyframeNodes(that, frameState) { const frameNumber = that._frameNumber; const priorityQueue = that._priorityQueue; @@ -521,23 +526,29 @@ function loadAndUnload(that, frameState) { highPriorityKeyframeNode; highPriorityKeyframeNodeCount++; } + that._highPriorityKeyframeNodeCount = highPriorityKeyframeNodeCount; +} + +/** + * @param {VoxelTraversal} that + * @param {FrameState} frameState + * + * @private + */ +function updateKeyframeNodes(that, frameState) { + const megatexture = that.megatextures[0]; + const keyframeNodesInMegatextureCount = megatexture.occupiedCount; // Sort the list of keyframe nodes in the megatexture by priority, so // we can remove the lowest priority nodes if we need space. const keyframeNodesInMegatexture = that._keyframeNodesInMegatexture; - // TODO: some of the megatexture state should be stored once, not duplicate for each megatexture - const megatexture = that.megatextures[0]; - const keyframeNodesInMegatextureCount = megatexture.occupiedCount; keyframeNodesInMegatexture.length = keyframeNodesInMegatextureCount; - keyframeNodesInMegatexture.sort(function (a, b) { - if (a.highPriorityFrameNumber === b.highPriorityFrameNumber) { - return b.priority - a.priority; - } - return b.highPriorityFrameNumber - a.highPriorityFrameNumber; - }); + keyframeNodesInMegatexture.sort(keyframeNodeSort); // Add the high priority nodes to the megatexture, // removing existing lower-priority nodes if necessary. + const highPriorityKeyframeNodes = that._highPriorityKeyframeNodes; + const highPriorityKeyframeNodeCount = that._highPriorityKeyframeNodeCount; let destroyedCount = 0; let addedCount = 0; @@ -546,7 +557,7 @@ function loadAndUnload(that, frameState) { highPriorityKeyframeNodeIndex < highPriorityKeyframeNodeCount; highPriorityKeyframeNodeIndex++ ) { - highPriorityKeyframeNode = + const highPriorityKeyframeNode = highPriorityKeyframeNodes[highPriorityKeyframeNodeIndex]; if ( @@ -585,6 +596,13 @@ function loadAndUnload(that, frameState) { } } +function keyframeNodeSort(a, b) { + if (a.highPriorityFrameNumber === b.highPriorityFrameNumber) { + return b.priority - a.priority; + } + return b.highPriorityFrameNumber - a.highPriorityFrameNumber; +} + /** * @param {SpatialNode} spatialNode * @param {number} visibilityPlaneMask From bd74f9cea3740faa6602a0a9d210d2b05b51d5b8 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 14 Jan 2025 19:06:46 -0500 Subject: [PATCH 05/26] Update voxel test datasets and specs to latest format --- .../Voxel/VoxelBox3DTiles/schema.json | 14 +++ .../Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf | 88 +++++++++++++++ .../Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json | 27 ----- .../Voxel/VoxelBox3DTiles/tileset.json | 16 +-- .../Voxel/VoxelCylinder3DTiles/schema.json | 14 +++ .../VoxelCylinder3DTiles/tiles/0/0/0/0.gltf | 88 +++++++++++++++ .../VoxelCylinder3DTiles/tiles/0/0/0/0.json | 27 ----- .../Voxel/VoxelCylinder3DTiles/tileset.json | 16 +-- .../Voxel/VoxelEllipsoid3DTiles/schema.json | 14 +++ .../VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf | 100 ++++++++++++++++++ .../VoxelEllipsoid3DTiles/tiles/0/0/0/0.json | 27 ----- .../Voxel/VoxelEllipsoid3DTiles/tileset.json | 16 +-- .../VoxelMultiAttribute3DTiles/schema.json | 27 +++++ .../tiles/0/0/0/0.gltf | 91 ++++++++++++++++ .../tiles/0/0/0/0.voxel | Bin 432 -> 0 bytes .../VoxelMultiAttribute3DTiles/tileset.json | 29 +---- .../Scene/Cesium3DTilesVoxelProviderSpec.js | 50 ++++++++- 17 files changed, 492 insertions(+), 152 deletions(-) create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf delete mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.gltf delete mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.json create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf delete mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.json create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/schema.json create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tiles/0/0/0/0.gltf delete mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tiles/0/0/0/0.voxel diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json new file mode 100644 index 000000000000..43c7cd03f778 --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json @@ -0,0 +1,14 @@ +{ + "id": "voxel", + "classes": { + "voxel": { + "properties": { + "a": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": false + } + } + } + } +} diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf new file mode 100644 index 000000000000..1cb53f70a4d7 --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf @@ -0,0 +1,88 @@ +{ + "asset": { + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "mode": 2147483648, + "attributes": { + "_a": 0 + }, + "extensions": { + "EXT_primitive_voxels": { + "dimensions": [ + 2, + 2, + 2 + ] + } + } + } + ] + } + ], + "extensionsUsed": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensionsRequired": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensions": { + "EXT_structural_metadata": { + "schemaUri": "../../../../schema.json", + "propertyAttributes": [ + { + "class": "voxel", + "properties": { + "a": { + "attribute": "_a" + } + } + } + ] + } + }, + "accessors": [ + { + "bufferView": 0, + "type": "SCALAR", + "componentType": 5126, + "min": [ + 0.0 + ], + "max": [ + 1.0 + ], + "count": 8 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 32 + } + ], + "buffers": [ + { + "uri": "a.bin", + "byteLength": 32 + } + ] +} \ No newline at end of file diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json deleted file mode 100644 index 688327759afe..000000000000 --- a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "buffers": [ - { - "uri": "a.bin", - "byteLength": 32 - } - ], - "bufferViews": [ - { - "buffer": 0, - "byteOffset": 0, - "byteLength": 32 - } - ], - "propertyTables": [ - { - "class": "voxel", - "count": 8, - "properties": { - "a": { - "values": 0 - } - } - } - ], - "voxelTable": 0 -} diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json index 7d704a8f00b5..a3bbf8bc97fa 100644 --- a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json @@ -2,19 +2,7 @@ "asset": { "version": "1.1" }, - "schema": { - "id": "voxel", - "classes": { - "voxel": { - "properties": { - "a": { - "type": "SCALAR", - "componentType": "FLOAT32" - } - } - } - } - }, + "schemaUri": "schema.json", "statistics": { "classes": { "voxel": { @@ -49,7 +37,7 @@ "geometricError": 0.0, "refine": "REPLACE", "content": { - "uri": "tiles/{level}/{x}/{y}/{z}.json", + "uri": "tiles/{level}/{x}/{y}/{z}.gltf", "extensions": { "3DTILES_content_voxels": { "dimensions": [ diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json new file mode 100644 index 000000000000..43c7cd03f778 --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json @@ -0,0 +1,14 @@ +{ + "id": "voxel", + "classes": { + "voxel": { + "properties": { + "a": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": false + } + } + } + } +} diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.gltf new file mode 100644 index 000000000000..d7247ac82e1d --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.gltf @@ -0,0 +1,88 @@ +{ + "asset": { + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "mode": 2147483650, + "attributes": { + "_a": 0 + }, + "extensions": { + "EXT_primitive_voxels": { + "dimensions": [ + 2, + 2, + 2 + ] + } + } + } + ] + } + ], + "extensionsUsed": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensionsRequired": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensions": { + "EXT_structural_metadata": { + "schemaUri": "../../../../schema.json", + "propertyAttributes": [ + { + "class": "voxel", + "properties": { + "a": { + "attribute": "_a" + } + } + } + ] + } + }, + "accessors": [ + { + "bufferView": 0, + "type": "SCALAR", + "componentType": 5126, + "min": [ + 0.0 + ], + "max": [ + 1.0 + ], + "count": 8 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 32 + } + ], + "buffers": [ + { + "uri": "a.bin", + "byteLength": 32 + } + ] +} \ No newline at end of file diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.json deleted file mode 100644 index 688327759afe..000000000000 --- a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "buffers": [ - { - "uri": "a.bin", - "byteLength": 32 - } - ], - "bufferViews": [ - { - "buffer": 0, - "byteOffset": 0, - "byteLength": 32 - } - ], - "propertyTables": [ - { - "class": "voxel", - "count": 8, - "properties": { - "a": { - "values": 0 - } - } - } - ], - "voxelTable": 0 -} diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json index d1652c7e8757..07abdcad5187 100644 --- a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json @@ -2,19 +2,7 @@ "asset": { "version": "1.1" }, - "schema": { - "id": "voxel", - "classes": { - "voxel": { - "properties": { - "a": { - "type": "SCALAR", - "componentType": "FLOAT32" - } - } - } - } - }, + "schemaUri": "schema.json", "statistics": { "classes": { "voxel": { @@ -53,7 +41,7 @@ "geometricError": 0.0, "refine": "REPLACE", "content": { - "uri": "tiles/{level}/{x}/{y}/{z}.json", + "uri": "tiles/{level}/{x}/{y}/{z}.gltf", "extensions": { "3DTILES_content_voxels": { "dimensions": [ diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json new file mode 100644 index 000000000000..43c7cd03f778 --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json @@ -0,0 +1,14 @@ +{ + "id": "voxel", + "classes": { + "voxel": { + "properties": { + "a": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": false + } + } + } + } +} diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf new file mode 100644 index 000000000000..5a1815f6d105 --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf @@ -0,0 +1,100 @@ +{ + "asset": { + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "mode": 2147483649, + "attributes": { + "_a": 0 + }, + "extensions": { + "EXT_primitive_voxels": { + "dimensions": [ + 2, + 2, + 2 + ], + "bounds": { + "min": [ + 0.0, + 0.0, + -1.0 + ], + "max": [ + 1.0, + 1.0, + 0.0 + ] + } + } + } + } + ] + } + ], + "extensionsUsed": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensionsRequired": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensions": { + "EXT_structural_metadata": { + "schemaUri": "../../../../schema.json", + "propertyAttributes": [ + { + "class": "voxel", + "properties": { + "a": { + "attribute": "_a" + } + } + } + ] + } + }, + "accessors": [ + { + "bufferView": 0, + "type": "SCALAR", + "componentType": 5126, + "min": [ + 0.0 + ], + "max": [ + 1.0 + ], + "count": 8 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 32 + } + ], + "buffers": [ + { + "uri": "a.bin", + "byteLength": 32 + } + ] +} \ No newline at end of file diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.json deleted file mode 100644 index 688327759afe..000000000000 --- a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "buffers": [ - { - "uri": "a.bin", - "byteLength": 32 - } - ], - "bufferViews": [ - { - "buffer": 0, - "byteOffset": 0, - "byteLength": 32 - } - ], - "propertyTables": [ - { - "class": "voxel", - "count": 8, - "properties": { - "a": { - "values": 0 - } - } - } - ], - "voxelTable": 0 -} diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json index cbc44570ebac..b194a281a32d 100644 --- a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json @@ -2,19 +2,7 @@ "asset": { "version": "1.1" }, - "schema": { - "id": "voxel", - "classes": { - "voxel": { - "properties": { - "a": { - "type": "SCALAR", - "componentType": "FLOAT32" - } - } - } - } - }, + "schemaUri": "schema.json", "statistics": { "classes": { "voxel": { @@ -43,7 +31,7 @@ "geometricError": 0.0, "refine": "REPLACE", "content": { - "uri": "tiles/{level}/{x}/{y}/{z}.json", + "uri": "tiles/{level}/{x}/{y}/{z}.gltf", "extensions": { "3DTILES_content_voxels": { "dimensions": [ diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/schema.json new file mode 100644 index 000000000000..4c9893312194 --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/schema.json @@ -0,0 +1,27 @@ +{ + "id": "voxel", + "classes": { + "voxel": { + "properties": { + "a": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": true, + "noData": -99999.0 + }, + "b": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": true, + "noData": -99999.0 + }, + "c": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": true, + "noData": -99999.0 + } + } + } + } +} diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tiles/0/0/0/0.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tiles/0/0/0/0.gltf new file mode 100644 index 000000000000..0a987473e24a --- /dev/null +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tiles/0/0/0/0.gltf @@ -0,0 +1,91 @@ +{ + "extensionsUsed": ["EXT_structural_metadata", "EXT_primitive_voxels"], + "extensionsRequired": ["EXT_primitive_voxels"], + "accessors": [{ + "bufferView": 0, + "componentType": 5126, + "count": 8, + "type": "SCALAR" + }, { + "bufferView": 1, + "componentType": 5126, + "count": 8, + "type": "SCALAR" + }, { + "bufferView": 2, + "componentType": 5126, + "count": 8, + "type": "SCALAR" + }], + "asset": { + "version": "2.0" + }, + "buffers": [{ + "uri": "data:application/octet-stream;base64,AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "byteLength": 96 + }], + "bufferViews": [{ + "buffer": 0, + "byteLength": 32, + "target": 34962 + }, { + "buffer": 0, + "byteOffset": 32, + "byteLength": 32, + "target": 34962 + }, { + "buffer": 0, + "byteOffset": 64, + "byteLength": 32, + "target": 34962 + }], + "meshes": [{ + "primitives": [{ + "attributes": { + "_c": 0, + "_b": 1, + "_a": 2 + }, + "mode": 2147483648, + "extensions": { + "EXT_structural_metadata": { + "propertyAttributes": [0] + }, + "EXT_primitive_voxels": { + "dimensions": [2, 2, 2], + "bounds": { + "min": [-1.0, -1.0, -1.0], + "max": [1.0, 1.0, 1.0] + } + } + } + }] + }], + "nodes": [{ + "matrix": [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], + "mesh": 0 + }], + "scene": 0, + "scenes": [{ + "nodes": [0] + }], + "extensions": { + "EXT_structural_metadata": { + "schemaUri": "../../../../schema.json", + "propertyAttributes": [{ + "class": "voxel", + "properties": { + "a": { + "attribute": "_a" + }, + "b": { + "attribute": "_b" + }, + "c": { + "attribute": "_c" + } + } + }] + } + } +} diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tiles/0/0/0/0.voxel b/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tiles/0/0/0/0.voxel deleted file mode 100644 index 765dc0b2d2d760e47718ff4601bc6be66b2aeb5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 432 zcma)3%?g7s4DQ}Ua`xbKi1V@+80<238G}`6H=$V3>J%yL(U;aAgbi6CX_D_t`n7Fj zD>j6X$T(bg>j~*0sX1q51@RT*PO&4)AL;M{?PjuIoy4bQB_gOf|aT}(?qaASSo0R5j3)80$fOCouja5CTE7&12wLp pqBbeYgk)LRVj*1?Jgv<$*s1n3ypDDphmiMS-3a2VHvYix?E@@paqs{D diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tileset.json index c9867877b88b..40b392283aed 100644 --- a/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tileset.json +++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tileset.json @@ -2,30 +2,7 @@ "asset": { "version": "1.1" }, - "schema": { - "id": "voxel", - "classes": { - "voxel": { - "properties": { - "a": { - "type": "SCALAR", - "componentType": "FLOAT32", - "required": true - }, - "b": { - "type": "SCALAR", - "componentType": "FLOAT32", - "required": true - }, - "c": { - "type": "SCALAR", - "componentType": "FLOAT32", - "required": true - } - } - } - } - }, + "schemaUri": "schema.json", "statistics": { "classes": { "voxel": { @@ -56,7 +33,7 @@ "refine": "REPLACE", "transform": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0], "content": { - "uri": "tiles/{level}/{x}/{y}/{z}.voxel", + "uri": "tiles/{level}/{x}/{y}/{z}.gltf", "extensions": { "3DTILES_content_voxels": { "dimensions": [2, 2, 2], @@ -75,4 +52,4 @@ }, "extensionsUsed": ["3DTILES_content_voxels"], "extensionsRequired": ["3DTILES_content_voxels"] -} \ No newline at end of file +} diff --git a/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js index 49f5ff44fdbc..942694fb3cb5 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js @@ -10,9 +10,17 @@ import { VoxelProvider, VoxelShapeType, } from "../../index.js"; +import createScene from "../../../../Specs/createScene.js"; describe("Scene/Cesium3DTilesVoxelProvider", function () { + let scene; + + beforeEach(async function () { + scene = createScene(); + }); + afterEach(function () { + scene.destroyForSpecs(); ResourceCache.clearForSpecs(); }); @@ -43,11 +51,45 @@ describe("Scene/Cesium3DTilesVoxelProvider", function () { expect(provider.maximumValues).toEqual([[1]]); }); - it("requestData works for root tile", async function () { + it("requestData works for root tile of ellipsoid tileset", async function () { const url = "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json"; const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); - const data = await provider.requestData(); + const data = await provider.requestData({ + frameState: scene.frameState, + }); + expect(data.length).toEqual(1); + + const dimensions = provider.dimensions; + const voxelCount = dimensions.x * dimensions.y * dimensions.z; + const componentCount = MetadataType.getComponentCount(provider.types[0]); + const expectedLength = voxelCount * componentCount; + expect(data[0].length).toEqual(expectedLength); + }); + + it("requestData works for root tile of box tileset", async function () { + const url = "./Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json"; + const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); + + const data = await provider.requestData({ + frameState: scene.frameState, + }); + expect(data.length).toEqual(1); + + const dimensions = provider.dimensions; + const voxelCount = dimensions.x * dimensions.y * dimensions.z; + const componentCount = MetadataType.getComponentCount(provider.types[0]); + const expectedLength = voxelCount * componentCount; + expect(data[0].length).toEqual(expectedLength); + }); + + it("requestData works for root tile of cylinder tileset", async function () { + const url = "./Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json"; + const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); + + const data = await provider.requestData({ + frameState: scene.frameState, + }); expect(data.length).toEqual(1); const dimensions = provider.dimensions; @@ -62,7 +104,9 @@ describe("Scene/Cesium3DTilesVoxelProvider", function () { "./Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tileset.json"; const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); - const data = await provider.requestData(); + const data = await provider.requestData({ + frameState: scene.frameState, + }); expect(data.length).toBe(3); expect(data[0][0]).toBe(0.0); expect(data[1][0]).toBe(0.5); From b79ca16eb190b2a102d57eaed5e018066156bd44 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 15 Jan 2025 19:01:21 -0500 Subject: [PATCH 06/26] Add separate Sandcastle for Voxels in 3D Tiles --- .../Voxel/VoxelBox3DTiles/schema.json | 14 ++ .../Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf | 91 +++++++++++++ .../Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json | 27 ---- .../Voxel/VoxelBox3DTiles/tileset.json | 16 +-- .../Voxel/VoxelCylinder3DTiles/schema.json | 14 ++ .../VoxelCylinder3DTiles/tiles/0/0/0/0.gltf | 88 ++++++++++++ .../VoxelCylinder3DTiles/tiles/0/0/0/0.json | 27 ---- .../Voxel/VoxelCylinder3DTiles/tileset.json | 16 +-- .../Voxel/VoxelEllipsoid3DTiles/schema.json | 14 ++ .../VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf | 100 ++++++++++++++ .../VoxelEllipsoid3DTiles/tiles/0/0/0/0.json | 27 ---- .../Voxel/VoxelEllipsoid3DTiles/tileset.json | 18 +-- .../gallery/Voxels in 3D Tiles.html | 127 ++++++++++++++++++ .../Sandcastle/gallery/Voxels in 3D Tiles.jpg | Bin 0 -> 18630 bytes Apps/Sandcastle/gallery/Voxels.html | 39 ------ .../Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf | 5 +- .../Voxel/VoxelEllipsoid3DTiles/tileset.json | 2 +- 17 files changed, 460 insertions(+), 165 deletions(-) create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf delete mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.gltf delete mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.json create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf delete mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.json create mode 100644 Apps/Sandcastle/gallery/Voxels in 3D Tiles.html create mode 100644 Apps/Sandcastle/gallery/Voxels in 3D Tiles.jpg diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json new file mode 100644 index 000000000000..43c7cd03f778 --- /dev/null +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json @@ -0,0 +1,14 @@ +{ + "id": "voxel", + "classes": { + "voxel": { + "properties": { + "a": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": false + } + } + } + } +} diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf new file mode 100644 index 000000000000..51189372b25e --- /dev/null +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.gltf @@ -0,0 +1,91 @@ +{ + "asset": { + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "mode": 2147483648, + "attributes": { + "_a": 0 + }, + "extensions": { + "EXT_primitive_voxels": { + "dimensions": [ + 2, + 2, + 2 + ] + }, + "EXT_structural_metadata": { + "propertyAttributes": [0] + } + } + } + ] + } + ], + "extensionsUsed": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensionsRequired": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensions": { + "EXT_structural_metadata": { + "schemaUri": "../../../../schema.json", + "propertyAttributes": [ + { + "class": "voxel", + "properties": { + "a": { + "attribute": "_a" + } + } + } + ] + } + }, + "accessors": [ + { + "bufferView": 0, + "type": "SCALAR", + "componentType": 5126, + "min": [ + 0.0 + ], + "max": [ + 1.0 + ], + "count": 8 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 32 + } + ], + "buffers": [ + { + "uri": "a.bin", + "byteLength": 32 + } + ] +} diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json deleted file mode 100644 index 688327759afe..000000000000 --- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tiles/0/0/0/0.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "buffers": [ - { - "uri": "a.bin", - "byteLength": 32 - } - ], - "bufferViews": [ - { - "buffer": 0, - "byteOffset": 0, - "byteLength": 32 - } - ], - "propertyTables": [ - { - "class": "voxel", - "count": 8, - "properties": { - "a": { - "values": 0 - } - } - } - ], - "voxelTable": 0 -} diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json index 7d704a8f00b5..a3bbf8bc97fa 100644 --- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json @@ -2,19 +2,7 @@ "asset": { "version": "1.1" }, - "schema": { - "id": "voxel", - "classes": { - "voxel": { - "properties": { - "a": { - "type": "SCALAR", - "componentType": "FLOAT32" - } - } - } - } - }, + "schemaUri": "schema.json", "statistics": { "classes": { "voxel": { @@ -49,7 +37,7 @@ "geometricError": 0.0, "refine": "REPLACE", "content": { - "uri": "tiles/{level}/{x}/{y}/{z}.json", + "uri": "tiles/{level}/{x}/{y}/{z}.gltf", "extensions": { "3DTILES_content_voxels": { "dimensions": [ diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json new file mode 100644 index 000000000000..43c7cd03f778 --- /dev/null +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json @@ -0,0 +1,14 @@ +{ + "id": "voxel", + "classes": { + "voxel": { + "properties": { + "a": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": false + } + } + } + } +} diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.gltf new file mode 100644 index 000000000000..d7247ac82e1d --- /dev/null +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.gltf @@ -0,0 +1,88 @@ +{ + "asset": { + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "mode": 2147483650, + "attributes": { + "_a": 0 + }, + "extensions": { + "EXT_primitive_voxels": { + "dimensions": [ + 2, + 2, + 2 + ] + } + } + } + ] + } + ], + "extensionsUsed": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensionsRequired": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensions": { + "EXT_structural_metadata": { + "schemaUri": "../../../../schema.json", + "propertyAttributes": [ + { + "class": "voxel", + "properties": { + "a": { + "attribute": "_a" + } + } + } + ] + } + }, + "accessors": [ + { + "bufferView": 0, + "type": "SCALAR", + "componentType": 5126, + "min": [ + 0.0 + ], + "max": [ + 1.0 + ], + "count": 8 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 32 + } + ], + "buffers": [ + { + "uri": "a.bin", + "byteLength": 32 + } + ] +} \ No newline at end of file diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.json deleted file mode 100644 index 688327759afe..000000000000 --- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tiles/0/0/0/0.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "buffers": [ - { - "uri": "a.bin", - "byteLength": 32 - } - ], - "bufferViews": [ - { - "buffer": 0, - "byteOffset": 0, - "byteLength": 32 - } - ], - "propertyTables": [ - { - "class": "voxel", - "count": 8, - "properties": { - "a": { - "values": 0 - } - } - } - ], - "voxelTable": 0 -} diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json index d1652c7e8757..07abdcad5187 100644 --- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json @@ -2,19 +2,7 @@ "asset": { "version": "1.1" }, - "schema": { - "id": "voxel", - "classes": { - "voxel": { - "properties": { - "a": { - "type": "SCALAR", - "componentType": "FLOAT32" - } - } - } - } - }, + "schemaUri": "schema.json", "statistics": { "classes": { "voxel": { @@ -53,7 +41,7 @@ "geometricError": 0.0, "refine": "REPLACE", "content": { - "uri": "tiles/{level}/{x}/{y}/{z}.json", + "uri": "tiles/{level}/{x}/{y}/{z}.gltf", "extensions": { "3DTILES_content_voxels": { "dimensions": [ diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json new file mode 100644 index 000000000000..43c7cd03f778 --- /dev/null +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json @@ -0,0 +1,14 @@ +{ + "id": "voxel", + "classes": { + "voxel": { + "properties": { + "a": { + "type": "SCALAR", + "componentType": "FLOAT32", + "required": false + } + } + } + } +} diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf new file mode 100644 index 000000000000..5a1815f6d105 --- /dev/null +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.gltf @@ -0,0 +1,100 @@ +{ + "asset": { + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "mode": 2147483649, + "attributes": { + "_a": 0 + }, + "extensions": { + "EXT_primitive_voxels": { + "dimensions": [ + 2, + 2, + 2 + ], + "bounds": { + "min": [ + 0.0, + 0.0, + -1.0 + ], + "max": [ + 1.0, + 1.0, + 0.0 + ] + } + } + } + } + ] + } + ], + "extensionsUsed": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensionsRequired": [ + "EXT_primitive_voxels", + "EXT_structural_metadata" + ], + "extensions": { + "EXT_structural_metadata": { + "schemaUri": "../../../../schema.json", + "propertyAttributes": [ + { + "class": "voxel", + "properties": { + "a": { + "attribute": "_a" + } + } + } + ] + } + }, + "accessors": [ + { + "bufferView": 0, + "type": "SCALAR", + "componentType": 5126, + "min": [ + 0.0 + ], + "max": [ + 1.0 + ], + "count": 8 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 32 + } + ], + "buffers": [ + { + "uri": "a.bin", + "byteLength": 32 + } + ] +} \ No newline at end of file diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.json deleted file mode 100644 index 688327759afe..000000000000 --- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tiles/0/0/0/0.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "buffers": [ - { - "uri": "a.bin", - "byteLength": 32 - } - ], - "bufferViews": [ - { - "buffer": 0, - "byteOffset": 0, - "byteLength": 32 - } - ], - "propertyTables": [ - { - "class": "voxel", - "count": 8, - "properties": { - "a": { - "values": 0 - } - } - } - ], - "voxelTable": 0 -} diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json index cbc44570ebac..fa43b62f031a 100644 --- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json +++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json @@ -2,19 +2,7 @@ "asset": { "version": "1.1" }, - "schema": { - "id": "voxel", - "classes": { - "voxel": { - "properties": { - "a": { - "type": "SCALAR", - "componentType": "FLOAT32" - } - } - } - } - }, + "schemaUri": "schema.json", "statistics": { "classes": { "voxel": { @@ -37,13 +25,13 @@ 1.0, 1.0, -1.0, - 0.0 + 500000.0 ] }, "geometricError": 0.0, "refine": "REPLACE", "content": { - "uri": "tiles/{level}/{x}/{y}/{z}.json", + "uri": "tiles/{level}/{x}/{y}/{z}.gltf", "extensions": { "3DTILES_content_voxels": { "dimensions": [ diff --git a/Apps/Sandcastle/gallery/Voxels in 3D Tiles.html b/Apps/Sandcastle/gallery/Voxels in 3D Tiles.html new file mode 100644 index 000000000000..0bcbd66972b9 --- /dev/null +++ b/Apps/Sandcastle/gallery/Voxels in 3D Tiles.html @@ -0,0 +1,127 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Voxels in 3D Tiles.jpg b/Apps/Sandcastle/gallery/Voxels in 3D Tiles.jpg new file mode 100644 index 0000000000000000000000000000000000000000..af1cc3d5c70e1b70119a42733ed5dce22ee6c97a GIT binary patch literal 18630 zcmbTebx<7P*EQHMSO^+)fB^yojo>gag9dkZLV`ok!CeAL$lw+T8W>y>B)A0`AXsn@ zFoZ#ZJ0Ys>)qP7zTNIRe{^+M*K?|$e(pW@p6=V(+ZBL9RZ&F|z`?-*tnPN; zb^(wF2=MR;@bL%;@CgVB35bX(h>7prBc>)JC!wIDrl+T)rln;1GJ{8F7!8mX03G@Q9O(n}=6eL{v;1 zDk1+|K~YIrMO9DVz|hFp#MIix*3RC+(Fx(@?c?j`9}xK_D*Ela_c2MyDXD4cA2TvP z7Zjq3ic3n%YHI81zcn;AHGl8!>Fq=J4-Ae^OioSD%+AfPu3^@HZ(ujKwhxbvPfpLy zFD|eC!wUxh{U5acC$s;;i}H>aE*>5TkMKXdaBzL^0HVahe<(okKvswFr3V$8U^o%A zTw?y$u6yi4x(76`Jbx0?atN(-5FcZSYaGzOr#6pg9 z0>{0IDf^T-!>CGc0b<0nrIq8+>E!Ij^8&88I^ee?2VpeYj(TEW70bc2k#L4#9Ro$2 zTH!!=8qom~9ybcyKN3eFBNE!UOgPi2I8VZ7@NVeykmTvP9u`{N)p$-hQZuK%JeD7$ffzkPYA-G(S08M)GtjGaR{46P0tDbWdsGtEc}Zj4?X#I3#>Wj z9t?#@?|C6^0XO?w;C&uBIVOvVK_Jp{E!0knB@wxKv3rvedXc%YaY1tk;YfHEwZYp2 zHhcF@d`~K9j4(r>pAd#YJ^MQ~y?8BfINYkJ62xA$3sJ5~xCt|qLanwrYa}R@I5MOb zegf?_*87e8GN+t~ywoO8#tPd*-~!_bfjUUV1JXuglNPl4Ah37l(z`TjS}_ZAD3)FFC*uL@&U)mD4_#@BbPnd0$m(j|f_b zM#vD;kq&dY6<0ztBEl1ZLI^)MPO((Vnm8KM%MH6;B8NM8W8lpmGDPjgtiHml$qnrA zD5aL;Q`EW@bH*?rnX}@toYA#)sdw%LY0q}VO%ey^;mD4R3*)q6N%_ISz2~Vy@Svd^0hC3c;`R9Jq!UTwpr8HF6Sp znny+^6h3yPjT zOJgT)xhU$qnCd77P$cw#1diwRi(flHM?1_j6*&^o!hpB+e5&H!Eg zQ9uY%M}Zpn4|F9e-t}*T>8(n^cmcXUWte_r!(r@!4Z9(9W5vAuB$+`P@kNG>ZGmT@ z7rx~S&73jwUcGV^XJTZ?Jf?%f6poVtU5^W+@}Dv+BF@q^A@NT&Kdf2EEmYBNQl=U1W*u z?5Aq-#%r>-4*2ahkCefXofJC^vETlSGMa&lf>m_nwe}n55atPN@IveF_`!78E%4)c zw{?<7mR*>%m_YQ(aVF7qu=5```j8(X%x&N752{eUs3q6OUl&=V9`L)jeMH26dJ>*P z>NgrIZQsG<=L&++COR8lcHRs<;fRE;_vwqddgK&j0!Jk4SkO@(wi*t18otT(En2K^ z`&T!8TqT&S`18vOvpf(uWgx8`_$rJl!JWLGk3usb)D%oCD$W&|5TNaUQ$P*ijFD+b z?u93w6dHn8m_OzpXfcsRgi%;?#?JK-vLoY)QWxCO+LGfTUO%a1LlZVwLqop z;(rnJ+m#_fcUWfWOp>cgWHCjJ284`};fx9cQ!gU~l6PcBRJP=wE=CYI^e==FPM+q0 zs(~BoPw}NFFzK;(0Z_Gk0%VK?TpcSS;!(`mac>V8Tbmh8W1=JK=C~^Eehfa;{-Ic85a&wn8jdx zK*%jwI<&G;A_x&N$VY$C6&G4(6;)48oE&i1C-i8@ID zKlg22$=NMHCEwPeXUcAnCVt)cuptU(bT98Y95G2U?b?Pv)scSvAh@-Kg=6kQ=|$@; z5bwgfr+y3g2o1ZK1wA`V9f7qG?=WAy*l^zRo!l2R*5c;+SF5e!EtI^W_HdMI12!4# znts&Laf8D=AEGq+H3)pznsp0U^Pf3tTc1|Ncjn#;dimLJp*yp3FYAMweW}?9W|wmP zO`>u5s5!{;n;kr(LqfS~8JwiN)Y|hfC8tOCYCS;y@D>PTo(>)@*sNx(xp-UoJv3;{ zoczj>g5`>guXsF!!$hM7uDWdIuhQjpnY=T@e)?Jbolw5P2g!Nfw$$CqlMvg~9-+K# zYxmhJ49d82KR6aQhCA=Sg2au=&rI%6qin(NgKF*MEA1}WMWnF*PJMH!I8%fK@!dkf z81f^NtN_*^MLoGE?8uT^GuR#k3ga(K*hwB3aBT&|t3NyBk&oO0OhJzh(<7d&H%Lzr3m30;x`_V^^Q-xPo7 zwOX!$T{ax{*!|Y^qLuLYrB=p3vV$w2OkIBH$~dIOZ*)9*h2%ezm_IKIfgVq0#eUu9 z@u_X#Ai>TjxJ!7!G6DEUEK--OAUwwZ_3p}{1`J7VxfX`2D2eps`H2NEvw`D0{p^u@ z4m!E6{{4A)Twu5g%A9s|P5OJ3xS}?p)M7kJj1ZKC*pUG}P*fSwf=zUpuYlrKGF|!6 z@J39Vgd)fK?mGSo&UhFDf$~7-X)m|d(*CZ-yqb0Layk?ZXyby=mum?DlQBKG1Rk4O zswxCTOeb40fOd$spuVrLy9Coe1uk7k#Ew*Z-vYaLJ!E$MH$a$bx-m~%JTvh)}? zcDgu`K`ovF(_z%b+LB&fMLC2+LNZJ^IhWMTO5!VLd3b#)qn49@L>KWfr-i?cFv4pY zFVeZ1gn=OvU+ORnxC^c7|F@C3EpWmkC+o^XhVCSrN_T3HmQ-{k@I5(w?7eE0yV=nB zi_)@HN&l#>-`thJ$6|;;C~71yexyC-YfDWXAHgRQ0Y&NP5&p95OZICg)LDnU3y&T* zJBtqmq(dt5v@&UWEYfZCI?hqwrzJ`09x4cYkd`z!Uw<;6)e?4u-bty!cuhuVyi%5t#$n6t z*QnBFDh2wR2eZ=Vrxn9J%=KBGXaj_**(I5AJ)tetPi#Xh;ux_P+E-S`b;0O6X2do# zK+SVzQuStOz=}E;C>PDlI5OFPws(G_|LztLbk6bDA8!r~SEHfbwRSbOVFHxTsr$YK z*|@`nOqv6KnfH&rsvVde@ZglWN%gjxoOtp$?I?F|kDVoYt& zm>@2%hkJx+W(ftEF&p~gN0AM%1PLR)H_2a?XCULr6Kw|M?!(~>tha!3X=%`G%FSWo z3+B2du5bH4boD=cGcBA~K+iwk%Upi#vRGVl!roYIeDF5z$*D`)eDsgNzK7b#-IQD{ z`Q2M!yPDbJ4X)@H7`BoprQF_U(UZ(ku^d&XkuOHICBgUR0!(0ZB|sY9{9XT=7zYS{vuT!d$6`JZhQxa8fthO%3@%g2wDZ+E;?f z3evrA4Z^7r-?C_<#yQyUaht>RGSlU#oG7o)oTOUZ*bw0j%tXg?WLcMCp_>v$?@u9w z#WYvhmX3#fD;-fVUzSfEPQptnDr{#1G?4e8e^z}=?h;3ml*xAsPqwCz%%a&XP(9sp zr$k=dyVg3mc{N{O+W4iD?dGS+N*4z(9A4kJ8LZ$AW%3?11=OjsY>qXhpP4gHh6h*| zcc|(_5D<9C^qssYraoom-!y70B!qGT`sxgrd+bFoWUcvo}I=^uF+}_n^2VPg!pZw@Q8!J)Ii4LOIWfZSwL0{-TuoF!g~C6bL(7Ff?$(> z$)#;yd>i~+ZhEbh82Drsd?_SVi@-+z?vI}|sKLt2Kf%@05;2C6uYX3>lf+WdAdCljx? zkN%_*AnH557aIDgCs4uG+MV=fGEH+Lr%dRz=c5HxJiv~h5{%vXWGT%0int5R|LuOH z5gu;DKp~wpy8Aj23&irZ0mMJ2%HIVg&h(?xgimd1)S@KY_uW(@97(KPw1M@U$fGih z_P31GLXrw{Ada^iW^^yg{g=F+gb?alxdq%*`g+jQ99H>_1}~>YOSR++&&P_yLB@7} zmTZTA32ez=m)V}$GvWv%|*GK(XLPS6kS7p@axb{?!lebeQ}M?qh7U1 z*kuGHw&EA^3S66%i5f+-BwP>taW;?>FEbzefPL`}AucJF-(c?{EQEe6x#lIAB;&3h zbAAhu>`?Ax!o(6|D?&`D{>Dkf@0_r;yCq%hE)nA@Ef3Ia-U9d{0~>8E6L#9HHbRQB zo1I~y0`Y@21<~bNznp$dZWoxs7TZQGKF9DO>bO700RL~WIaKnv-wku*(B&~wGA{>B zQUt_YRjrv z{5F&JN7vZaqT=nMKG80*;9-4dKPzInLs9Bckoa9B>v{5c!P%$FRiB*mibe-!JOaG- zY~uP23xUn_{Gx5A-nk34&;!h_FQM!c^J)*w_ygM2%u!NB#_qsX>bFI1Jpex$j zgqBTxJW%v~bQOKD@02UXI$+&1fY@AeS7$QIDody3!kh$o#EoIJ{UOKGVH6K!`bXPH5M5kS9}BXJ#kabI#{)6TVN80?snSm+u=T*I zrqEWRvdW^6=F9pTvOg!(z`K?jBWY8wPjj-5$^6#WCya5zh&IyQT>b6@Q1S?Y+*z>; zp=2ESOXNnxtVMB&aIbD)yrl*p&i^nn1aIsu6mNwA=T1$6Hyy=XEf>9dfnZ)}-akju zN5KNepKpW6W*je*Y-#gWy?_hFh*nx1#5LtokhM~m#NgR~)Qw!}A3H`S5Gi8P-l z+|W~S)!RSLNcLSaPnDeu1D~(l-5uC}oHw>6!d1vq+W++9tq7>W{|cpQjy zi8nVajbQ!NqLZsFT?xlLW1hw`txu%64c-F2j1PlfXqde3Us-1fSUz~NKF!QKj*ZRG z+a*e(2n|bM6yJ?B*n6^;!0KLtZWNyV)7li-%~$@Aq%v-;nb}d%z3$}FTnEKHARMfr zKy5_1i%+d1^WVZ~vcjbvF16NoL$vqgDZ2wIMmE)(9gpFw0uU5_SbOHz`g7=NCJYle zEc|UtcnZI$!Q5L;Mer#Cy$psZDqr>*d16JbK3ReeCsv+x#Mm^=v~ticSiT0kcz*5j z2=i28QubTVVnRe9WLW5}o+{}nBG8)uB&I6$G@BW{U9W-{jGf7FB9@to(&XxyenR9g5oJs8o@lr-#G8J1%zMdzFt@kTRADJ zjREA`9p4;JCwy~kI{l1M_BqZW)pCj81%xT-`*ax^;TZxu#HTcN%^6nF! z_t-|`x^`AS(9d^K(3r31k2pKl{Suk89u&Yv_S!AZ!nmACl7IOI zM7cK8Yo;JKt-aC%#QB!YPs^P-NLJ*G5KQ=lNg=7J!S=#WEfn=ifs@MkxAP8DK^8^N z0UML&RgC#WzLZx+^-PZiD9&;wY{xLI`A^MUgCRvSXU#6C*3k_WY92=E^LGH1p~L4| zxc285Zs30bpuro_Am#}vcE^2$>%*EuCMR0PaWW>*IR~f&EkD4b$+wzI;Si7;eahpt zI-BqAijf(4R1 zmDadH$1`9;aZ7Yu-Aj*S@*dyHP#V8Ly@TR+PfuK22_lZ>4hFIZJ;iO|UG!ZQ5uz)n zkOK!C8V~@NCiam*t6M`HJ9zNOgvLNJ{q%gx3ExG2gg1Jx=Y;955Ip=8H;bi4xvQ{m z^m)WstwsxFzM=zcFw7AV@&}97jT_lVd(yo-Tk5uw(aVL0GDPSiO@2ooA*Qj;FyE`= z<%qefPzeIQr~WIL7&*bbv#p6r)?!zdx8|=B0No5c%5YXTI@KVGg%i{F88M z(BnbG1WdbH=R>O7s_^jj0dE~mhCOsg-8wFn?CeQSDu+wjWfU$4fHRS)Ti`YrQit;U z9M6gWfpLOx5dHhgt5R$#Kf}QGsp>5NhB)U+2gIzEr%#k!ezQC1)gi;kXUNv))TZirXDV{Fq1c`Srkx#idbWX&ED7mefN;Xu%yX zK~W=;XGNZ}qtwv=uBN*RR&v@m8ze#V5nxb{>=DMnaKI(S!u((Sjy7fRPxA$EDpp?z6D6IB}i zcE`w*U0Vg}b?fPax;-{M)FLYDuc*T2MukaIHQ<`0vf0Ink$U(T5tXEqC(l$3)=xh_ z_V+JaKq4qYw*K)XX|VPx`jt*KRFR5Fiu2M3Ffcm5q!=FyOyGy}@Z>=(e;u%~wKH|3 zgdZ=_5OxrCU@TcI(j#Gkfve7RH4)?Ufxan&zGo?+61)7K0=+k?K+#=%BhT`{mh5ey@cQx>82ETwW z7oP(Tt2ND(w>FE=Y^vdF-d=F9rfjhxXa0ZH^Z(5;A@XMI7t!uDqClg<;lSlkq3}iX zY?s0~u3#+s333;P2-B1ZlJ$0el$C5|(fwFVK-t>MqppA~@2-bpwEZOL$6vi5cC+%p zc!mp4fGf(^$uLo==fi+d{MlQ^#&wc2C~zsOHK=kOC18Bo7pNPinwa(Xu8kRTUdj>$ zqNL+hx|@u7SWx~c(?o6ky8%La&IBt&QsA)+4!sWYYJ8Vb#Z$s#M`L6S_sSz#b7|>Y zmPh=KOmuXoISv}D#`CDgeePseu6WQLp>{2ffo#$*{#&Fx>4jPG(1Qt+r&HS<`Y`<~ z?mYPiqSuuP#W?4s5FL5?_wO3g6F}*&^aYA~ z-Eh&1y# zUhr?JSeG*^3!l#Q5L5vt8YdNRFsB~Z_Pt^^nT*|^-H&4?qlE@Sc%2Jo97f2T=Efi@ z|Mo#u3a-Y&j|&yMpN$AbW|n%Fpt2`8dOpqx676Ibf5h-hBT{MCVl&_e<))}B~Hs!Ykd^UPJztw_uZvoAg)RUcs4|a%S zdh??FDv48(Wk0Po&h{k17s9?{VbarihL8Y$bc6XxwNE9vz1F-~ewKhoN|PzPJqA-( z&Eh!2QF-EKvs8Qc@L5Aux9a{Wj_Sk*#||O5cfK4+71VpmRd5~DmJ0vy(ot~W|L@YB zyf9fj8)hP59T^s~n{k$V3!PVEQ+49T7e1G9Jr=uXia4He=-LOW$Fp&Z7p?vrfZr&% zdP0lVFQmKI64LS0+wb~^WIxg^Ozh#Godg+MaaK$T&E@VPd)It?SdbZ_U|X%>hm>vI zTz}4kp2t~UBTz`319*T5nRu>M$?{zWeNy_dob*Q)mV<0^D58KpDU=wvE2)=svQu}T zyDLF45MJ4%eZ&6Kjo9CcQ>+i6OrG3;4No@>43x?m^AhH%49e@{Sp@jC@-Z>aLEYg< zkGpc%Uz70HX-qcQO!Zyay3kmt=*sVRc+6SH%8grf0x&FR0`Mq|WXOzBUyKzuFeJ26 z%z==J$rrVPfgl!j^K32?kSG1(!ac!sj1&tR%7xZniZw{mj{H6IRp8{N5w`P95a%r_ z3-TQ2CO-1CkA&)nn#jGkxq~UCw*5@LrdE=6N^P8D1vz!=P)LCOx3Z)~?nLua*c-5L z;iaBc>e4&*508H``Z0k+HQ_+cdi`w-yfOdhooimFTN=1B@f2+)#L{$ZwD9^sD3VGw(-<| z%l-w^`%w7O@MAYXX-dKUJKD);UT*`lqmFcSg%)Ns%`fVLnOu-7hdDgk?w8b^GOX4y zK_4&ZSMVeod2>qrfvu7F+PY03eFKx3At(4V!iOX9Jq!5d z9|h$GPq3`r!ffKN9fKj4Nl%_OiNj&}?uo$sHEPbLfg41!8WR(qw6mJcFfoseR} zHiruzY5_2P*_rr`DYLnPC2IlsztO?EYLI~kC;?8FYaUO^f zHkRi-j5M#Y{R85WJjW$ zhuPUHNgX$s*Kg@_qfzV}lPCBt*z-NAzz-vi5o#E3+B%g?PpOd_BmWVvdMP0eUMo|Z z?uQvMoj7n2#z3ML|ljEsE_G#s|Yi(1s_ugGw@VcxwXaVKS z3Ds3IDg4$5Ar{cPVA!qOf5(s=5nunN?L%W|>MejPT_V!Iqng#k+c)&5*i>ltbdWE7 z4CaP%`pJ1oit1YZoSrAW?{#A^Y4|hC{-EX4U3o$O|548WPx!aAxa(=I)`e2Q*nU~Z zJ(_u~mF>vK{UiMRGnb<{YZ7q7+J3q%d{@KkNgQC5P_0OM$lKqIue?npJy>wY>Q!AJ z?ffjd#?iDcM{xXerWzymZjsbiudHhXpzzdD#I?CEZo8LxPPpvpFu-o@283w=X9ZAK zcaJe*OWfEW3$XKiWe|zrN6a^F9FyAfbS8IhR=k0Jw=ESm7cTm(EE^By4&RNP*-V0` zAY9M9oQ(vloVn3eEpIOH2x5kM3L3=P=O>FJIR<5AtUQWPwhp+?DA znKQRm$rKV!N-QPMeY`$mSRFBr?!PjGa?00*a5ihv6FgWT99id}imM+1-6SZlwf%uL zefIOl{9t?4q7U$dY#(afFil7tNU%&dlX(i9%br*65^DU_w0rU7OUTXxqU$oGgu-7r z4%$CdVEiva7DjKng5IyjLDR#JT)3{CBN^Z66i$NYq6-)IIb6)txYF`s7d*1!>YJ=w zc6T$kKG?%BSs^rXMwCw{*-=-HU zoH$wg2?CDz(my5CkQHUqTp#RResHD7*8BUX;rgNcL=XWq2Jn%rFUTkob9+4OBd2c_ z$TQdS8Sq(!Bp)`mKOs#CDpfzG1$*Y)D!MreWW?RNL5zvO zhUkc*U(w85*D_Cz^CBW?IBfs}5{~KBR3faTP8nBw&gTEr*2~tu-}Q4o^?Wo&Z816YVd>?IVNYYP3uqrg~0 z@!JDQ)kV|bG<5d$?EtZ~#74#JTcoI>qAZ$b+N_7}hrznF_{mzU`nL02;kg9=*YgkG zj+BVkuk&&|%nYy2ii_FWv)%PMm1E!AxP5Orq>f%m>S*Z`xR|_o)$k*M`nq7}gIh)QhW6J0t-Nv_ek^o--^SB zA1!77u3t9_2<}Nc|H+c3FAkSrsa8F4u%^z8)qwR%=&Bx0UeU%JhLT;Iw$`RrTx;0b z`TcTid`mogu5NXMXH@Qxx$&vW>~h{<(U(~2Kri}5S&owC>6T_nUQ^D}h=T$0#4J-^ zN5Rg4T1Nr+zr*e#{dw&n%iH~vV2%58%iqWP3AKr~;Y8xBXRJn8={VRE|CL{~P7<6& zaY+lRhERd1G@)~ICQqW9g8GnDqp+Ll4)XYSLC;v;pRD^#dif0wGkNSeR{p>Z2z5_$ z%Xb1U24(9T>AU(!)nkT2%pWK%UGoz3({qWRl}@Uy&}B0GDe2%{%z>LA+t zL8Z^4He7G(YPO-?usyhvEi8|8kqq1Bv0?PMB~EU9aKq#Sb5SE#I5IV3{HtIbI*_cv z&D-{IG4S0}pv!4P#EoLn0?i@A`+SKoVDRtr2oUY{LnzF^`k0?B^)bGL+b8!NUWDNT zp}OlS_W1PpmibI26mwtOedCR(me|kfF{;2<=LmPl86^i|3Fdl8>AwwLv#*_})Ik3T zx$ugt$UT=bQYe9@WacZ5MDewfnoHdPkkP>RUAR+y|6pdCuwbg9h9IoQzKCauKU6{) z9Jsdt1o)K1ks2q;%S8Fpm)XbUlWmO`I-&&LJoT|h87(8Uj_+-$stOu9&`fvYKaqHF z@3KsV9UJ2qjxVZ2+*Ft*6AA9x)vvNS)*~UZg%VDq`U|V*e)r2MT8pS>oopGxhKHIq zKbQ9-*aPhHzBXZFbaHo8^h`?}^_j7iteak@HgcGLy@q0upU*|jv$H~Z(c`c91gr># z8wr=|yLyma?z8xc5?3?BSQ=>YU^Argc~VnJTqB4nv*gG6(k`s9m^FYw&7`4*WfeAY zYK6z_z{PXSWE5QoR~AxTeoj)DK(1B!XaX|QdDy^No2g#I`%MK);|jgktiTRl(fzHG z&1(@S-}5al-NiF98sv#hzL}ZGO#a?*G`IXXL4)Jjn4o~3X$bGT51xa>&7u?Wdo$1M zRGV*foT+LN~wN%u`npp(w}l1Q_%}?U%s96t(qS^TRwXxIIW); zxbh!G;O838n%T#Oew0EyW3u-;`|0Z<2bAW{Vt`RR@o_QNy&dZr{Q_NLoy9W=zLc?R z6G)H8ql4L@jvXbrV6@Y=wRa+@Z*VYx21?M9uPbEq0s5Pe&Q2nRyPV^H^!a!o20s%g z1;2FZ3KF62oQP5(B7W2tAauIKtKNqbT)2TejHK=C3+&C=o#!jeE4gqkdl*?agm15U z-mPTwL*u8l(QM(fcBDMwuXFGp{#_iWS&soYF2mU}bGNtMh3=KmNqQ*b_@SK0 z*^qW0Nru5FKK0>;N$juPr@Y^J?3i3)^2>EPaKpe7yIXSKh}en}2$=Lie3|(VBIwr- zbu?QU47!S&hC+feDv%Y#TPA%mDgu0&&#SM7o+J@xZB!~R;szRWAm|rZ zCo*?4-b01C7F{`x2`=BwE9I97DtkoJ6QWe@>~xFU3_bQUqjf17gpoq3H1GO93xaHr zE8{qiUOb5ooQ_L*H}_Z;9$scd7&LNmLwTV)JIJ1W-wPEIS^sl(>$O299a!Cd%T)4U zD@UF@_3L=kMYWh_G0F z9BWM(!neTF!*N>O@(5*-z27ub+=C*tSTYIbpH+Gl2bZx^F0+5^z<6HtOoqpxv(S$( zeVbX{qvsm_HRnZt0`zSi3QcN=wx3WmO@q zcc;HG5TYjTuC|5r$~4^{qs%(Zd)1GN+@(hoPA9+QOU|+`DK3kCXq;b}WmT@la96nmASHah(w>;RE#D)UQB zc+64%fdlRfVP58}Nu5&=+|k4%d~qUwVWY&O7kG@ZaT#4+%X`rZl)9P)@t?(oA%~q7jruc|%<~iB@t+oP+^KTC=v|}0*J=vyT;&PWdB7*@ zhLy30byoEI4e++|G{>_cdiU%AvL`D)CF_b%kK@ zG%7w-lAazUh%#hux@R8ZN{qV04k?=|U%9x<=h0j=@2#YgxI#8jrI;^hYEb)Fw2B*D|# z`2+$BW7CUID=v{?mtgwc9dnncZkZeR8>+~Ys-g$-9C;^Q*L)ktBrKELRWG7)+Nmga z3#vBTip+r-$|ho(rn4Q>AMXq1`EnX{?G4|tN zq`VLmeWI0u&Iz=uTg~;H)<*6v2lz6la<~p&Cr;OXa2RyokT8R&I>UrRVM_aoS}bo- z-lAfOmU0|Xwf|O^G~9h^U3JCJ%MBYIm}+duEofb?weT2u{IbsOn}u0 zBLeqy=G!fl<@y$mipy5wLaVsJ3`J%RE8)rE%W?8Sw}1fCzik9G_JA=$C^4=Op~j&2 zdi~SLdt=d`_eR!;w#9n^dCv70jg16hb2 z<06f7gO-@Y+VB`>k{+c1dI~K0X))!ojUHoc{WI6eXvJ$Uris9{obEWsurXXQO`aI{ zwVWwhcTaBIvujU3`ZA=i!1}~-3U~nRWc_dP6tabb7kg}Tc7l44Ysvw3*S6C4+a`TD zn>*ZpUR3wimErR`$>h)O)Wv_BU`lNzCu=!E5h1f`aciB`selOh7XNcOe&YncLWjTD zm}EzT-C>agr0>9%vNbMZH`9a)%P7s@Zys$a$WuN_IB|rsV&4fzB8HXAK*yJ3M%?hw zZ1&$0M~&AdUr19RbEBX(Dso;9JIus#@k0A->ffb*+dLCPwc45{0rvGI4z^8}@~7LR z3hr~ZTZt-6EmB5${hh)|WH>aBNSC`jvXCdk5>OK|*^++_BaYE;KAnqdnWl+x`;xVl zeRH4P3pCjDNu|t*6J&&wJ~U3c@kvpb4IgbYj@}VnrC#mA(0qExb!TX}*GnOt&f9UW z>G;b+rDog7e5gu0PV#t1OXZjxE?dTsaI{ZRddXq&J*)EL3fGsTdLLS?xzV!xPwy?dn2m+}|J4+o-?A%dLMF$ab_{B@4dY)ZTDs5mPQ>XUl;``>WHvN!?ZE{e*2M+OCP@NLU#M}^*zJjj9Wlzt^L*0g9hu( ze4BYmZpILttr(Y{2!1iLR=dv=1NQw}hWmMj>SS$B3f{x7=(*T*;sp70P(!MrAmdea z)r`~f{5vzg5=QHlEn_B|Kc%j-4<9cxIm)~FX-VcAJ%!IX^Hi}V(x$KqnfVQQmz&-# z@I7wiN^8}4&JwW1^0waEcfL$6DI^8<-xz5wfBI8T&rmH`^t?&~GeBwH4poq|-au)t zyk8L9Ooq^fk_8!eWK##H8|swEbtXs5Jpd)n_*sYNfJ0JnHqLgKFH`f6mkPdr``2zV zYMFI7pCRm+9L=Z#1GUhLXhKmVtH<_{Es45HUfbK&f3NRc;^7fh)7Vl>PD+`{m$Krx z$v^Rr3Fx~KaUJFptLvq1saijh$HoZTZ3YBfm&!CFq5v`;#+>?-wDO=)@V|7E;muStD)5{cGp4k4Y@%%xg4@QKY^8Mes zpQ_io&z6b+C%V5=PyGvR2Zs5OuO7rVGI;;smgeMNQV^8y<0-t3%rC^5<_Ee#O8ERC zHx4g`w8@9Z!($EbJGmV8^(1crB9;hi~|A zg+ve^I-)62TEZ@FSDe3C3=7Z(%myv;PD?8X`#a+>1m&VCp!mMRZ;xytQBMnKHy-(Q zZt7ool{GP2c1n5gIsIGdejvd-m9rjE%pRl5z`k==*b{iAK)R@Bbf4?4Bc3V4sf{Vw zRki`vm=26Zp}R(_4n6nU2oRG~bULY40)0fKb_3tcnehP)VSJNMOe~*bs_UYG$Qc@^ z5yg*qumFZM=TIKwfoNinoRa~tkFJ`ZQM{l4a>D)l633|Gyn^6{EJt|6idBe+o#z7_ zvPHVf+W9cHtLgT1cc0gG57SfGzg9%`c^H1yP2wXV3QsvV`m%c$O zDQh8We{%hzXK%zKo^P=@?CG?I8SxcJCwl%&@Y#D5+&rURSi_s#=Sk`}y%|tzq3mkq z#u|B^oOn_qb_*2H!j%Xb>^|_)pY)Nwth43Hq#8rU=*1@J1K`86Ysx5vQ4z12Br|N6Q@3?cnmu;p;@-KJ(^jU2#U1y?@ zfuia2$cIklN4~3`7n3N;vM=fm-0u=X!#tIE1r&7~d6n=eCz-|tEGp>5Pez>p_P;}* zkXIF5Jh88M%Z~)aJE3P#%t;A(;_sqv&g1D-5cH#8>gLGM?`r3D{U8e@ z&U_GvkfCVc_Ba;INtqPy?-bt--n#5o&BL&LqPUb{2gu;V_W(2@FzyZ0TiB4AT;>V; z?q7UXl#Ak2E~Q8J`IpQxdF<#i*G;eb6%n~{p@0>SVWMKZWKI3fr7QhKYvtJ{g2ZNf zq;Gz^_0f%f1uljispC#TLX!La&Wf(;xo{_sr3FCLcVSq}9He38hdnH1NnHq6H|XyR z90Lg&AFJD*tW^pJhBCzJ!4BLVaB1|i3-*0W1?v9lx37@Is6PMkn*yEg2Ih_m{P@`S#3*P=fSr;Eds^R)O#C$!z)iGV*HBQ)SDZj$Y@7O9* z<*4Rr_9~txRwgnhXDhL1Fy^|LhfrRD>4YR}^lz;U_dj%b!d-EWIId%VyzZNx6E}#~ zRE?3-i^Mm$v>(oh)8spgU44e$oa#QxXdxb`JCf}+Ym~C_2ta41ZLl({h+el(Q4bQu z{*2y3!WATmF%&`HEBoAisQT9p*i$~{@Aiw>;s0WP;LlbHQn&^9F9QRnJ~FzB=6Lk# zG~g2if+91&g6DWN3lWCdCqk|Zkh2f&PaoE0w4W)5R=U=qAVygPRZ3kA#Je62w?LrO zt5DO;spYjTL?<5T1zl{xALl_n>uvs2IErKLXK-Pwdij<<77+H#>F}ERAdbjE%x5Fq zK8{Eb_o-#jW`3yQRiZcsop3w5km(x=f$Au?Ow&Sz9e5ojB`MAHGqTIWnLfp9V#mu; z354Q`bNlMNt*T~WXCi6aa);MkF&jNW&*Alw9R$uS^Ua8g%I58*Zz?&3EX|?Cs}}hF z_HRxFiv4n^gTG6TE{aMD{4IaUqf;Fp^Y^E6seH&+Fy%HNiE%o%y) z`JHn@o++39L zS-w70zAFXZvj4=J1M95 zxBaA-aU?VkcpdnD4f{A&QV@y^zg$ zZW$${*Yy^0VhW4jkrqn%%Q4|8Fsx1i}N@w{fv~l2MZs* z`=o3kLFLYF8iVt{1!@MB`D458ft=8QZ8=eUo-c76*!JawV>XE9d?DXQpbI8IXhgLa%BO zj3FOdr7799z3ZO7>~G;QwW%84%esVq`W6zdKfI?S^%dy*0*VZmk}hYGoBa& z{cFaxsLRIq>@$k_&*Nk{)O6dTUAJ4CatR{^8vqZ$iuCf&dYqSSPCjm0oW0Ms^&c8| zTTHx`(^By+iD`Ec#E{%y#T%iUMVnz!3^2sH$kXttU?vH5ZaIdlW5 zUwZkh{Kx?%`|b3mO19oQRt4@!h{=soJ&DuoO__Hm5l zR9k$;0Y=_BPzTYs+aqZe4&)pPuOIrQt2fqnP+ThA+s|%K%skl2Zu(}RZY$_w?vJ06 zvl?l%W`R^8ezfInt-|NfQ)yEpl4uz+oxGk&r*-eotwO9icI{FFkST#7VvI4#ZV9N~ zJ*vpZ_hjVby*M@xwF`#HZdb7#sR(SCFSP~~bfnx@x8qL#09NJjN|DI^wATPCfOET% z^q>YeQJ$x@MR4FSNb!<+#XeTUVQ^TE52aTL^L6K@YFj8&LUJ?mXC(TXu^TCn0ns_8 z3}7!zb*3>0KV0{z0zy^U!OzTk&`=&*oN=F8Y-a;;>qhOM5;MjpB3C89 z3TYf}Jx@XDKz*l@OCQRR634k&vC#IWfwE8D4oKiqvu)e!N{kB@bMto105&s~BXKR> znobzwtx6CyWG+LVb4iS^ZNPJpOhZOH8U_L8nliW~Q}eWsN?;=&lV{^dj5gyGj6rg9 zk4jF|3`sdvJurGyZ^+4|W13J$Zbz*EL%ad&QV01*JX0AyYDPI2sTmsgLOAuP0NFRF z%~);@YBrB}$f>YgNdP;DsJ4+LXVB!-s4zIsJQ|us@{Ug+(y>XI^0ru!&Q5bqg#6&s zWXEol=)PJ2y0Y!*Qz+V1z4B_3WSY>o!>Lo;Ri;ZN)fFDv7Cg3aJ#ksljj984Q{28? zRQ~_~z^FmylH3t?Hro#4oDRO!k-pXh4C08N_bi0)+!{cqs2vAB!mq8#W}MURxPjX_ zDpa54UWM@QP<^slvC69Xi=R$}SDM|cL@d~{vPgxL4%O56KT};lQG#i)<+$?L`efH0 zDw3^2N-JZcjNLk^-1RxtRnf;m(xsV~%N~ZEBCL=SxGF|eilRw2<#KWcE9GCh+Go2T zcv(nO20y>Hv>yTj-N3A=PGAlF)(@J!bs#X{9=z6ZvP>klE8mfo8SPXqNA6$SnwD2C zvA3`l9N8m^<*lo@ Date: Thu, 16 Jan 2025 15:40:56 -0500 Subject: [PATCH 07/26] Fix spec --- packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js index 942694fb3cb5..90697ed503fa 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js @@ -40,7 +40,7 @@ describe("Scene/Cesium3DTilesVoxelProvider", function () { ); expect(provider.shape).toEqual(VoxelShapeType.ELLIPSOID); expect(provider.minBounds).toEqual(new Cartesian3(0.0, 0.0, -1.0)); - expect(provider.maxBounds).toEqual(new Cartesian3(1.0, 1.0, 0.0)); + expect(provider.maxBounds).toEqual(new Cartesian3(1.0, 1.0, 500000.0)); expect(provider.dimensions).toEqual(new Cartesian3(2, 2, 2)); expect(provider.paddingBefore).toBeUndefined(); expect(provider.paddingAfter).toBeUndefined(); From 4542ae77dc27466a8fe84744b5a9ad0835b702a2 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 22 Jan 2025 09:49:41 -0500 Subject: [PATCH 08/26] Clean up voxel provider error handling --- .../Scene/Cesium3DTilesVoxelProvider.js | 7 +++--- packages/engine/Source/Scene/KeyframeNode.js | 24 +++++++++++++++++++ packages/engine/Source/Scene/Model/Model.js | 2 +- packages/engine/Source/Scene/SpatialNode.js | 10 +------- .../engine/Source/Scene/VoxelTraversal.js | 18 ++++++-------- 5 files changed, 37 insertions(+), 24 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index 1ef10b3102a8..fd23463a855e 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -510,9 +510,10 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { Check.typeOf.object("options.frameState", frameState); //>>includeEnd('debug'); - // 3D Tiles currently doesn't support time-dynamic data. if (keyframe !== 0) { - return undefined; + return Promise.reject( + `3D Tiles currently doesn't support time-dynamic data.`, + ); } // 1. Load the subtree that the tile belongs to (possibly from the subtree cache) @@ -553,7 +554,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { if (!available) { return Promise.reject( - `Tile is not available at level ${tileLevel}, x ${tileX}, y ${tileY}, z ${tileZ}`, + `Tile is not available at level ${tileLevel}, x ${tileX}, y ${tileY}, z ${tileZ}.`, ); } diff --git a/packages/engine/Source/Scene/KeyframeNode.js b/packages/engine/Source/Scene/KeyframeNode.js index 43071b8a9d0a..607c591fe35f 100644 --- a/packages/engine/Source/Scene/KeyframeNode.js +++ b/packages/engine/Source/Scene/KeyframeNode.js @@ -13,6 +13,7 @@ const LoadState = Object.freeze({ * * @param {SpatialNode} spatialNode * @param {number} keyframe + * // TODO: add contentResource param? Or does that go in VoxelContent? * * @private */ @@ -20,12 +21,35 @@ function KeyframeNode(spatialNode, keyframe) { this.spatialNode = spatialNode; this.keyframe = keyframe; this.state = LoadState.UNLOADED; + // TODO: switch to .content this.metadata = []; + //this.content = undefined; + //this.contentResource = contentResource; this.megatextureIndex = -1; this.priority = -Number.MAX_VALUE; this.highPriorityFrameNumber = -1; } +//KeyframeNode.prototype.requestContent = function() { +//}; + +/** + * Frees the resources used by this object. + * TODO: replace with a destroy method? + * @private + */ +KeyframeNode.prototype.unload = function () { + // TODO: switch to .content + //this.content = this.content && this.content.destroy(); + this.metadata = []; + + this.spatialNode = undefined; + this.state = LoadState.UNLOADED; + this.megatextureIndex = -1; + this.priority = -Number.MAX_VALUE; + this.highPriorityFrameNumber = -1; +}; + /** * @param {KeyframeNode} a * @param {KeyframeNode} b diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 1e76382b423c..d1fb11b98a45 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -120,9 +120,9 @@ import pickModel from "./pickModel.js"; * @alias Model * @internalConstructor * + * @privateParam {object} options Object with the following properties: * @privateParam {ResourceLoader} options.loader The loader used to load resources for this model. * @privateParam {ModelType} options.type Type of this model, to distinguish individual glTF files from 3D Tiles internally. - * @privateParam {object} options Object with the following properties: * @privateParam {Resource} options.resource The Resource to the 3D model. * @privateParam {boolean} [options.show=true] Whether or not to render the model. * @privateParam {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates. diff --git a/packages/engine/Source/Scene/SpatialNode.js b/packages/engine/Source/Scene/SpatialNode.js index 1617bb41ab23..57f29ee6d650 100644 --- a/packages/engine/Source/Scene/SpatialNode.js +++ b/packages/engine/Source/Scene/SpatialNode.js @@ -320,12 +320,7 @@ SpatialNode.prototype.destroyKeyframeNode = function ( this.renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 1); } - keyframeNode.spatialNode = undefined; - keyframeNode.state = KeyframeNode.LoadState.UNLOADED; - keyframeNode.metadata = {}; - keyframeNode.megatextureIndex = -1; - keyframeNode.priority = -Number.MAX_VALUE; - keyframeNode.highPriorityFrameNumber = -1; + keyframeNode.unload(); }; /** @@ -337,7 +332,6 @@ SpatialNode.prototype.addKeyframeNodeToMegatextures = function ( megatextures, ) { if ( - keyframeNode.state !== KeyframeNode.LoadState.RECEIVED || keyframeNode.megatextureIndex !== -1 || keyframeNode.metadata.length !== megatextures.length ) { @@ -349,8 +343,6 @@ SpatialNode.prototype.addKeyframeNodeToMegatextures = function ( keyframeNode.megatextureIndex = megatexture.add(keyframeNode.metadata[i]); } - keyframeNode.state = KeyframeNode.LoadState.LOADED; - const renderableKeyframeNodes = this.renderableKeyframeNodes; let renderableKeyframeNodeIndex = findKeyframeIndex( keyframeNode.keyframe, diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index 27f790a96cdd..d6753ed5ffa9 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -433,8 +433,6 @@ function requestData(that, keyframeNode, frameState) { if (!defined(result)) { keyframeNode.state = KeyframeNode.LoadState.UNAVAILABLE; - } else if (result === KeyframeNode.LoadState.FAILED) { - keyframeNode.state = KeyframeNode.LoadState.FAILED; } else if (!Array.isArray(result) || result.length !== length) { // TODO should this throw runtime error? keyframeNode.state = KeyframeNode.LoadState.FAILED; @@ -473,15 +471,12 @@ function requestData(that, keyframeNode, frameState) { keyframe: keyframe, frameState: frameState, }; - const promise = provider.requestData(requestParameters); - - if (defined(promise)) { - that._simultaneousRequestCount++; - keyframeNode.state = KeyframeNode.LoadState.RECEIVING; - promise.then(postRequestSuccess).catch(postRequestFailure); - } else { - keyframeNode.state = KeyframeNode.LoadState.FAILED; - } + that._simultaneousRequestCount++; + keyframeNode.state = KeyframeNode.LoadState.RECEIVING; + provider + .requestData(requestParameters) + .then(postRequestSuccess) + .catch(postRequestFailure); } /** @@ -591,6 +586,7 @@ function updateKeyframeNodes(that, frameState) { highPriorityKeyframeNode, that.megatextures, ); + highPriorityKeyframeNode.state = KeyframeNode.LoadState.LOADED; keyframeNodesInMegatexture[addNodeIndex] = highPriorityKeyframeNode; } } From 10d30e2f38bcada1f36b36f21980306478951401 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 22 Jan 2025 10:22:28 -0500 Subject: [PATCH 09/26] Use smaller functions in Cesium3DTilesVoxelProvider --- .../Scene/Cesium3DTilesVoxelProvider.js | 69 ++++++++----------- .../engine/Source/Scene/VoxelTraversal.js | 3 +- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index fd23463a855e..fe10eab066cf 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -182,14 +182,6 @@ function Cesium3DTilesVoxelProvider(options) { */ this.maximumTileCount = undefined; - /** - * glTFs that are in the process of being loaded. - * - * @type {GltfLoader[]} - * @private - */ - this._gltfLoaders = new Array(); - this._implicitTileset = undefined; this._subtreeCache = new ImplicitSubtreeCache(); } @@ -519,10 +511,8 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { // 1. Load the subtree that the tile belongs to (possibly from the subtree cache) // 2. Load the voxel content if available - const implicitTileset = this._implicitTileset; - const { names, types, componentTypes } = this; - // Can't use a scratch variable here because the object is used inside the promise chain. + const implicitTileset = this._implicitTileset; const tileCoordinates = new ImplicitTileCoordinates({ subdivisionScheme: implicitTileset.subdivisionScheme, subtreeLevels: implicitTileset.subtreeLevels, @@ -545,9 +535,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { const that = this; const subtree = await getSubtree(that, subtreeCoord); - // TODO: this looks wrong? We get !available for tiles that are present if we ignore availability // NOTE: these two subtree methods are ONLY used by voxels! - // NOTE: the errors are coming from the first call, childSubtreeIsAvailableAtCoordinates const available = isSubtreeRoot ? subtree.childSubtreeIsAvailableAtCoordinates(tileCoordinates) : subtree.tileIsAvailableAtCoordinates(tileCoordinates); @@ -558,17 +546,42 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { ); } - const gltfLoader = getGltfLoader(implicitTileset, tileCoordinates); - that._gltfLoaders.push(gltfLoader); - // TODO: try / catch + const { contentUriTemplates, baseResource } = implicitTileset; + const gltfRelative = contentUriTemplates[0].getDerivedResource({ + templateValues: tileCoordinates.getTemplateValues(), + }); + const gltfResource = baseResource.getDerivedResource({ + url: gltfRelative.url, + }); + + const gltfLoader = new GltfLoader({ + gltfResource: gltfResource, + releaseGltfJson: false, + loadAttributesAsTypedArray: true, + }); + await gltfLoader.load(); gltfLoader.process(frameState); await gltfLoader._loadResourcesPromise; gltfLoader.process(frameState); const { attributes } = gltfLoader.components.scene.nodes[0].primitives[0]; + return processAttributes(attributes, that); +}; +/** + * Processes the attributes from the glTF loader, reordering them into the order expected by the primitive. + * TODO: use a MetadataTable? + * + * @param {ModelComponents.Attribute[]} attributes The attributes to process. + * @param {VoxelProvider} provider The provider from which these attributes were received. + * @returns {TypedArray[]} An array of typed arrays containing the attribute values. + * @private + */ +function processAttributes(attributes, provider) { + const { names, types, componentTypes } = provider; const data = new Array(attributes.length); + for (let i = 0; i < attributes.length; i++) { // The attributes array from GltfLoader is not in the same order as // names, types, etc. from the provider. @@ -593,31 +606,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { ); } - that._gltfLoaders.splice(that._gltfLoaders.indexOf(gltfLoader), 1); return data; -}; - -/** - * Get a loader for a glTF tile from a tileset - * @param {ImplicitTileset} implicitTileset The tileset from which the loader will retrieve a glTF tile - * @param {ImplicitTileCoordinates} tileCoord The coordinates of the desired tile - * @returns {GltfLoader} A loader for the requested tile - * @private - */ -function getGltfLoader(implicitTileset, tileCoord) { - const { contentUriTemplates, baseResource } = implicitTileset; - const gltfRelative = contentUriTemplates[0].getDerivedResource({ - templateValues: tileCoord.getTemplateValues(), - }); - const gltfResource = baseResource.getDerivedResource({ - url: gltfRelative.url, - }); - - return new GltfLoader({ - gltfResource: gltfResource, - releaseGltfJson: false, - loadAttributesAsTypedArray: true, - }); } export default Cesium3DTilesVoxelProvider; diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index d6753ed5ffa9..5d9da05ed359 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -425,7 +425,8 @@ function requestData(that, keyframeNode, frameState) { return; } - const provider = that._primitive._provider; + const primitive = that._primitive; + const provider = primitive.provider; function postRequestSuccess(result) { that._simultaneousRequestCount--; From 20ac82c398b591e77b6c293bc1a84bfb60c0e514 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 22 Jan 2025 15:02:22 -0500 Subject: [PATCH 10/26] Move glTF handling from Cesium3DTilesVoxelProvider to VoxelContent --- .../Scene/Cesium3DTilesVoxelProvider.js | 67 +----- packages/engine/Source/Scene/KeyframeNode.js | 13 +- packages/engine/Source/Scene/SpatialNode.js | 5 +- packages/engine/Source/Scene/VoxelContent.js | 194 +++++++++++++++++- packages/engine/Source/Scene/VoxelProvider.js | 4 +- .../engine/Source/Scene/VoxelTraversal.js | 16 +- 6 files changed, 205 insertions(+), 94 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index fe10eab066cf..49b3fd8496f9 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -4,7 +4,6 @@ import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import Ellipsoid from "../Core/Ellipsoid.js"; -import GltfLoader from "./GltfLoader.js"; import hasExtension from "./hasExtension.js"; import ImplicitSubtree from "./ImplicitSubtree.js"; import ImplicitSubtreeCache from "./ImplicitSubtreeCache.js"; @@ -21,8 +20,7 @@ import RuntimeError from "../Core/RuntimeError.js"; import VoxelBoxShape from "./VoxelBoxShape.js"; import VoxelCylinderShape from "./VoxelCylinderShape.js"; import VoxelShapeType from "./VoxelShapeType.js"; -import MetadataComponentType from "./MetadataComponentType.js"; -import ComponentDatatype from "../Core/ComponentDatatype.js"; +import VoxelContent from "./VoxelContent.js"; /** * A {@link VoxelProvider} that fetches voxel data from a 3D Tiles tileset. @@ -474,7 +472,7 @@ async function getSubtree(provider, subtreeCoord) { } /** - * Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z. + * Requests the data for a given tile. * * @private * @@ -483,9 +481,8 @@ async function getSubtree(provider, subtreeCoord) { * @param {number} [options.tileX=0] The tile's X coordinate. * @param {number} [options.tileY=0] The tile's Y coordinate. * @param {number} [options.tileZ=0] The tile's Z coordinate. - * @param {FrameState} options.frameState The frame state * @privateparam {number} [options.keyframe=0] The requested keyframe. - * @returns {Promise|undefined} A promise to an array of typed arrays containing the requested voxel data or undefined if there was a problem loading the data. + * @returns {Promise} A promise resolving to a VoxelContent containing the data for the tile. */ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -498,10 +495,6 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { frameState, } = options; - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("options.frameState", frameState); - //>>includeEnd('debug'); - if (keyframe !== 0) { return Promise.reject( `3D Tiles currently doesn't support time-dynamic data.`, @@ -554,59 +547,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { url: gltfRelative.url, }); - const gltfLoader = new GltfLoader({ - gltfResource: gltfResource, - releaseGltfJson: false, - loadAttributesAsTypedArray: true, - }); - - await gltfLoader.load(); - gltfLoader.process(frameState); - await gltfLoader._loadResourcesPromise; - gltfLoader.process(frameState); - - const { attributes } = gltfLoader.components.scene.nodes[0].primitives[0]; - return processAttributes(attributes, that); + return VoxelContent.fromGltf(gltfResource, this, frameState); }; -/** - * Processes the attributes from the glTF loader, reordering them into the order expected by the primitive. - * TODO: use a MetadataTable? - * - * @param {ModelComponents.Attribute[]} attributes The attributes to process. - * @param {VoxelProvider} provider The provider from which these attributes were received. - * @returns {TypedArray[]} An array of typed arrays containing the attribute values. - * @private - */ -function processAttributes(attributes, provider) { - const { names, types, componentTypes } = provider; - const data = new Array(attributes.length); - - for (let i = 0; i < attributes.length; i++) { - // The attributes array from GltfLoader is not in the same order as - // names, types, etc. from the provider. - // Find the appropriate glTF attribute based on its name. - // Note: glTF custom attribute names are prefixed with "_" - const name = `_${names[i]}`; - const attribute = attributes.find((a) => a.name === name); - if (!defined(attribute)) { - continue; - } - - const componentDatatype = MetadataComponentType.toComponentDatatype( - componentTypes[i], - ); - const componentCount = MetadataType.getComponentCount(types[i]); - const totalCount = attribute.count * componentCount; - data[i] = ComponentDatatype.createArrayBufferView( - componentDatatype, - attribute.typedArray.buffer, - attribute.typedArray.byteOffset + attribute.byteOffset, - totalCount, - ); - } - - return data; -} - export default Cesium3DTilesVoxelProvider; diff --git a/packages/engine/Source/Scene/KeyframeNode.js b/packages/engine/Source/Scene/KeyframeNode.js index 607c591fe35f..2c255500ed50 100644 --- a/packages/engine/Source/Scene/KeyframeNode.js +++ b/packages/engine/Source/Scene/KeyframeNode.js @@ -13,7 +13,6 @@ const LoadState = Object.freeze({ * * @param {SpatialNode} spatialNode * @param {number} keyframe - * // TODO: add contentResource param? Or does that go in VoxelContent? * * @private */ @@ -21,27 +20,19 @@ function KeyframeNode(spatialNode, keyframe) { this.spatialNode = spatialNode; this.keyframe = keyframe; this.state = LoadState.UNLOADED; - // TODO: switch to .content - this.metadata = []; - //this.content = undefined; - //this.contentResource = contentResource; + this.content = undefined; this.megatextureIndex = -1; this.priority = -Number.MAX_VALUE; this.highPriorityFrameNumber = -1; } -//KeyframeNode.prototype.requestContent = function() { -//}; - /** * Frees the resources used by this object. * TODO: replace with a destroy method? * @private */ KeyframeNode.prototype.unload = function () { - // TODO: switch to .content - //this.content = this.content && this.content.destroy(); - this.metadata = []; + this.content = this.content && this.content.destroy(); this.spatialNode = undefined; this.state = LoadState.UNLOADED; diff --git a/packages/engine/Source/Scene/SpatialNode.js b/packages/engine/Source/Scene/SpatialNode.js index 57f29ee6d650..099777d264c6 100644 --- a/packages/engine/Source/Scene/SpatialNode.js +++ b/packages/engine/Source/Scene/SpatialNode.js @@ -333,14 +333,15 @@ SpatialNode.prototype.addKeyframeNodeToMegatextures = function ( ) { if ( keyframeNode.megatextureIndex !== -1 || - keyframeNode.metadata.length !== megatextures.length + keyframeNode.content.metadata.length !== megatextures.length ) { throw new DeveloperError("Keyframe node cannot be added to megatexture"); } + const { metadata } = keyframeNode.content; for (let i = 0; i < megatextures.length; i++) { const megatexture = megatextures[i]; - keyframeNode.megatextureIndex = megatexture.add(keyframeNode.metadata[i]); + keyframeNode.megatextureIndex = megatexture.add(metadata[i]); } const renderableKeyframeNodes = this.renderableKeyframeNodes; diff --git a/packages/engine/Source/Scene/VoxelContent.js b/packages/engine/Source/Scene/VoxelContent.js index 451be03d2493..a4516b961c6b 100644 --- a/packages/engine/Source/Scene/VoxelContent.js +++ b/packages/engine/Source/Scene/VoxelContent.js @@ -1,44 +1,197 @@ +import { destroyObject } from "@cesium/engine"; import Check from "../Core/Check.js"; +import ComponentDatatype from "../Core/ComponentDatatype.js"; import DeveloperError from "../Core/DeveloperError.js"; import defined from "../Core/defined.js"; import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js"; +import GltfLoader from "./GltfLoader.js"; +import MetadataComponentType from "./MetadataComponentType.js"; import MetadataTable from "./MetadataTable.js"; +import MetadataType from "./MetadataType.js"; /** + *
+ * To construct a VoxelContent, call {@link VoxelContent.fromGltf}. Do not call the constructor directly. + *
* An object representing voxel content for a {@link Cesium3DTilesVoxelProvider}. * * @alias VoxelContent * @constructor * - * @param {Resource} resource The resource for this voxel content. This is used for fetching external buffers as needed. + * @param {object} options An object with the following properties: + * @param {Resource} [options.resource] The resource for this voxel content. This is used for fetching external buffers as needed. + * @param {ResourceLoader} [options.loader] The loader used to load the voxel content. + * @param {TypedArray[]} [options.metadata] The metadata for this voxel content. + * + * @exception {DeveloperError} One of loader and metadata must be defined. + * @exception {DeveloperError} metadata must be an array of TypedArrays. * * @private * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. */ -function VoxelContent(resource) { +function VoxelContent(options) { + const { resource, loader, metadata } = options; //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("resource", resource); + Check.typeOf.object("options", options); + if (!defined(loader)) { + if (!defined(metadata)) { + throw new DeveloperError("One of loader and metadata must be defined."); + } + if (!Array.isArray(metadata)) { + throw new DeveloperError("metadata must be an array of TypedArrays."); + } + } //>>includeEnd('debug'); + // TODO: do we need resource? this._resource = resource; - this._metadataTable = undefined; + this._loader = loader; + + this._metadata = metadata; + this._resourcesLoaded = false; + this._ready = false; } Object.defineProperties(VoxelContent.prototype, { /** - * The {@link MetadataTable} storing voxel property values. + * Returns true when the content is ready to render; otherwise false + * + * @memberof VoxelContent.prototype * - * @type {MetadataTable} + * @type {boolean} * @readonly * @private */ - metadataTable: { + ready: { get: function () { - return this._metadataTable; + return this._ready; + }, + }, + + /** + * The metadata for this voxel content. + * The metadata is an array of typed arrays, one for each field. + * The data for one field is a flattened 3D array ordered by X, then Y, then Z. + * TODO: use a MetadataTable + * @type {TypedArray[]} + * @readonly + */ + metadata: { + get: function () { + return this._metadata; }, }, }); +VoxelContent.fromGltf = async function (resource, provider, frameState) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("resource", resource); + //>>includeEnd('debug'); + + // Construct the glTF loader + const loader = new GltfLoader({ + gltfResource: resource, + releaseGltfJson: false, + loadAttributesAsTypedArray: true, + }); + + try { + // This loads the gltf JSON and ensures the gltf is valid + // Further resource loading is handled synchronously in loader.process() + // via voxelContent.update() as the frameState is needed + await loader.load(); + } catch (error) { + loader.destroy(); + throw error; + } + + await loader.load(); + loader.process(frameState); + await loader._loadResourcesPromise; + loader.process(frameState); + + const metadata = processAttributes( + loader.components.scene.nodes[0].primitives[0].attributes, + provider, + ); + return new VoxelContent({ resource, loader, metadata }); +}; + +/** + * Updates the content until all resources are ready for rendering. + * @param {FrameState} frameState The frame state + * @private + */ +VoxelContent.prototype.update = function (primitive, frameState) { + const loader = this._loader; + + if (this._ready) { + // Nothing to do + return; + } + + // Ensures frames continue to render in requestRender mode while resources are processing + frameState.afterRender.push(() => true); + + if (!defined(loader)) { + this._ready = true; + return; + } + + if (this._resourcesLoaded) { + // TODO: load to megatexture? + const { attributes } = loader.components.scene.nodes[0].primitives[0]; + this._metadata = processAttributes(attributes, primitive); + this._ready = true; + return; + } + + // TODO: handle errors from GltfLoader.prototype.process + this._resourcesLoaded = loader.process(frameState); +}; + +/** + * Processes the attributes from the glTF loader, reordering them into the order expected by the primitive. + * TODO: use a MetadataTable? + * + * @param {ModelComponents.Attribute[]} attributes The attributes to process + * @param {VoxelPrimitive} primitive The primitive for which this voxel content will be used. + * @returns {TypedArray[]} An array of typed arrays containing the attribute values + * @private + */ +function processAttributes(attributes, provider) { + //function processAttributes(attributes, primitive) { + //const { names, types, componentTypes } = primitive.provider; + const { names, types, componentTypes } = provider; + const data = new Array(attributes.length); + + for (let i = 0; i < attributes.length; i++) { + // The attributes array from GltfLoader is not in the same order as + // names, types, etc. from the provider. + // Find the appropriate glTF attribute based on its name. + // Note: glTF custom attribute names are prefixed with "_" + const name = `_${names[i]}`; + const attribute = attributes.find((a) => a.name === name); + if (!defined(attribute)) { + continue; + } + + const componentDatatype = MetadataComponentType.toComponentDatatype( + componentTypes[i], + ); + const componentCount = MetadataType.getComponentCount(types[i]); + const totalCount = attribute.count * componentCount; + data[i] = ComponentDatatype.createArrayBufferView( + componentDatatype, + attribute.typedArray.buffer, + attribute.typedArray.byteOffset + attribute.byteOffset, + totalCount, + ); + } + + return data; +} + /** * Creates an object representing voxel content for a {@link Cesium3DTilesVoxelProvider}. * @@ -164,4 +317,29 @@ function parseVoxelChunks(binaryView) { }; } +/** + * 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 VoxelContent#destroy + * + * @private + */ +VoxelContent.prototype.isDestroyed = function () { + return false; +}; + +/** + * Frees the resources used by this object. + * @private + */ +VoxelContent.prototype.destroy = function () { + this._loader = this._loader && this._loader.destroy(); + return destroyObject(this); +}; + export default VoxelContent; diff --git a/packages/engine/Source/Scene/VoxelProvider.js b/packages/engine/Source/Scene/VoxelProvider.js index 8a7c80ea03b6..d2f667f7493f 100644 --- a/packages/engine/Source/Scene/VoxelProvider.js +++ b/packages/engine/Source/Scene/VoxelProvider.js @@ -204,7 +204,6 @@ Object.defineProperties(VoxelProvider.prototype, { /** * Requests the data for a given tile. - * The data is a flattened 3D array ordered by X, then Y, then Z. * * @private * @@ -213,9 +212,8 @@ Object.defineProperties(VoxelProvider.prototype, { * @param {number} [options.tileX=0] The tile's X coordinate. * @param {number} [options.tileY=0] The tile's Y coordinate. * @param {number} [options.tileZ=0] The tile's Z coordinate. - * @param {FrameState} options.frameState The frame state * @privateparam {number} [options.keyframe=0] The requested keyframe. - * @returns {Promise|undefined} A promise to an array of typed arrays containing the requested voxel data or undefined if there was a problem loading the data. + * @returns {Promise} A promise resolving to a VoxelContent containing the data for the tile. */ VoxelProvider.prototype.requestData = DeveloperError.throwInstantiationError; diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index 5d9da05ed359..7a71464e7f18 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -434,24 +434,26 @@ function requestData(that, keyframeNode, frameState) { if (!defined(result)) { keyframeNode.state = KeyframeNode.LoadState.UNAVAILABLE; - } else if (!Array.isArray(result) || result.length !== length) { + //} else if (!Array.isArray(result) || result.length !== length) { // TODO should this throw runtime error? - keyframeNode.state = KeyframeNode.LoadState.FAILED; + // TODO what if result is a VoxelContent? How do we check the metadata? + // keyframeNode.state = KeyframeNode.LoadState.FAILED; } else { const megatextures = that.megatextures; + keyframeNode.content = result; + keyframeNode.state = KeyframeNode.LoadState.RECEIVED; + const { metadata } = result; for (let i = 0; i < length; i++) { const { voxelCountPerTile, channelCount } = megatextures[i]; const { x, y, z } = voxelCountPerTile; const tileVoxelCount = x * y * z; - const data = result[i]; + const data = metadata[i]; const expectedLength = tileVoxelCount * channelCount; - if (data.length === expectedLength) { - keyframeNode.metadata[i] = data; + if (data.length !== expectedLength) { // State is received only when all metadata requests have been received - keyframeNode.state = KeyframeNode.LoadState.RECEIVED; - } else { keyframeNode.state = KeyframeNode.LoadState.FAILED; + keyframeNode.content = undefined; break; } } From 1b42873dff1f30426439bc987b647cf8070ad069 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 23 Jan 2025 11:35:10 -0500 Subject: [PATCH 11/26] Update VoxelContent from the update loop in VoxelTraversal --- Apps/Sandcastle/gallery/Voxel Picking.html | 5 +- Apps/Sandcastle/gallery/Voxels.html | 12 ++- .../Scene/Cesium3DTilesVoxelProvider.js | 3 +- packages/engine/Source/Scene/KeyframeNode.js | 2 +- packages/engine/Source/Scene/VoxelCell.js | 11 +-- packages/engine/Source/Scene/VoxelContent.js | 18 +--- .../engine/Source/Scene/VoxelTraversal.js | 82 +++++++++++-------- .../Scene/Cesium3DTilesVoxelProviderSpec.js | 77 ++++++++++++----- 8 files changed, 128 insertions(+), 82 deletions(-) diff --git a/Apps/Sandcastle/gallery/Voxel Picking.html b/Apps/Sandcastle/gallery/Voxel Picking.html index 18040bdca8c7..6178bfb1d186 100644 --- a/Apps/Sandcastle/gallery/Voxel Picking.html +++ b/Apps/Sandcastle/gallery/Voxel Picking.html @@ -74,7 +74,7 @@ const { tileLevel, tileX, tileY, tileZ } = options; if (tileLevel >= this._levelCount) { - return undefined; + return Promise.reject(`No tiles available beyond level ${this._levelCount}`); } const dimensions = this.dimensions; @@ -83,7 +83,8 @@ tileZ * dimensions.y * dimensions.x + tileY * dimensions.x + tileX; const dataTile = constructRandomTileData(dimensions, type, randomSeed); - return Promise.resolve([dataTile]); + const content = new Cesium.VoxelContent({ metadata: [dataTile] }); + return Promise.resolve(content); }; function constructRandomTileData(dimensions, type, randomSeed) { diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html index 0b9f0508cbd7..23747d27e841 100644 --- a/Apps/Sandcastle/gallery/Voxels.html +++ b/Apps/Sandcastle/gallery/Voxels.html @@ -58,7 +58,7 @@ ProceduralSingleTileVoxelProvider.prototype.requestData = function (options) { if (options.tileLevel >= 1) { - return undefined; + return Promise.reject(`No tiles available beyond level 0`); } const dimensions = this.dimensions; @@ -93,7 +93,8 @@ } } - return Promise.resolve([dataColor]); + const content = new Cesium.VoxelContent({ metadata: [dataColor] }); + return Promise.resolve(content); }; function ProceduralMultiTileVoxelProvider(shape) { @@ -143,7 +144,9 @@ const { tileLevel, tileX, tileY, tileZ } = options; if (tileLevel >= this._levelCount) { - return undefined; + return Promise.reject( + `No tiles available beyond level ${this._levelCount - 1}`, + ); } const type = this.types[0]; @@ -204,7 +207,8 @@ } } } - return Promise.resolve([dataTile]); + const content = new Cesium.VoxelContent({ metadata: [dataTile] }); + return Promise.resolve(content); }; function createPrimitive(provider, customShader, modelMatrix) { diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index 49b3fd8496f9..53235d9fb319 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -492,7 +492,6 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { tileY = 0, tileZ = 0, keyframe = 0, - frameState, } = options; if (keyframe !== 0) { @@ -547,7 +546,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) { url: gltfRelative.url, }); - return VoxelContent.fromGltf(gltfResource, this, frameState); + return VoxelContent.fromGltf(gltfResource); }; export default Cesium3DTilesVoxelProvider; diff --git a/packages/engine/Source/Scene/KeyframeNode.js b/packages/engine/Source/Scene/KeyframeNode.js index 2c255500ed50..cb5fb8e96bdf 100644 --- a/packages/engine/Source/Scene/KeyframeNode.js +++ b/packages/engine/Source/Scene/KeyframeNode.js @@ -1,7 +1,7 @@ const LoadState = Object.freeze({ UNLOADED: 0, // Has no data and is in dormant state RECEIVING: 1, // Is waiting on data from the provider - RECEIVED: 2, // Received data from the provider + PROCESSING: 2, // Data received. Contents are being processed for rendering. Depending on the content, it might make its own requests for external data. LOADED: 3, // Processed data from provider FAILED: 4, // Failed to receive data from the provider UNAVAILABLE: 5, // No data available for this tile diff --git a/packages/engine/Source/Scene/VoxelCell.js b/packages/engine/Source/Scene/VoxelCell.js index b87d0e235bde..ead0d793a7c4 100644 --- a/packages/engine/Source/Scene/VoxelCell.js +++ b/packages/engine/Source/Scene/VoxelCell.js @@ -71,8 +71,8 @@ VoxelCell.fromKeyframeNode = function ( //>>includeEnd('debug'); const voxelCell = new VoxelCell(primitive, tileIndex, sampleIndex); - const { spatialNode, metadata } = keyframeNode; - voxelCell._metadata = getMetadataForSample(primitive, metadata, sampleIndex); + const { spatialNode, content } = keyframeNode; + voxelCell._metadata = getMetadataForSample(primitive, content, sampleIndex); voxelCell._orientedBoundingBox = getOrientedBoundingBox( primitive, spatialNode, @@ -85,15 +85,16 @@ VoxelCell.fromKeyframeNode = function ( /** * @private * @param {VoxelPrimitive} primitive - * @param {object} metadata + * @param {VoxelContent} content * @param {number} sampleIndex * @returns {object} */ -function getMetadataForSample(primitive, metadata, sampleIndex) { - if (!defined(metadata)) { +function getMetadataForSample(primitive, content, sampleIndex) { + if (!defined(content) || !defined(content.metadata)) { return undefined; } const { names, types } = primitive.provider; + const { metadata } = content; const metadataMap = {}; for (let i = 0; i < names.length; i++) { const name = names[i]; diff --git a/packages/engine/Source/Scene/VoxelContent.js b/packages/engine/Source/Scene/VoxelContent.js index a4516b961c6b..70cf922c5e85 100644 --- a/packages/engine/Source/Scene/VoxelContent.js +++ b/packages/engine/Source/Scene/VoxelContent.js @@ -26,7 +26,6 @@ import MetadataType from "./MetadataType.js"; * @exception {DeveloperError} One of loader and metadata must be defined. * @exception {DeveloperError} metadata must be an array of TypedArrays. * - * @private * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. */ function VoxelContent(options) { @@ -83,7 +82,7 @@ Object.defineProperties(VoxelContent.prototype, { }, }); -VoxelContent.fromGltf = async function (resource, provider, frameState) { +VoxelContent.fromGltf = async function (resource) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("resource", resource); //>>includeEnd('debug'); @@ -105,16 +104,7 @@ VoxelContent.fromGltf = async function (resource, provider, frameState) { throw error; } - await loader.load(); - loader.process(frameState); - await loader._loadResourcesPromise; - loader.process(frameState); - - const metadata = processAttributes( - loader.components.scene.nodes[0].primitives[0].attributes, - provider, - ); - return new VoxelContent({ resource, loader, metadata }); + return new VoxelContent({ resource, loader }); }; /** @@ -159,10 +149,10 @@ VoxelContent.prototype.update = function (primitive, frameState) { * @returns {TypedArray[]} An array of typed arrays containing the attribute values * @private */ -function processAttributes(attributes, provider) { +function processAttributes(attributes, primitive) { //function processAttributes(attributes, primitive) { //const { names, types, componentTypes } = primitive.provider; - const { names, types, componentTypes } = provider; + const { names, types, componentTypes } = primitive.provider; const data = new Array(attributes.length); for (let i = 0; i < attributes.length; i++) { diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index 7a71464e7f18..9d7c9bcd553c 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -413,11 +413,10 @@ function recomputeBoundingVolumesRecursive(that, node) { * * @param {VoxelTraversal} that * @param {KeyframeNode} keyframeNode - * @param {FrameState} frameState * * @private */ -function requestData(that, keyframeNode, frameState) { +function requestData(that, keyframeNode) { if ( that._simultaneousRequestCount >= VoxelTraversal.simultaneousRequestCountMaximum @@ -430,34 +429,10 @@ function requestData(that, keyframeNode, frameState) { function postRequestSuccess(result) { that._simultaneousRequestCount--; - const length = provider.types.length; - - if (!defined(result)) { - keyframeNode.state = KeyframeNode.LoadState.UNAVAILABLE; - //} else if (!Array.isArray(result) || result.length !== length) { - // TODO should this throw runtime error? - // TODO what if result is a VoxelContent? How do we check the metadata? - // keyframeNode.state = KeyframeNode.LoadState.FAILED; - } else { - const megatextures = that.megatextures; - keyframeNode.content = result; - keyframeNode.state = KeyframeNode.LoadState.RECEIVED; - const { metadata } = result; - for (let i = 0; i < length; i++) { - const { voxelCountPerTile, channelCount } = megatextures[i]; - const { x, y, z } = voxelCountPerTile; - const tileVoxelCount = x * y * z; - - const data = metadata[i]; - const expectedLength = tileVoxelCount * channelCount; - if (data.length !== expectedLength) { - // State is received only when all metadata requests have been received - keyframeNode.state = KeyframeNode.LoadState.FAILED; - keyframeNode.content = undefined; - break; - } - } - } + keyframeNode.content = result; + keyframeNode.state = defined(result) + ? KeyframeNode.LoadState.PROCESSING + : KeyframeNode.LoadState.UNAVAILABLE; } function postRequestFailure(error) { @@ -472,7 +447,6 @@ function requestData(that, keyframeNode, frameState) { tileY: spatialNode.y, tileZ: spatialNode.z, keyframe: keyframe, - frameState: frameState, }; that._simultaneousRequestCount++; keyframeNode.state = KeyframeNode.LoadState.RECEIVING; @@ -567,9 +541,20 @@ function updateKeyframeNodes(that, frameState) { continue; } if (highPriorityKeyframeNode.state === KeyframeNode.LoadState.UNLOADED) { - requestData(that, highPriorityKeyframeNode, frameState); + requestData(that, highPriorityKeyframeNode); } - if (highPriorityKeyframeNode.state === KeyframeNode.LoadState.RECEIVED) { + if (highPriorityKeyframeNode.state === KeyframeNode.LoadState.PROCESSING) { + const { content } = highPriorityKeyframeNode; + content.update(that._primitive, frameState); + if (!content.ready) { + continue; + } + if (!validateMetadata(content.metadata, that)) { + // TODO should this throw runtime error? + highPriorityKeyframeNode.content = undefined; + highPriorityKeyframeNode.state = KeyframeNode.LoadState.FAILED; + continue; + } let addNodeIndex = 0; if (megatexture.isFull()) { // If the megatexture is full, try removing a discardable node with the lowest priority. @@ -602,6 +587,35 @@ function keyframeNodeSort(a, b) { return b.highPriorityFrameNumber - a.highPriorityFrameNumber; } +/** + * Check if an array of metadata is of the expected type and size + * + * @param {TypedArray[]} metadata The metadata to validate + * @param {VoxelTraversal} traversal The traversal to validate against + * @returns {boolean} true if the metadata is valid, false otherwise + * + * @private + */ +function validateMetadata(metadata, traversal) { + const length = traversal._primitive.provider.types.length; + if (!Array.isArray(metadata) || metadata.length !== length) { + return false; + } + const { megatextures } = traversal; + for (let i = 0; i < length; i++) { + const { voxelCountPerTile, channelCount } = megatextures[i]; + const { x, y, z } = voxelCountPerTile; + const tileVoxelCount = x * y * z; + + const data = metadata[i]; + const expectedLength = tileVoxelCount * channelCount; + if (data.length !== expectedLength) { + return false; + } + } + return true; +} + /** * @param {SpatialNode} spatialNode * @param {number} visibilityPlaneMask @@ -794,7 +808,7 @@ function printDebugInformation( const loadStateStatistics = `UNLOADED: ${loadStateByCount[KeyframeNode.LoadState.UNLOADED]} | ` + `RECEIVING: ${loadStateByCount[KeyframeNode.LoadState.RECEIVING]} | ` + - `RECEIVED: ${loadStateByCount[KeyframeNode.LoadState.RECEIVED]} | ` + + `PROCESSING: ${loadStateByCount[KeyframeNode.LoadState.PROCESSING]} | ` + `LOADED: ${loadStateByCount[KeyframeNode.LoadState.LOADED]} | ` + `FAILED: ${loadStateByCount[KeyframeNode.LoadState.FAILED]} | ` + `UNAVAILABLE: ${loadStateByCount[KeyframeNode.LoadState.UNAVAILABLE]} | ` + diff --git a/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js index 90697ed503fa..7c9305a47e9b 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js @@ -11,6 +11,7 @@ import { VoxelShapeType, } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; +import pollToPromise from "../../../../Specs/pollToPromise.js"; describe("Scene/Cesium3DTilesVoxelProvider", function () { let scene; @@ -54,62 +55,98 @@ describe("Scene/Cesium3DTilesVoxelProvider", function () { it("requestData works for root tile of ellipsoid tileset", async function () { const url = "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json"; const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); + const primitive = { provider }; - const data = await provider.requestData({ + const content = await provider.requestData({ frameState: scene.frameState, }); - expect(data.length).toEqual(1); - const dimensions = provider.dimensions; - const voxelCount = dimensions.x * dimensions.y * dimensions.z; + await pollToPromise(function () { + scene.renderForSpecs(); + content.update(primitive, scene.frameState); + return content.ready; + }); + + const { metadata } = content; + expect(metadata.length).toEqual(1); + + const { x, y, z } = provider.dimensions; + const voxelCount = x * y * z; const componentCount = MetadataType.getComponentCount(provider.types[0]); const expectedLength = voxelCount * componentCount; - expect(data[0].length).toEqual(expectedLength); + expect(metadata[0].length).toEqual(expectedLength); }); it("requestData works for root tile of box tileset", async function () { const url = "./Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json"; const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); + const primitive = { provider }; - const data = await provider.requestData({ + const content = await provider.requestData({ frameState: scene.frameState, }); - expect(data.length).toEqual(1); - const dimensions = provider.dimensions; - const voxelCount = dimensions.x * dimensions.y * dimensions.z; + await pollToPromise(function () { + scene.renderForSpecs(); + content.update(primitive, scene.frameState); + return content.ready; + }); + + const { metadata } = content; + expect(metadata.length).toEqual(1); + + const { x, y, z } = provider.dimensions; + const voxelCount = x * y * z; const componentCount = MetadataType.getComponentCount(provider.types[0]); const expectedLength = voxelCount * componentCount; - expect(data[0].length).toEqual(expectedLength); + expect(metadata[0].length).toEqual(expectedLength); }); it("requestData works for root tile of cylinder tileset", async function () { const url = "./Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json"; const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); + const primitive = { provider }; - const data = await provider.requestData({ + const content = await provider.requestData({ frameState: scene.frameState, }); - expect(data.length).toEqual(1); - const dimensions = provider.dimensions; - const voxelCount = dimensions.x * dimensions.y * dimensions.z; + await pollToPromise(function () { + scene.renderForSpecs(); + content.update(primitive, scene.frameState); + return content.ready; + }); + + const { metadata } = content; + expect(metadata.length).toEqual(1); + + const { x, y, z } = provider.dimensions; + const voxelCount = x * y * z; const componentCount = MetadataType.getComponentCount(provider.types[0]); const expectedLength = voxelCount * componentCount; - expect(data[0].length).toEqual(expectedLength); + expect(metadata[0].length).toEqual(expectedLength); }); it("requestData loads multiple attributes correctly", async function () { const url = "./Data/Cesium3DTiles/Voxel/VoxelMultiAttribute3DTiles/tileset.json"; const provider = await Cesium3DTilesVoxelProvider.fromUrl(url); + const primitive = { provider }; - const data = await provider.requestData({ + const content = await provider.requestData({ frameState: scene.frameState, }); - expect(data.length).toBe(3); - expect(data[0][0]).toBe(0.0); - expect(data[1][0]).toBe(0.5); - expect(data[2][0]).toBe(1.0); + + await pollToPromise(function () { + scene.renderForSpecs(); + content.update(primitive, scene.frameState); + return content.ready; + }); + + const { metadata } = content; + expect(metadata.length).toBe(3); + expect(metadata[0][0]).toBe(0.0); + expect(metadata[1][0]).toBe(0.5); + expect(metadata[2][0]).toBe(1.0); }); }); From 0182394533f06d639a29706eac1b19873ddb05f4 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 23 Jan 2025 11:57:36 -0500 Subject: [PATCH 12/26] Cleanup from self-review --- .../Scene/Cesium3DTilesVoxelProvider.js | 2 +- packages/engine/Source/Scene/KeyframeNode.js | 1 - packages/engine/Source/Scene/VoxelContent.js | 149 +----------------- .../engine/Source/Scene/VoxelPrimitive.js | 20 +-- .../engine/Source/Scene/VoxelTraversal.js | 2 - 5 files changed, 20 insertions(+), 154 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index 53235d9fb319..ee80f3b6d572 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -18,9 +18,9 @@ import Resource from "../Core/Resource.js"; import ResourceCache from "./ResourceCache.js"; import RuntimeError from "../Core/RuntimeError.js"; import VoxelBoxShape from "./VoxelBoxShape.js"; +import VoxelContent from "./VoxelContent.js"; import VoxelCylinderShape from "./VoxelCylinderShape.js"; import VoxelShapeType from "./VoxelShapeType.js"; -import VoxelContent from "./VoxelContent.js"; /** * A {@link VoxelProvider} that fetches voxel data from a 3D Tiles tileset. diff --git a/packages/engine/Source/Scene/KeyframeNode.js b/packages/engine/Source/Scene/KeyframeNode.js index cb5fb8e96bdf..4b8ad648ab20 100644 --- a/packages/engine/Source/Scene/KeyframeNode.js +++ b/packages/engine/Source/Scene/KeyframeNode.js @@ -28,7 +28,6 @@ function KeyframeNode(spatialNode, keyframe) { /** * Frees the resources used by this object. - * TODO: replace with a destroy method? * @private */ KeyframeNode.prototype.unload = function () { diff --git a/packages/engine/Source/Scene/VoxelContent.js b/packages/engine/Source/Scene/VoxelContent.js index 70cf922c5e85..76d80560508e 100644 --- a/packages/engine/Source/Scene/VoxelContent.js +++ b/packages/engine/Source/Scene/VoxelContent.js @@ -3,23 +3,17 @@ import Check from "../Core/Check.js"; import ComponentDatatype from "../Core/ComponentDatatype.js"; import DeveloperError from "../Core/DeveloperError.js"; import defined from "../Core/defined.js"; -import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js"; import GltfLoader from "./GltfLoader.js"; import MetadataComponentType from "./MetadataComponentType.js"; -import MetadataTable from "./MetadataTable.js"; import MetadataType from "./MetadataType.js"; /** - *
- * To construct a VoxelContent, call {@link VoxelContent.fromGltf}. Do not call the constructor directly. - *
* An object representing voxel content for a {@link Cesium3DTilesVoxelProvider}. * * @alias VoxelContent * @constructor * * @param {object} options An object with the following properties: - * @param {Resource} [options.resource] The resource for this voxel content. This is used for fetching external buffers as needed. * @param {ResourceLoader} [options.loader] The loader used to load the voxel content. * @param {TypedArray[]} [options.metadata] The metadata for this voxel content. * @@ -29,7 +23,7 @@ import MetadataType from "./MetadataType.js"; * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. */ function VoxelContent(options) { - const { resource, loader, metadata } = options; + const { loader, metadata } = options; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options", options); if (!defined(loader)) { @@ -42,10 +36,7 @@ function VoxelContent(options) { } //>>includeEnd('debug'); - // TODO: do we need resource? - this._resource = resource; this._loader = loader; - this._metadata = metadata; this._resourcesLoaded = false; this._ready = false; @@ -82,6 +73,12 @@ Object.defineProperties(VoxelContent.prototype, { }, }); +/** + * Constructs a VoxelContent from a glTF resource. + * + * @param {Resource} resource A Resource pointing to a glTF containing voxel content + * @returns {Promise} A promise that resolves to the voxel content + */ VoxelContent.fromGltf = async function (resource) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("resource", resource); @@ -104,7 +101,7 @@ VoxelContent.fromGltf = async function (resource) { throw error; } - return new VoxelContent({ resource, loader }); + return new VoxelContent({ loader }); }; /** @@ -129,20 +126,17 @@ VoxelContent.prototype.update = function (primitive, frameState) { } if (this._resourcesLoaded) { - // TODO: load to megatexture? const { attributes } = loader.components.scene.nodes[0].primitives[0]; this._metadata = processAttributes(attributes, primitive); this._ready = true; return; } - // TODO: handle errors from GltfLoader.prototype.process this._resourcesLoaded = loader.process(frameState); }; /** * Processes the attributes from the glTF loader, reordering them into the order expected by the primitive. - * TODO: use a MetadataTable? * * @param {ModelComponents.Attribute[]} attributes The attributes to process * @param {VoxelPrimitive} primitive The primitive for which this voxel content will be used. @@ -150,8 +144,6 @@ VoxelContent.prototype.update = function (primitive, frameState) { * @private */ function processAttributes(attributes, primitive) { - //function processAttributes(attributes, primitive) { - //const { names, types, componentTypes } = primitive.provider; const { names, types, componentTypes } = primitive.provider; const data = new Array(attributes.length); @@ -182,131 +174,6 @@ function processAttributes(attributes, primitive) { return data; } -/** - * Creates an object representing voxel content for a {@link Cesium3DTilesVoxelProvider}. - * - * @param {Resource} resource The resource for this voxel content. This is used for fetching external buffers as needed. - * @param {object} [json] Voxel JSON contents. Mutually exclusive with binary. - * @param {Uint8Array} [binary] Voxel binary contents. Mutually exclusive with json. - * @param {MetadataSchema} metadataSchema The metadata schema used by property tables in the voxel content - * @returns {Promise} - * - * @exception {DeveloperError} One of json and binary must be defined. - */ -VoxelContent.fromJson = async function ( - resource, - json, - binary, - metadataSchema, -) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("resource", resource); - if (defined(json) === defined(binary)) { - throw new DeveloperError("One of json and binary must be defined."); - } - //>>includeEnd('debug'); - - let chunks; - if (defined(json)) { - chunks = { - json: json, - binary: undefined, - }; - } else { - chunks = parseVoxelChunks(binary); - } - - const buffersU8 = await requestBuffers(resource, chunks.json, chunks.binary); - const bufferViewsU8 = {}; - const bufferViewsLength = chunks.json.bufferViews.length; - for (let i = 0; i < bufferViewsLength; ++i) { - const bufferViewJson = chunks.json.bufferViews[i]; - const start = bufferViewJson.byteOffset; - const end = start + bufferViewJson.byteLength; - const buffer = buffersU8[bufferViewJson.buffer]; - const bufferView = buffer.subarray(start, end); - bufferViewsU8[i] = bufferView; - } - - const propertyTableIndex = chunks.json.voxelTable; - const propertyTableJson = chunks.json.propertyTables[propertyTableIndex]; - - const content = new VoxelContent(resource); - - content._metadataTable = new MetadataTable({ - count: propertyTableJson.count, - properties: propertyTableJson.properties, - class: metadataSchema.classes[propertyTableJson.class], - bufferViews: bufferViewsU8, - }); - - return content; -}; - -function requestBuffers(resource, json, binary) { - const buffersLength = json.buffers.length; - const bufferPromises = new Array(buffersLength); - for (let i = 0; i < buffersLength; i++) { - const buffer = json.buffers[i]; - if (defined(buffer.uri)) { - const baseResource = resource; - const bufferResource = baseResource.getDerivedResource({ - url: buffer.uri, - }); - bufferPromises[i] = bufferResource - .fetchArrayBuffer() - .then(function (arrayBuffer) { - return new Uint8Array(arrayBuffer); - }); - } else { - bufferPromises[i] = Promise.resolve(binary); - } - } - - return Promise.all(bufferPromises); -} - -/** - * A helper object for storing the two parts of the binary voxel content - * - * @typedef {object} VoxelChunks - * @property {object} json The json chunk of the binary voxel content - * @property {Uint8Array} binary The binary chunk of the binary voxel content. This represents the internal buffer. - * @private - */ - -/** - * Given binary voxel content, split into JSON and binary chunks - * - * @param {Uint8Array} binaryView The binary voxel content - * @returns {VoxelChunks} An object containing the JSON and binary chunks - * @private - */ -function parseVoxelChunks(binaryView) { - // Parse the header - const littleEndian = true; - const reader = new DataView(binaryView.buffer, binaryView.byteOffset); - // Skip to the chunk lengths - let byteOffset = 8; - - // Read the bottom 32 bits of the 64-bit byte length. This is ok for now because: - // 1) not all browsers have native 64-bit operations - // 2) the data is well under 4GB - const jsonByteLength = reader.getUint32(byteOffset, littleEndian); - byteOffset += 8; - const binaryByteLength = reader.getUint32(byteOffset, littleEndian); - byteOffset += 8; - - const json = getJsonFromTypedArray(binaryView, byteOffset, jsonByteLength); - byteOffset += jsonByteLength; - const binary = binaryView.subarray(byteOffset, byteOffset + binaryByteLength); - - return { - json: json, - binary: binary, - }; -} - /** * Returns true if this object was destroyed; otherwise, false. *

diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index 0942fbb817a4..a49631757890 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -2,29 +2,30 @@ import buildVoxelDrawCommands from "./buildVoxelDrawCommands.js"; import Cartesian2 from "../Core/Cartesian2.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartesian4 from "../Core/Cartesian4.js"; +import Cartographic from "../Core/Cartographic.js"; import CesiumMath from "../Core/Math.js"; import Check from "../Core/Check.js"; -import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; +import ClippingPlaneCollection from "./ClippingPlaneCollection.js"; +import clone from "../Core/clone.js"; +import CustomShader from "./Model/CustomShader.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; +import Ellipsoid from "../Core/Ellipsoid.js"; import Event from "../Core/Event.js"; import JulianDate from "../Core/JulianDate.js"; +import Material from "./Material.js"; import Matrix3 from "../Core/Matrix3.js"; import Matrix4 from "../Core/Matrix4.js"; -import oneTimeWarning from "../Core/oneTimeWarning.js"; -import ClippingPlaneCollection from "./ClippingPlaneCollection.js"; -import Material from "./Material.js"; import MetadataComponentType from "./MetadataComponentType.js"; import MetadataType from "./MetadataType.js"; +import oneTimeWarning from "../Core/oneTimeWarning.js"; import PolylineCollection from "./PolylineCollection.js"; +import VerticalExaggeration from "../Core/VerticalExaggeration.js"; +import VoxelContent from "./VoxelContent.js"; import VoxelShapeType from "./VoxelShapeType.js"; import VoxelTraversal from "./VoxelTraversal.js"; -import CustomShader from "./Model/CustomShader.js"; -import Cartographic from "../Core/Cartographic.js"; -import Ellipsoid from "../Core/Ellipsoid.js"; -import VerticalExaggeration from "../Core/VerticalExaggeration.js"; /** * A primitive that renders voxel data from a {@link VoxelProvider}. @@ -1955,7 +1956,8 @@ DefaultVoxelProvider.prototype.requestData = function (options) { return undefined; } - return Promise.resolve([new Float32Array(1)]); + const content = new VoxelContent({ metadata: [new Float32Array(1)] }); + return Promise.resolve(content); }; VoxelPrimitive.DefaultProvider = new DefaultVoxelProvider(); diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index 9d7c9bcd553c..bddc33210675 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -43,7 +43,6 @@ function VoxelTraversal( maximumTextureMemoryByteLength, ) { /** - * TODO: maybe this shouldn't be stored or passed into update function? * @type {VoxelPrimitive} * @private */ @@ -550,7 +549,6 @@ function updateKeyframeNodes(that, frameState) { continue; } if (!validateMetadata(content.metadata, that)) { - // TODO should this throw runtime error? highPriorityKeyframeNode.content = undefined; highPriorityKeyframeNode.state = KeyframeNode.LoadState.FAILED; continue; From 04ab29a6ff2cf0bd40c78c570bd21ee2b97d7eed Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 23 Jan 2025 17:23:44 -0500 Subject: [PATCH 13/26] Fix type errors, add VoxelContent.fromMetadataArray --- Apps/Sandcastle/gallery/Voxel Picking.html | 2 +- Apps/Sandcastle/gallery/Voxels.html | 4 +- packages/engine/Source/Scene/VoxelContent.js | 40 +++++++++++++++----- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Apps/Sandcastle/gallery/Voxel Picking.html b/Apps/Sandcastle/gallery/Voxel Picking.html index 6178bfb1d186..7218163f19c1 100644 --- a/Apps/Sandcastle/gallery/Voxel Picking.html +++ b/Apps/Sandcastle/gallery/Voxel Picking.html @@ -83,7 +83,7 @@ tileZ * dimensions.y * dimensions.x + tileY * dimensions.x + tileX; const dataTile = constructRandomTileData(dimensions, type, randomSeed); - const content = new Cesium.VoxelContent({ metadata: [dataTile] }); + const content = Cesium.VoxelContent.fromMetadataArray([dataTile]); return Promise.resolve(content); }; diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html index 23747d27e841..968249e89c3a 100644 --- a/Apps/Sandcastle/gallery/Voxels.html +++ b/Apps/Sandcastle/gallery/Voxels.html @@ -93,7 +93,7 @@ } } - const content = new Cesium.VoxelContent({ metadata: [dataColor] }); + const content = Cesium.VoxelContent.fromMetadataArray([dataColor]); return Promise.resolve(content); }; @@ -207,7 +207,7 @@ } } } - const content = new Cesium.VoxelContent({ metadata: [dataTile] }); + const content = Cesium.VoxelContent.fromMetadataArray([dataTile]); return Promise.resolve(content); }; diff --git a/packages/engine/Source/Scene/VoxelContent.js b/packages/engine/Source/Scene/VoxelContent.js index 76d80560508e..d60269d75b2f 100644 --- a/packages/engine/Source/Scene/VoxelContent.js +++ b/packages/engine/Source/Scene/VoxelContent.js @@ -8,14 +8,17 @@ import MetadataComponentType from "./MetadataComponentType.js"; import MetadataType from "./MetadataType.js"; /** + *
+ * To construct a Model, call {@link Model.fromGltfAsync}. Do not call the constructor directly. + *
* An object representing voxel content for a {@link Cesium3DTilesVoxelProvider}. * * @alias VoxelContent - * @constructor + * @internalConstructor * - * @param {object} options An object with the following properties: - * @param {ResourceLoader} [options.loader] The loader used to load the voxel content. - * @param {TypedArray[]} [options.metadata] The metadata for this voxel content. + * @privateParam {object} options An object with the following properties: + * @privateParam {ResourceLoader} [options.loader] The loader used to load the voxel content. + * @privateParam {Int8Array[]|Uint8Array[]|Int16Array[]|Uint16Array[]|Int32Array[]|Uint32Array[]|Float32Array[]|Float64Array[]} [options.metadata] The metadata for this voxel content. * * @exception {DeveloperError} One of loader and metadata must be defined. * @exception {DeveloperError} metadata must be an array of TypedArrays. @@ -62,8 +65,8 @@ Object.defineProperties(VoxelContent.prototype, { * The metadata for this voxel content. * The metadata is an array of typed arrays, one for each field. * The data for one field is a flattened 3D array ordered by X, then Y, then Z. - * TODO: use a MetadataTable - * @type {TypedArray[]} + * TODO: use a MetadataTable? + * @type {Int8Array[]|Uint8Array[]|Int16Array[]|Uint16Array[]|Int32Array[]|Uint32Array[]|Float32Array[]|Float64Array[]} * @readonly */ metadata: { @@ -73,11 +76,30 @@ Object.defineProperties(VoxelContent.prototype, { }, }); +/** + * Constructs a VoxelContent from an array of metadata. + * + * @param {Int8Array[]|Uint8Array[]|Int16Array[]|Uint16Array[]|Int32Array[]|Uint32Array[]|Float32Array[]|Float64Array[]} metadata The metadata to use for this voxel content. + * @returns {VoxelContent} A VoxelContent containing the specified metadata. + */ +VoxelContent.fromMetadataArray = function (metadata) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("metadata", metadata); + if (!Array.isArray(metadata)) { + throw new DeveloperError("metadata must be an array of TypedArrays."); + } + //>>includeEnd('debug'); + + return new VoxelContent({ metadata }); +}; + /** * Constructs a VoxelContent from a glTF resource. * - * @param {Resource} resource A Resource pointing to a glTF containing voxel content - * @returns {Promise} A promise that resolves to the voxel content + * @param {Resource} resource A Resource pointing to a glTF containing voxel content. + * @returns {Promise} A promise that resolves to the voxel content. + * + * @private */ VoxelContent.fromGltf = async function (resource) { //>>includeStart('debug', pragmas.debug); @@ -140,7 +162,7 @@ VoxelContent.prototype.update = function (primitive, frameState) { * * @param {ModelComponents.Attribute[]} attributes The attributes to process * @param {VoxelPrimitive} primitive The primitive for which this voxel content will be used. - * @returns {TypedArray[]} An array of typed arrays containing the attribute values + * @returns {Int8Array[]|Uint8Array[]|Int16Array[]|Uint16Array[]|Int32Array[]|Uint32Array[]|Float32Array[]|Float64Array[]} An array of typed arrays containing the attribute values * @private */ function processAttributes(attributes, primitive) { From 3efe3b01497c8af73683e0904a4d8326993fd3a5 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 23 Jan 2025 22:13:59 -0500 Subject: [PATCH 14/26] Fix specs --- .../engine/Specs/Scene/KeyframeNodeSpec.js | 2 +- .../engine/Specs/Scene/VoxelTraversalSpec.js | 58 +------------------ 2 files changed, 2 insertions(+), 58 deletions(-) diff --git a/packages/engine/Specs/Scene/KeyframeNodeSpec.js b/packages/engine/Specs/Scene/KeyframeNodeSpec.js index ef8c86607285..dc06f386bd1b 100644 --- a/packages/engine/Specs/Scene/KeyframeNodeSpec.js +++ b/packages/engine/Specs/Scene/KeyframeNodeSpec.js @@ -10,7 +10,7 @@ describe("Scene/KeyframeNode", function () { expect(keyframeNode.spatialNode).toBe(dummySpatialNode); expect(keyframeNode.keyframe).toBe(keyframe); expect(keyframeNode.state).toBe(KeyframeNode.LoadState.UNLOADED); - expect(keyframeNode.metadata).toEqual([]); + expect(keyframeNode.content).toEqual(undefined); expect(keyframeNode.megatextureIndex).toBe(-1); expect(keyframeNode.priority).toBe(-Number.MAX_VALUE); expect(keyframeNode.highPriorityFrameNumber).toBe(-1); diff --git a/packages/engine/Specs/Scene/VoxelTraversalSpec.js b/packages/engine/Specs/Scene/VoxelTraversalSpec.js index c70e0dc1b73c..a0a177d01048 100644 --- a/packages/engine/Specs/Scene/VoxelTraversalSpec.js +++ b/packages/engine/Specs/Scene/VoxelTraversalSpec.js @@ -186,63 +186,7 @@ describe( expect(keyframeNode).toBeDefined(); expect(keyframeNode.state).toBe(KeyframeNode.LoadState.LOADED); const expectedMetadata = new Float32Array([0, 0, 0, 0, 1, 1, 1, 1]); - expect(keyframeNode.metadata[0]).toEqual(expectedMetadata); - }); - - xit("unloads tiles in megatexture", function () { - const keyFrameLocation = 0; - const recomputeBoundingVolumes = true; - const pauseUpdate = false; - function updateTraversalTenTimes() { - // to fully fetch data and copy to texture - function updateTraversal() { - traversal.update( - scene.frameState, - keyFrameLocation, - recomputeBoundingVolumes, - pauseUpdate, - ); - } - for (let i = 0; i < 10; i++) { - updateTraversal(); - } - } - - const eps = CesiumMath.EPSILON7; - const bottomLeftNearCorner = Cartesian3.fromElements( - -0.5 - eps, - -0.5 - eps, - -0.5 - eps, - ); - const topRightFarCorner = Cartesian3.fromElements( - 0.5 + eps, - 0.5 + eps, - 0.5 + eps, - ); - scene.camera.position = bottomLeftNearCorner; - updateTraversalTenTimes(); - const numberOfNodesOnGPU = traversal._keyframeNodesInMegatexture.length; - const deepestNode = - traversal._keyframeNodesInMegatexture[numberOfNodesOnGPU - 1]; - const deepestSpatialNode = deepestNode.spatialNode; - const nodeIsInMegatexture = - deepestNode.state === VoxelTraversal.LoadState.LOADED; - expect(nodeIsInMegatexture).toBe(true); - - scene.camera.position = topRightFarCorner; - turnCameraAround(scene); - updateTraversalTenTimes(); - const nodeNoLongerInMegatexture = - traversal._keyframeNodesInMegatexture.filter(function (keyFrameNode) { - const spatialNode = keyFrameNode.spatialNode; - return ( - spatialNode.level === deepestSpatialNode.level && - spatialNode.x === deepestSpatialNode.x && - spatialNode.y === deepestSpatialNode.y && - spatialNode.x === deepestSpatialNode.z - ); - }).length === 0; - expect(nodeNoLongerInMegatexture).toBe(true); + expect(keyframeNode.content.metadata[0]).toEqual(expectedMetadata); }); }, "WebGL", From 41dbe65ce3746a4bb80962698ee49b6af0eca6bd Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 24 Jan 2025 19:16:44 -0500 Subject: [PATCH 15/26] Update CHANGES.md --- CHANGES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 60a35a7f0cfe..d7b28353d0e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,17 @@ #### Breaking Changes :mega: - Changed behavior of `DataSourceDisplay.ready` to always stay `true` once it is initially set to `true`. [#12429](https://github.com/CesiumGS/cesium/pull/12429) +- Updated `Cesium3DTilesVoxelProvider` to load glTF tiles using the new [`EXT_primitive_voxels` extension](https://github.com/CesiumGS/glTF/pull/69). Tilesets using the previous custom JSON format are no longer supported. [#12432](https://github.com/CesiumGS/cesium/pull/12432) +- Updated the `requestData` method of the `VoxelProvider` interface to return a `Promise` to a `VoxelContent`. Custom providers should now use the `VoxelContent.fromMetadataArray` method to construct the returned data object. For example: + +```js +CustomVoxelProvider.prototype.requestData = function (options) { + const metadataColumn = new Float32Array(options.dataLength); + // ... Fill metadataColumn with metadata values ... + const content = VoxelContent.fromMetadataArray([metadataColumn]); + return Promise.resolve(content); +}; +``` #### Fixes :wrench: From b247ba6a16936e67c385519dadfdf48f006f7edc Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 28 Jan 2025 22:06:08 -0500 Subject: [PATCH 16/26] Use glTF propertyAttribute to map voxel property names to attributes --- .../Scene/Cesium3DTilesVoxelProvider.js | 10 ++++++++ packages/engine/Source/Scene/GltfLoader.js | 12 ++++----- .../engine/Source/Scene/Model/ModelUtility.js | 2 ++ packages/engine/Source/Scene/VoxelContent.js | 25 ++++++++++++------- .../Source/Scene/processVoxelProperties.js | 3 +++ 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js index ee80f3b6d572..6a942a8cad14 100644 --- a/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js +++ b/packages/engine/Source/Scene/Cesium3DTilesVoxelProvider.js @@ -124,6 +124,15 @@ function Cesium3DTilesVoxelProvider(options) { */ this.paddingAfter = undefined; + /** + * The metadata class for this tileset. + * + * @memberof Cesium3DTilesVoxelProvider.prototype + * @type {string|undefined} + * @readonly + */ + this.className = undefined; + /** * Gets the metadata names. * @@ -405,6 +414,7 @@ function addAttributeInfo(provider, metadata, className) { return { id, type, componentType, minValue, maxValue }; }); + provider.className = className; provider.names = propertyInfo.map((info) => info.id); provider.types = propertyInfo.map((info) => info.type); provider.componentTypes = propertyInfo.map((info) => info.componentType); diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index 35cdc7585012..e1fa984f6b73 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -2191,16 +2191,14 @@ function loadPrimitiveMetadata(primitive, structuralMetadataExtension) { if (!defined(structuralMetadataExtension)) { return; } + const { propertyTextures, propertyAttributes } = structuralMetadataExtension; - // Property Textures - if (defined(structuralMetadataExtension.propertyTextures)) { - primitive.propertyTextureIds = structuralMetadataExtension.propertyTextures; + if (defined(propertyTextures)) { + primitive.propertyTextureIds = propertyTextures; } - // Property Attributes - if (defined(structuralMetadataExtension.propertyAttributes)) { - primitive.propertyAttributeIds = - structuralMetadataExtension.propertyAttributes; + if (defined(propertyAttributes)) { + primitive.propertyAttributeIds = propertyAttributes; } } diff --git a/packages/engine/Source/Scene/Model/ModelUtility.js b/packages/engine/Source/Scene/Model/ModelUtility.js index e36c35619b4f..f21a96071eb9 100644 --- a/packages/engine/Source/Scene/Model/ModelUtility.js +++ b/packages/engine/Source/Scene/Model/ModelUtility.js @@ -346,6 +346,8 @@ ModelUtility.supportedExtensions = { CESIUM_primitive_outline: true, CESIUM_RTC: true, EXT_feature_metadata: true, + EXT_implicit_cylinder_region: true, + EXT_implicit_ellipsoid_region: true, EXT_instance_features: true, EXT_mesh_features: true, EXT_mesh_gpu_instancing: true, diff --git a/packages/engine/Source/Scene/VoxelContent.js b/packages/engine/Source/Scene/VoxelContent.js index d60269d75b2f..43e0d80b6fe0 100644 --- a/packages/engine/Source/Scene/VoxelContent.js +++ b/packages/engine/Source/Scene/VoxelContent.js @@ -148,8 +148,13 @@ VoxelContent.prototype.update = function (primitive, frameState) { } if (this._resourcesLoaded) { - const { attributes } = loader.components.scene.nodes[0].primitives[0]; - this._metadata = processAttributes(attributes, primitive); + const { structuralMetadata, scene } = loader.components; + const { attributes } = scene.nodes[0].primitives[0]; + this._metadata = processAttributes( + attributes, + structuralMetadata, + primitive, + ); this._ready = true; return; } @@ -161,20 +166,22 @@ VoxelContent.prototype.update = function (primitive, frameState) { * Processes the attributes from the glTF loader, reordering them into the order expected by the primitive. * * @param {ModelComponents.Attribute[]} attributes The attributes to process + * @param {StructuralMetadata} structuralMetadata Information from the glTF EXT_structural_metadata extension * @param {VoxelPrimitive} primitive The primitive for which this voxel content will be used. * @returns {Int8Array[]|Uint8Array[]|Int16Array[]|Uint16Array[]|Int32Array[]|Uint32Array[]|Float32Array[]|Float64Array[]} An array of typed arrays containing the attribute values * @private */ -function processAttributes(attributes, primitive) { - const { names, types, componentTypes } = primitive.provider; - const data = new Array(attributes.length); +function processAttributes(attributes, structuralMetadata, primitive) { + const { className, names, types, componentTypes } = primitive.provider; + const propertyAttribute = structuralMetadata.propertyAttributes.find( + (p) => p.class.id === className, + ); + const { properties } = propertyAttribute; + const data = new Array(names.length); for (let i = 0; i < attributes.length; i++) { - // The attributes array from GltfLoader is not in the same order as - // names, types, etc. from the provider. // Find the appropriate glTF attribute based on its name. - // Note: glTF custom attribute names are prefixed with "_" - const name = `_${names[i]}`; + const name = properties[names[i]].attribute; const attribute = attributes.find((a) => a.name === name); if (!defined(attribute)) { continue; diff --git a/packages/engine/Source/Scene/processVoxelProperties.js b/packages/engine/Source/Scene/processVoxelProperties.js index cc90a6db5ead..d820a9ccf444 100644 --- a/packages/engine/Source/Scene/processVoxelProperties.js +++ b/packages/engine/Source/Scene/processVoxelProperties.js @@ -310,6 +310,9 @@ function processVoxelProperties(renderResources, primitive) { const glslField = getGlslField(type, j); const minimumValue = minimumValues[i][j]; const maximumValue = maximumValues[i][j]; + if (!defined(minimumValue) || !defined(maximumValue)) { + continue; + } shaderBuilder.addFunctionLines(functionId, [ `${statisticsFieldName}.${name}.min${glslField} = ${getGlslNumberAsFloat( minimumValue, From c1aea433699c6a25a61c71b6846517c417f83929 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 29 Jan 2025 13:08:45 -0500 Subject: [PATCH 17/26] Update VoxelCylinderShape to use radius, angle, height ordering --- CHANGES.md | 2 + .../engine/Source/Scene/VoxelCylinderShape.js | 110 +++++++++--------- .../Shaders/Voxels/convertUvToCylinder.glsl | 30 ++--- 3 files changed, 72 insertions(+), 70 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d7b28353d0e7..72a74cc84c74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,8 @@ CustomVoxelProvider.prototype.requestData = function (options) { }; ``` +- Changed `VoxelCylinderShape` to assume coordinates in the order (radius, angle, height). See [CesiumGS/3d-tiles#780](https://github.com/CesiumGS/3d-tiles/pull/780) + #### Fixes :wrench: - Fixed error when resetting `Cesium3DTileset.modelMatrix` to its initial value. [#12409](https://github.com/CesiumGS/cesium/pull/12409) diff --git a/packages/engine/Source/Scene/VoxelCylinderShape.js b/packages/engine/Source/Scene/VoxelCylinderShape.js index 7418193bdfc9..28cfe6647ffc 100644 --- a/packages/engine/Source/Scene/VoxelCylinderShape.js +++ b/packages/engine/Source/Scene/VoxelCylinderShape.js @@ -71,37 +71,37 @@ function VoxelCylinderShape() { * @type {number} * @private */ - this._minimumHeight = VoxelCylinderShape.DefaultMinBounds.y; + this._minimumAngle = VoxelCylinderShape.DefaultMinBounds.y; /** * @type {number} * @private */ - this._maximumHeight = VoxelCylinderShape.DefaultMaxBounds.y; + this._maximumAngle = VoxelCylinderShape.DefaultMaxBounds.y; /** * @type {number} * @private */ - this._minimumAngle = VoxelCylinderShape.DefaultMinBounds.z; + this._minimumHeight = VoxelCylinderShape.DefaultMinBounds.z; /** * @type {number} * @private */ - this._maximumAngle = VoxelCylinderShape.DefaultMaxBounds.z; + this._maximumHeight = VoxelCylinderShape.DefaultMaxBounds.z; /** * @type {Object} * @readonly */ this.shaderUniforms = { - cylinderRenderHeightMinMax: new Cartesian2(), cylinderRenderRadiusMinMax: new Cartesian2(), cylinderRenderAngleMinMax: new Cartesian2(), + cylinderRenderHeightMinMax: new Cartesian2(), cylinderUvToShapeUvRadius: new Cartesian2(), - cylinderUvToShapeUvHeight: new Cartesian2(), cylinderUvToShapeUvAngle: new Cartesian2(), + cylinderUvToShapeUvHeight: new Cartesian2(), cylinderShapeUvAngleMinMax: new Cartesian2(), cylinderShapeUvAngleRangeZeroMid: 0.0, }; @@ -173,10 +173,10 @@ VoxelCylinderShape.prototype.update = function ( const defaultMinRadius = VoxelCylinderShape.DefaultMinBounds.x; const defaultMaxRadius = VoxelCylinderShape.DefaultMaxBounds.x; - const defaultMinHeight = VoxelCylinderShape.DefaultMinBounds.y; - const defaultMaxHeight = VoxelCylinderShape.DefaultMaxBounds.y; - const defaultMinAngle = VoxelCylinderShape.DefaultMinBounds.z; - const defaultMaxAngle = VoxelCylinderShape.DefaultMaxBounds.z; + const defaultMinAngle = VoxelCylinderShape.DefaultMinBounds.y; + const defaultMaxAngle = VoxelCylinderShape.DefaultMaxBounds.y; + const defaultMinHeight = VoxelCylinderShape.DefaultMinBounds.z; + const defaultMaxHeight = VoxelCylinderShape.DefaultMaxBounds.z; const defaultAngleRange = defaultMaxAngle - defaultMinAngle; const defaultAngleRangeHalf = 0.5 * defaultAngleRange; @@ -208,38 +208,38 @@ VoxelCylinderShape.prototype.update = function ( const renderMinRadius = Math.max(shapeMinRadius, clipMinRadius); const renderMaxRadius = Math.min(shapeMaxRadius, clipMaxRadius); + // Clamp the angles to the valid range + const shapeMinAngle = CesiumMath.negativePiToPi(minBounds.y); + const shapeMaxAngle = CesiumMath.negativePiToPi(maxBounds.y); + const clipMinAngle = CesiumMath.negativePiToPi(clipMinBounds.y); + const clipMaxAngle = CesiumMath.negativePiToPi(clipMaxBounds.y); + const renderMinAngle = Math.max(shapeMinAngle, clipMinAngle); + const renderMaxAngle = Math.min(shapeMaxAngle, clipMaxAngle); + // Clamp the heights to the valid range const shapeMinHeight = CesiumMath.clamp( - minBounds.y, + minBounds.z, defaultMinHeight, defaultMaxHeight, ); const shapeMaxHeight = CesiumMath.clamp( - maxBounds.y, + maxBounds.z, defaultMinHeight, defaultMaxHeight, ); const clipMinHeight = CesiumMath.clamp( - clipMinBounds.y, + clipMinBounds.z, defaultMinHeight, defaultMaxHeight, ); const clipMaxHeight = CesiumMath.clamp( - clipMaxBounds.y, + clipMaxBounds.z, defaultMinHeight, defaultMaxHeight, ); const renderMinHeight = Math.max(shapeMinHeight, clipMinHeight); const renderMaxHeight = Math.min(shapeMaxHeight, clipMaxHeight); - // Clamp the angles to the valid range - const shapeMinAngle = CesiumMath.negativePiToPi(minBounds.z); - const shapeMaxAngle = CesiumMath.negativePiToPi(maxBounds.z); - const clipMinAngle = CesiumMath.negativePiToPi(clipMinBounds.z); - const clipMaxAngle = CesiumMath.negativePiToPi(clipMaxBounds.z); - const renderMinAngle = Math.max(shapeMinAngle, clipMinAngle); - const renderMaxAngle = Math.min(shapeMaxAngle, clipMaxAngle); - const scale = Matrix4.getScale(modelMatrix, scratchScale); // Exit early if the shape is not visible. @@ -263,20 +263,20 @@ VoxelCylinderShape.prototype.update = function ( this._minimumRadius = shapeMinRadius; // [0,1] this._maximumRadius = shapeMaxRadius; // [0,1] - this._minimumHeight = shapeMinHeight; // [-1,+1] - this._maximumHeight = shapeMaxHeight; // [-1,+1] this._minimumAngle = shapeMinAngle; // [-pi,+pi] this._maximumAngle = shapeMaxAngle; // [-pi,+pi] + this._minimumHeight = shapeMinHeight; // [-1,+1] + this._maximumHeight = shapeMaxHeight; // [-1,+1] this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform); this.orientedBoundingBox = getCylinderChunkObb( renderMinRadius, renderMaxRadius, - renderMinHeight, - renderMaxHeight, renderMinAngle, renderMaxAngle, + renderMinHeight, + renderMaxHeight, this.shapeTransform, this.orientedBoundingBox, ); @@ -296,8 +296,6 @@ VoxelCylinderShape.prototype.update = function ( const shapeIsDefaultMinRadius = shapeMinRadius === defaultMinRadius; const shapeIsDefaultRadius = shapeIsDefaultMinRadius && shapeIsDefaultMaxRadius; - const shapeIsDefaultHeight = - shapeMinHeight === defaultMinHeight && shapeMaxHeight === defaultMaxHeight; const shapeIsAngleReversed = shapeMaxAngle < shapeMinAngle; const shapeAngleRange = shapeMaxAngle - shapeMinAngle + shapeIsAngleReversed * defaultAngleRange; @@ -323,6 +321,8 @@ VoxelCylinderShape.prototype.update = function ( undefined, epsilonAngleDiscontinuity, ); + const shapeIsDefaultHeight = + shapeMinHeight === defaultMinHeight && shapeMaxHeight === defaultMaxHeight; const renderIsDefaultMinRadius = renderMinRadius === defaultMinRadius; const renderIsAngleReversed = renderMaxAngle < renderMinAngle; @@ -529,10 +529,10 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForTile = function ( const minimumRadius = this._minimumRadius; const maximumRadius = this._maximumRadius; - const minimumHeight = this._minimumHeight; - const maximumHeight = this._maximumHeight; const minimumAngle = this._minimumAngle; const maximumAngle = this._maximumAngle; + const minimumHeight = this._minimumHeight; + const maximumHeight = this._maximumHeight; const sizeAtLevel = 1.0 / Math.pow(2.0, tileLevel); @@ -546,34 +546,34 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForTile = function ( maximumRadius, (tileX + 1) * sizeAtLevel, ); - const heightStart = CesiumMath.lerp( - minimumHeight, - maximumHeight, - tileY * sizeAtLevel, - ); - const heightEnd = CesiumMath.lerp( - minimumHeight, - maximumHeight, - (tileY + 1) * sizeAtLevel, - ); const angleStart = CesiumMath.lerp( minimumAngle, maximumAngle, - tileZ * sizeAtLevel, + tileY * sizeAtLevel, ); const angleEnd = CesiumMath.lerp( minimumAngle, maximumAngle, + (tileY + 1) * sizeAtLevel, + ); + const heightStart = CesiumMath.lerp( + minimumHeight, + maximumHeight, + tileZ * sizeAtLevel, + ); + const heightEnd = CesiumMath.lerp( + minimumHeight, + maximumHeight, (tileZ + 1) * sizeAtLevel, ); return getCylinderChunkObb( radiusStart, radiusEnd, - heightStart, - heightEnd, angleStart, angleEnd, + heightStart, + heightEnd, this.shapeTransform, result, ); @@ -651,10 +651,10 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForSample = function ( return getCylinderChunkObb( radiusStart, radiusEnd, - heightStart, - heightEnd, angleStart, angleEnd, + heightStart, + heightEnd, this.shapeTransform, result, ); @@ -670,7 +670,7 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForSample = function ( * @private */ VoxelCylinderShape.DefaultMinBounds = Object.freeze( - new Cartesian3(0.0, -1.0, -CesiumMath.PI), + new Cartesian3(0.0, -CesiumMath.PI, -1.0), ); /** @@ -683,7 +683,7 @@ VoxelCylinderShape.DefaultMinBounds = Object.freeze( * @private */ VoxelCylinderShape.DefaultMaxBounds = Object.freeze( - new Cartesian3(1.0, +1.0, +CesiumMath.PI), + new Cartesian3(1.0, +CesiumMath.PI, +1.0), ); const maxTestAngles = 5; @@ -744,10 +744,10 @@ function computeLooseOrientedBoundingBox(matrix, result) { * * @param {number} radiusStart The radiusStart. * @param {number} radiusEnd The radiusEnd. - * @param {number} heightStart The heightStart. - * @param {number} heightEnd The heightEnd. * @param {number} angleStart The angleStart. * @param {number} angleEnd The angleEnd. + * @param {number} heightStart The heightStart. + * @param {number} heightEnd The heightEnd. * @param {Matrix4} matrix The matrix to transform the points. * @param {OrientedBoundingBox} result The object onto which to store the result. * @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion. @@ -757,10 +757,10 @@ function computeLooseOrientedBoundingBox(matrix, result) { function getCylinderChunkObb( radiusStart, radiusEnd, - heightStart, - heightEnd, angleStart, angleEnd, + heightStart, + heightEnd, matrix, result, ) { @@ -768,10 +768,10 @@ function getCylinderChunkObb( const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds; const defaultMinRadius = defaultMinBounds.x; // 0 const defaultMaxRadius = defaultMaxBounds.x; // 1 - const defaultMinHeight = defaultMinBounds.y; // -1 - const defaultMaxHeight = defaultMaxBounds.y; // +1 - const defaultMinAngle = defaultMinBounds.z; // -pi - const defaultMaxAngle = defaultMaxBounds.z; // +pi + const defaultMinAngle = defaultMinBounds.y; // -pi + const defaultMaxAngle = defaultMaxBounds.y; // +pi + const defaultMinHeight = defaultMinBounds.z; // -1 + const defaultMaxHeight = defaultMaxBounds.z; // +1 // Return early if using the default bounds if ( diff --git a/packages/engine/Source/Shaders/Voxels/convertUvToCylinder.glsl b/packages/engine/Source/Shaders/Voxels/convertUvToCylinder.glsl index 4acf081b1119..32bae86e7699 100644 --- a/packages/engine/Source/Shaders/Voxels/convertUvToCylinder.glsl +++ b/packages/engine/Source/Shaders/Voxels/convertUvToCylinder.glsl @@ -37,7 +37,7 @@ PointJacobianT convertUvToShapeSpaceDerivative(in vec3 positionUv) { float angle = atan(position.y, position.x); vec3 east = normalize(vec3(-position.y, position.x, 0.0)); - vec3 point = vec3(radius, height, angle); + vec3 point = vec3(radius, angle, height); mat3 jacobianT = mat3(radial, z, east / length(position.xy)); return PointJacobianT(point, jacobianT); } @@ -48,12 +48,7 @@ vec3 convertShapeToShapeUvSpace(in vec3 positionShape) { radius = radius * u_cylinderUvToShapeUvRadius.x + u_cylinderUvToShapeUvRadius.y; #endif - float height = positionShape.y; - #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT) - height = height * u_cylinderUvToShapeUvHeight.x + u_cylinderUvToShapeUvHeight.y; - #endif - - float angle = (positionShape.z + czm_pi) / czm_twoPi; + float angle = (positionShape.y + czm_pi) / czm_twoPi; #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE) #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED) // Comparing against u_cylinderShapeUvAngleMinMax has precision problems. u_cylinderShapeUvAngleRangeZeroMid is more conservative. @@ -70,7 +65,12 @@ vec3 convertShapeToShapeUvSpace(in vec3 positionShape) { angle = angle * u_cylinderUvToShapeUvAngle.x + u_cylinderUvToShapeUvAngle.y; #endif - return vec3(radius, height, angle); + float height = positionShape.z; + #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT) + height = height * u_cylinderUvToShapeUvHeight.x + u_cylinderUvToShapeUvHeight.y; + #endif + + return vec3(radius, angle, height); } PointJacobianT convertUvToShapeUvSpaceDerivative(in vec3 positionUv) { @@ -85,15 +85,15 @@ vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) { radius /= u_cylinderUvToShapeUvRadius.x; #endif - float height = shapeUv.y; - #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT) - height /= u_cylinderUvToShapeUvHeight.x; - #endif - - float angle = shapeUv.z * czm_twoPi; + float angle = shapeUv.y * czm_twoPi; #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE) angle /= u_cylinderUvToShapeUvAngle.x; #endif - return vec3(radius, height, angle); + float height = shapeUv.z; + #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT) + height /= u_cylinderUvToShapeUvHeight.x; + #endif + + return vec3(radius, angle, height); } From 3957db7a990ddd5397554f3c6e1dde3fbf300292 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 29 Jan 2025 14:19:31 -0500 Subject: [PATCH 18/26] Update VoxelCylinderShapeSpec --- .../Specs/Scene/VoxelCylinderShapeSpec.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/engine/Specs/Scene/VoxelCylinderShapeSpec.js b/packages/engine/Specs/Scene/VoxelCylinderShapeSpec.js index 224fbc926397..0e980ad9a2b8 100644 --- a/packages/engine/Specs/Scene/VoxelCylinderShapeSpec.js +++ b/packages/engine/Specs/Scene/VoxelCylinderShapeSpec.js @@ -85,12 +85,12 @@ describe("Scene/VoxelCylinderShape", function () { // Half revolution const minRadius = 0.25; const maxRadius = 0.75; - const minHeight = -0.5; - const maxHeight = +0.5; const minAngle = -CesiumMath.PI; const maxAngle = 0.0; - const minBounds = new Cartesian3(minRadius, minHeight, minAngle); - const maxBounds = new Cartesian3(maxRadius, maxHeight, maxAngle); + const minHeight = -0.5; + const maxHeight = +0.5; + const minBounds = new Cartesian3(minRadius, minAngle, minHeight); + const maxBounds = new Cartesian3(maxRadius, maxAngle, maxHeight); const visible = shape.update(modelMatrix, minBounds, maxBounds); const expectedMinX = translation.x - maxRadius * scale.x; @@ -172,13 +172,13 @@ describe("Scene/VoxelCylinderShape", function () { const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds; const minBounds = new Cartesian3( defaultMinBounds.x, - defaultMinBounds.y, minAngle, + defaultMinBounds.z, ); const maxBounds = new Cartesian3( defaultMaxBounds.x, - defaultMaxBounds.y, maxAngle, + defaultMaxBounds.z, ); const visible = shape.update(modelMatrix, minBounds, maxBounds); @@ -241,12 +241,12 @@ describe("Scene/VoxelCylinderShape", function () { // Half revolution const minRadius = 0.25; const maxRadius = 0.75; - const minHeight = -0.5; - const maxHeight = +0.5; const minAngle = -CesiumMath.PI; const maxAngle = 0.0; - const minBounds = new Cartesian3(minRadius, minHeight, minAngle); - const maxBounds = new Cartesian3(maxRadius, maxHeight, maxAngle); + const minHeight = -0.5; + const maxHeight = +0.5; + const minBounds = new Cartesian3(minRadius, minAngle, minHeight); + const maxBounds = new Cartesian3(maxRadius, maxAngle, maxHeight); shape.update(modelMatrix, minBounds, maxBounds); result = shape.computeOrientedBoundingBoxForTile(0, 0, 0, 0, result); expect(result.center.x).toEqual(1.0); From 078c98e35eb7cc1a2aa7dab9bc77bc86b6740a38 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 29 Jan 2025 18:48:22 -0500 Subject: [PATCH 19/26] PR feedback part 1 --- Apps/Sandcastle/gallery/Voxel Picking.html | 7 +++++-- Apps/Sandcastle/gallery/Voxels in 3D Tiles.html | 4 ++-- Apps/Sandcastle/gallery/Voxels.html | 7 +++++-- CHANGES.md | 2 +- .../Source/Scene/Cesium3DTilesVoxelProvider.js | 8 +++++--- packages/engine/Source/Scene/ImplicitSubtree.js | 2 ++ packages/engine/Source/Scene/VoxelContent.js | 13 ++++++------- 7 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Apps/Sandcastle/gallery/Voxel Picking.html b/Apps/Sandcastle/gallery/Voxel Picking.html index 7218163f19c1..53a92a051a07 100644 --- a/Apps/Sandcastle/gallery/Voxel Picking.html +++ b/Apps/Sandcastle/gallery/Voxel Picking.html @@ -7,8 +7,11 @@ name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> - - + + Cesium Demo diff --git a/Apps/Sandcastle/gallery/Voxels in 3D Tiles.html b/Apps/Sandcastle/gallery/Voxels in 3D Tiles.html index 0bcbd66972b9..4b79212a38e8 100644 --- a/Apps/Sandcastle/gallery/Voxels in 3D Tiles.html +++ b/Apps/Sandcastle/gallery/Voxels in 3D Tiles.html @@ -7,8 +7,8 @@ name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> - - + + Cesium Demo