From 656c638e0905ab4bd83452a7980d8ae2c6f14ea0 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 9 Jul 2024 16:23:13 +0200 Subject: [PATCH 01/63] Removed some unused properties --- .../Source/Scene/Model/SelectedFeatureIdPipelineStage.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/engine/Source/Scene/Model/SelectedFeatureIdPipelineStage.js b/packages/engine/Source/Scene/Model/SelectedFeatureIdPipelineStage.js index e55a1e9ae1df..df30ad52509f 100644 --- a/packages/engine/Source/Scene/Model/SelectedFeatureIdPipelineStage.js +++ b/packages/engine/Source/Scene/Model/SelectedFeatureIdPipelineStage.js @@ -17,10 +17,6 @@ const SelectedFeatureIdPipelineStage = { STRUCT_ID_SELECTED_FEATURE: "SelectedFeature", STRUCT_NAME_SELECTED_FEATURE: "SelectedFeature", - FUNCTION_ID_FEATURE_VARYINGS_VS: "updateFeatureStructVS", - FUNCTION_ID_FEATURE_VARYINGS_FS: "updateFeatureStructFS", - FUNCTION_SIGNATURE_UPDATE_FEATURE: - "void updateFeatureStruct(inout SelectedFeature feature)", }; /** From 247102feb7bd3c988b63576d2a3c3c5e28e2e785 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 9 Jul 2024 16:48:14 +0200 Subject: [PATCH 02/63] Draft of metadata picking --- packages/engine/Source/Scene/FrameState.js | 25 ++ .../Model/MetadataPickingPipelineStage.js | 87 +++++++ .../Scene/Model/ModelRuntimePrimitive.js | 2 + .../engine/Source/Scene/PickFramebuffer.js | 12 +- packages/engine/Source/Scene/Picking.js | 217 ++++++++++++++---- packages/engine/Source/Scene/Scene.js | 94 ++++++++ .../Source/Scene/hasMetadataProperty.js | 41 ++++ .../Shaders/Model/MetadataPickingStageFS.glsl | 19 ++ .../engine/Source/Shaders/Model/ModelFS.glsl | 26 +++ 9 files changed, 477 insertions(+), 46 deletions(-) create mode 100644 packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js create mode 100644 packages/engine/Source/Scene/hasMetadataProperty.js create mode 100644 packages/engine/Source/Shaders/Model/MetadataPickingStageFS.glsl diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 5db2dee20264..bc2705a58e87 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -421,6 +421,31 @@ function FrameState(context, creditDisplay, jobScheduler) { * @default 0.0 */ this.minimumTerrainHeight = 0.0; + + /** + * The schema ID for the metadata values that are supposed to be picked + * with `Scene.pickMetadata`, or `undefined` to pick values from any + * schema. + * + * @type {string|undefined} + */ + this.pickedMetadataSchemaId = undefined; + + /** + * The name of the metadata class that values should be picked from + * with `Scene.pickMetadata` + * + * @type {string|undefined} + */ + this.pickedMetadataClassName = undefined; + + /** + * The name of the metadata property that values should be picked from + * with `Scene.pickMetadata` + * + * @type {string|undefined} + */ + this.pickedMetadataPropertyName = undefined; } /** diff --git a/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js b/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js new file mode 100644 index 000000000000..9646c9d9c564 --- /dev/null +++ b/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js @@ -0,0 +1,87 @@ +import ShaderDestination from "../../Renderer/ShaderDestination.js"; +import MetadataPickingStageFS from "../../Shaders/Model/MetadataPickingStageFS.js"; +import hasMetadataProperty from "../hasMetadataProperty.js"; + +/** + * A pipeline stage that updates `ModelRenderResources` for + * picking metadata. + * + * It adds the `METADATA_PICKING` define that causes the parts + * of the `ModelFS.glsl` to be skipped that are not related to + * picking, and inserts the `METADATA_PICKING_PROPERTY_NAME` + * define for the metadata property that should be picked. + * The latter will directly access the property of the `Metadata` + * struct that was generated by the `MetadataPipelineStage`. + * + * @namespace MetadataPickingPipelineStage + * @private + */ +const MetadataPickingPipelineStage = { + name: "MetadataPickingPipelineStage", // Helps with debugging if you know where to look +}; + +/** + * Process a primitive. + * + * If the given render resources are for a model that has metadata that + * matches the current `pickedMetadataSchemaId`, `pickedMetadataClassName` + * and `pickedMetadataPropertyName` of the `FrameState`, then this pass... + * + * @param {PrimitiveRenderResources} renderResources The render resources for the primitive + * @param {ModelComponents.Primitive} primitive The primitive to be rendered + * @param {FrameState} frameState The frame state + * @private + */ +MetadataPickingPipelineStage.process = function ( + renderResources, + primitive, + frameState +) { + const XXX_METADATA_PICKING_DEBUG_LOG = false; + + if (XXX_METADATA_PICKING_DEBUG_LOG) { + console.log("MetadataPickingPipelineStage processing:"); + console.log(" renderResources: ", renderResources); + console.log(" primitive: ", primitive); + console.log(" frameState: ", frameState); + } + + const schemaId = frameState.pickedMetadataSchemaId; + const className = frameState.pickedMetadataClassName; + const propertyName = frameState.pickedMetadataPropertyName; + + // Verify that the given render resources contain a model with + // metadata that has a schema that matches the given description + const model = renderResources.model; + const structuralMetadata = model?.structuralMetadata; + const schema = structuralMetadata?.schema; + if (!hasMetadataProperty(schema, schemaId, className, propertyName)) { + if (XXX_METADATA_PICKING_DEBUG_LOG) { + console.log("The metadata property was not found"); + } + return; + } + + const shaderBuilder = renderResources.shaderBuilder; + + // Enable METADATA_PICKING in `ModelFS.glsl` + shaderBuilder.addDefine( + "METADATA_PICKING", + undefined, + ShaderDestination.FRAGMENT + ); + + // Add the MetadataPickingStageFS.glsl lines, and define + // the property that should be picked + shaderBuilder.addFragmentLines(MetadataPickingStageFS); + shaderBuilder.addDefine( + "METADATA_PICKING_PROPERTY_NAME", + propertyName, + ShaderDestination.FRAGMENT + ); +}; + +export default MetadataPickingPipelineStage; diff --git a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js index ed82ca255cf4..1e42efbe24f7 100644 --- a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js +++ b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js @@ -15,6 +15,7 @@ import GeometryPipelineStage from "./GeometryPipelineStage.js"; import LightingPipelineStage from "./LightingPipelineStage.js"; import MaterialPipelineStage from "./MaterialPipelineStage.js"; import MetadataPipelineStage from "./MetadataPipelineStage.js"; +import MetadataPickingPipelineStage from "./MetadataPickingPipelineStage.js"; import ModelUtility from "./ModelUtility.js"; import MorphTargetsPipelineStage from "./MorphTargetsPipelineStage.js"; import PickingPipelineStage from "./PickingPipelineStage.js"; @@ -276,6 +277,7 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) { // are declared to avoid compilation errors. pipelineStages.push(FeatureIdPipelineStage); pipelineStages.push(MetadataPipelineStage); + pipelineStages.push(MetadataPickingPipelineStage); if (featureIdFlags.hasPropertyTable) { pipelineStages.push(SelectedFeatureIdPipelineStage); diff --git a/packages/engine/Source/Scene/PickFramebuffer.js b/packages/engine/Source/Scene/PickFramebuffer.js index 84a64a5b61ee..d103b2b2647e 100644 --- a/packages/engine/Source/Scene/PickFramebuffer.js +++ b/packages/engine/Source/Scene/PickFramebuffer.js @@ -121,13 +121,17 @@ PickFramebuffer.prototype.end = function (screenSpaceRectangle) { }; /** - * Return voxel tile and sample information as rendered by a pickVoxel pass, - * within a given rectangle. + * Return a typed array containing the RGBA (byte) components of the + * pixel that is at the center of the given rectangle. + * + * This may, for example, be voxel tile and sample information as rendered + * by a pickVoxel pass, within a given rectangle. Or it may be the result + * of a metadata picking rendering pass. * * @param {BoundingRectangle} screenSpaceRectangle - * @returns {TypedArray} + * @returns {TypedArray} The RGBA components */ -PickFramebuffer.prototype.readVoxelInfo = function (screenSpaceRectangle) { +PickFramebuffer.prototype.readCenterPixel = function (screenSpaceRectangle) { const width = defaultValue(screenSpaceRectangle.width, 1.0); const height = defaultValue(screenSpaceRectangle.height, 1.0); diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index e248e9632c33..226149997760 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -39,6 +39,10 @@ const pickTilesetPassState = new Cesium3DTilePassState({ pass: Cesium3DTilePass.PICK, }); +const renderTilesetPassState = new Cesium3DTilePassState({ + pass: Cesium3DTilePass.RENDER, +}); + /** * @private */ @@ -224,18 +228,42 @@ function getPickCullingVolume( ); } -// pick rectangle width and height, assumed odd -let scratchRectangleWidth = 3.0; -let scratchRectangleHeight = 3.0; -let scratchRectangle = new BoundingRectangle( - 0.0, - 0.0, - scratchRectangleWidth, - scratchRectangleHeight -); +// Pick position and rectangle, used in all picking functions, +// filled in computePickingDrawingBufferRectangle and passed +// the the FrameBuffer being/end methods +const scratchRectangle = new BoundingRectangle(0.0, 0.0, 3.0, 3.0); const scratchPosition = new Cartesian2(); + +// Dummy color that is passed to updateAndExecuteCommands in +// all picking functions, used as the "background color" const scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0); +/** + * Compute the rectangle that describes the part of the drawing buffer + * that is relevant for picking. + * + * @param {number} drawingBufferHeight - The height of the drawing buffer + * @param {Cartesian2} position - The position inside the drawing buffer + * @param {number|undefined} width - The width of the rectangle, asssumed to + * be an odd integer number, default: 3.0 + * @param {number|undefined} height - The height of the rectangle, default: width + * @param {BoundingRectangle} result The result rectangle + * @returns The result rectangle + */ +function computePickingDrawingBufferRectangle( + drawingBufferHeight, + position, + width, + height, + result +) { + result.width = defaultValue(width, 3.0); + result.height = defaultValue(height, result.width); + result.x = position.x - (result.width - 1.0) * 0.5; + result.y = drawingBufferHeight - position.y - (result.height - 1.0) * 0.5; + return result; +} + /** * Returns an object with a primitive property that contains the first (top) primitive in the scene * at a particular window coordinate or undefined if nothing is at the location. Other properties may @@ -254,9 +282,6 @@ Picking.prototype.pick = function (scene, windowPosition, width, height) { Check.defined("windowPosition", windowPosition); //>>includeEnd('debug'); - scratchRectangleWidth = defaultValue(width, 3.0); - scratchRectangleHeight = defaultValue(height, scratchRectangleWidth); - const { context, frameState, defaultView } = scene; const { viewport, pickFramebuffer } = defaultView; @@ -275,6 +300,13 @@ Picking.prototype.pick = function (scene, windowPosition, width, height) { windowPosition, scratchPosition ); + const drawingBufferRectangle = computePickingDrawingBufferRectangle( + context.drawingBufferHeight, + drawingBufferPosition, + width, + height, + scratchRectangle + ); scene.jobScheduler.disableThisFrame(); @@ -282,8 +314,8 @@ Picking.prototype.pick = function (scene, windowPosition, width, height) { frameState.cullingVolume = getPickCullingVolume( scene, drawingBufferPosition, - scratchRectangleWidth, - scratchRectangleHeight, + drawingBufferRectangle.width, + drawingBufferRectangle.height, viewport ); frameState.invertClassification = false; @@ -294,20 +326,12 @@ Picking.prototype.pick = function (scene, windowPosition, width, height) { scene.updateEnvironment(); - scratchRectangle.x = - drawingBufferPosition.x - (scratchRectangleWidth - 1.0) * 0.5; - scratchRectangle.y = - scene.drawingBufferHeight - - drawingBufferPosition.y - - (scratchRectangleHeight - 1.0) * 0.5; - scratchRectangle.width = scratchRectangleWidth; - scratchRectangle.height = scratchRectangleHeight; - passState = pickFramebuffer.begin(scratchRectangle, viewport); + passState = pickFramebuffer.begin(drawingBufferRectangle, viewport); scene.updateAndExecuteCommands(passState, scratchColorZero); scene.resolveFramebuffers(passState); - const object = pickFramebuffer.end(scratchRectangle); + const object = pickFramebuffer.end(drawingBufferRectangle); context.endFrame(); return object; }; @@ -333,9 +357,6 @@ Picking.prototype.pickVoxelCoordinate = function ( Check.defined("windowPosition", windowPosition); //>>includeEnd('debug'); - scratchRectangleWidth = defaultValue(width, 3.0); - scratchRectangleHeight = defaultValue(height, scratchRectangleWidth); - const { context, frameState, defaultView } = scene; const { viewport, pickFramebuffer } = defaultView; @@ -354,6 +375,13 @@ Picking.prototype.pickVoxelCoordinate = function ( windowPosition, scratchPosition ); + const drawingBufferRectangle = computePickingDrawingBufferRectangle( + context.drawingBufferHeight, + drawingBufferPosition, + width, + height, + scratchRectangle + ); scene.jobScheduler.disableThisFrame(); @@ -361,8 +389,8 @@ Picking.prototype.pickVoxelCoordinate = function ( frameState.cullingVolume = getPickCullingVolume( scene, drawingBufferPosition, - scratchRectangleWidth, - scratchRectangleHeight, + drawingBufferRectangle.width, + drawingBufferRectangle.height, viewport ); frameState.invertClassification = false; @@ -373,24 +401,123 @@ Picking.prototype.pickVoxelCoordinate = function ( scene.updateEnvironment(); - scratchRectangle.x = - drawingBufferPosition.x - (scratchRectangleWidth - 1.0) * 0.5; - scratchRectangle.y = - scene.drawingBufferHeight - - drawingBufferPosition.y - - (scratchRectangleHeight - 1.0) * 0.5; - scratchRectangle.width = scratchRectangleWidth; - scratchRectangle.height = scratchRectangleHeight; - passState = pickFramebuffer.begin(scratchRectangle, viewport); + passState = pickFramebuffer.begin(drawingBufferRectangle, viewport); scene.updateAndExecuteCommands(passState, scratchColorZero); scene.resolveFramebuffers(passState); - const voxelInfo = pickFramebuffer.readVoxelInfo(scratchRectangle); + const voxelInfo = pickFramebuffer.readCenterPixel(drawingBufferRectangle); context.endFrame(); return voxelInfo; }; +/** + * Pick a metadata value at the given window position. + * + * @param {Cartesian2} windowPosition Window coordinates to perform picking on. + * @param {string|undefined} schemaId The ID of the metadata schema to pick values + * from, or `undefined` to pick values from any schema. + * @param {string} className The name of the metadata class to pick + * values from + * @param {string} propertyName The Name of the metadata property to pick + * values from + * @param {number} [width=3] Width of the pick rectangle. + * @param {number} [height=3] Height of the pick rectangle. + * @returns The metadata values as a raw, 4-byte buffer + * + * @private + */ +Picking.prototype.pickMetadata = function ( + scene, + windowPosition, + schemaId, + className, + propertyName, + width, + height +) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("windowPosition", windowPosition); + Check.typeOf.string("className", className); + Check.typeOf.string("propertyName", propertyName); + //>>includeEnd('debug'); + + const { context, frameState, defaultView } = scene; + const { viewport, pickFramebuffer } = defaultView; + + scene.view = defaultView; + + viewport.x = 0; + viewport.y = 0; + viewport.width = context.drawingBufferWidth; + viewport.height = context.drawingBufferHeight; + + let passState = defaultView.passState; + passState.viewport = BoundingRectangle.clone(viewport, passState.viewport); + + const drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer( + scene, + windowPosition, + scratchPosition + ); + const drawingBufferRectangle = computePickingDrawingBufferRectangle( + context.drawingBufferHeight, + drawingBufferPosition, + width, + height, + scratchRectangle + ); + + scene.jobScheduler.disableThisFrame(); + + scene.updateFrameState(); + frameState.cullingVolume = getPickCullingVolume( + scene, + drawingBufferPosition, + drawingBufferRectangle.width, + drawingBufferRectangle.height, + viewport + ); + frameState.invertClassification = false; + + // This has to be set to 'true', because otherwise, the + // submitCommandsForPass variable will be "false" in + // Model.js, and nothing will be rendered. + frameState.passes.render = true; + frameState.tilesetPassState = renderTilesetPassState; + + frameState.pickedMetadataSchemaId = schemaId; + frameState.pickedMetadataClassName = className; + frameState.pickedMetadataPropertyName = propertyName; + + context.uniformState.update(frameState); + + scene.updateEnvironment(); + + passState = pickFramebuffer.begin(drawingBufferRectangle, viewport); + + scene.updateAndExecuteCommands(passState, scratchColorZero); + + // When OIT is enabled, then the resolveFrameBuffers function + // will juggle around several frame buffers, and eventually use + // the "environmentState.originalFramebuffer" instead of the + // picking frame buffer. Skipping a million questions, just + // switch OIT off here: + const oldOIT = scene._environmentState.useOIT; + scene._environmentState.useOIT = false; + scene.resolveFramebuffers(passState); + scene._environmentState.useOIT = oldOIT; + + const metadataInfo = pickFramebuffer.readCenterPixel(drawingBufferRectangle); + context.endFrame(); + + delete frameState.pickedMetadataSchemaId; + delete frameState.pickedMetadataClassName; + delete frameState.pickedMetadataPropertyName; + + return metadataInfo; +}; + function renderTranslucentDepthForPick(scene, drawingBufferPosition) { // PERFORMANCE_IDEA: render translucent only and merge with the previous frame const { defaultView, context, frameState, environmentState } = scene; @@ -828,9 +955,15 @@ function getRayIntersection( updateOffscreenCameraFromRay(picking, ray, width, view.camera); - scratchRectangle = BoundingRectangle.clone(view.viewport, scratchRectangle); + const drawingBufferRectangle = BoundingRectangle.clone( + view.viewport, + scratchRectangle + ); - const passState = view.pickFramebuffer.begin(scratchRectangle, view.viewport); + const passState = view.pickFramebuffer.begin( + drawingBufferRectangle, + view.viewport + ); scene.jobScheduler.disableThisFrame(); @@ -852,7 +985,7 @@ function getRayIntersection( scene.resolveFramebuffers(passState); let position; - const object = view.pickFramebuffer.end(scratchRectangle); + const object = view.pickFramebuffer.end(drawingBufferRectangle); if (scene.context.depthTexture) { const numFrustums = view.frustumCommandsList.length; diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 2d81a94ff10b..7c1ce19db909 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -77,6 +77,7 @@ import View from "./View.js"; import DebugInspector from "./DebugInspector.js"; import VoxelCell from "./VoxelCell.js"; import VoxelPrimitive from "./VoxelPrimitive.js"; +import hasMetadataProperty from "./hasMetadataProperty.js"; const requestRenderAfterFrame = function (scene) { return function () { @@ -4254,6 +4255,99 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { ); }; +/** + * Pick a metadata value at the given window position. + * + * @param {Cartesian2} windowPosition Window coordinates to perform picking on. + * @param {string|undefined} schemaId The ID of the metadata schema to pick values + * from, or `undefined` to pick values from any schema. + * @param {string} className The name of the metadata class to pick + * values from + * @param {string} propertyName The Name of the metadata property to pick + * values from + * @param {number} [width=3] Width of the pick rectangle. + * @param {number} [height=3] Height of the pick rectangle. + * @returns The metadata value + * + * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. + */ +Scene.prototype.pickMetadata = function ( + windowPosition, + schemaId, + className, + propertyName, + width, + height +) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("windowPosition", windowPosition); + Check.typeOf.string("className", className); + Check.typeOf.string("propertyName", propertyName); + //>>includeEnd('debug'); + + const pickedObject = this.pick(windowPosition, width, height); + if (!defined(pickedObject)) { + return undefined; + } + + // Check if the picked object is a model that has structural + // metadata, with a schema that contains the specified + // property. + const detail = pickedObject?.detail; + const model = detail?.model; + const structuralMetadata = model?.structuralMetadata; + const schema = structuralMetadata?.schema; + + const XXX_METADATA_PICKING_DEBUG_LOG = false; + if (XXX_METADATA_PICKING_DEBUG_LOG) { + console.log("pickedObject ", pickedObject); + console.log("model ", model); + console.log("structuralMetadata ", structuralMetadata); + console.log("schema ", schema); + } + + if (!hasMetadataProperty(schema, schemaId, className, propertyName)) { + if (XXX_METADATA_PICKING_DEBUG_LOG) { + console.log("The metadata property was not found"); + console.log("schema ", schema); + console.log("schemaId ", schemaId); + console.log("className ", className); + console.log("propertyName ", propertyName); + } + return undefined; + } + + // The `Picking.pickMetadata` function will update the FrameState + // to add the `schemaId`, `className`, and `propertyName`. Based + // these values, the `MetadataPickingPipelineStage` will update + // the shaders for the metadata picking. Reset the draw commands + // here to trigger a rebuild of the draw commands with the updated + // shaders for the metadata picking render pass. + model.resetDrawCommands(); + + const pickedMetadataValues = this._picking.pickMetadata( + this, + windowPosition, + schemaId, + className, + propertyName, + width, + height + ); + + if (XXX_METADATA_PICKING_DEBUG_LOG) { + console.log("pickedMetadataValues ", pickedMetadataValues); + } + + // Trigger a rebuild of the draw commands to use the + // original shaders after picking. See notes above. + model.resetDrawCommands(); + + // TODO_METADATA_PICKING The result here is a 4-element (byte) buffer. + // Convert this to the required target type + return pickedMetadataValues; +}; + /** * Returns the cartesian position reconstructed from the depth buffer and window position. * The returned position is in world coordinates. Used internally by camera functions to diff --git a/packages/engine/Source/Scene/hasMetadataProperty.js b/packages/engine/Source/Scene/hasMetadataProperty.js new file mode 100644 index 000000000000..17f92e7114e0 --- /dev/null +++ b/packages/engine/Source/Scene/hasMetadataProperty.js @@ -0,0 +1,41 @@ +import defined from "../Core/defined.js"; + +/** + * Check if the given schema has a property that matches the + * given description. + * + * If the given schema is `undefined`, then `false` is returned. + * If the given `schemaId` is defined but does not match the ID + * of the given schema, then `false` is returned. + * Otherwise, this function returns whether the given schema + * has a class with the given name, that has a property with + * the given name. + * + * @param {object} schema The schema object + * @param {string|undefined} schemaId The ID of the metadata schema + * @param {string} className The name of the metadata class + * @param {string} propertyName The Name of the metadata property + * @returns {boolean} True if the property is present + * @private + */ +function hasMetadataProperty(schema, schemaId, className, propertyName) { + if (!defined(schema)) { + return false; + } + if (defined(schemaId) && schema.id !== schemaId) { + return false; + } + const classes = schema.classes || {}; + const metadataClass = classes[className]; + if (!defined(metadataClass)) { + return false; + } + const properties = metadataClass.properties || {}; + const metadataProperty = properties[propertyName]; + if (!defined(metadataProperty)) { + return false; + } + return true; +} + +export default hasMetadataProperty; diff --git a/packages/engine/Source/Shaders/Model/MetadataPickingStageFS.glsl b/packages/engine/Source/Shaders/Model/MetadataPickingStageFS.glsl new file mode 100644 index 000000000000..ef0ccb61daa1 --- /dev/null +++ b/packages/engine/Source/Shaders/Model/MetadataPickingStageFS.glsl @@ -0,0 +1,19 @@ + +/** + * A function that is called in ModelFS.glsl when the METADATA_PICKING + * define was set. It will be called after the `metadataStage`, and + * fill a vec4 with property values from the `Metadata` struct. + * The properties are selected via the METADATA_PICKING_PROPERTY_NAME + * define. + */ +void metadataPickingStage( + Metadata metadata, + MetadataClass metadataClass, + inout vec4 metadataValues +) { + float value = float(metadata.METADATA_PICKING_PROPERTY_NAME); + metadataValues.x = value / 255.0; + metadataValues.y = 0.0; + metadataValues.z = 0.0; + metadataValues.w = 0.0; +} diff --git a/packages/engine/Source/Shaders/Model/ModelFS.glsl b/packages/engine/Source/Shaders/Model/ModelFS.glsl index 9756cbe5cda3..ca3a2f2602c4 100644 --- a/packages/engine/Source/Shaders/Model/ModelFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelFS.glsl @@ -45,6 +45,10 @@ void main() MetadataStatistics metadataStatistics; metadataStage(metadata, metadataClass, metadataStatistics, attributes); + //======================================================================== + // When not picking metadata START + #ifndef METADATA_PICKING + #ifdef HAS_SELECTED_FEATURE_ID selectedFeatureIdStage(selectedFeature, featureIds); #endif @@ -73,6 +77,20 @@ void main() vec4 color = handleAlpha(material.diffuse, material.alpha); + // When not picking metadata END + //======================================================================== + #else + //======================================================================== + // When picking metadata START + + vec4 metadataValues = vec4(0.0, 0.0, 0.0, 0.0); + metadataPickingStage(metadata, metadataClass, metadataValues); + vec4 color = metadataValues; + + #endif + // When picking metadata END + //======================================================================== + #ifdef HAS_CLIPPING_PLANES modelClippingPlanesStage(color); #endif @@ -81,6 +99,10 @@ void main() modelClippingPolygonsStage(); #endif + //======================================================================== + // When not picking metadata START + #ifndef METADATA_PICKING + #if defined(HAS_SILHOUETTE) && defined(HAS_NORMALS) silhouetteStage(color); #endif @@ -89,5 +111,9 @@ void main() atmosphereStage(color, attributes); #endif + #endif + // When not picking metadata END + //======================================================================== + out_FragColor = color; } From e561decb7ef7dd9d0418882af002579260ba0220 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 9 Jul 2024 16:59:15 +0200 Subject: [PATCH 03/63] Fix typo --- packages/engine/Source/Scene/Picking.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 226149997760..6f27a4bcd236 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -230,7 +230,7 @@ function getPickCullingVolume( // Pick position and rectangle, used in all picking functions, // filled in computePickingDrawingBufferRectangle and passed -// the the FrameBuffer being/end methods +// the the FrameBuffer begin/end methods const scratchRectangle = new BoundingRectangle(0.0, 0.0, 3.0, 3.0); const scratchPosition = new Cartesian2(); From cc1b8264af7580fccf78f7bef2397659642053a1 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 15 Jul 2024 22:05:00 +0200 Subject: [PATCH 04/63] Try to avoid recompilation --- packages/engine/Source/Scene/FrameState.js | 7 + .../Model/MetadataPickingPipelineStage.js | 87 ------ packages/engine/Source/Scene/Model/Model.js | 29 ++ .../Source/Scene/Model/ModelDrawCommands.js | 272 ++++++++++++++++++ .../Scene/Model/ModelMatrixUpdateStage.js | 31 +- .../Scene/Model/ModelRuntimePrimitive.js | 2 - .../Source/Scene/Model/ModelSceneGraph.js | 44 ++- packages/engine/Source/Scene/Picking.js | 6 +- packages/engine/Source/Scene/Scene.js | 12 - .../Shaders/Model/MetadataPickingStageFS.glsl | 19 -- 10 files changed, 368 insertions(+), 141 deletions(-) delete mode 100644 packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js create mode 100644 packages/engine/Source/Scene/Model/ModelDrawCommands.js delete mode 100644 packages/engine/Source/Shaders/Model/MetadataPickingStageFS.glsl diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index bc2705a58e87..0b333ff2cf2a 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -422,6 +422,13 @@ function FrameState(context, creditDisplay, jobScheduler) { */ this.minimumTerrainHeight = 0.0; + /** + * Whether the current drawing pass is intended for metadata picking + * + * @type {boolean} + */ + this.pickMetadata = false; + /** * The schema ID for the metadata values that are supposed to be picked * with `Scene.pickMetadata`, or `undefined` to pick values from any diff --git a/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js b/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js deleted file mode 100644 index 9646c9d9c564..000000000000 --- a/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js +++ /dev/null @@ -1,87 +0,0 @@ -import ShaderDestination from "../../Renderer/ShaderDestination.js"; -import MetadataPickingStageFS from "../../Shaders/Model/MetadataPickingStageFS.js"; -import hasMetadataProperty from "../hasMetadataProperty.js"; - -/** - * A pipeline stage that updates `ModelRenderResources` for - * picking metadata. - * - * It adds the `METADATA_PICKING` define that causes the parts - * of the `ModelFS.glsl` to be skipped that are not related to - * picking, and inserts the `METADATA_PICKING_PROPERTY_NAME` - * define for the metadata property that should be picked. - * The latter will directly access the property of the `Metadata` - * struct that was generated by the `MetadataPipelineStage`. - * - * @namespace MetadataPickingPipelineStage - * @private - */ -const MetadataPickingPipelineStage = { - name: "MetadataPickingPipelineStage", // Helps with debugging if you know where to look -}; - -/** - * Process a primitive. - * - * If the given render resources are for a model that has metadata that - * matches the current `pickedMetadataSchemaId`, `pickedMetadataClassName` - * and `pickedMetadataPropertyName` of the `FrameState`, then this pass... - *
    - *
  • adds the `METADATA_PICKING` define to the fragment shader
  • - *
  • adds the `METADATA_PICKING_PROPERTY_NAME` to the fragment shader, using the given property name
  • - *
- * @param {PrimitiveRenderResources} renderResources The render resources for the primitive - * @param {ModelComponents.Primitive} primitive The primitive to be rendered - * @param {FrameState} frameState The frame state - * @private - */ -MetadataPickingPipelineStage.process = function ( - renderResources, - primitive, - frameState -) { - const XXX_METADATA_PICKING_DEBUG_LOG = false; - - if (XXX_METADATA_PICKING_DEBUG_LOG) { - console.log("MetadataPickingPipelineStage processing:"); - console.log(" renderResources: ", renderResources); - console.log(" primitive: ", primitive); - console.log(" frameState: ", frameState); - } - - const schemaId = frameState.pickedMetadataSchemaId; - const className = frameState.pickedMetadataClassName; - const propertyName = frameState.pickedMetadataPropertyName; - - // Verify that the given render resources contain a model with - // metadata that has a schema that matches the given description - const model = renderResources.model; - const structuralMetadata = model?.structuralMetadata; - const schema = structuralMetadata?.schema; - if (!hasMetadataProperty(schema, schemaId, className, propertyName)) { - if (XXX_METADATA_PICKING_DEBUG_LOG) { - console.log("The metadata property was not found"); - } - return; - } - - const shaderBuilder = renderResources.shaderBuilder; - - // Enable METADATA_PICKING in `ModelFS.glsl` - shaderBuilder.addDefine( - "METADATA_PICKING", - undefined, - ShaderDestination.FRAGMENT - ); - - // Add the MetadataPickingStageFS.glsl lines, and define - // the property that should be picked - shaderBuilder.addFragmentLines(MetadataPickingStageFS); - shaderBuilder.addDefine( - "METADATA_PICKING_PROPERTY_NAME", - propertyName, - ShaderDestination.FRAGMENT - ); -}; - -export default MetadataPickingPipelineStage; diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 3bf1c787578d..0885dc04b5d8 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -479,6 +479,10 @@ function Model(options) { this._fogRenderable = undefined; + this._pickedMetadataSchemaId = undefined; + this._pickedMetadataClassName = undefined; + this._pickedMetadataPropertyName = undefined; + this._skipLevelOfDetail = false; this._ignoreCommands = defaultValue(options.ignoreCommands, false); @@ -1841,6 +1845,7 @@ Model.prototype.update = function (frameState) { updateSceneMode(this, frameState); updateFog(this, frameState); updateVerticalExaggeration(this, frameState); + updatePickedMetadata(this, frameState); this._defaultTexture = frameState.context.defaultTexture; @@ -2069,6 +2074,30 @@ function updateVerticalExaggeration(model, frameState) { } } +function updatePickedMetadata(model, frameState) { + if ( + frameState.pickedMetadataSchemaId !== model._pickedMetadataSchemaId || + frameState.pickedMetadataClassName !== model._pickedMetadataClassName || + frameState.pickedMetadataPropertyName !== model._pickedMetadataPropertyName + ) { + console.log("pickedMetadata changed, reset draw commands"); + console.log(" pickedMetadataSchemaId", frameState.pickedMetadataSchemaId); + console.log( + " pickedMetadataClassName", + frameState.pickedMetadataClassName + ); + console.log( + " pickedMetadataPropertyName", + frameState.pickedMetadataPropertyName + ); + + model.resetDrawCommands(); + model._pickedMetadataSchemaId = frameState.pickedMetadataSchemaId; + model._pickedMetadataClassName = frameState.pickedMetadataClassName; + model._pickedMetadataPropertyName = frameState.pickedMetadataPropertyName; + } +} + function buildDrawCommands(model, frameState) { if (!model._drawCommandsBuilt) { model.destroyPipelineResources(); diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js new file mode 100644 index 000000000000..e5735324ce2e --- /dev/null +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -0,0 +1,272 @@ +import BoundingSphere from "../../Core/BoundingSphere.js"; +import clone from "../../Core/clone.js"; +import defined from "../../Core/defined.js"; +import Matrix4 from "../../Core/Matrix4.js"; +import DrawCommand from "../../Renderer/DrawCommand.js"; +import RenderState from "../../Renderer/RenderState.js"; +import SceneMode from "../SceneMode.js"; +import ShadowMode from "../ShadowMode.js"; +import ShaderDestination from "../../Renderer/ShaderDestination.js"; +import ClassificationModelDrawCommand from "./ClassificationModelDrawCommand.js"; +import ModelDrawCommand from "./ModelDrawCommand.js"; +import VertexArray from "../../Renderer/VertexArray.js"; +import ModelVS from "../../Shaders/Model/ModelVS.js"; +import ModelFS from "../../Shaders/Model/ModelFS.js"; +import ModelUtility from "./ModelUtility.js"; +import DeveloperError from "../../Core/DeveloperError.js"; + +/** + * Internal functions to build draw commands for models. + * + * (The core of these functions was taken from `buildDrawCommand.jsĀ“, + * as of commit hash 7b93161da1cc03bdc796b204e7aa51fb7acebf04) + * + * @private + */ +function ModelDrawCommands() {} + +/** + * Builds the {@link ModelDrawCommand} for a {@link ModelRuntimePrimitive} + * using its render resources. If the model classifies another asset, it + * builds a {@link ClassificationModelDrawCommand} instead. + * + * @param {PrimitiveRenderResources} primitiveRenderResources The render resources for a primitive. + * @param {FrameState} frameState The frame state for creating GPU resources. + * + * @returns {ModelDrawCommand|ClassificationModelDrawCommand} The generated ModelDrawCommand or ClassificationModelDrawCommand. + * + * @private + */ +ModelDrawCommands.buildModelDrawCommand = function ( + primitiveRenderResources, + frameState, + shaderBuilder, + pickMetadata +) { + if (frameState.pickMetadata === true && pickMetadata === true) { + console.log("Now adding that stuff..."); + console.log(" pickedMetadataSchemaId", frameState.pickedMetadataSchemaId); + console.log( + " pickedMetadataClassName", + frameState.pickedMetadataClassName + ); + console.log( + " pickedMetadataPropertyName", + frameState.pickedMetadataPropertyName + ); + + shaderBuilder.addFunction( + "metadataPickingStage", + "void metadataPickingStage(Metadata metadata, MetadataClass metadataClass, inout vec4 metadataValues)", + ShaderDestination.FRAGMENT + ); + const lines = [ + "float value = float(metadata.METADATA_PICKING_PROPERTY_NAME);", + "metadataValues.x = value / 255.0;", + "metadataValues.y = 1.0;", + "metadataValues.z = 0.0;", + "metadataValues.w = 1.0;", + ]; + shaderBuilder.addFunctionLines( + "metadataPickingStage", + lines, + ShaderDestination.FRAGMENT + ); + + //const schemaId = frameState.pickedMetadataSchemaId; + //const className = frameState.pickedMetadataClassName; + const propertyName = frameState.pickedMetadataPropertyName; + + // Enable METADATA_PICKING in `ModelFS.glsl` + shaderBuilder.addDefine( + "METADATA_PICKING", + undefined, + ShaderDestination.FRAGMENT + ); + + // Add the define for the property that should be picked + shaderBuilder.addDefine( + "METADATA_PICKING_PROPERTY_NAME", + propertyName, + ShaderDestination.FRAGMENT + ); + shaderBuilder.addFragmentLines("test()"); + } + + const shaderProgram = ModelDrawCommands.createShaderProgram( + primitiveRenderResources, + shaderBuilder, + frameState + ); + + const command = ModelDrawCommands.buildDrawCommandForModel( + primitiveRenderResources, + shaderProgram, + frameState + ); + const model = primitiveRenderResources.model; + const hasClassification = defined(model.classificationType); + if (hasClassification) { + return new ClassificationModelDrawCommand({ + primitiveRenderResources: primitiveRenderResources, + command: command, + }); + } + + return new ModelDrawCommand({ + primitiveRenderResources: primitiveRenderResources, + command: command, + }); +}; + +/** + * @private + */ +ModelDrawCommands.createShaderProgram = function ( + primitiveRenderResources, + shaderBuilder, + frameState +) { + shaderBuilder.addVertexLines(ModelVS); + shaderBuilder.addFragmentLines(ModelFS); + + const model = primitiveRenderResources.model; + const shaderProgram = shaderBuilder.buildShaderProgram(frameState.context); + model._pipelineResources.push(shaderProgram); + return shaderProgram; +}; + +/** + * Builds the {@link DrawCommand} that serves as the basis for either creating + * a {@link ModelDrawCommand} or a {@link ModelRuntimePrimitive} + * + * @param {PrimitiveRenderResources} primitiveRenderResources The render resources for a primitive. + * @param {ShaderProgram} shaderProgram The shader program + * @param {FrameState} frameState The frame state for creating GPU resources. + * + * @returns {DrawCommand} The generated DrawCommand, to be passed to + * the ModelDrawCommand or ClassificationModelDrawCommand + * + * @private + */ +ModelDrawCommands.buildDrawCommandForModel = function ( + primitiveRenderResources, + shaderProgram, + frameState +) { + const indexBuffer = ModelDrawCommands.getIndexBuffer( + primitiveRenderResources + ); + + const vertexArray = new VertexArray({ + context: frameState.context, + indexBuffer: indexBuffer, + attributes: primitiveRenderResources.attributes, + }); + + const model = primitiveRenderResources.model; + model._pipelineResources.push(vertexArray); + + const pass = primitiveRenderResources.alphaOptions.pass; + const sceneGraph = model.sceneGraph; + + const is3D = frameState.mode === SceneMode.SCENE3D; + let modelMatrix, boundingSphere; + + if (!is3D && !frameState.scene3DOnly && model._projectTo2D) { + modelMatrix = Matrix4.multiplyTransformation( + sceneGraph._computedModelMatrix, + primitiveRenderResources.runtimeNode.computedTransform, + new Matrix4() + ); + + const runtimePrimitive = primitiveRenderResources.runtimePrimitive; + boundingSphere = runtimePrimitive.boundingSphere2D; + } else { + const computedModelMatrix = is3D + ? sceneGraph._computedModelMatrix + : sceneGraph._computedModelMatrix2D; + + modelMatrix = Matrix4.multiplyTransformation( + computedModelMatrix, + primitiveRenderResources.runtimeNode.computedTransform, + new Matrix4() + ); + + boundingSphere = BoundingSphere.transform( + primitiveRenderResources.boundingSphere, + modelMatrix, + primitiveRenderResources.boundingSphere + ); + } + + // Initialize render state with default values + let renderState = clone( + RenderState.fromCache(primitiveRenderResources.renderStateOptions), + true + ); + + renderState.cull.face = ModelUtility.getCullFace( + modelMatrix, + primitiveRenderResources.primitiveType + ); + renderState = RenderState.fromCache(renderState); + + const hasClassification = defined(model.classificationType); + const castShadows = hasClassification + ? false + : ShadowMode.castShadows(model.shadows); + const receiveShadows = hasClassification + ? false + : ShadowMode.receiveShadows(model.shadows); + // Pick IDs are only added to specific draw commands for classification. + // This behavior is handled by ClassificationModelDrawCommand. + const pickId = hasClassification + ? undefined + : primitiveRenderResources.pickId; + + const command = new DrawCommand({ + boundingVolume: boundingSphere, + modelMatrix: modelMatrix, + uniformMap: primitiveRenderResources.uniformMap, + renderState: renderState, + vertexArray: vertexArray, + shaderProgram: shaderProgram, + cull: model.cull, + pass: pass, + count: primitiveRenderResources.count, + owner: model, + pickId: pickId, + instanceCount: primitiveRenderResources.instanceCount, + primitiveType: primitiveRenderResources.primitiveType, + debugShowBoundingVolume: model.debugShowBoundingVolume, + castShadows: castShadows, + receiveShadows: receiveShadows, + }); + return command; +}; + +/** + * @private + */ +ModelDrawCommands.getIndexBuffer = function (primitiveRenderResources) { + const wireframeIndexBuffer = primitiveRenderResources.wireframeIndexBuffer; + if (defined(wireframeIndexBuffer)) { + return wireframeIndexBuffer; + } + + const indices = primitiveRenderResources.indices; + if (!defined(indices)) { + return undefined; + } + + //>>includeStart('debug', pragmas.debug); + if (!defined(indices.buffer)) { + throw new DeveloperError("Indices must be provided as a Buffer"); + } + //>>includeEnd('debug'); + + return indices.buffer; +}; + +export default ModelDrawCommands; diff --git a/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js b/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js index c0c21674846b..3a212b088b28 100644 --- a/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js +++ b/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js @@ -48,6 +48,23 @@ ModelMatrixUpdateStage.update = function (runtimeNode, sceneGraph, frameState) { } }; +/** + * Update the modelMatrix and cullFrace of the given draw command. + * + * @private + */ +function updateDrawCommand(drawCommand, modelMatrix, transformToRoot) { + drawCommand.modelMatrix = Matrix4.multiplyTransformation( + modelMatrix, + transformToRoot, + drawCommand.modelMatrix + ); + drawCommand.cullFace = ModelUtility.getCullFace( + drawCommand.modelMatrix, + drawCommand.primitiveType + ); +} + /** * Recursively update all child runtime nodes and their runtime primitives. * @@ -73,15 +90,15 @@ function updateRuntimeNode( const primitivesLength = runtimeNode.runtimePrimitives.length; for (i = 0; i < primitivesLength; i++) { const runtimePrimitive = runtimeNode.runtimePrimitives[i]; - const drawCommand = runtimePrimitive.drawCommand; - drawCommand.modelMatrix = Matrix4.multiplyTransformation( + updateDrawCommand( + runtimePrimitive.drawCommand, modelMatrix, - transformToRoot, - drawCommand.modelMatrix + transformToRoot ); - drawCommand.cullFace = ModelUtility.getCullFace( - drawCommand.modelMatrix, - drawCommand.primitiveType + updateDrawCommand( + runtimePrimitive.metadataPickingDrawCommand, + modelMatrix, + transformToRoot ); } diff --git a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js index 1e42efbe24f7..ed82ca255cf4 100644 --- a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js +++ b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js @@ -15,7 +15,6 @@ import GeometryPipelineStage from "./GeometryPipelineStage.js"; import LightingPipelineStage from "./LightingPipelineStage.js"; import MaterialPipelineStage from "./MaterialPipelineStage.js"; import MetadataPipelineStage from "./MetadataPipelineStage.js"; -import MetadataPickingPipelineStage from "./MetadataPickingPipelineStage.js"; import ModelUtility from "./ModelUtility.js"; import MorphTargetsPipelineStage from "./MorphTargetsPipelineStage.js"; import PickingPipelineStage from "./PickingPipelineStage.js"; @@ -277,7 +276,6 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) { // are declared to avoid compilation errors. pipelineStages.push(FeatureIdPipelineStage); pipelineStages.push(MetadataPipelineStage); - pipelineStages.push(MetadataPickingPipelineStage); if (featureIdFlags.hasPropertyTable) { pipelineStages.push(SelectedFeatureIdPipelineStage); diff --git a/packages/engine/Source/Scene/Model/ModelSceneGraph.js b/packages/engine/Source/Scene/Model/ModelSceneGraph.js index 954c63081acc..c5d7e7967166 100644 --- a/packages/engine/Source/Scene/Model/ModelSceneGraph.js +++ b/packages/engine/Source/Scene/Model/ModelSceneGraph.js @@ -7,7 +7,6 @@ import Matrix4 from "../../Core/Matrix4.js"; import Transforms from "../../Core/Transforms.js"; import SceneMode from "../SceneMode.js"; import SplitDirection from "../SplitDirection.js"; -import buildDrawCommand from "./buildDrawCommand.js"; import TilesetPipelineStage from "./TilesetPipelineStage.js"; import AtmospherePipelineStage from "./AtmospherePipelineStage.js"; import ImageBasedLightingPipelineStage from "./ImageBasedLightingPipelineStage.js"; @@ -26,6 +25,7 @@ import ModelSplitterPipelineStage from "./ModelSplitterPipelineStage.js"; import ModelType from "./ModelType.js"; import NodeRenderResources from "./NodeRenderResources.js"; import PrimitiveRenderResources from "./PrimitiveRenderResources.js"; +import ModelDrawCommands from "./ModelDrawCommands.js"; /** * An in memory representation of the scene graph for a {@link Model} @@ -560,11 +560,24 @@ ModelSceneGraph.prototype.buildDrawCommands = function (frameState) { modelPositionMax ); - const drawCommand = buildDrawCommand( + const shaderBuilderRender = primitiveRenderResources.shaderBuilder.clone(); + const shaderBuilderPickMetadata = primitiveRenderResources.shaderBuilder.clone(); + + const drawCommand = ModelDrawCommands.buildModelDrawCommand( primitiveRenderResources, - frameState + frameState, + shaderBuilderRender, + false ); runtimePrimitive.drawCommand = drawCommand; + + const metadataPickingDrawCommand = ModelDrawCommands.buildModelDrawCommand( + primitiveRenderResources, + frameState, + shaderBuilderPickMetadata, + true + ); + runtimePrimitive.metadataPickingDrawCommand = metadataPickingDrawCommand; } } @@ -818,8 +831,9 @@ ModelSceneGraph.prototype.updateBackFaceCulling = function (backFaceCulling) { // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveBackFaceCulling(runtimePrimitive, options) { - const drawCommand = runtimePrimitive.drawCommand; - drawCommand.backFaceCulling = options.backFaceCulling; + runtimePrimitive.drawCommand.backFaceCulling = options.backFaceCulling; + runtimePrimitive.metadataPickingDrawCommand.backFaceCulling = + options.backFaceCulling; } const scratchShadowOptions = { @@ -841,8 +855,8 @@ ModelSceneGraph.prototype.updateShadows = function (shadowMode) { // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveShadows(runtimePrimitive, options) { - const drawCommand = runtimePrimitive.drawCommand; - drawCommand.shadows = options.shadowMode; + runtimePrimitive.drawCommand.shadows = options.shadows; + runtimePrimitive.metadataPickingDrawCommand.shadows = options.shadows; } const scratchShowBoundingVolumeOptions = { @@ -872,8 +886,10 @@ ModelSceneGraph.prototype.updateShowBoundingVolume = function ( // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveShowBoundingVolume(runtimePrimitive, options) { - const drawCommand = runtimePrimitive.drawCommand; - drawCommand.debugShowBoundingVolume = options.debugShowBoundingVolume; + runtimePrimitive.drawCommand.debugShowBoundingVolume = + options.debugShowBoundingVolume; + runtimePrimitive.metadataPickingDrawCommand.debugShowBoundingVolume = + options.debugShowBoundingVolume; } const scratchSilhouetteCommands = []; @@ -921,7 +937,15 @@ function pushPrimitiveDrawCommands(runtimePrimitive, options) { const passes = frameState.passes; const silhouetteCommands = scratchSilhouetteCommands; - const primitiveDrawCommand = runtimePrimitive.drawCommand; + let primitiveDrawCommand; + + //const schemaId = frameState.pickedMetadataSchemaId; + if (frameState.pickMetadata) { + console.log("Using metadata picking command"); + primitiveDrawCommand = runtimePrimitive.metadataPickingDrawCommand; + } else { + primitiveDrawCommand = runtimePrimitive.drawCommand; + } primitiveDrawCommand.pushCommands(frameState, frameState.commandList); diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 6f27a4bcd236..0a387fa0ba20 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -486,6 +486,7 @@ Picking.prototype.pickMetadata = function ( frameState.passes.render = true; frameState.tilesetPassState = renderTilesetPassState; + frameState.pickMetadata = true; frameState.pickedMetadataSchemaId = schemaId; frameState.pickedMetadataClassName = className; frameState.pickedMetadataPropertyName = propertyName; @@ -511,10 +512,7 @@ Picking.prototype.pickMetadata = function ( const metadataInfo = pickFramebuffer.readCenterPixel(drawingBufferRectangle); context.endFrame(); - delete frameState.pickedMetadataSchemaId; - delete frameState.pickedMetadataClassName; - delete frameState.pickedMetadataPropertyName; - + frameState.pickMetadata = false; return metadataInfo; }; diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 7c1ce19db909..673bb60778e9 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4317,14 +4317,6 @@ Scene.prototype.pickMetadata = function ( return undefined; } - // The `Picking.pickMetadata` function will update the FrameState - // to add the `schemaId`, `className`, and `propertyName`. Based - // these values, the `MetadataPickingPipelineStage` will update - // the shaders for the metadata picking. Reset the draw commands - // here to trigger a rebuild of the draw commands with the updated - // shaders for the metadata picking render pass. - model.resetDrawCommands(); - const pickedMetadataValues = this._picking.pickMetadata( this, windowPosition, @@ -4339,10 +4331,6 @@ Scene.prototype.pickMetadata = function ( console.log("pickedMetadataValues ", pickedMetadataValues); } - // Trigger a rebuild of the draw commands to use the - // original shaders after picking. See notes above. - model.resetDrawCommands(); - // TODO_METADATA_PICKING The result here is a 4-element (byte) buffer. // Convert this to the required target type return pickedMetadataValues; diff --git a/packages/engine/Source/Shaders/Model/MetadataPickingStageFS.glsl b/packages/engine/Source/Shaders/Model/MetadataPickingStageFS.glsl deleted file mode 100644 index ef0ccb61daa1..000000000000 --- a/packages/engine/Source/Shaders/Model/MetadataPickingStageFS.glsl +++ /dev/null @@ -1,19 +0,0 @@ - -/** - * A function that is called in ModelFS.glsl when the METADATA_PICKING - * define was set. It will be called after the `metadataStage`, and - * fill a vec4 with property values from the `Metadata` struct. - * The properties are selected via the METADATA_PICKING_PROPERTY_NAME - * define. - */ -void metadataPickingStage( - Metadata metadata, - MetadataClass metadataClass, - inout vec4 metadataValues -) { - float value = float(metadata.METADATA_PICKING_PROPERTY_NAME); - metadataValues.x = value / 255.0; - metadataValues.y = 0.0; - metadataValues.z = 0.0; - metadataValues.w = 0.0; -} From a28f62d4b1f19d89274adaa7fa0716a3e01bc695 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 16 Jul 2024 16:54:16 +0200 Subject: [PATCH 05/63] Do not modify the bounding sphere... --- packages/engine/Source/Scene/Model/ModelDrawCommands.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js index e5735324ce2e..9a1072e5606b 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommands.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -195,8 +195,7 @@ ModelDrawCommands.buildDrawCommandForModel = function ( boundingSphere = BoundingSphere.transform( primitiveRenderResources.boundingSphere, - modelMatrix, - primitiveRenderResources.boundingSphere + modelMatrix ); } From 122c4e81344db9a8fcc540ee304d4832439c3c5a Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 16 Jul 2024 16:55:07 +0200 Subject: [PATCH 06/63] Remove debug stuff That bounding sphere modification. Sigh. --- packages/engine/Source/Scene/Model/ModelDrawCommands.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js index 9a1072e5606b..b74e1b0ba5db 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommands.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -63,9 +63,9 @@ ModelDrawCommands.buildModelDrawCommand = function ( const lines = [ "float value = float(metadata.METADATA_PICKING_PROPERTY_NAME);", "metadataValues.x = value / 255.0;", - "metadataValues.y = 1.0;", + "metadataValues.y = 0.0;", "metadataValues.z = 0.0;", - "metadataValues.w = 1.0;", + "metadataValues.w = 0.0;", ]; shaderBuilder.addFunctionLines( "metadataPickingStage", @@ -90,7 +90,6 @@ ModelDrawCommands.buildModelDrawCommand = function ( propertyName, ShaderDestination.FRAGMENT ); - shaderBuilder.addFragmentLines("test()"); } const shaderProgram = ModelDrawCommands.createShaderProgram( From a44ac07cfffe10774f0b23adb7d5afdb44d07b01 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 17 Jul 2024 16:33:57 +0200 Subject: [PATCH 07/63] Draft for warnings for unsupported property types --- .../Source/Scene/PropertyTextureProperty.js | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/packages/engine/Source/Scene/PropertyTextureProperty.js b/packages/engine/Source/Scene/PropertyTextureProperty.js index 9c1ed66239b9..aed6e39e4bab 100644 --- a/packages/engine/Source/Scene/PropertyTextureProperty.js +++ b/packages/engine/Source/Scene/PropertyTextureProperty.js @@ -4,6 +4,7 @@ import defined from "../Core/defined.js"; import GltfLoaderUtil from "./GltfLoaderUtil.js"; import MetadataType from "./MetadataType.js"; import MetadataComponentType from "./MetadataComponentType.js"; +import oneTimeWarning from "../Core/oneTimeWarning.js"; /** * A property in a property texture. @@ -185,20 +186,48 @@ PropertyTextureProperty.prototype.isGpuCompatible = function () { if (classProperty.isArray) { // only support arrays of 1-4 UINT8 scalars (normalized or unnormalized) - return ( - !classProperty.isVariableLengthArray && - classProperty.arrayLength <= 4 && - type === MetadataType.SCALAR && - componentType === MetadataComponentType.UINT8 - ); + if (classProperty.isVariableLengthArray) { + oneTimeWarning( + `Property texture property ${classProperty.id} is a variable-length array, which is not supported` + ); + return false; + } + if (classProperty.arrayLength > 4) { + oneTimeWarning( + `Property texture property ${classProperty.id} is an array of length ${classProperty.arrayLength}, but may have at most a length of 4` + ); + return false; + } + if (type !== MetadataType.SCALAR) { + oneTimeWarning( + `Property texture property ${classProperty.id} is an array of type ${type}, but only SCALAR is supported` + ); + return false; + } + if (componentType !== MetadataComponentType.UINT8) { + oneTimeWarning( + `Property texture property ${classProperty.id} is an array with component type ${componentType}, but only UINT8 is supported` + ); + return false; + } + return true; } if (MetadataType.isVectorType(type) || type === MetadataType.SCALAR) { - return componentType === MetadataComponentType.UINT8; + if (componentType !== MetadataComponentType.UINT8) { + oneTimeWarning( + `Property texture property ${classProperty.id} has component type ${componentType}, but only UINT8 is supported` + ); + return false; + } + return true; } // For this initial implementation, only UINT8-based properties // are supported. + oneTimeWarning( + `Property texture property ${classProperty.id} has an unsupported type` + ); return false; }; From e0c57f6df516ae3772b40f6250a00a85f9fbacdf Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 17 Jul 2024 16:34:33 +0200 Subject: [PATCH 08/63] Draft for encoding metadata values for picking --- packages/engine/Source/Scene/FrameState.js | 8 + packages/engine/Source/Scene/Model/Model.js | 9 ++ .../Source/Scene/Model/ModelDrawCommands.js | 153 ++++++++++++------ .../Source/Scene/Model/ModelSceneGraph.js | 17 +- packages/engine/Source/Scene/Picking.js | 20 ++- packages/engine/Source/Scene/Scene.js | 47 ++++-- .../Source/Scene/getMetadataClassProperty.js | 43 +++++ .../Source/Scene/hasMetadataProperty.js | 41 ----- 8 files changed, 226 insertions(+), 112 deletions(-) create mode 100644 packages/engine/Source/Scene/getMetadataClassProperty.js delete mode 100644 packages/engine/Source/Scene/hasMetadataProperty.js diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 0b333ff2cf2a..9a56ef25cd79 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -453,6 +453,14 @@ function FrameState(context, creditDisplay, jobScheduler) { * @type {string|undefined} */ this.pickedMetadataPropertyName = undefined; + + /** + * The metadata class property that values should be picked from + * with `Scene.pickMetadata` + * + * @type {MetadataClassProperty|undefined} + */ + this.pickedMetadataClassProperty = undefined; } /** diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 0885dc04b5d8..b60a6f4270f6 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2074,6 +2074,15 @@ function updateVerticalExaggeration(model, frameState) { } } +/** + * Will be called during `update`, and detect changes in the `pickedMetadata*` + * properties. When they changed, then the draw commands will be reset, to + * eventually be rebuilt by the `ModelDrawCommands` class, with the new picked + * metadata property values + * + * @param {Model} model The model + * @param {FrameState} frameState The frame state + */ function updatePickedMetadata(model, frameState) { if ( frameState.pickedMetadataSchemaId !== model._pickedMetadataSchemaId || diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js index b74e1b0ba5db..55ec109cff90 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommands.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -14,6 +14,7 @@ import ModelVS from "../../Shaders/Model/ModelVS.js"; import ModelFS from "../../Shaders/Model/ModelFS.js"; import ModelUtility from "./ModelUtility.js"; import DeveloperError from "../../Core/DeveloperError.js"; +import MetadataType from "../MetadataType.js"; /** * Internal functions to build draw commands for models. @@ -25,6 +26,105 @@ import DeveloperError from "../../Core/DeveloperError.js"; */ function ModelDrawCommands() {} +function getComponentCount(classProperty) { + if (!classProperty.isArray) { + return MetadataType.getComponentCount(classProperty.type); + } + return classProperty.arrayLength; +} +function getGlslType(classProperty) { + const componentCount = getComponentCount(classProperty); + if (classProperty.normalized) { + if (componentCount === 1) { + return "float"; + } + return `vec${componentCount}`; + } + if (componentCount === 1) { + return "int"; + } + return `ivec${componentCount}`; +} + +ModelDrawCommands.prepareMetadataPickingStage = function ( + shaderBuilder, + schemaId, + className, + propertyName, + classProperty +) { + console.log("Now adding that stuff..."); + console.log(" schemaId", schemaId); + console.log(" className", className); + console.log(" propertyName", propertyName); + console.log(" classProperty", classProperty); + + shaderBuilder.addFunction( + "metadataPickingStage", + "void metadataPickingStage(Metadata metadata, MetadataClass metadataClass, inout vec4 metadataValues)", + ShaderDestination.FRAGMENT + ); + + const lines = []; + + const glslType = getGlslType(classProperty); + lines.push( + `${glslType} value = ${glslType}(metadata.METADATA_PICKING_PROPERTY_NAME);` + ); + + const normalized = classProperty.normalized === true; + //const glslComponentType = getGlslComponentType(classProperty); + const componentCount = getComponentCount(classProperty); + const sourceValueStrings = ["0.0", "0.0", "0.0", "0.0"]; + if (componentCount === 1) { + const valueString = `value`; + sourceValueStrings[0] = `float(${valueString})`; + if (normalized) { + sourceValueStrings[0] += " / 255.0"; + } + } else { + const components = ["x", "y", "z", "w"]; + for (let i = 0; i < componentCount; i++) { + const component = components[i]; + const valueString = `value.${component}`; + sourceValueStrings[i] = `float(${valueString}) / 255.0`; + if (normalized) { + sourceValueStrings[i] += " / 255.0"; + } + } + } + lines.push(`metadataValues.x = ${sourceValueStrings[0]};`); + lines.push(`metadataValues.y = ${sourceValueStrings[1]};`); + lines.push(`metadataValues.z = ${sourceValueStrings[2]};`); + lines.push(`metadataValues.w = ${sourceValueStrings[3]};`); + shaderBuilder.addFunctionLines( + "metadataPickingStage", + lines, + ShaderDestination.FRAGMENT + ); + + console.log("Name is ", propertyName); + console.log("lines"); + console.log(" ", lines[1]); + console.log(" ", lines[2]); + console.log(" ", lines[3]); + console.log(" ", lines[4]); + + // Enable METADATA_PICKING in `ModelFS.glsl` + shaderBuilder.addDefine( + "METADATA_PICKING", + undefined, + ShaderDestination.FRAGMENT + ); + + // Add the define for the property that should be picked + shaderBuilder.addDefine( + "METADATA_PICKING_PROPERTY_NAME", + propertyName, + ShaderDestination.FRAGMENT + ); +}; + /** * Builds the {@link ModelDrawCommand} for a {@link ModelRuntimePrimitive} * using its render resources. If the model classifies another asset, it @@ -40,58 +140,8 @@ function ModelDrawCommands() {} ModelDrawCommands.buildModelDrawCommand = function ( primitiveRenderResources, frameState, - shaderBuilder, - pickMetadata + shaderBuilder ) { - if (frameState.pickMetadata === true && pickMetadata === true) { - console.log("Now adding that stuff..."); - console.log(" pickedMetadataSchemaId", frameState.pickedMetadataSchemaId); - console.log( - " pickedMetadataClassName", - frameState.pickedMetadataClassName - ); - console.log( - " pickedMetadataPropertyName", - frameState.pickedMetadataPropertyName - ); - - shaderBuilder.addFunction( - "metadataPickingStage", - "void metadataPickingStage(Metadata metadata, MetadataClass metadataClass, inout vec4 metadataValues)", - ShaderDestination.FRAGMENT - ); - const lines = [ - "float value = float(metadata.METADATA_PICKING_PROPERTY_NAME);", - "metadataValues.x = value / 255.0;", - "metadataValues.y = 0.0;", - "metadataValues.z = 0.0;", - "metadataValues.w = 0.0;", - ]; - shaderBuilder.addFunctionLines( - "metadataPickingStage", - lines, - ShaderDestination.FRAGMENT - ); - - //const schemaId = frameState.pickedMetadataSchemaId; - //const className = frameState.pickedMetadataClassName; - const propertyName = frameState.pickedMetadataPropertyName; - - // Enable METADATA_PICKING in `ModelFS.glsl` - shaderBuilder.addDefine( - "METADATA_PICKING", - undefined, - ShaderDestination.FRAGMENT - ); - - // Add the define for the property that should be picked - shaderBuilder.addDefine( - "METADATA_PICKING_PROPERTY_NAME", - propertyName, - ShaderDestination.FRAGMENT - ); - } - const shaderProgram = ModelDrawCommands.createShaderProgram( primitiveRenderResources, shaderBuilder, @@ -103,6 +153,7 @@ ModelDrawCommands.buildModelDrawCommand = function ( shaderProgram, frameState ); + const model = primitiveRenderResources.model; const hasClassification = defined(model.classificationType); if (hasClassification) { diff --git a/packages/engine/Source/Scene/Model/ModelSceneGraph.js b/packages/engine/Source/Scene/Model/ModelSceneGraph.js index c5d7e7967166..9bbe175ea3d6 100644 --- a/packages/engine/Source/Scene/Model/ModelSceneGraph.js +++ b/packages/engine/Source/Scene/Model/ModelSceneGraph.js @@ -561,8 +561,6 @@ ModelSceneGraph.prototype.buildDrawCommands = function (frameState) { ); const shaderBuilderRender = primitiveRenderResources.shaderBuilder.clone(); - const shaderBuilderPickMetadata = primitiveRenderResources.shaderBuilder.clone(); - const drawCommand = ModelDrawCommands.buildModelDrawCommand( primitiveRenderResources, frameState, @@ -571,6 +569,21 @@ ModelSceneGraph.prototype.buildDrawCommands = function (frameState) { ); runtimePrimitive.drawCommand = drawCommand; + const shaderBuilderPickMetadata = primitiveRenderResources.shaderBuilder.clone(); + if (frameState.pickMetadata === true) { + const schemaId = frameState.pickedMetadataSchemaId; + const className = frameState.pickedMetadataClassName; + const propertyName = frameState.pickedMetadataPropertyName; + const classProperty = frameState.pickedMetadataClassProperty; + ModelDrawCommands.prepareMetadataPickingStage( + shaderBuilderPickMetadata, + schemaId, + className, + propertyName, + classProperty + ); + } + const metadataPickingDrawCommand = ModelDrawCommands.buildModelDrawCommand( primitiveRenderResources, frameState, diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 0a387fa0ba20..92fa31baf987 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -419,10 +419,9 @@ Picking.prototype.pickVoxelCoordinate = function ( * from, or `undefined` to pick values from any schema. * @param {string} className The name of the metadata class to pick * values from - * @param {string} propertyName The Name of the metadata property to pick + * @param {string} propertyName The name of the metadata property to pick * values from - * @param {number} [width=3] Width of the pick rectangle. - * @param {number} [height=3] Height of the pick rectangle. + * @param {MetadataClassProperty} classProperty The actual class property * @returns The metadata values as a raw, 4-byte buffer * * @private @@ -433,8 +432,7 @@ Picking.prototype.pickMetadata = function ( schemaId, className, propertyName, - width, - height + classProperty ) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("windowPosition", windowPosition); @@ -463,8 +461,8 @@ Picking.prototype.pickMetadata = function ( const drawingBufferRectangle = computePickingDrawingBufferRectangle( context.drawingBufferHeight, drawingBufferPosition, - width, - height, + 1.0, + 1.0, scratchRectangle ); @@ -486,11 +484,17 @@ Picking.prototype.pickMetadata = function ( frameState.passes.render = true; frameState.tilesetPassState = renderTilesetPassState; + // Insert the information about the picked metadata property + // into the frame state, so that ... + // - changes in these properties are picked up by the Model.update + // function to trigger a rebuild of the draw commands + // - the ModelDrawCommands functions will insert the proper code + // for picking the values into the shader frameState.pickMetadata = true; frameState.pickedMetadataSchemaId = schemaId; frameState.pickedMetadataClassName = className; frameState.pickedMetadataPropertyName = propertyName; - + frameState.pickedMetadataClassProperty = classProperty; context.uniformState.update(frameState); scene.updateEnvironment(); diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 673bb60778e9..ae97860788da 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -77,7 +77,7 @@ import View from "./View.js"; import DebugInspector from "./DebugInspector.js"; import VoxelCell from "./VoxelCell.js"; import VoxelPrimitive from "./VoxelPrimitive.js"; -import hasMetadataProperty from "./hasMetadataProperty.js"; +import getMetadataClassProperty from "./getMetadataClassProperty.js"; const requestRenderAfterFrame = function (scene) { return function () { @@ -4265,8 +4265,6 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { * values from * @param {string} propertyName The Name of the metadata property to pick * values from - * @param {number} [width=3] Width of the pick rectangle. - * @param {number} [height=3] Height of the pick rectangle. * @returns The metadata value * * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. @@ -4275,9 +4273,7 @@ Scene.prototype.pickMetadata = function ( windowPosition, schemaId, className, - propertyName, - width, - height + propertyName ) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("windowPosition", windowPosition); @@ -4285,7 +4281,7 @@ Scene.prototype.pickMetadata = function ( Check.typeOf.string("propertyName", propertyName); //>>includeEnd('debug'); - const pickedObject = this.pick(windowPosition, width, height); + const pickedObject = this.pick(windowPosition); if (!defined(pickedObject)) { return undefined; } @@ -4306,7 +4302,13 @@ Scene.prototype.pickMetadata = function ( console.log("schema ", schema); } - if (!hasMetadataProperty(schema, schemaId, className, propertyName)) { + const classProperty = getMetadataClassProperty( + schema, + schemaId, + className, + propertyName + ); + if (classProperty === undefined) { if (XXX_METADATA_PICKING_DEBUG_LOG) { console.log("The metadata property was not found"); console.log("schema ", schema); @@ -4323,8 +4325,7 @@ Scene.prototype.pickMetadata = function ( schemaId, className, propertyName, - width, - height + classProperty ); if (XXX_METADATA_PICKING_DEBUG_LOG) { @@ -4336,6 +4337,32 @@ Scene.prototype.pickMetadata = function ( return pickedMetadataValues; }; +/** + * Pick the schema of the metadata of the object at the given position + * + * @param {Cartesian2} windowPosition Window coordinates to perform picking on. + * @returns The metadata schema, or `undefined` if there is no object with + * associated metadata at the given position. + * + * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. + */ +Scene.prototype.pickMetadataSchema = function (windowPosition) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("windowPosition", windowPosition); + //>>includeEnd('debug'); + + const pickedObject = this.pick(windowPosition); + if (!defined(pickedObject)) { + return undefined; + } + + const detail = pickedObject?.detail; + const model = detail?.model; + const structuralMetadata = model?.structuralMetadata; + const schema = structuralMetadata?.schema; + return schema; +}; + /** * Returns the cartesian position reconstructed from the depth buffer and window position. * The returned position is in world coordinates. Used internally by camera functions to diff --git a/packages/engine/Source/Scene/getMetadataClassProperty.js b/packages/engine/Source/Scene/getMetadataClassProperty.js new file mode 100644 index 000000000000..3fb88ad7367e --- /dev/null +++ b/packages/engine/Source/Scene/getMetadataClassProperty.js @@ -0,0 +1,43 @@ +import defined from "../Core/defined.js"; + +/** + * Return the `MetadataClassProperty` from the given schema that + * matches the given description. + * + * If the given schema is `undefined`, then `undefined` is returned. + * If the given `schemaId` is defined but does not match the ID + * of the given schema, then `undefined` is returned. + * If the given schema does not have a class with the given name, + * or the class does not have a property with the given name, + * then `undefined` is returned. + * + * Otherwise, the `MetadataClassProperty` is returned. + * + * @param {object} schema The schema object + * @param {string|undefined} schemaId The ID of the metadata schema + * @param {string} className The name of the metadata class + * @param {string} propertyName The name of the metadata property + * @returns {MetadataClassProperty|undefined} + * @private + */ +function getMetadataClassProperty(schema, schemaId, className, propertyName) { + if (!defined(schema)) { + return undefined; + } + if (defined(schemaId) && schema.id !== schemaId) { + return undefined; + } + const classes = schema.classes || {}; + const metadataClass = classes[className]; + if (!defined(metadataClass)) { + return undefined; + } + const properties = metadataClass.properties || {}; + const metadataProperty = properties[propertyName]; + if (!defined(metadataProperty)) { + return undefined; + } + return metadataProperty; +} + +export default getMetadataClassProperty; diff --git a/packages/engine/Source/Scene/hasMetadataProperty.js b/packages/engine/Source/Scene/hasMetadataProperty.js deleted file mode 100644 index 17f92e7114e0..000000000000 --- a/packages/engine/Source/Scene/hasMetadataProperty.js +++ /dev/null @@ -1,41 +0,0 @@ -import defined from "../Core/defined.js"; - -/** - * Check if the given schema has a property that matches the - * given description. - * - * If the given schema is `undefined`, then `false` is returned. - * If the given `schemaId` is defined but does not match the ID - * of the given schema, then `false` is returned. - * Otherwise, this function returns whether the given schema - * has a class with the given name, that has a property with - * the given name. - * - * @param {object} schema The schema object - * @param {string|undefined} schemaId The ID of the metadata schema - * @param {string} className The name of the metadata class - * @param {string} propertyName The Name of the metadata property - * @returns {boolean} True if the property is present - * @private - */ -function hasMetadataProperty(schema, schemaId, className, propertyName) { - if (!defined(schema)) { - return false; - } - if (defined(schemaId) && schema.id !== schemaId) { - return false; - } - const classes = schema.classes || {}; - const metadataClass = classes[className]; - if (!defined(metadataClass)) { - return false; - } - const properties = metadataClass.properties || {}; - const metadataProperty = properties[propertyName]; - if (!defined(metadataProperty)) { - return false; - } - return true; -} - -export default hasMetadataProperty; From dc7552cac40e70aded7922a14c3098f17dc6339d Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 19 Jul 2024 13:03:09 +0200 Subject: [PATCH 09/63] Update matrix only when command is defined --- .../Source/Scene/Model/ModelMatrixUpdateStage.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js b/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js index 3a212b088b28..3420dcf679ed 100644 --- a/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js +++ b/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js @@ -1,6 +1,7 @@ import Matrix4 from "../../Core/Matrix4.js"; import ModelUtility from "./ModelUtility.js"; import SceneMode from "../SceneMode.js"; +import defined from "../../Core/defined.js"; /** * The model matrix update stage is responsible for updating the model matrices and bounding volumes of the draw commands. @@ -95,11 +96,13 @@ function updateRuntimeNode( modelMatrix, transformToRoot ); - updateDrawCommand( - runtimePrimitive.metadataPickingDrawCommand, - modelMatrix, - transformToRoot - ); + if (defined(runtimePrimitive.metadataPickingDrawCommand)) { + updateDrawCommand( + runtimePrimitive.metadataPickingDrawCommand, + modelMatrix, + transformToRoot + ); + } } const childrenLength = runtimeNode.children.length; From 609e277511688bb2e879c15792a2adf2f0180b37 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 19 Jul 2024 22:57:35 +0200 Subject: [PATCH 10/63] Minor comment fix --- packages/engine/Source/Scene/PickFramebuffer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/PickFramebuffer.js b/packages/engine/Source/Scene/PickFramebuffer.js index d103b2b2647e..b439802f6a45 100644 --- a/packages/engine/Source/Scene/PickFramebuffer.js +++ b/packages/engine/Source/Scene/PickFramebuffer.js @@ -129,7 +129,7 @@ PickFramebuffer.prototype.end = function (screenSpaceRectangle) { * of a metadata picking rendering pass. * * @param {BoundingRectangle} screenSpaceRectangle - * @returns {TypedArray} The RGBA components + * @returns {Uint8Array} The RGBA components */ PickFramebuffer.prototype.readCenterPixel = function (screenSpaceRectangle) { const width = defaultValue(screenSpaceRectangle.width, 1.0); From 927d01ba8cc3129c398df3251ce06f80ca3a64e5 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 19 Jul 2024 22:57:55 +0200 Subject: [PATCH 11/63] Draft for metadata value decoding --- .../Source/Scene/Model/ModelDrawCommands.js | 21 +++--- packages/engine/Source/Scene/Picking.js | 67 ++++++++++++++++++- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js index 55ec109cff90..4db239c37c20 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommands.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -72,27 +72,27 @@ ModelDrawCommands.prepareMetadataPickingStage = function ( `${glslType} value = ${glslType}(metadata.METADATA_PICKING_PROPERTY_NAME);` ); - const normalized = classProperty.normalized === true; - //const glslComponentType = getGlslComponentType(classProperty); - const componentCount = getComponentCount(classProperty); const sourceValueStrings = ["0.0", "0.0", "0.0", "0.0"]; + const componentCount = getComponentCount(classProperty); if (componentCount === 1) { const valueString = `value`; - sourceValueStrings[0] = `float(${valueString})`; - if (normalized) { - sourceValueStrings[0] += " / 255.0"; - } + sourceValueStrings[0] = `float(${valueString}) / 255.0`; } else { const components = ["x", "y", "z", "w"]; for (let i = 0; i < componentCount; i++) { const component = components[i]; const valueString = `value.${component}`; sourceValueStrings[i] = `float(${valueString}) / 255.0`; - if (normalized) { - sourceValueStrings[i] += " / 255.0"; - } } } + /* + const normalized = classProperty.normalized === true; + if (normalized) { + for (let i = 0; i < componentCount; i++) { + sourceValueStrings[i] += ` / 255.0`; + } + } + */ lines.push(`metadataValues.x = ${sourceValueStrings[0]};`); lines.push(`metadataValues.y = ${sourceValueStrings[1]};`); lines.push(`metadataValues.z = ${sourceValueStrings[2]};`); @@ -105,6 +105,7 @@ ModelDrawCommands.prepareMetadataPickingStage = function ( console.log("Name is ", propertyName); console.log("lines"); + console.log(" ", lines[0]); console.log(" ", lines[1]); console.log(" ", lines[2]); console.log(" ", lines[3]); diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 92fa31baf987..b6d3261c02fb 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -19,6 +19,7 @@ import Camera from "./Camera.js"; import Cesium3DTileFeature from "./Cesium3DTileFeature.js"; import Cesium3DTilePass from "./Cesium3DTilePass.js"; import Cesium3DTilePassState from "./Cesium3DTilePassState.js"; +import MetadataComponentType from "./MetadataComponentType.js"; import PickDepth from "./PickDepth.js"; import PrimitiveCollection from "./PrimitiveCollection.js"; import SceneMode from "./SceneMode.js"; @@ -411,6 +412,62 @@ Picking.prototype.pickVoxelCoordinate = function ( return voxelInfo; }; +Picking.prototype.getDecodedMetadataValue = function ( + componentType, + dataView, + index +) { + switch (componentType) { + case MetadataComponentType.INT8: + return dataView.getInt8(index); + case MetadataComponentType.UINT8: + return dataView.getUint8(index); + case MetadataComponentType.INT16: + return dataView.getInt16(index); + case MetadataComponentType.UINT16: + return dataView.getUint16(index); + case MetadataComponentType.INT32: + return dataView.getInt32(index); + case MetadataComponentType.UINT32: + return dataView.getUint32(index); + case MetadataComponentType.FLOAT32: + return dataView.getFloat32(index); + case MetadataComponentType.FLOAT64: + return dataView.getFloat64(index); + } + return undefined; +}; + +Picking.prototype.decodePickedMetadataValues = function ( + classProperty, + rawValues +) { + const componentType = classProperty.componentType; + const dataView = new DataView( + rawValues.buffer, + rawValues.byteOffset, + rawValues.byteLength + ); + if (classProperty.isArray) { + const arrayLength = classProperty.arrayLength; + const result = Array(arrayLength); + for (let i = 0; i < arrayLength; i++) { + const element = this.getDecodedMetadataValue(componentType, dataView, i); + if (classProperty.normalized) { + result[i] = element / 255.0; + } else { + result[i] = element; + } + } + return result; + } + const value = this.getDecodedMetadataValue(componentType, dataView, 0); + if (classProperty.normalized) { + return value / 255.0; + } + return value; +}; + /** * Pick a metadata value at the given window position. * @@ -422,7 +479,7 @@ Picking.prototype.pickVoxelCoordinate = function ( * @param {string} propertyName The name of the metadata property to pick * values from * @param {MetadataClassProperty} classProperty The actual class property - * @returns The metadata values as a raw, 4-byte buffer + * @returns The metadata values * * @private */ @@ -438,6 +495,7 @@ Picking.prototype.pickMetadata = function ( Check.typeOf.object("windowPosition", windowPosition); Check.typeOf.string("className", className); Check.typeOf.string("propertyName", propertyName); + Check.typeOf.object("classProperty", classProperty); //>>includeEnd('debug'); const { context, frameState, defaultView } = scene; @@ -517,6 +575,13 @@ Picking.prototype.pickMetadata = function ( context.endFrame(); frameState.pickMetadata = false; + + const metadataValue = this.decodePickedMetadataValues( + classProperty, + metadataInfo + ); + console.log("metadataValue ", metadataValue); + return metadataInfo; }; From 461821bc95accc2c25488818a6920606d49166ba Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 20 Jul 2024 23:40:53 +0200 Subject: [PATCH 12/63] Minor consolidation --- .../engine/Source/Scene/MetadataPicking.js | 103 ++++++++++++++++++ .../Source/Scene/Model/ModelDrawCommands.js | 82 +++++++++----- .../Source/Scene/Model/ModelSceneGraph.js | 31 ++++-- packages/engine/Source/Scene/Picking.js | 68 ++---------- packages/engine/Source/Scene/Scene.js | 2 - 5 files changed, 182 insertions(+), 104 deletions(-) create mode 100644 packages/engine/Source/Scene/MetadataPicking.js diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js new file mode 100644 index 000000000000..f0d4f503b4c6 --- /dev/null +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -0,0 +1,103 @@ +import MetadataComponentType from "./MetadataComponentType"; + +/** + * Utility functions for metadata picking. + * + * These are used by the `Picking.pickMetadata` function to decode + * the metadata values that have been read from the frame buffer + * into the actual metadata values, according to the structure + * defined by the `MetadataClassProperty`. + * + * @private (to be used by `Picking.js`) + */ +function MetadataPicking() {} + +/** + * Returns the value at the specified inded of the given data view, + * interpreting the data to have the given component type. + * + * @param {MetadataComponentType} componentType The `MetadataComponentType` + * @param {DataView} dataView The data view + * @param {number} index The index + * @returns {number|bigint|undefined} The value + * + * @private + */ +MetadataPicking.decodeMetadataValue = function ( + componentType, + dataView, + index +) { + switch (componentType) { + case MetadataComponentType.INT8: + return dataView.getInt8(index); + case MetadataComponentType.UINT8: + return dataView.getUint8(index); + case MetadataComponentType.INT16: + return dataView.getInt16(index); + case MetadataComponentType.UINT16: + return dataView.getUint16(index); + case MetadataComponentType.INT32: + return dataView.getInt32(index); + case MetadataComponentType.UINT32: + return dataView.getUint32(index); + case MetadataComponentType.INT64: + return dataView.getBigInt64(index); + case MetadataComponentType.UINT64: + return dataView.getBigUint64(index); + case MetadataComponentType.FLOAT32: + return dataView.getFloat32(index); + case MetadataComponentType.FLOAT64: + return dataView.getFloat64(index); + } + // Appropriate error handling? + return undefined; +}; + +/** + * Decode the given raw values into a metadata property value. + * + * The given values are a `Uint8Array` containing the RGBA + * values that have been read from the metadata picking + * frame buffer. They are assumed to contain the value for + * the given class property, as encoded by the + * `ModelDrawCommands` for metadata picking. + * + * @param {MetadataClassProperty} classProperty The `MetadataClassProperty` + * @param {Uint8Array} rawValues The raw values + * @returns {number|bigint|undefined} The value + * + * @private + */ +MetadataPicking.decodeMetadataValues = function (classProperty, rawValues) { + const componentType = classProperty.componentType; + const dataView = new DataView( + rawValues.buffer, + rawValues.byteOffset, + rawValues.byteLength + ); + if (classProperty.isArray) { + const arrayLength = classProperty.arrayLength; + const result = Array(arrayLength); + for (let i = 0; i < arrayLength; i++) { + const element = MetadataPicking.decodeMetadataValue( + componentType, + dataView, + i + ); + if (classProperty.normalized) { + result[i] = element / 255.0; + } else { + result[i] = element; + } + } + return result; + } + const value = MetadataPicking.decodeMetadataValue(componentType, dataView, 0); + if (classProperty.normalized) { + return value / 255.0; + } + return value; +}; + +export default Object.freeze(MetadataPicking); diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js index 4db239c37c20..9dcc9908ae64 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommands.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -59,6 +59,23 @@ ModelDrawCommands.prepareMetadataPickingStage = function ( console.log(" propertyName", propertyName); console.log(" classProperty", classProperty); + // Enable METADATA_PICKING in `ModelFS.glsl` + shaderBuilder.addDefine( + "METADATA_PICKING", + undefined, + ShaderDestination.FRAGMENT + ); + + // Add the define for the property that should be picked + shaderBuilder.addDefine( + "METADATA_PICKING_PROPERTY_NAME", + propertyName, + ShaderDestination.FRAGMENT + ); + + // Define the function that will compute the 'metadataValues' + // that will serve as the 'color' that is written into the + // frame buffer shaderBuilder.addFunction( "metadataPickingStage", "void metadataPickingStage(Metadata metadata, MetadataClass metadataClass, inout vec4 metadataValues)", @@ -67,32 +84,40 @@ ModelDrawCommands.prepareMetadataPickingStage = function ( const lines = []; + // In the function, obtain the specified property name const glslType = getGlslType(classProperty); lines.push( `${glslType} value = ${glslType}(metadata.METADATA_PICKING_PROPERTY_NAME);` ); + // Define the components that will go into the `metadataValues`. + // By default, all of them are 0.0. const sourceValueStrings = ["0.0", "0.0", "0.0", "0.0"]; const componentCount = getComponentCount(classProperty); if (componentCount === 1) { + // When the property is a scalar, store its value directly + // in `metadataValues.x` const valueString = `value`; - sourceValueStrings[0] = `float(${valueString}) / 255.0`; + sourceValueStrings[0] = `float(${valueString})`; } else { + // When the property is an array, store the array elements + // in `metadataValues.x/y/z/w` const components = ["x", "y", "z", "w"]; for (let i = 0; i < componentCount; i++) { const component = components[i]; const valueString = `value.${component}`; - sourceValueStrings[i] = `float(${valueString}) / 255.0`; + sourceValueStrings[i] = `float(${valueString})`; } } - /* - const normalized = classProperty.normalized === true; - if (normalized) { + + // Make sure that the `metadataValues` components are all in + // the range [0, 1] (which will result in RGBA components + // in [0, 255] during rendering) + if (!classProperty.normalized) { for (let i = 0; i < componentCount; i++) { - sourceValueStrings[i] += ` / 255.0`; + sourceValueStrings[i] += " / 255.0"; } } - */ lines.push(`metadataValues.x = ${sourceValueStrings[0]};`); lines.push(`metadataValues.y = ${sourceValueStrings[1]};`); lines.push(`metadataValues.z = ${sourceValueStrings[2]};`); @@ -103,27 +128,16 @@ ModelDrawCommands.prepareMetadataPickingStage = function ( ShaderDestination.FRAGMENT ); - console.log("Name is ", propertyName); - console.log("lines"); - console.log(" ", lines[0]); - console.log(" ", lines[1]); - console.log(" ", lines[2]); - console.log(" ", lines[3]); - console.log(" ", lines[4]); - - // Enable METADATA_PICKING in `ModelFS.glsl` - shaderBuilder.addDefine( - "METADATA_PICKING", - undefined, - ShaderDestination.FRAGMENT - ); - - // Add the define for the property that should be picked - shaderBuilder.addDefine( - "METADATA_PICKING_PROPERTY_NAME", - propertyName, - ShaderDestination.FRAGMENT - ); + const XXX_METADATA_PICKING_DEBUG_LOG = true; + if (XXX_METADATA_PICKING_DEBUG_LOG) { + console.log("Name is ", propertyName); + console.log("lines"); + console.log(" ", lines[0]); + console.log(" ", lines[1]); + console.log(" ", lines[2]); + console.log(" ", lines[3]); + console.log(" ", lines[4]); + } }; /** @@ -133,7 +147,8 @@ ModelDrawCommands.prepareMetadataPickingStage = function ( * * @param {PrimitiveRenderResources} primitiveRenderResources The render resources for a primitive. * @param {FrameState} frameState The frame state for creating GPU resources. - * + * @param {ShaderBuilder} shaderBuilder The shader builder + * @param {boolean|undefined} metadataPicking Whether the command is built for metadata picking * @returns {ModelDrawCommand|ClassificationModelDrawCommand} The generated ModelDrawCommand or ClassificationModelDrawCommand. * * @private @@ -141,7 +156,8 @@ ModelDrawCommands.prepareMetadataPickingStage = function ( ModelDrawCommands.buildModelDrawCommand = function ( primitiveRenderResources, frameState, - shaderBuilder + shaderBuilder, + metadataPicking ) { const shaderProgram = ModelDrawCommands.createShaderProgram( primitiveRenderResources, @@ -155,6 +171,12 @@ ModelDrawCommands.buildModelDrawCommand = function ( frameState ); + if (metadataPicking) { + command.debugShowBoundingVolume = false; + command.castShadows = false; + command.receiveShadows = false; + } + const model = primitiveRenderResources.model; const hasClassification = defined(model.classificationType); if (hasClassification) { diff --git a/packages/engine/Source/Scene/Model/ModelSceneGraph.js b/packages/engine/Source/Scene/Model/ModelSceneGraph.js index 9bbe175ea3d6..1724ccf8cce4 100644 --- a/packages/engine/Source/Scene/Model/ModelSceneGraph.js +++ b/packages/engine/Source/Scene/Model/ModelSceneGraph.js @@ -570,11 +570,15 @@ ModelSceneGraph.prototype.buildDrawCommands = function (frameState) { runtimePrimitive.drawCommand = drawCommand; const shaderBuilderPickMetadata = primitiveRenderResources.shaderBuilder.clone(); - if (frameState.pickMetadata === true) { - const schemaId = frameState.pickedMetadataSchemaId; - const className = frameState.pickedMetadataClassName; - const propertyName = frameState.pickedMetadataPropertyName; - const classProperty = frameState.pickedMetadataClassProperty; + const schemaId = frameState.pickedMetadataSchemaId; + const className = frameState.pickedMetadataClassName; + const propertyName = frameState.pickedMetadataPropertyName; + const classProperty = frameState.pickedMetadataClassProperty; + if ( + defined(className) && + defined(propertyName) && + defined(classProperty) + ) { ModelDrawCommands.prepareMetadataPickingStage( shaderBuilderPickMetadata, schemaId, @@ -583,7 +587,6 @@ ModelSceneGraph.prototype.buildDrawCommands = function (frameState) { classProperty ); } - const metadataPickingDrawCommand = ModelDrawCommands.buildModelDrawCommand( primitiveRenderResources, frameState, @@ -845,8 +848,10 @@ ModelSceneGraph.prototype.updateBackFaceCulling = function (backFaceCulling) { // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveBackFaceCulling(runtimePrimitive, options) { runtimePrimitive.drawCommand.backFaceCulling = options.backFaceCulling; - runtimePrimitive.metadataPickingDrawCommand.backFaceCulling = - options.backFaceCulling; + if (defined(runtimePrimitive.metadataPickingDrawCommand)) { + runtimePrimitive.metadataPickingDrawCommand.backFaceCulling = + options.backFaceCulling; + } } const scratchShadowOptions = { @@ -869,7 +874,9 @@ ModelSceneGraph.prototype.updateShadows = function (shadowMode) { // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveShadows(runtimePrimitive, options) { runtimePrimitive.drawCommand.shadows = options.shadows; - runtimePrimitive.metadataPickingDrawCommand.shadows = options.shadows; + if (defined(runtimePrimitive.metadataPickingDrawCommand)) { + runtimePrimitive.metadataPickingDrawCommand.shadows = options.shadows; + } } const scratchShowBoundingVolumeOptions = { @@ -901,8 +908,10 @@ ModelSceneGraph.prototype.updateShowBoundingVolume = function ( function updatePrimitiveShowBoundingVolume(runtimePrimitive, options) { runtimePrimitive.drawCommand.debugShowBoundingVolume = options.debugShowBoundingVolume; - runtimePrimitive.metadataPickingDrawCommand.debugShowBoundingVolume = - options.debugShowBoundingVolume; + if (defined(runtimePrimitive.metadataPickingDrawCommand)) { + runtimePrimitive.metadataPickingDrawCommand.debugShowBoundingVolume = + options.debugShowBoundingVolume; + } } const scratchSilhouetteCommands = []; diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index b6d3261c02fb..71e6139ca6cb 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -19,7 +19,7 @@ import Camera from "./Camera.js"; import Cesium3DTileFeature from "./Cesium3DTileFeature.js"; import Cesium3DTilePass from "./Cesium3DTilePass.js"; import Cesium3DTilePassState from "./Cesium3DTilePassState.js"; -import MetadataComponentType from "./MetadataComponentType.js"; +import MetadataPicking from "./MetadataPicking.js"; import PickDepth from "./PickDepth.js"; import PrimitiveCollection from "./PrimitiveCollection.js"; import SceneMode from "./SceneMode.js"; @@ -412,62 +412,6 @@ Picking.prototype.pickVoxelCoordinate = function ( return voxelInfo; }; -Picking.prototype.getDecodedMetadataValue = function ( - componentType, - dataView, - index -) { - switch (componentType) { - case MetadataComponentType.INT8: - return dataView.getInt8(index); - case MetadataComponentType.UINT8: - return dataView.getUint8(index); - case MetadataComponentType.INT16: - return dataView.getInt16(index); - case MetadataComponentType.UINT16: - return dataView.getUint16(index); - case MetadataComponentType.INT32: - return dataView.getInt32(index); - case MetadataComponentType.UINT32: - return dataView.getUint32(index); - case MetadataComponentType.FLOAT32: - return dataView.getFloat32(index); - case MetadataComponentType.FLOAT64: - return dataView.getFloat64(index); - } - return undefined; -}; - -Picking.prototype.decodePickedMetadataValues = function ( - classProperty, - rawValues -) { - const componentType = classProperty.componentType; - const dataView = new DataView( - rawValues.buffer, - rawValues.byteOffset, - rawValues.byteLength - ); - if (classProperty.isArray) { - const arrayLength = classProperty.arrayLength; - const result = Array(arrayLength); - for (let i = 0; i < arrayLength; i++) { - const element = this.getDecodedMetadataValue(componentType, dataView, i); - if (classProperty.normalized) { - result[i] = element / 255.0; - } else { - result[i] = element; - } - } - return result; - } - const value = this.getDecodedMetadataValue(componentType, dataView, 0); - if (classProperty.normalized) { - return value / 255.0; - } - return value; -}; - /** * Pick a metadata value at the given window position. * @@ -571,18 +515,20 @@ Picking.prototype.pickMetadata = function ( scene.resolveFramebuffers(passState); scene._environmentState.useOIT = oldOIT; - const metadataInfo = pickFramebuffer.readCenterPixel(drawingBufferRectangle); + const rawMetadataPixel = pickFramebuffer.readCenterPixel( + drawingBufferRectangle + ); context.endFrame(); frameState.pickMetadata = false; - const metadataValue = this.decodePickedMetadataValues( + const metadataValue = MetadataPicking.decodeMetadataValues( classProperty, - metadataInfo + rawMetadataPixel ); console.log("metadataValue ", metadataValue); - return metadataInfo; + return metadataValue; }; function renderTranslucentDepthForPick(scene, drawingBufferPosition) { diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index ae97860788da..977510775a41 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4332,8 +4332,6 @@ Scene.prototype.pickMetadata = function ( console.log("pickedMetadataValues ", pickedMetadataValues); } - // TODO_METADATA_PICKING The result here is a 4-element (byte) buffer. - // Convert this to the required target type return pickedMetadataValues; }; From 261dbbacb6fc59c7aeac65a8fe99e23bc8225118 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 25 Jul 2024 22:29:32 +0200 Subject: [PATCH 13/63] Fix JSDoc type --- packages/engine/Source/Scene/Model/PrimitiveRenderResources.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Model/PrimitiveRenderResources.js b/packages/engine/Source/Scene/Model/PrimitiveRenderResources.js index ccf947bd3fd0..ce24f71f05ce 100644 --- a/packages/engine/Source/Scene/Model/PrimitiveRenderResources.js +++ b/packages/engine/Source/Scene/Model/PrimitiveRenderResources.js @@ -287,7 +287,7 @@ function PrimitiveRenderResources(nodeRenderResources, runtimePrimitive) { * The shader variable to use for picking. If picking is enabled, this value * is set by PickingPipelineStage. * - * @type {string} + * @type {string|undefined} * * @private */ From 381738942385bf4115bf01784f5e0ab9e04a2a98 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 25 Jul 2024 22:41:31 +0200 Subject: [PATCH 14/63] Do not duplicate full command - use derived command --- .../engine/Source/Renderer/DrawCommand.js | 26 ++- .../engine/Source/Scene/DerivedCommand.js | 170 ++++++++++++++++++ packages/engine/Source/Scene/FrameState.js | 40 +---- .../Model/MetadataPickingPipelineStage.js | 84 +++++++++ packages/engine/Source/Scene/Model/Model.js | 35 ++-- .../Source/Scene/Model/ModelDrawCommands.js | 130 +------------- .../Scene/Model/ModelMatrixUpdateStage.js | 8 - .../Scene/Model/ModelRuntimePrimitive.js | 2 + .../Source/Scene/Model/ModelSceneGraph.js | 53 +----- packages/engine/Source/Scene/Picking.js | 39 +--- packages/engine/Source/Scene/Scene.js | 54 ++++-- .../engine/Source/Shaders/Model/ModelFS.glsl | 4 +- 12 files changed, 357 insertions(+), 288 deletions(-) create mode 100644 packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js diff --git a/packages/engine/Source/Renderer/DrawCommand.js b/packages/engine/Source/Renderer/DrawCommand.js index 14d75d5c7ee5..c5a34cecae3c 100644 --- a/packages/engine/Source/Renderer/DrawCommand.js +++ b/packages/engine/Source/Renderer/DrawCommand.js @@ -40,6 +40,7 @@ function DrawCommand(options) { this._owner = options.owner; this._debugOverlappingFrustums = 0; this._pickId = options.pickId; + this._pickedMetadataInfo = options.pickedMetadataInfo; // Set initial flags. this._flags = 0; @@ -511,7 +512,7 @@ Object.defineProperties(DrawCommand.prototype, { * during the pick pass. * * @memberof DrawCommand.prototype - * @type {string} + * @type {string|undefined} * @default undefined */ pickId: { @@ -525,6 +526,28 @@ Object.defineProperties(DrawCommand.prototype, { } }, }, + + /** + * Information about picked metadata. + * + * XXX_METADATA_PICKING Documentation. On just say "@private". Lol. + * + * @memberof DrawCommand.prototype + * @type {object|undefined} + * @default undefined + */ + pickedMetadataInfo: { + get: function () { + return this._pickedMetadataInfo; + }, + set: function (value) { + if (this._pickedMetadataInfo !== value) { + this._pickedMetadataInfo = value; + this.dirty = true; + } + }, + }, + /** * Whether this command should be executed in the pick pass only. * @@ -590,6 +613,7 @@ DrawCommand.shallowClone = function (command, result) { result._owner = command._owner; result._debugOverlappingFrustums = command._debugOverlappingFrustums; result._pickId = command._pickId; + result._pickedMetadataInfo = command._pickedMetadataInfo; result._flags = command._flags; result.dirty = true; diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 431de308f79b..99e8bbf9472c 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -2,6 +2,7 @@ import defined from "../Core/defined.js"; import DrawCommand from "../Renderer/DrawCommand.js"; import RenderState from "../Renderer/RenderState.js"; import ShaderSource from "../Renderer/ShaderSource.js"; +import MetadataType from "./MetadataType.js"; /** * @private @@ -375,6 +376,175 @@ DerivedCommand.createPickDerivedCommand = function ( return result; }; +function replaceDefine(defines, defineName, newDefineValue) { + const n = defines.length; + for (let i = 0; i < n; i++) { + if (defines[i].startsWith(defineName)) { + defines[i] = `${defineName} ${newDefineValue}`; + } + } +} + +function getComponentCount(classProperty) { + if (!classProperty.isArray) { + return MetadataType.getComponentCount(classProperty.type); + } + return classProperty.arrayLength; +} +function getGlslType(classProperty) { + const componentCount = getComponentCount(classProperty); + if (classProperty.normalized) { + if (componentCount === 1) { + return "float"; + } + return `vec${componentCount}`; + } + if (componentCount === 1) { + return "int"; + } + return `ivec${componentCount}`; +} + +function getPickMetadataShaderProgram( + context, + shaderProgram, + pickedMetadataInfo +) { + let shader = context.shaderCache.getDerivedShaderProgram( + shaderProgram, + "pickMetadata" + ); + if (defined(shader)) { + return shader; + } + + const propertyName = pickedMetadataInfo.propertyName; + const classProperty = pickedMetadataInfo.classProperty; + const glslType = getGlslType(classProperty); + + // Define the components that will go into the output `metadataValues`. + // By default, all of them are 0.0. + const sourceValueStrings = ["0.0", "0.0", "0.0", "0.0"]; + const componentCount = getComponentCount(classProperty); + if (componentCount === 1) { + // When the property is a scalar, store its value directly + // in `metadataValues.x` + sourceValueStrings[0] = `float(value)`; + } else { + // When the property is an array, store the array elements + // in `metadataValues.x/y/z/w` + const components = ["x", "y", "z", "w"]; + for (let i = 0; i < componentCount; i++) { + const component = components[i]; + const valueString = `value.${component}`; + sourceValueStrings[i] = `float(${valueString})`; + } + } + + // Make sure that the `metadataValues` components are all in + // the range [0, 1] (which will result in RGBA components + // in [0, 255] during rendering) + if (!classProperty.normalized) { + for (let i = 0; i < componentCount; i++) { + sourceValueStrings[i] += " / 255.0"; + } + } + + let fs = shaderProgram.fragmentShaderSource; + const newDefines = [...fs.defines]; + newDefines.push("METADATA_PICKING_ENABLED"); + + // Replace the defines of the shader, using the type, property + // access, and value components that have been determined + replaceDefine(newDefines, "METADATA_PICKING_VALUE_TYPE", glslType); + replaceDefine( + newDefines, + "METADATA_PICKING_VALUE_STRING", + `metadata.${propertyName}` + ); + replaceDefine( + newDefines, + "METADATA_PICKING_VALUE_COMPONENT_X", + sourceValueStrings[0] + ); + replaceDefine( + newDefines, + "METADATA_PICKING_VALUE_COMPONENT_Y", + sourceValueStrings[1] + ); + replaceDefine( + newDefines, + "METADATA_PICKING_VALUE_COMPONENT_Z", + sourceValueStrings[2] + ); + replaceDefine( + newDefines, + "METADATA_PICKING_VALUE_COMPONENT_W", + sourceValueStrings[3] + ); + + console.log("newDefines"); + for (const d of newDefines) { + console.log(d); + } + + fs = new ShaderSource({ + sources: fs.sources, + defines: newDefines, + }); + shader = context.shaderCache.createDerivedShaderProgram( + shaderProgram, + "pickMetadata", + { + vertexShaderSource: shaderProgram.vertexShaderSource, + fragmentShaderSource: fs, + attributeLocations: shaderProgram._attributeLocations, + } + ); + return shader; +} + +DerivedCommand.createPickMetadataDerivedCommand = function ( + scene, + command, + context, + result +) { + if (!defined(result)) { + result = {}; + } + + let shader; + let renderState; + if (defined(result.pickMetadataCommand)) { + shader = result.pickMetadataCommand.shaderProgram; + renderState = result.pickMetadataCommand.renderState; + } + + result.pickMetadataCommand = DrawCommand.shallowClone( + command, + result.pickMetadataCommand + ); + + if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) { + result.pickMetadataCommand.shaderProgram = getPickMetadataShaderProgram( + context, + command.shaderProgram, + command.pickedMetadataInfo + ); + result.pickMetadataCommand.renderState = getPickRenderState( + scene, + command.renderState + ); + result.shaderProgramId = command.shaderProgram.id; + } else { + result.pickMetadataCommand.shaderProgram = shader; + result.pickMetadataCommand.renderState = renderState; + } + + return result; +}; + function getHdrShaderProgram(context, shaderProgram) { let shader = context.shaderCache.getDerivedShaderProgram( shaderProgram, diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 9a56ef25cd79..c3098f6dcbe4 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -422,45 +422,17 @@ function FrameState(context, creditDisplay, jobScheduler) { */ this.minimumTerrainHeight = 0.0; - /** - * Whether the current drawing pass is intended for metadata picking - * - * @type {boolean} - */ - this.pickMetadata = false; - - /** - * The schema ID for the metadata values that are supposed to be picked - * with `Scene.pickMetadata`, or `undefined` to pick values from any - * schema. - * - * @type {string|undefined} - */ - this.pickedMetadataSchemaId = undefined; + // XXX_METADATA_PICKING + this.pickingMetadata = false; /** - * The name of the metadata class that values should be picked from - * with `Scene.pickMetadata` + * Metadata picking information. * - * @type {string|undefined} - */ - this.pickedMetadataClassName = undefined; - - /** - * The name of the metadata property that values should be picked from - * with `Scene.pickMetadata` - * - * @type {string|undefined} - */ - this.pickedMetadataPropertyName = undefined; - - /** - * The metadata class property that values should be picked from - * with `Scene.pickMetadata` + * XXX_METADATA_PICKING Documentation... * - * @type {MetadataClassProperty|undefined} + * @type {object} */ - this.pickedMetadataClassProperty = undefined; + this.pickedMetadataInfo = undefined; } /** diff --git a/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js b/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js new file mode 100644 index 000000000000..c93ef5ae0ca5 --- /dev/null +++ b/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js @@ -0,0 +1,84 @@ +import ShaderDestination from "../../Renderer/ShaderDestination.js"; + +/** + * The MetadataPickingPipelineStage is inserting the + * metadataPickingStage function into the shader code, + * including the 'defines' that will be filled with + * the proper values for metadata picking in the + * 'DerivedCommands' + * + * @namespace MetadataPickingPipelineStage + * @private + */ +const MetadataPickingPipelineStage = { + name: "MetadataPickingPipelineStage", // Helps with debugging +}; + +/** + * Process a primitive. This modifies the following parts of the render resources: + *
    + *
  • adds the required defines and "metadataPickingStage" to function in the shader
  • + *
+ * + * @param {PrimitiveRenderResources} renderResources The render resources for this primitive. + * @param {ModelComponents.Primitive} primitive The primitive. + * @param {FrameState} frameState The frame state. + */ +MetadataPickingPipelineStage.process = function ( + renderResources, + primitive, + frameState +) { + const shaderBuilder = renderResources.shaderBuilder; + + shaderBuilder.addDefine( + "METADATA_PICKING_VALUE_TYPE", + "float", + ShaderDestination.FRAGMENT + ); + shaderBuilder.addDefine( + "METADATA_PICKING_VALUE_STRING", + "0.0", + ShaderDestination.FRAGMENT + ); + shaderBuilder.addDefine( + "METADATA_PICKING_VALUE_COMPONENT_X", + "0.0", + ShaderDestination.FRAGMENT + ); + shaderBuilder.addDefine( + "METADATA_PICKING_VALUE_COMPONENT_Y", + "0.0", + ShaderDestination.FRAGMENT + ); + shaderBuilder.addDefine( + "METADATA_PICKING_VALUE_COMPONENT_Z", + "0.0", + ShaderDestination.FRAGMENT + ); + shaderBuilder.addDefine( + "METADATA_PICKING_VALUE_COMPONENT_W", + "0.0", + ShaderDestination.FRAGMENT + ); + + shaderBuilder.addFunction( + "metadataPickingStage", + "void metadataPickingStage(Metadata metadata, MetadataClass metadataClass, inout vec4 metadataValues)", + ShaderDestination.FRAGMENT + ); + + shaderBuilder.addFunctionLines( + "metadataPickingStage", + [ + "METADATA_PICKING_VALUE_TYPE value = METADATA_PICKING_VALUE_TYPE(METADATA_PICKING_VALUE_STRING);", + "metadataValues.x = METADATA_PICKING_VALUE_COMPONENT_X;", + "metadataValues.y = METADATA_PICKING_VALUE_COMPONENT_Y;", + "metadataValues.z = METADATA_PICKING_VALUE_COMPONENT_Z;", + "metadataValues.w = METADATA_PICKING_VALUE_COMPONENT_W;", + ], + ShaderDestination.FRAGMENT + ); +}; + +export default MetadataPickingPipelineStage; diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index b60a6f4270f6..017ef302076d 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -479,9 +479,7 @@ function Model(options) { this._fogRenderable = undefined; - this._pickedMetadataSchemaId = undefined; - this._pickedMetadataClassName = undefined; - this._pickedMetadataPropertyName = undefined; + this._pickedMetadataInfo = undefined; this._skipLevelOfDetail = false; this._ignoreCommands = defaultValue(options.ignoreCommands, false); @@ -1845,7 +1843,7 @@ Model.prototype.update = function (frameState) { updateSceneMode(this, frameState); updateFog(this, frameState); updateVerticalExaggeration(this, frameState); - updatePickedMetadata(this, frameState); + updatePickedMetadataInfo(this, frameState); this._defaultTexture = frameState.context.defaultTexture; @@ -2075,7 +2073,7 @@ function updateVerticalExaggeration(model, frameState) { } /** - * Will be called during `update`, and detect changes in the `pickedMetadata*` + * Will be called during `update`, and detect changes in the `pickedMetadataInfo*` * properties. When they changed, then the draw commands will be reset, to * eventually be rebuilt by the `ModelDrawCommands` class, with the new picked * metadata property values @@ -2083,30 +2081,23 @@ function updateVerticalExaggeration(model, frameState) { * @param {Model} model The model * @param {FrameState} frameState The frame state */ -function updatePickedMetadata(model, frameState) { +function updatePickedMetadataInfo(model, frameState) { if ( - frameState.pickedMetadataSchemaId !== model._pickedMetadataSchemaId || - frameState.pickedMetadataClassName !== model._pickedMetadataClassName || - frameState.pickedMetadataPropertyName !== model._pickedMetadataPropertyName + frameState.pickedMetadataInfo?.schemaId !== + model._pickedMetadataInfo?.schemaId || + frameState.pickedMetadataInfo?.classnName !== + model._pickedMetadataInfo?.classnName || + frameState.pickedMetadataInfo?.propertyName !== + model._pickedMetadataInfo?.propertyName ) { - console.log("pickedMetadata changed, reset draw commands"); - console.log(" pickedMetadataSchemaId", frameState.pickedMetadataSchemaId); console.log( - " pickedMetadataClassName", - frameState.pickedMetadataClassName + "pickedMetadataInfo changed, reset draw commands ", + frameState.pickedMetadataInfo ); - console.log( - " pickedMetadataPropertyName", - frameState.pickedMetadataPropertyName - ); - model.resetDrawCommands(); - model._pickedMetadataSchemaId = frameState.pickedMetadataSchemaId; - model._pickedMetadataClassName = frameState.pickedMetadataClassName; - model._pickedMetadataPropertyName = frameState.pickedMetadataPropertyName; + model._pickedMetadataInfo = frameState.pickedMetadataInfo; } } - function buildDrawCommands(model, frameState) { if (!model._drawCommandsBuilt) { model.destroyPipelineResources(); diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js index 9dcc9908ae64..8dfe37d43186 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommands.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -6,7 +6,6 @@ import DrawCommand from "../../Renderer/DrawCommand.js"; import RenderState from "../../Renderer/RenderState.js"; import SceneMode from "../SceneMode.js"; import ShadowMode from "../ShadowMode.js"; -import ShaderDestination from "../../Renderer/ShaderDestination.js"; import ClassificationModelDrawCommand from "./ClassificationModelDrawCommand.js"; import ModelDrawCommand from "./ModelDrawCommand.js"; import VertexArray from "../../Renderer/VertexArray.js"; @@ -14,7 +13,6 @@ import ModelVS from "../../Shaders/Model/ModelVS.js"; import ModelFS from "../../Shaders/Model/ModelFS.js"; import ModelUtility from "./ModelUtility.js"; import DeveloperError from "../../Core/DeveloperError.js"; -import MetadataType from "../MetadataType.js"; /** * Internal functions to build draw commands for models. @@ -26,120 +24,6 @@ import MetadataType from "../MetadataType.js"; */ function ModelDrawCommands() {} -function getComponentCount(classProperty) { - if (!classProperty.isArray) { - return MetadataType.getComponentCount(classProperty.type); - } - return classProperty.arrayLength; -} -function getGlslType(classProperty) { - const componentCount = getComponentCount(classProperty); - if (classProperty.normalized) { - if (componentCount === 1) { - return "float"; - } - return `vec${componentCount}`; - } - if (componentCount === 1) { - return "int"; - } - return `ivec${componentCount}`; -} - -ModelDrawCommands.prepareMetadataPickingStage = function ( - shaderBuilder, - schemaId, - className, - propertyName, - classProperty -) { - console.log("Now adding that stuff..."); - console.log(" schemaId", schemaId); - console.log(" className", className); - console.log(" propertyName", propertyName); - console.log(" classProperty", classProperty); - - // Enable METADATA_PICKING in `ModelFS.glsl` - shaderBuilder.addDefine( - "METADATA_PICKING", - undefined, - ShaderDestination.FRAGMENT - ); - - // Add the define for the property that should be picked - shaderBuilder.addDefine( - "METADATA_PICKING_PROPERTY_NAME", - propertyName, - ShaderDestination.FRAGMENT - ); - - // Define the function that will compute the 'metadataValues' - // that will serve as the 'color' that is written into the - // frame buffer - shaderBuilder.addFunction( - "metadataPickingStage", - "void metadataPickingStage(Metadata metadata, MetadataClass metadataClass, inout vec4 metadataValues)", - ShaderDestination.FRAGMENT - ); - - const lines = []; - - // In the function, obtain the specified property name - const glslType = getGlslType(classProperty); - lines.push( - `${glslType} value = ${glslType}(metadata.METADATA_PICKING_PROPERTY_NAME);` - ); - - // Define the components that will go into the `metadataValues`. - // By default, all of them are 0.0. - const sourceValueStrings = ["0.0", "0.0", "0.0", "0.0"]; - const componentCount = getComponentCount(classProperty); - if (componentCount === 1) { - // When the property is a scalar, store its value directly - // in `metadataValues.x` - const valueString = `value`; - sourceValueStrings[0] = `float(${valueString})`; - } else { - // When the property is an array, store the array elements - // in `metadataValues.x/y/z/w` - const components = ["x", "y", "z", "w"]; - for (let i = 0; i < componentCount; i++) { - const component = components[i]; - const valueString = `value.${component}`; - sourceValueStrings[i] = `float(${valueString})`; - } - } - - // Make sure that the `metadataValues` components are all in - // the range [0, 1] (which will result in RGBA components - // in [0, 255] during rendering) - if (!classProperty.normalized) { - for (let i = 0; i < componentCount; i++) { - sourceValueStrings[i] += " / 255.0"; - } - } - lines.push(`metadataValues.x = ${sourceValueStrings[0]};`); - lines.push(`metadataValues.y = ${sourceValueStrings[1]};`); - lines.push(`metadataValues.z = ${sourceValueStrings[2]};`); - lines.push(`metadataValues.w = ${sourceValueStrings[3]};`); - shaderBuilder.addFunctionLines( - "metadataPickingStage", - lines, - ShaderDestination.FRAGMENT - ); - - const XXX_METADATA_PICKING_DEBUG_LOG = true; - if (XXX_METADATA_PICKING_DEBUG_LOG) { - console.log("Name is ", propertyName); - console.log("lines"); - console.log(" ", lines[0]); - console.log(" ", lines[1]); - console.log(" ", lines[2]); - console.log(" ", lines[3]); - console.log(" ", lines[4]); - } -}; - /** * Builds the {@link ModelDrawCommand} for a {@link ModelRuntimePrimitive} * using its render resources. If the model classifies another asset, it @@ -147,18 +31,15 @@ ModelDrawCommands.prepareMetadataPickingStage = function ( * * @param {PrimitiveRenderResources} primitiveRenderResources The render resources for a primitive. * @param {FrameState} frameState The frame state for creating GPU resources. - * @param {ShaderBuilder} shaderBuilder The shader builder - * @param {boolean|undefined} metadataPicking Whether the command is built for metadata picking * @returns {ModelDrawCommand|ClassificationModelDrawCommand} The generated ModelDrawCommand or ClassificationModelDrawCommand. * * @private */ ModelDrawCommands.buildModelDrawCommand = function ( primitiveRenderResources, - frameState, - shaderBuilder, - metadataPicking + frameState ) { + const shaderBuilder = primitiveRenderResources.shaderBuilder; const shaderProgram = ModelDrawCommands.createShaderProgram( primitiveRenderResources, shaderBuilder, @@ -171,12 +52,6 @@ ModelDrawCommands.buildModelDrawCommand = function ( frameState ); - if (metadataPicking) { - command.debugShowBoundingVolume = false; - command.castShadows = false; - command.receiveShadows = false; - } - const model = primitiveRenderResources.model; const hasClassification = defined(model.classificationType); if (hasClassification) { @@ -309,6 +184,7 @@ ModelDrawCommands.buildDrawCommandForModel = function ( count: primitiveRenderResources.count, owner: model, pickId: pickId, + pickedMetadataInfo: frameState.pickedMetadataInfo, instanceCount: primitiveRenderResources.instanceCount, primitiveType: primitiveRenderResources.primitiveType, debugShowBoundingVolume: model.debugShowBoundingVolume, diff --git a/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js b/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js index 3420dcf679ed..6ad9b7b63c20 100644 --- a/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js +++ b/packages/engine/Source/Scene/Model/ModelMatrixUpdateStage.js @@ -1,7 +1,6 @@ import Matrix4 from "../../Core/Matrix4.js"; import ModelUtility from "./ModelUtility.js"; import SceneMode from "../SceneMode.js"; -import defined from "../../Core/defined.js"; /** * The model matrix update stage is responsible for updating the model matrices and bounding volumes of the draw commands. @@ -96,13 +95,6 @@ function updateRuntimeNode( modelMatrix, transformToRoot ); - if (defined(runtimePrimitive.metadataPickingDrawCommand)) { - updateDrawCommand( - runtimePrimitive.metadataPickingDrawCommand, - modelMatrix, - transformToRoot - ); - } } const childrenLength = runtimeNode.children.length; diff --git a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js index ed82ca255cf4..8eb6e9995c0c 100644 --- a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js +++ b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js @@ -14,6 +14,7 @@ import FeatureIdPipelineStage from "./FeatureIdPipelineStage.js"; import GeometryPipelineStage from "./GeometryPipelineStage.js"; import LightingPipelineStage from "./LightingPipelineStage.js"; import MaterialPipelineStage from "./MaterialPipelineStage.js"; +import MetadataPickingPipelineStage from "./MetadataPickingPipelineStage.js"; import MetadataPipelineStage from "./MetadataPipelineStage.js"; import ModelUtility from "./ModelUtility.js"; import MorphTargetsPipelineStage from "./MorphTargetsPipelineStage.js"; @@ -276,6 +277,7 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) { // are declared to avoid compilation errors. pipelineStages.push(FeatureIdPipelineStage); pipelineStages.push(MetadataPipelineStage); + pipelineStages.push(MetadataPickingPipelineStage); if (featureIdFlags.hasPropertyTable) { pipelineStages.push(SelectedFeatureIdPipelineStage); diff --git a/packages/engine/Source/Scene/Model/ModelSceneGraph.js b/packages/engine/Source/Scene/Model/ModelSceneGraph.js index 1724ccf8cce4..757106750be0 100644 --- a/packages/engine/Source/Scene/Model/ModelSceneGraph.js +++ b/packages/engine/Source/Scene/Model/ModelSceneGraph.js @@ -560,40 +560,11 @@ ModelSceneGraph.prototype.buildDrawCommands = function (frameState) { modelPositionMax ); - const shaderBuilderRender = primitiveRenderResources.shaderBuilder.clone(); const drawCommand = ModelDrawCommands.buildModelDrawCommand( primitiveRenderResources, - frameState, - shaderBuilderRender, - false + frameState ); runtimePrimitive.drawCommand = drawCommand; - - const shaderBuilderPickMetadata = primitiveRenderResources.shaderBuilder.clone(); - const schemaId = frameState.pickedMetadataSchemaId; - const className = frameState.pickedMetadataClassName; - const propertyName = frameState.pickedMetadataPropertyName; - const classProperty = frameState.pickedMetadataClassProperty; - if ( - defined(className) && - defined(propertyName) && - defined(classProperty) - ) { - ModelDrawCommands.prepareMetadataPickingStage( - shaderBuilderPickMetadata, - schemaId, - className, - propertyName, - classProperty - ); - } - const metadataPickingDrawCommand = ModelDrawCommands.buildModelDrawCommand( - primitiveRenderResources, - frameState, - shaderBuilderPickMetadata, - true - ); - runtimePrimitive.metadataPickingDrawCommand = metadataPickingDrawCommand; } } @@ -848,10 +819,6 @@ ModelSceneGraph.prototype.updateBackFaceCulling = function (backFaceCulling) { // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveBackFaceCulling(runtimePrimitive, options) { runtimePrimitive.drawCommand.backFaceCulling = options.backFaceCulling; - if (defined(runtimePrimitive.metadataPickingDrawCommand)) { - runtimePrimitive.metadataPickingDrawCommand.backFaceCulling = - options.backFaceCulling; - } } const scratchShadowOptions = { @@ -874,9 +841,6 @@ ModelSceneGraph.prototype.updateShadows = function (shadowMode) { // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveShadows(runtimePrimitive, options) { runtimePrimitive.drawCommand.shadows = options.shadows; - if (defined(runtimePrimitive.metadataPickingDrawCommand)) { - runtimePrimitive.metadataPickingDrawCommand.shadows = options.shadows; - } } const scratchShowBoundingVolumeOptions = { @@ -908,10 +872,6 @@ ModelSceneGraph.prototype.updateShowBoundingVolume = function ( function updatePrimitiveShowBoundingVolume(runtimePrimitive, options) { runtimePrimitive.drawCommand.debugShowBoundingVolume = options.debugShowBoundingVolume; - if (defined(runtimePrimitive.metadataPickingDrawCommand)) { - runtimePrimitive.metadataPickingDrawCommand.debugShowBoundingVolume = - options.debugShowBoundingVolume; - } } const scratchSilhouetteCommands = []; @@ -959,16 +919,7 @@ function pushPrimitiveDrawCommands(runtimePrimitive, options) { const passes = frameState.passes; const silhouetteCommands = scratchSilhouetteCommands; - let primitiveDrawCommand; - - //const schemaId = frameState.pickedMetadataSchemaId; - if (frameState.pickMetadata) { - console.log("Using metadata picking command"); - primitiveDrawCommand = runtimePrimitive.metadataPickingDrawCommand; - } else { - primitiveDrawCommand = runtimePrimitive.drawCommand; - } - + const primitiveDrawCommand = runtimePrimitive.drawCommand; primitiveDrawCommand.pushCommands(frameState, frameState.commandList); // If a model has silhouettes, the commands that draw the silhouettes for diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 71e6139ca6cb..c4ce22c5ac0b 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -40,10 +40,6 @@ const pickTilesetPassState = new Cesium3DTilePassState({ pass: Cesium3DTilePass.PICK, }); -const renderTilesetPassState = new Cesium3DTilePassState({ - pass: Cesium3DTilePass.RENDER, -}); - /** * @private */ @@ -416,13 +412,7 @@ Picking.prototype.pickVoxelCoordinate = function ( * Pick a metadata value at the given window position. * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. - * @param {string|undefined} schemaId The ID of the metadata schema to pick values - * from, or `undefined` to pick values from any schema. - * @param {string} className The name of the metadata class to pick - * values from - * @param {string} propertyName The name of the metadata property to pick - * values from - * @param {MetadataClassProperty} classProperty The actual class property + * @param {object} pickedMetadataInfo The picked metadata * @returns The metadata values * * @private @@ -430,16 +420,11 @@ Picking.prototype.pickVoxelCoordinate = function ( Picking.prototype.pickMetadata = function ( scene, windowPosition, - schemaId, - className, - propertyName, - classProperty + pickedMetadataInfo ) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("windowPosition", windowPosition); - Check.typeOf.string("className", className); - Check.typeOf.string("propertyName", propertyName); - Check.typeOf.object("classProperty", classProperty); + Check.typeOf.object("pickedMetadataInfo", pickedMetadataInfo); //>>includeEnd('debug'); const { context, frameState, defaultView } = scene; @@ -480,11 +465,8 @@ Picking.prototype.pickMetadata = function ( ); frameState.invertClassification = false; - // This has to be set to 'true', because otherwise, the - // submitCommandsForPass variable will be "false" in - // Model.js, and nothing will be rendered. - frameState.passes.render = true; - frameState.tilesetPassState = renderTilesetPassState; + frameState.passes.pick = true; + frameState.tilesetPassState = pickTilesetPassState; // Insert the information about the picked metadata property // into the frame state, so that ... @@ -492,11 +474,8 @@ Picking.prototype.pickMetadata = function ( // function to trigger a rebuild of the draw commands // - the ModelDrawCommands functions will insert the proper code // for picking the values into the shader - frameState.pickMetadata = true; - frameState.pickedMetadataSchemaId = schemaId; - frameState.pickedMetadataClassName = className; - frameState.pickedMetadataPropertyName = propertyName; - frameState.pickedMetadataClassProperty = classProperty; + frameState.pickingMetadata = true; + frameState.pickedMetadataInfo = pickedMetadataInfo; context.uniformState.update(frameState); scene.updateEnvironment(); @@ -520,10 +499,10 @@ Picking.prototype.pickMetadata = function ( ); context.endFrame(); - frameState.pickMetadata = false; + frameState.pickingMetadata = false; const metadataValue = MetadataPicking.decodeMetadataValues( - classProperty, + pickedMetadataInfo.classProperty, rawMetadataPixel ); console.log("metadataValue ", metadataValue); diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 977510775a41..4f665ccddd8f 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -1732,7 +1732,17 @@ function updateDerivedCommands(scene, command, shadowsDirty) { derivedCommands.picking ); } - + ///* + if (defined(command.pickedMetadataInfo)) { + console.log("Creating derived command for ", command.pickedMetadataInfo); + derivedCommands.pickingMetadata = DerivedCommand.createPickMetadataDerivedCommand( + scene, + command, + context, + derivedCommands.pickingMetadata + ); + } + //*/ if (!command.pickOnly) { derivedCommands.depth = DerivedCommand.createDepthOnlyDerivedCommand( scene, @@ -2175,14 +2185,28 @@ function executeCommand(command, scene, context, passState, debugFramebuffer) { } if (passes.pick || passes.depth) { - if ( - passes.pick && - !passes.depth && - defined(command.derivedCommands.picking) - ) { - command = command.derivedCommands.picking.pickCommand; - command.execute(context, passState); - return; + if (passes.pick && !passes.depth) { + if ( + frameState.pickingMetadata && + defined(command.derivedCommands.pickingMetadata) + ) { + //XXX_METADATA_PICKING + // TODO The same has to be done in executeIdCommand! + console.log("Actually executing the pickingMetadata command"); + command = command.derivedCommands.pickingMetadata.pickMetadataCommand; + command.execute(context, passState); + return; + } + if ( + !frameState.pickingMetadata && + defined(command.derivedCommands.picking) + ) { + //XXX_METADATA_PICKING + console.log("Executing the picking command"); + command = command.derivedCommands.picking.pickCommand; + command.execute(context, passState); + return; + } } else if (defined(command.derivedCommands.depth)) { command = command.derivedCommands.depth.depthOnlyCommand; command.execute(context, passState); @@ -4319,13 +4343,17 @@ Scene.prototype.pickMetadata = function ( return undefined; } + const pickedMetadataInfo = { + schemaId: schemaId, + className: className, + propertyName: propertyName, + classProperty: classProperty, + }; + const pickedMetadataValues = this._picking.pickMetadata( this, windowPosition, - schemaId, - className, - propertyName, - classProperty + pickedMetadataInfo ); if (XXX_METADATA_PICKING_DEBUG_LOG) { diff --git a/packages/engine/Source/Shaders/Model/ModelFS.glsl b/packages/engine/Source/Shaders/Model/ModelFS.glsl index ca3a2f2602c4..0500c0b9f1ff 100644 --- a/packages/engine/Source/Shaders/Model/ModelFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelFS.glsl @@ -47,7 +47,7 @@ void main() //======================================================================== // When not picking metadata START - #ifndef METADATA_PICKING + #ifndef METADATA_PICKING_ENABLED #ifdef HAS_SELECTED_FEATURE_ID selectedFeatureIdStage(selectedFeature, featureIds); @@ -101,7 +101,7 @@ void main() //======================================================================== // When not picking metadata START - #ifndef METADATA_PICKING + #ifndef METADATA_PICKING_ENABLED #if defined(HAS_SILHOUETTE) && defined(HAS_NORMALS) silhouetteStage(color); From 7848f5d93b8ca2df1fc4b2ce9c5240d6986afd1c Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 30 Jul 2024 17:01:30 +0200 Subject: [PATCH 15/63] Minor consolidation --- .../engine/Source/Renderer/DrawCommand.js | 22 ++- .../engine/Source/Scene/DerivedCommand.js | 58 ++++--- packages/engine/Source/Scene/FrameState.js | 25 ++- packages/engine/Source/Scene/Model/Model.js | 29 ---- .../Source/Scene/Model/ModelDrawCommands.js | 2 +- .../Source/Scene/Model/ModelSceneGraph.js | 10 +- .../Source/Scene/Model/buildDrawCommand.js | 161 ------------------ packages/engine/Source/Scene/Picking.js | 25 ++- packages/engine/Source/Scene/Scene.js | 60 +++++-- 9 files changed, 140 insertions(+), 252 deletions(-) delete mode 100644 packages/engine/Source/Scene/Model/buildDrawCommand.js diff --git a/packages/engine/Source/Renderer/DrawCommand.js b/packages/engine/Source/Renderer/DrawCommand.js index c5a34cecae3c..3ced55cd4d89 100644 --- a/packages/engine/Source/Renderer/DrawCommand.js +++ b/packages/engine/Source/Renderer/DrawCommand.js @@ -40,7 +40,8 @@ function DrawCommand(options) { this._owner = options.owner; this._debugOverlappingFrustums = 0; this._pickId = options.pickId; - this._pickedMetadataInfo = options.pickedMetadataInfo; + this._pickMetadataAllowed = options.pickMetadataAllowed === true; + this._pickedMetadataInfo = undefined; // Set initial flags. this._flags = 0; @@ -528,12 +529,24 @@ Object.defineProperties(DrawCommand.prototype, { }, /** - * Information about picked metadata. + * Whether metadata picking is allowed * - * XXX_METADATA_PICKING Documentation. On just say "@private". Lol. + * @memberof DrawCommand.prototype + * @type {boolean} + * @default undefined + * @private + */ + pickMetadataAllowed: { + get: function () { + return this._pickMetadataAllowed; + }, + }, + + /** + * Information about picked metadata. * * @memberof DrawCommand.prototype - * @type {object|undefined} + * @type {PickedMetadataInfo|undefined} * @default undefined */ pickedMetadataInfo: { @@ -613,6 +626,7 @@ DrawCommand.shallowClone = function (command, result) { result._owner = command._owner; result._debugOverlappingFrustums = command._debugOverlappingFrustums; result._pickId = command._pickId; + result._pickMetadataAllowed = command._pickMetadataAllowed; result._pickedMetadataInfo = command._pickedMetadataInfo; result._flags = command._flags; diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 99e8bbf9472c..8cf0452d81e7 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -376,6 +376,9 @@ DerivedCommand.createPickDerivedCommand = function ( return result; }; +/** + * @private (undocumented) + */ function replaceDefine(defines, defineName, newDefineValue) { const n = defines.length; for (let i = 0; i < n; i++) { @@ -385,6 +388,9 @@ function replaceDefine(defines, defineName, newDefineValue) { } } +/** + * @private (undocumented) + */ function getComponentCount(classProperty) { if (!classProperty.isArray) { return MetadataType.getComponentCount(classProperty.type); @@ -405,20 +411,26 @@ function getGlslType(classProperty) { return `ivec${componentCount}`; } +/** + * @private (undocumented) + */ function getPickMetadataShaderProgram( context, shaderProgram, pickedMetadataInfo ) { + const schemaId = pickedMetadataInfo.schemaId; + const className = pickedMetadataInfo.className; + const propertyName = pickedMetadataInfo.propertyName; + const keyword = `pickMetadata-${schemaId}-${className}-${propertyName}`; let shader = context.shaderCache.getDerivedShaderProgram( shaderProgram, - "pickMetadata" + keyword ); if (defined(shader)) { return shader; } - const propertyName = pickedMetadataInfo.propertyName; const classProperty = pickedMetadataInfo.classProperty; const glslType = getGlslType(classProperty); @@ -483,18 +495,20 @@ function getPickMetadataShaderProgram( sourceValueStrings[3] ); + // XXX_METADATA_PICKING + //*/ console.log("newDefines"); for (const d of newDefines) { console.log(d); } - + //*/ fs = new ShaderSource({ sources: fs.sources, defines: newDefines, }); shader = context.shaderCache.createDerivedShaderProgram( shaderProgram, - "pickMetadata", + keyword, { vertexShaderSource: shaderProgram.vertexShaderSource, fragmentShaderSource: fs, @@ -504,6 +518,9 @@ function getPickMetadataShaderProgram( return shader; } +/** + * @private (undocumented) + */ DerivedCommand.createPickMetadataDerivedCommand = function ( scene, command, @@ -513,34 +530,21 @@ DerivedCommand.createPickMetadataDerivedCommand = function ( if (!defined(result)) { result = {}; } - - let shader; - let renderState; - if (defined(result.pickMetadataCommand)) { - shader = result.pickMetadataCommand.shaderProgram; - renderState = result.pickMetadataCommand.renderState; - } - result.pickMetadataCommand = DrawCommand.shallowClone( command, result.pickMetadataCommand ); - if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) { - result.pickMetadataCommand.shaderProgram = getPickMetadataShaderProgram( - context, - command.shaderProgram, - command.pickedMetadataInfo - ); - result.pickMetadataCommand.renderState = getPickRenderState( - scene, - command.renderState - ); - result.shaderProgramId = command.shaderProgram.id; - } else { - result.pickMetadataCommand.shaderProgram = shader; - result.pickMetadataCommand.renderState = renderState; - } + result.pickMetadataCommand.shaderProgram = getPickMetadataShaderProgram( + context, + command.shaderProgram, + command.pickedMetadataInfo + ); + result.pickMetadataCommand.renderState = getPickRenderState( + scene, + command.renderState + ); + result.shaderProgramId = command.shaderProgram.id; return result; }; diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index c3098f6dcbe4..f2b45b73c44c 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -422,15 +422,34 @@ function FrameState(context, creditDisplay, jobScheduler) { */ this.minimumTerrainHeight = 0.0; - // XXX_METADATA_PICKING + /** + * Whether metadata picking is currently in progress. + * + * This is set to `true` in the `Picking.pickMetadata` function, + * immediately before updating and executing the draw commands, + * and set back to `false` immediately afterwards. It will be + * used to determine whether the metadata picking draw commands + * should be executed, in the `Scene.executeCommand` function. + * + * @type {boolean} + * @default false + */ this.pickingMetadata = false; /** * Metadata picking information. * - * XXX_METADATA_PICKING Documentation... + * This describes the metadata property that is supposed to be picked + * in a `Picking.pickMetadata` call. * - * @type {object} + * This is stored in the frame state and in the metadata picking draw + * commands. In the `Scene.updateDerivedCommands` call, it will be + * checked whether the instance that is stored in the frame state + * is different from the one in the draw command, and if necessary, + * the derived commands for metadata picking will be updated based + * on this information. + * + * @type {PickedMetadataInfo|undefined} */ this.pickedMetadataInfo = undefined; } diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 28ddee3be883..3da886945f37 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -479,8 +479,6 @@ function Model(options) { this._fogRenderable = undefined; - this._pickedMetadataInfo = undefined; - this._skipLevelOfDetail = false; this._ignoreCommands = defaultValue(options.ignoreCommands, false); @@ -1843,7 +1841,6 @@ Model.prototype.update = function (frameState) { updateSceneMode(this, frameState); updateFog(this, frameState); updateVerticalExaggeration(this, frameState); - updatePickedMetadataInfo(this, frameState); this._defaultTexture = frameState.context.defaultTexture; @@ -2072,32 +2069,6 @@ function updateVerticalExaggeration(model, frameState) { } } -/** - * Will be called during `update`, and detect changes in the `pickedMetadataInfo*` - * properties. When they changed, then the draw commands will be reset, to - * eventually be rebuilt by the `ModelDrawCommands` class, with the new picked - * metadata property values - * - * @param {Model} model The model - * @param {FrameState} frameState The frame state - */ -function updatePickedMetadataInfo(model, frameState) { - if ( - frameState.pickedMetadataInfo?.schemaId !== - model._pickedMetadataInfo?.schemaId || - frameState.pickedMetadataInfo?.classnName !== - model._pickedMetadataInfo?.classnName || - frameState.pickedMetadataInfo?.propertyName !== - model._pickedMetadataInfo?.propertyName - ) { - console.log( - "pickedMetadataInfo changed, reset draw commands ", - frameState.pickedMetadataInfo - ); - model.resetDrawCommands(); - model._pickedMetadataInfo = frameState.pickedMetadataInfo; - } -} function buildDrawCommands(model, frameState) { if (!model._drawCommandsBuilt) { model.destroyPipelineResources(); diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js index 8dfe37d43186..34508267a110 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommands.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -184,7 +184,7 @@ ModelDrawCommands.buildDrawCommandForModel = function ( count: primitiveRenderResources.count, owner: model, pickId: pickId, - pickedMetadataInfo: frameState.pickedMetadataInfo, + pickMetadataAllowed: true, instanceCount: primitiveRenderResources.instanceCount, primitiveType: primitiveRenderResources.primitiveType, debugShowBoundingVolume: model.debugShowBoundingVolume, diff --git a/packages/engine/Source/Scene/Model/ModelSceneGraph.js b/packages/engine/Source/Scene/Model/ModelSceneGraph.js index 757106750be0..29fb80b9279d 100644 --- a/packages/engine/Source/Scene/Model/ModelSceneGraph.js +++ b/packages/engine/Source/Scene/Model/ModelSceneGraph.js @@ -818,7 +818,8 @@ ModelSceneGraph.prototype.updateBackFaceCulling = function (backFaceCulling) { // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveBackFaceCulling(runtimePrimitive, options) { - runtimePrimitive.drawCommand.backFaceCulling = options.backFaceCulling; + const drawCommand = runtimePrimitive.drawCommand; + drawCommand.backFaceCulling = options.backFaceCulling; } const scratchShadowOptions = { @@ -840,7 +841,8 @@ ModelSceneGraph.prototype.updateShadows = function (shadowMode) { // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveShadows(runtimePrimitive, options) { - runtimePrimitive.drawCommand.shadows = options.shadows; + const drawCommand = runtimePrimitive.drawCommand; + drawCommand.shadows = options.shadowMode; } const scratchShowBoundingVolumeOptions = { @@ -870,8 +872,8 @@ ModelSceneGraph.prototype.updateShowBoundingVolume = function ( // Callback is defined here to avoid allocating a closure in the render loop function updatePrimitiveShowBoundingVolume(runtimePrimitive, options) { - runtimePrimitive.drawCommand.debugShowBoundingVolume = - options.debugShowBoundingVolume; + const drawCommand = runtimePrimitive.drawCommand; + drawCommand.debugShowBoundingVolume = options.debugShowBoundingVolume; } const scratchSilhouetteCommands = []; diff --git a/packages/engine/Source/Scene/Model/buildDrawCommand.js b/packages/engine/Source/Scene/Model/buildDrawCommand.js deleted file mode 100644 index f23b712f06d4..000000000000 --- a/packages/engine/Source/Scene/Model/buildDrawCommand.js +++ /dev/null @@ -1,161 +0,0 @@ -import BoundingSphere from "../../Core/BoundingSphere.js"; -import clone from "../../Core/clone.js"; -import defined from "../../Core/defined.js"; -import DeveloperError from "../../Core/DeveloperError.js"; -import Matrix4 from "../../Core/Matrix4.js"; -import DrawCommand from "../../Renderer/DrawCommand.js"; -import RenderState from "../../Renderer/RenderState.js"; -import VertexArray from "../../Renderer/VertexArray.js"; -import ModelFS from "../../Shaders/Model/ModelFS.js"; -import ModelVS from "../../Shaders/Model/ModelVS.js"; -import SceneMode from "../SceneMode.js"; -import ShadowMode from "../ShadowMode.js"; -import ClassificationModelDrawCommand from "./ClassificationModelDrawCommand.js"; -import ModelUtility from "./ModelUtility.js"; -import ModelDrawCommand from "./ModelDrawCommand.js"; - -/** - * Builds the {@link ModelDrawCommand} for a {@link ModelRuntimePrimitive} - * using its render resources. If the model classifies another asset, it - * builds a {@link ClassificationModelDrawCommand} instead. - * - * @param {PrimitiveRenderResources} primitiveRenderResources The render resources for a primitive. - * @param {FrameState} frameState The frame state for creating GPU resources. - * - * @returns {ModelDrawCommand|ClassificationModelDrawCommand} The generated ModelDrawCommand or ClassificationModelDrawCommand. - * - * @private - */ -function buildDrawCommand(primitiveRenderResources, frameState) { - const shaderBuilder = primitiveRenderResources.shaderBuilder; - shaderBuilder.addVertexLines(ModelVS); - shaderBuilder.addFragmentLines(ModelFS); - - const indexBuffer = getIndexBuffer(primitiveRenderResources); - - const vertexArray = new VertexArray({ - context: frameState.context, - indexBuffer: indexBuffer, - attributes: primitiveRenderResources.attributes, - }); - - const model = primitiveRenderResources.model; - model._pipelineResources.push(vertexArray); - - const shaderProgram = shaderBuilder.buildShaderProgram(frameState.context); - model._pipelineResources.push(shaderProgram); - - const pass = primitiveRenderResources.alphaOptions.pass; - const sceneGraph = model.sceneGraph; - - const is3D = frameState.mode === SceneMode.SCENE3D; - let modelMatrix, boundingSphere; - - if (!is3D && !frameState.scene3DOnly && model._projectTo2D) { - modelMatrix = Matrix4.multiplyTransformation( - sceneGraph._computedModelMatrix, - primitiveRenderResources.runtimeNode.computedTransform, - new Matrix4() - ); - - const runtimePrimitive = primitiveRenderResources.runtimePrimitive; - boundingSphere = runtimePrimitive.boundingSphere2D; - } else { - const computedModelMatrix = is3D - ? sceneGraph._computedModelMatrix - : sceneGraph._computedModelMatrix2D; - - modelMatrix = Matrix4.multiplyTransformation( - computedModelMatrix, - primitiveRenderResources.runtimeNode.computedTransform, - new Matrix4() - ); - - boundingSphere = BoundingSphere.transform( - primitiveRenderResources.boundingSphere, - modelMatrix, - primitiveRenderResources.boundingSphere - ); - } - - // Initialize render state with default values - let renderState = clone( - RenderState.fromCache(primitiveRenderResources.renderStateOptions), - true - ); - - renderState.cull.face = ModelUtility.getCullFace( - modelMatrix, - primitiveRenderResources.primitiveType - ); - renderState = RenderState.fromCache(renderState); - - const hasClassification = defined(model.classificationType); - const castShadows = hasClassification - ? false - : ShadowMode.castShadows(model.shadows); - const receiveShadows = hasClassification - ? false - : ShadowMode.receiveShadows(model.shadows); - // Pick IDs are only added to specific draw commands for classification. - // This behavior is handled by ClassificationModelDrawCommand. - const pickId = hasClassification - ? undefined - : primitiveRenderResources.pickId; - - const command = new DrawCommand({ - boundingVolume: boundingSphere, - modelMatrix: modelMatrix, - uniformMap: primitiveRenderResources.uniformMap, - renderState: renderState, - vertexArray: vertexArray, - shaderProgram: shaderProgram, - cull: model.cull, - pass: pass, - count: primitiveRenderResources.count, - owner: model, - pickId: pickId, - instanceCount: primitiveRenderResources.instanceCount, - primitiveType: primitiveRenderResources.primitiveType, - debugShowBoundingVolume: model.debugShowBoundingVolume, - castShadows: castShadows, - receiveShadows: receiveShadows, - }); - - if (hasClassification) { - return new ClassificationModelDrawCommand({ - primitiveRenderResources: primitiveRenderResources, - command: command, - }); - } - - return new ModelDrawCommand({ - primitiveRenderResources: primitiveRenderResources, - command: command, - }); -} - -/** - * @private - */ -function getIndexBuffer(primitiveRenderResources) { - const wireframeIndexBuffer = primitiveRenderResources.wireframeIndexBuffer; - if (defined(wireframeIndexBuffer)) { - return wireframeIndexBuffer; - } - - const indices = primitiveRenderResources.indices; - if (!defined(indices)) { - return undefined; - } - - //>>includeStart('debug', pragmas.debug); - if (!defined(indices.buffer)) { - throw new DeveloperError("Indices must be provided as a Buffer"); - } - //>>includeEnd('debug'); - - return indices.buffer; -} - -export default buildDrawCommand; diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index c4ce22c5ac0b..6f95e80fed0b 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -411,8 +411,11 @@ Picking.prototype.pickVoxelCoordinate = function ( /** * Pick a metadata value at the given window position. * + * The given `pickedMetadataInfo` defines the metadata value that is + * supposed to be picked. + * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. - * @param {object} pickedMetadataInfo The picked metadata + * @param {PickedMetadataInfo} pickedMetadataInfo Information about the picked metadata. * @returns The metadata values * * @private @@ -469,11 +472,10 @@ Picking.prototype.pickMetadata = function ( frameState.tilesetPassState = pickTilesetPassState; // Insert the information about the picked metadata property - // into the frame state, so that ... - // - changes in these properties are picked up by the Model.update - // function to trigger a rebuild of the draw commands - // - the ModelDrawCommands functions will insert the proper code - // for picking the values into the shader + // into the frame state, so that the `Scene.updateDerivedCommands` + // call can detect any changes in the picked metadata description, + // and update the derived commands for the new picked metadata + // property frameState.pickingMetadata = true; frameState.pickedMetadataInfo = pickedMetadataInfo; context.uniformState.update(frameState); @@ -510,6 +512,17 @@ Picking.prototype.pickMetadata = function ( return metadataValue; }; +/** + * @typedef {object} PickedMetadataInfo + * + * Information about metadata that is supposed to be picked + * + * @property {string|undefined} schemaId The optional ID of the metadata schema + * @property {string} className The name of the metadata class + * @property {string} propertyName The name of the metadata property + * @property {MetadataClassProperty} classProperty The metadata class property + */ + function renderTranslucentDepthForPick(scene, drawingBufferPosition) { // PERFORMANCE_IDEA: render translucent only and merge with the previous frame const { defaultView, context, frameState, environmentState } = scene; diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 4f665ccddd8f..8d9d8fe4747f 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -1715,6 +1715,23 @@ Scene.prototype.getCompressedTextureFormatSupported = function (format) { ); }; +function pickedMetadataInfoChanged(command, frameState) { + const oldPickedMetadataInfo = command.pickedMetadataInfo; + const newPickedMetadataInfo = frameState.pickedMetadataInfo; + if (oldPickedMetadataInfo?.schemaId !== newPickedMetadataInfo?.schemaId) { + return true; + } + if (oldPickedMetadataInfo?.className !== newPickedMetadataInfo?.className) { + return true; + } + if ( + oldPickedMetadataInfo?.propertyName !== newPickedMetadataInfo?.propertyName + ) { + return true; + } + return false; +} + function updateDerivedCommands(scene, command, shadowsDirty) { const frameState = scene._frameState; const context = scene._context; @@ -1732,17 +1749,19 @@ function updateDerivedCommands(scene, command, shadowsDirty) { derivedCommands.picking ); } - ///* - if (defined(command.pickedMetadataInfo)) { - console.log("Creating derived command for ", command.pickedMetadataInfo); - derivedCommands.pickingMetadata = DerivedCommand.createPickMetadataDerivedCommand( - scene, - command, - context, - derivedCommands.pickingMetadata - ); + if (frameState.pickingMetadata && command.pickMetadataAllowed) { + if (pickedMetadataInfoChanged(command, frameState)) { + command.pickedMetadataInfo = frameState.pickedMetadataInfo; + if (defined(command.pickedMetadataInfo)) { + derivedCommands.pickingMetadata = DerivedCommand.createPickMetadataDerivedCommand( + scene, + command, + context, + derivedCommands.pickingMetadata + ); + } + } } - //*/ if (!command.pickOnly) { derivedCommands.depth = DerivedCommand.createDepthOnlyDerivedCommand( scene, @@ -1825,13 +1844,14 @@ Scene.prototype.updateDerivedCommands = function (command) { useLogDepth && !hasLogDepthDerivedCommands; const needsHdrCommands = useHdr && !hasHdrCommands; const needsDerivedCommands = (!useLogDepth || !useHdr) && !hasDerivedCommands; + command.dirty = command.dirty || needsLogDepthDerivedCommands || needsHdrCommands || needsDerivedCommands; - if (command.dirty) { + if (command.dirty || frameState.pickingMetadata) { command.dirty = false; const shadowMaps = frameState.shadowState.shadowMaps; @@ -1858,7 +1878,11 @@ Scene.prototype.updateDerivedCommands = function (command) { shadowsDirty ); } - if (hasDerivedCommands || needsDerivedCommands) { + if ( + hasDerivedCommands || + needsDerivedCommands || + frameState.pickingMetadata + ) { updateDerivedCommands(this, command, shadowsDirty); } } @@ -2190,8 +2214,7 @@ function executeCommand(command, scene, context, passState, debugFramebuffer) { frameState.pickingMetadata && defined(command.derivedCommands.pickingMetadata) ) { - //XXX_METADATA_PICKING - // TODO The same has to be done in executeIdCommand! + // XXX_METADATA_PICKING console.log("Actually executing the pickingMetadata command"); command = command.derivedCommands.pickingMetadata.pickMetadataCommand; command.execute(context, passState); @@ -2201,8 +2224,6 @@ function executeCommand(command, scene, context, passState, debugFramebuffer) { !frameState.pickingMetadata && defined(command.derivedCommands.picking) ) { - //XXX_METADATA_PICKING - console.log("Executing the picking command"); command = command.derivedCommands.picking.pickCommand; command.execute(context, passState); return; @@ -2249,6 +2270,11 @@ function executeIdCommand(command, scene, context, passState) { } derivedCommands = command.derivedCommands; + + if (defined(derivedCommands.pickingMetadata)) { + command = derivedCommands.pickingMetadata.pickMetadataCommand; + command.execute(context, passState); + } if (defined(derivedCommands.picking)) { command = derivedCommands.picking.pickCommand; command.execute(context, passState); @@ -4287,7 +4313,7 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { * from, or `undefined` to pick values from any schema. * @param {string} className The name of the metadata class to pick * values from - * @param {string} propertyName The Name of the metadata property to pick + * @param {string} propertyName The name of the metadata property to pick * values from * @returns The metadata value * From 66b3252090dd1c970f635ce23fe5711fbf769cd0 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 30 Jul 2024 21:30:18 +0200 Subject: [PATCH 16/63] Update ModelRuntimePrimitiveSpec --- .../Scene/Model/ModelRuntimePrimitiveSpec.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js index 6e73cc3fbc8b..81ceb258eb9e 100644 --- a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js @@ -30,6 +30,7 @@ import { VerticalExaggerationPipelineStage, WireframePipelineStage, ClassificationType, + MetadataPickingPipelineStage, } from "../../../index.js"; import createFrameState from "../../../../../Specs/createFrameState.js"; @@ -127,6 +128,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, @@ -150,6 +152,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -189,6 +192,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, SelectedFeatureIdPipelineStage, BatchTexturePipelineStage, CPUStylingPipelineStage, @@ -235,6 +239,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, SelectedFeatureIdPipelineStage, BatchTexturePipelineStage, CPUStylingPipelineStage, @@ -294,6 +299,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, @@ -321,6 +327,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -351,6 +358,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { GeometryPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -381,6 +389,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -421,6 +430,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -455,6 +465,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -488,6 +499,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -518,6 +530,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -549,6 +562,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -578,6 +592,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -610,6 +625,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -651,6 +667,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -683,6 +700,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -714,6 +732,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -745,6 +764,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -775,6 +795,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -806,6 +827,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -836,6 +858,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -866,6 +889,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -897,6 +921,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, PrimitiveOutlinePipelineStage, AlphaPipelineStage, @@ -929,6 +954,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -959,6 +985,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -982,6 +1009,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, VerticalExaggerationPipelineStage, LightingPipelineStage, PickingPipelineStage, From 016a1a8d6ba298bb08848e90a042debd43dab0ac Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 30 Jul 2024 21:33:11 +0200 Subject: [PATCH 17/63] Fix import file extension --- packages/engine/Source/Scene/MetadataPicking.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index f0d4f503b4c6..1c7882170c5c 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -1,4 +1,4 @@ -import MetadataComponentType from "./MetadataComponentType"; +import MetadataComponentType from "./MetadataComponentType.js"; /** * Utility functions for metadata picking. From d575317b691d69296fbbad5423d4502b3fa68f87 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 31 Jul 2024 13:34:47 +0200 Subject: [PATCH 18/63] Trying to make JSDoc happier --- packages/engine/Source/Scene/DerivedCommand.js | 8 ++++---- packages/engine/Source/Scene/MetadataPicking.js | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 8cf0452d81e7..37f39dfc6fed 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -377,7 +377,7 @@ DerivedCommand.createPickDerivedCommand = function ( }; /** - * @private (undocumented) + * @private */ function replaceDefine(defines, defineName, newDefineValue) { const n = defines.length; @@ -389,7 +389,7 @@ function replaceDefine(defines, defineName, newDefineValue) { } /** - * @private (undocumented) + * @private */ function getComponentCount(classProperty) { if (!classProperty.isArray) { @@ -412,7 +412,7 @@ function getGlslType(classProperty) { } /** - * @private (undocumented) + * @private */ function getPickMetadataShaderProgram( context, @@ -519,7 +519,7 @@ function getPickMetadataShaderProgram( } /** - * @private (undocumented) + * @private */ DerivedCommand.createPickMetadataDerivedCommand = function ( scene, diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index 1c7882170c5c..97f78c656c53 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -8,7 +8,9 @@ import MetadataComponentType from "./MetadataComponentType.js"; * into the actual metadata values, according to the structure * defined by the `MetadataClassProperty`. * - * @private (to be used by `Picking.js`) + * This is marked as 'private', but supposed to be used in Picking.js. + * + * @private */ function MetadataPicking() {} From 2f290197c174a39ffb2b27ee93d38149775fcb1a Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sun, 18 Aug 2024 15:03:17 +0200 Subject: [PATCH 19/63] Remove debug log --- packages/engine/Source/Scene/Scene.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 8d9d8fe4747f..2d4af7458db3 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -2214,8 +2214,6 @@ function executeCommand(command, scene, context, passState, debugFramebuffer) { frameState.pickingMetadata && defined(command.derivedCommands.pickingMetadata) ) { - // XXX_METADATA_PICKING - console.log("Actually executing the pickingMetadata command"); command = command.derivedCommands.pickingMetadata.pickMetadataCommand; command.execute(context, passState); return; From a1989c096ea307b78522b1242908f56754d4a4d4 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sun, 18 Aug 2024 15:03:30 +0200 Subject: [PATCH 20/63] First pass of PR feedback --- .../engine/Source/Scene/DerivedCommand.js | 76 +++++++++++++++---- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 37f39dfc6fed..39f4daa0111a 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -377,18 +377,44 @@ DerivedCommand.createPickDerivedCommand = function ( }; /** + * Replaces the value of the specified 'define' directive identifier + * with the given value. + * + * The given defines are the parts of the define directives that are + * stored in the `ShaderSource`. For example, the defines may be + * `["EXAMPLE", "EXAMPLE_VALUE 123"]` + * + * Calling `replaceDefine(defines, "EXAMPLE", 999)` will result in + * the defines being + * `["EXAMPLE 999", "EXAMPLE_VALUE 123"]` + * + * @param {string[]} defines The define directive identifiers + * @param {string} defineName The name (identifier) of the define directive + * @param {any} newDefineValue The new value whose string representation + * will become the token string for the define directive * @private */ function replaceDefine(defines, defineName, newDefineValue) { const n = defines.length; for (let i = 0; i < n; i++) { - if (defines[i].startsWith(defineName)) { + const define = defines[i]; + const tokens = define.trimStart().split(/\s+/); + if (tokens[0] === defineName) { defines[i] = `${defineName} ${newDefineValue}`; } } } /** + * Returns the component count for the given class property, or + * its array length if it is an array. + * + * This will be + * `[1, 2, 3, 4]` for `[SCALAR, VEC2, VEC3, VEC4`] types, + * or the array length if it is an array. + * + * @param {MetadataClassProperty} classProperty The class property + * @returns The component count * @private */ function getComponentCount(classProperty) { @@ -397,6 +423,16 @@ function getComponentCount(classProperty) { } return classProperty.arrayLength; } + +/** + * Returns the type that the given class property has in a GLSL shader. + * + * It returns the same string as `PropertyTextureProperty.prototype.getGlslType` + * for a property texture property with the given class property + * + * @param {MetadataClassProperty} classProperty The class property + * @returns The GLSL shader type string for the property + */ function getGlslType(classProperty) { const componentCount = getComponentCount(classProperty); if (classProperty.normalized) { @@ -412,6 +448,22 @@ function getGlslType(classProperty) { } /** + * Creates a new `ShaderProgram` from the given input that renders metadata + * values into the frame buffer, according to the given picked metadata info. + * + * This will update the `defines` of the fragment shader of the given shader + * program, by setting `METADATA_PICKING_ENABLED`, and updating the + * `METADATA_PICKING_VALUE_*` defines so that they reflect the components + * of the metadata that should be written into the RGBA (vec4) that + * ends up as the 'color' in the frame buffer. + * + * The RGBA values will eventually be converted back into an actual metadata + * value in `Picking.js`, by calling `MetadataPicking.decodeMetadataValues`. + * + * @param {Context} context The context + * @param {ShaderProgram} shaderProgram The shader program + * @param {PickedMetadataInfo} pickedMetadataInfo The picked metadata info + * @returns The new shader program * @private */ function getPickMetadataShaderProgram( @@ -423,7 +475,7 @@ function getPickMetadataShaderProgram( const className = pickedMetadataInfo.className; const propertyName = pickedMetadataInfo.propertyName; const keyword = `pickMetadata-${schemaId}-${className}-${propertyName}`; - let shader = context.shaderCache.getDerivedShaderProgram( + const shader = context.shaderCache.getDerivedShaderProgram( shaderProgram, keyword ); @@ -462,8 +514,7 @@ function getPickMetadataShaderProgram( } } - let fs = shaderProgram.fragmentShaderSource; - const newDefines = [...fs.defines]; + const newDefines = shaderProgram.fragmentShaderSource.defines.slice(); newDefines.push("METADATA_PICKING_ENABLED"); // Replace the defines of the shader, using the type, property @@ -495,27 +546,20 @@ function getPickMetadataShaderProgram( sourceValueStrings[3] ); - // XXX_METADATA_PICKING - //*/ - console.log("newDefines"); - for (const d of newDefines) { - console.log(d); - } - //*/ - fs = new ShaderSource({ - sources: fs.sources, + const newFragmentShaderSource = new ShaderSource({ + sources: shaderProgram.fragmentShaderSource.sources, defines: newDefines, }); - shader = context.shaderCache.createDerivedShaderProgram( + const newShader = context.shaderCache.createDerivedShaderProgram( shaderProgram, keyword, { vertexShaderSource: shaderProgram.vertexShaderSource, - fragmentShaderSource: fs, + fragmentShaderSource: newFragmentShaderSource, attributeLocations: shaderProgram._attributeLocations, } ); - return shader; + return newShader; } /** From 88500ec9d8bf72e2f31ac68be24167d1c28a2ab9 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sun, 18 Aug 2024 15:12:37 +0200 Subject: [PATCH 21/63] Removed debug log --- packages/engine/Source/Scene/Picking.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 6f95e80fed0b..ade2b8a9e8eb 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -507,7 +507,6 @@ Picking.prototype.pickMetadata = function ( pickedMetadataInfo.classProperty, rawMetadataPixel ); - console.log("metadataValue ", metadataValue); return metadataValue; }; From fe4c2bd0c905397b557a9980d9b7c0c276d76a95 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sun, 18 Aug 2024 15:30:22 +0200 Subject: [PATCH 22/63] Replace repeated strings with constants --- .../engine/Source/Scene/DerivedCommand.js | 19 +++++++---- .../engine/Source/Scene/MetadataPicking.js | 2 +- .../Model/MetadataPickingPipelineStage.js | 34 +++++++++++++------ 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 39f4daa0111a..7d3b29b4288a 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -3,6 +3,7 @@ import DrawCommand from "../Renderer/DrawCommand.js"; import RenderState from "../Renderer/RenderState.js"; import ShaderSource from "../Renderer/ShaderSource.js"; import MetadataType from "./MetadataType.js"; +import MetadataPickingPipelineStage from "./Model/MetadataPickingPipelineStage.js"; /** * @private @@ -515,34 +516,38 @@ function getPickMetadataShaderProgram( } const newDefines = shaderProgram.fragmentShaderSource.defines.slice(); - newDefines.push("METADATA_PICKING_ENABLED"); + newDefines.push(MetadataPickingPipelineStage.METADATA_PICKING_ENABLED); // Replace the defines of the shader, using the type, property // access, and value components that have been determined - replaceDefine(newDefines, "METADATA_PICKING_VALUE_TYPE", glslType); replaceDefine( newDefines, - "METADATA_PICKING_VALUE_STRING", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_TYPE, + glslType + ); + replaceDefine( + newDefines, + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_STRING, `metadata.${propertyName}` ); replaceDefine( newDefines, - "METADATA_PICKING_VALUE_COMPONENT_X", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_X, sourceValueStrings[0] ); replaceDefine( newDefines, - "METADATA_PICKING_VALUE_COMPONENT_Y", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_Y, sourceValueStrings[1] ); replaceDefine( newDefines, - "METADATA_PICKING_VALUE_COMPONENT_Z", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_Z, sourceValueStrings[2] ); replaceDefine( newDefines, - "METADATA_PICKING_VALUE_COMPONENT_W", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_W, sourceValueStrings[3] ); diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index 97f78c656c53..663356eba889 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -12,7 +12,7 @@ import MetadataComponentType from "./MetadataComponentType.js"; * * @private */ -function MetadataPicking() {} +const MetadataPicking = {}; /** * Returns the value at the specified inded of the given data view, diff --git a/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js b/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js index c93ef5ae0ca5..3b4ad3e757b8 100644 --- a/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js +++ b/packages/engine/Source/Scene/Model/MetadataPickingPipelineStage.js @@ -12,6 +12,18 @@ import ShaderDestination from "../../Renderer/ShaderDestination.js"; */ const MetadataPickingPipelineStage = { name: "MetadataPickingPipelineStage", // Helps with debugging + + // The identifiers for 'define' directives that are inserted into the + // shader code. The values of these defines will be be assigned + // in the `DerivedCommands` class when a derived command for metadata + // picking is created. + METADATA_PICKING_ENABLED: "METADATA_PICKING_ENABLED", + METADATA_PICKING_VALUE_TYPE: "METADATA_PICKING_VALUE_TYPE", + METADATA_PICKING_VALUE_STRING: "METADATA_PICKING_VALUE_STRING", + METADATA_PICKING_VALUE_COMPONENT_X: "METADATA_PICKING_VALUE_COMPONENT_X", + METADATA_PICKING_VALUE_COMPONENT_Y: "METADATA_PICKING_VALUE_COMPONENT_Y", + METADATA_PICKING_VALUE_COMPONENT_Z: "METADATA_PICKING_VALUE_COMPONENT_Z", + METADATA_PICKING_VALUE_COMPONENT_W: "METADATA_PICKING_VALUE_COMPONENT_W", }; /** @@ -32,32 +44,32 @@ MetadataPickingPipelineStage.process = function ( const shaderBuilder = renderResources.shaderBuilder; shaderBuilder.addDefine( - "METADATA_PICKING_VALUE_TYPE", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_TYPE, "float", ShaderDestination.FRAGMENT ); shaderBuilder.addDefine( - "METADATA_PICKING_VALUE_STRING", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_STRING, "0.0", ShaderDestination.FRAGMENT ); shaderBuilder.addDefine( - "METADATA_PICKING_VALUE_COMPONENT_X", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_X, "0.0", ShaderDestination.FRAGMENT ); shaderBuilder.addDefine( - "METADATA_PICKING_VALUE_COMPONENT_Y", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_Y, "0.0", ShaderDestination.FRAGMENT ); shaderBuilder.addDefine( - "METADATA_PICKING_VALUE_COMPONENT_Z", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_Z, "0.0", ShaderDestination.FRAGMENT ); shaderBuilder.addDefine( - "METADATA_PICKING_VALUE_COMPONENT_W", + MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_W, "0.0", ShaderDestination.FRAGMENT ); @@ -71,11 +83,11 @@ MetadataPickingPipelineStage.process = function ( shaderBuilder.addFunctionLines( "metadataPickingStage", [ - "METADATA_PICKING_VALUE_TYPE value = METADATA_PICKING_VALUE_TYPE(METADATA_PICKING_VALUE_STRING);", - "metadataValues.x = METADATA_PICKING_VALUE_COMPONENT_X;", - "metadataValues.y = METADATA_PICKING_VALUE_COMPONENT_Y;", - "metadataValues.z = METADATA_PICKING_VALUE_COMPONENT_Z;", - "metadataValues.w = METADATA_PICKING_VALUE_COMPONENT_W;", + `${MetadataPickingPipelineStage.METADATA_PICKING_VALUE_TYPE} value = ${MetadataPickingPipelineStage.METADATA_PICKING_VALUE_TYPE}(${MetadataPickingPipelineStage.METADATA_PICKING_VALUE_STRING});`, + `metadataValues.x = ${MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_X};`, + `metadataValues.y = ${MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_Y};`, + `metadataValues.z = ${MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_Z};`, + `metadataValues.w = ${MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_W};`, ], ShaderDestination.FRAGMENT ); From f8bac8f7251138c69f4180dee2126f5f254be2db Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 23 Aug 2024 20:23:38 +0200 Subject: [PATCH 23/63] Generalizations and cleanups for metadata decoding --- .../engine/Source/Scene/MetadataPicking.js | 245 ++++++++++++++++-- 1 file changed, 224 insertions(+), 21 deletions(-) diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index 663356eba889..8a1d16b76d37 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -1,4 +1,12 @@ +import Cartesian2 from "../Core/Cartesian2.js"; +import Cartesian3 from "../Core/Cartesian3.js"; +import Cartesian4 from "../Core/Cartesian4.js"; +import defined from "../Core/defined.js"; +import Matrix2 from "../Core/Matrix2.js"; +import Matrix3 from "../Core/Matrix3.js"; +import Matrix4 from "../Core/Matrix4.js"; import MetadataComponentType from "./MetadataComponentType.js"; +import MetadataType from "./MetadataType.js"; /** * Utility functions for metadata picking. @@ -15,17 +23,17 @@ import MetadataComponentType from "./MetadataComponentType.js"; const MetadataPicking = {}; /** - * Returns the value at the specified inded of the given data view, + * Returns the value at the specified index of the given data view, * interpreting the data to have the given component type. * * @param {MetadataComponentType} componentType The `MetadataComponentType` * @param {DataView} dataView The data view - * @param {number} index The index + * @param {number} index The index (byte offset) * @returns {number|bigint|undefined} The value * * @private */ -MetadataPicking.decodeMetadataValue = function ( +MetadataPicking.decodeRawMetadataValue = function ( componentType, dataView, index @@ -57,7 +65,99 @@ MetadataPicking.decodeMetadataValue = function ( }; /** - * Decode the given raw values into a metadata property value. + * Decodes one component of a metadata value with the given property type + * from the given data view. + * + * This will decode one component (e.g. one entry of a SCALAR array, + * or one component of a VEC2 element). + * + * This will apply normalization to the raw component value if the given + * class property is 'normalized'. + * + * @param {MetadataClassProperty} classProperty The class property + * @param {DataView} dataView The data view containing the raw metadata values + * @param {number} dataViewOffset The byte offset within the data view from + * which the component should be read + * @returns The metadata value component + */ +MetadataPicking.decodeRawMetadataValueComponent = function ( + classProperty, + dataView, + dataViewOffset +) { + const componentType = classProperty.componentType; + const component = MetadataPicking.decodeRawMetadataValue( + componentType, + dataView, + dataViewOffset + ); + if (classProperty.normalized) { + return MetadataComponentType.normalize(component, componentType); + } + return component; +}; + +/** + * Decodes one element of a metadata value with the given property type + * from the given data view. + * + * When the given class property is vector- or matrix typed, then the + * result will be an array, with a length that corresponds to the + * number of vector- or matrix components. + * + * Otherwise, it will be a single value. + * + * In any case, the return value will be the "raw" value, which does + * take into account normalization, but does NOT take into account + * any offset/scale, or default/noData value handling. + * + * @param {MetadataClassProperty} classProperty The metadata class property + * @param {DataView} dataView The data view containing the raw metadata values + * @param {number} elementIndex The index of the element. This is the index + * inside the array for array-typed properties, and 0 for non-array types. + * @returns The decoded metadata value element + */ +MetadataPicking.decodeRawMetadataValueElement = function ( + classProperty, + dataView, + elementIndex +) { + const componentType = classProperty.componentType; + const componentSizeInBytes = MetadataComponentType.getSizeInBytes( + componentType + ); + const type = classProperty.type; + const componentCount = MetadataType.getComponentCount(type); + const elementSizeInBytes = componentSizeInBytes * componentCount; + if (componentCount > 1) { + const result = Array(componentCount); + for (let i = 0; i < componentCount; i++) { + const offset = + elementIndex * elementSizeInBytes + i * componentSizeInBytes; + const component = MetadataPicking.decodeRawMetadataValueComponent( + classProperty, + dataView, + offset + ); + result[i] = component; + } + return result; + } + const offset = elementIndex * elementSizeInBytes; + const result = MetadataPicking.decodeRawMetadataValueComponent( + classProperty, + dataView, + offset + ); + return result; +}; + +/** + * Decode the given raw values into the raw (array-based) form of + * a metadata property value. + * + * (For decoding to types like `CartesianN`, the `decodeMetadataValues` + * function can be used) * * The given values are a `Uint8Array` containing the RGBA * values that have been read from the metadata picking @@ -65,41 +165,144 @@ MetadataPicking.decodeMetadataValue = function ( * the given class property, as encoded by the * `ModelDrawCommands` for metadata picking. * + * When the given class property is an array, then (it has to be + * a fixed-length array, and) the result will be an array with + * the respective length. + * + * When the given class property is vector- or matrix typed, + * then the result will be an array, with a length that corresponds + * to the number of vector- or matrix components. + * + * (The case that the property is an array of vector- or matrix + * elements is not supported on the side of the general metadata + * shader infrastructure, but handled here nevertheless. For such + * an input, the result would be an array of arrays, with each + * element representing one of the vectors or matrices). + * + * In any case, the return value will be the "raw" value, which does + * take into account normalization, but does NOT take into account + * any offset/scale, or default/noData value handling. + * * @param {MetadataClassProperty} classProperty The `MetadataClassProperty` - * @param {Uint8Array} rawValues The raw values - * @returns {number|bigint|undefined} The value + * @param {Uint8Array} rawPixelValues The raw values + * @returns {number|bigint|number[]|bigint[]|undefined} The value * * @private */ -MetadataPicking.decodeMetadataValues = function (classProperty, rawValues) { - const componentType = classProperty.componentType; +MetadataPicking.decodeRawMetadataValues = function ( + classProperty, + rawPixelValues +) { const dataView = new DataView( - rawValues.buffer, - rawValues.byteOffset, - rawValues.byteLength + rawPixelValues.buffer, + rawPixelValues.byteOffset, + rawPixelValues.byteLength ); if (classProperty.isArray) { const arrayLength = classProperty.arrayLength; const result = Array(arrayLength); for (let i = 0; i < arrayLength; i++) { - const element = MetadataPicking.decodeMetadataValue( - componentType, + const element = MetadataPicking.decodeRawMetadataValueElement( + classProperty, dataView, i ); - if (classProperty.normalized) { - result[i] = element / 255.0; - } else { - result[i] = element; - } + result[i] = element; } return result; } - const value = MetadataPicking.decodeMetadataValue(componentType, dataView, 0); - if (classProperty.normalized) { - return value / 255.0; + const result = MetadataPicking.decodeRawMetadataValueElement( + classProperty, + dataView, + 0 + ); + return result; +}; + +/** + * Converts the given type into an object representation where appropriate. + * + * When the given type is `SCALAR`, `STRING`, `BOOLEAN`, or `ENUM`, or + * when the given value is `undefined`, then the given value will be + * returned. + * + * Otherwise, for the `VECn/MATn` types, the given value is assumed to be + * a numeric array, and is converted into the matching `CartesianN/MatrixN` + * value. + * + * @param {string} type The `ClassProperty` type + * @param {number|bigint|number[]|bigint[]|undefined} value The input value + * @returns The object representation + */ +MetadataPicking.convertToObjectType = function (type, value) { + if (!defined(value)) { + return value; + } + if ( + type === "SCALAR" || + type === "STRING" || + type === "BOOLEAN" || + type === "ENUM" + ) { + return value; } + const numbers = value.map((n) => Number(n)); + switch (type) { + case "VEC2": + return Cartesian2.unpack(numbers, 0, new Cartesian2()); + case "VEC3": + return Cartesian3.unpack(numbers, 0, new Cartesian3()); + case "VEC4": + return Cartesian4.unpack(numbers, 0, new Cartesian3()); + case "MAT2": + return Matrix2.unpack(numbers, 0, new Matrix2()); + case "MAT3": + return Matrix3.unpack(numbers, 0, new Matrix3()); + case "MAT4": + return Matrix4.unpack(numbers, 0, new Matrix4()); + } + // Should never happen: return value; }; +/** + * Decode the given raw values into a metadata property value. + * + * This just converts the result of `decodeRawMetadataValues` + * from array-based types into object types like `CartesianN`. + * + * @param {MetadataClassProperty} classProperty The `MetadataClassProperty` + * @param {Uint8Array} rawPixelValues The raw values + * @returns {any} The value + * + * @private + */ +MetadataPicking.decodeMetadataValues = function ( + classProperty, + rawPixelValues +) { + const arrayBasedResult = MetadataPicking.decodeRawMetadataValues( + classProperty, + rawPixelValues + ); + if (classProperty.isArray) { + const arrayLength = classProperty.arrayLength; + const result = Array(arrayLength); + for (let i = 0; i < arrayLength; i++) { + const arrayBasedValue = arrayBasedResult[i]; + const objectBasedValue = MetadataPicking.convertToObjectType( + classProperty.type, + arrayBasedValue + ); + result[i] = objectBasedValue; + } + return result; + } + const result = MetadataPicking.convertToObjectType( + classProperty.type, + arrayBasedResult + ); + return result; +}; + export default Object.freeze(MetadataPicking); From 8a24da2022de402d7311463650378946838c7159 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 28 Aug 2024 17:25:21 +0200 Subject: [PATCH 24/63] Hacky spec experiments --- .../engine/Source/Renderer/DrawCommand.js | 6 +- .../engine/Source/Scene/DerivedCommand.js | 35 +- .../engine/Source/Scene/MetadataPicking.js | 4 +- .../engine/Source/Scene/PickFramebuffer.js | 22 +- packages/engine/Source/Scene/Scene.js | 1 + .../engine/Specs/Scene/Model/ModelSpec.js | 414 +++++++++++++++++- 6 files changed, 465 insertions(+), 17 deletions(-) diff --git a/packages/engine/Source/Renderer/DrawCommand.js b/packages/engine/Source/Renderer/DrawCommand.js index 3ced55cd4d89..3ea24f1afe28 100644 --- a/packages/engine/Source/Renderer/DrawCommand.js +++ b/packages/engine/Source/Renderer/DrawCommand.js @@ -529,7 +529,11 @@ Object.defineProperties(DrawCommand.prototype, { }, /** - * Whether metadata picking is allowed + * Whether metadata picking is allowed. + * + * This is essentially only set to `true` for draw commands that are + * part of a `ModelDrawCommand`, to check whether a derived command + * for metadata picking has to be created. * * @memberof DrawCommand.prototype * @type {boolean} diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 7d3b29b4288a..278bc2aff51c 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -268,11 +268,15 @@ DerivedCommand.createLogDepthCommand = function (command, context, result) { return result; }; -function getPickShaderProgram(context, shaderProgram, pickId) { +function getPickShaderProgram(context, shaderProgram, pickId, pickingMetadata) { + const keyword = `pick-pickingMetadata-${pickingMetadata}`; let shader = context.shaderCache.getDerivedShaderProgram( shaderProgram, - "pick" + keyword ); + //console.log("getPickShaderProgram with pickId ", pickId); + //console.log("getPickShaderProgram with pickingMetadata ", pickingMetadata); + //console.log("getPickShaderProgram with shader ", shader); if (!defined(shader)) { const attributeLocations = shaderProgram._attributeLocations; let fs = shaderProgram.fragmentShaderSource; @@ -286,7 +290,18 @@ function getPickShaderProgram(context, shaderProgram, pickId) { const outputColorVariable = hasFragData ? "out_FragData_0" : "out_FragColor"; - const newMain = `void main () + + // The output fragment should be discarded only when NOT + // trying to pick metadata + let newMain; + if (pickingMetadata === true) { + newMain = `void main () +{ + czm_non_pick_main(); + ${outputColorVariable} = ${pickId}; +} `; + } else { + newMain = `void main () { czm_non_pick_main(); if (${outputColorVariable}.a == 0.0) { @@ -294,6 +309,7 @@ function getPickShaderProgram(context, shaderProgram, pickId) { } ${outputColorVariable} = ${pickId}; } `; + } const newSources = new Array(length + 1); for (let i = 0; i < length; ++i) { @@ -306,7 +322,7 @@ function getPickShaderProgram(context, shaderProgram, pickId) { }); shader = context.shaderCache.createDerivedShaderProgram( shaderProgram, - "pick", + keyword, { vertexShaderSource: shaderProgram.vertexShaderSource, fragmentShaderSource: fs, @@ -358,11 +374,18 @@ DerivedCommand.createPickDerivedCommand = function ( result.pickCommand = DrawCommand.shallowClone(command, result.pickCommand); - if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) { + const frameState = scene._frameState; + const pickingMetadata = frameState.pickingMetadata; + if ( + !defined(shader) || + pickingMetadata || + result.shaderProgramId !== command.shaderProgram.id + ) { result.pickCommand.shaderProgram = getPickShaderProgram( context, command.shaderProgram, - command.pickId + command.pickId, + pickingMetadata ); result.pickCommand.renderState = getPickRenderState( scene, diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index 8a1d16b76d37..55a377555592 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -109,7 +109,7 @@ MetadataPicking.decodeRawMetadataValueComponent = function ( * * In any case, the return value will be the "raw" value, which does * take into account normalization, but does NOT take into account - * any offset/scale, or default/noData value handling. + * default/noData value handling. * * @param {MetadataClassProperty} classProperty The metadata class property * @param {DataView} dataView The data view containing the raw metadata values @@ -163,7 +163,7 @@ MetadataPicking.decodeRawMetadataValueElement = function ( * values that have been read from the metadata picking * frame buffer. They are assumed to contain the value for * the given class property, as encoded by the - * `ModelDrawCommands` for metadata picking. + * `MetadataPickingPipelineStage` for metadata picking. * * When the given class property is an array, then (it has to be * a fixed-length array, and) the result will be an array with diff --git a/packages/engine/Source/Scene/PickFramebuffer.js b/packages/engine/Source/Scene/PickFramebuffer.js index b439802f6a45..80abd4284578 100644 --- a/packages/engine/Source/Scene/PickFramebuffer.js +++ b/packages/engine/Source/Scene/PickFramebuffer.js @@ -49,7 +49,7 @@ PickFramebuffer.prototype.begin = function (screenSpaceRectangle, viewport) { return this._passState; }; -const colorScratch = new Color(); +const colorScratchForPickFramebuffer = new Color(); /** * Return the picked object rendered within a given rectangle. @@ -94,12 +94,20 @@ PickFramebuffer.prototype.end = function (screenSpaceRectangle) { ) { const index = 4 * ((halfHeight - y) * width + x + halfWidth); - colorScratch.red = Color.byteToFloat(pixels[index]); - colorScratch.green = Color.byteToFloat(pixels[index + 1]); - colorScratch.blue = Color.byteToFloat(pixels[index + 2]); - colorScratch.alpha = Color.byteToFloat(pixels[index + 3]); - - const object = context.getObjectByPickColor(colorScratch); + colorScratchForPickFramebuffer.red = Color.byteToFloat(pixels[index]); + colorScratchForPickFramebuffer.green = Color.byteToFloat( + pixels[index + 1] + ); + colorScratchForPickFramebuffer.blue = Color.byteToFloat( + pixels[index + 2] + ); + colorScratchForPickFramebuffer.alpha = Color.byteToFloat( + pixels[index + 3] + ); + + const object = context.getObjectByPickColor( + colorScratchForPickFramebuffer + ); if (defined(object)) { return object; } diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 2d4af7458db3..3209f661593b 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4331,6 +4331,7 @@ Scene.prototype.pickMetadata = function ( const pickedObject = this.pick(windowPosition); if (!defined(pickedObject)) { + console.log("pickedObject ", pickedObject); return undefined; } diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index f91aab5bd9a5..41d9fb42dbf0 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -52,9 +52,347 @@ import { WireframeIndexGenerator, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; +import createCanvas from "../../../../../Specs/createCanvas.js"; import pollToPromise from "../../../../../Specs/pollToPromise.js"; import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; +/** + * Creates the RGBA bytes of a property texture image for the specs. + * + * The result will be the RGBA bytes of a 16x16 texture, with + * the upper 9x9 pixels filled with the pattern that contains + * all combinations of (0, 127, 255) for the RGBA components. + * + * @returns The pixels + */ +function createExamplePropertyTexturePixelsRgbaBytes() { + const w = 16; + const h = 16; + const pixelsRgbaBytes = Array(w * h * 4); + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + let r = 0; + let g = 0; + let b = 0; + let a = 0; + + if (x > 8 || y > 8) { + continue; + } + + const lox = x % 3; + const hix = Math.floor(x / 3); + const loy = y % 3; + const hiy = Math.floor(y / 3); + + if ((lox & 0x1) !== 0) { + b = 127; + } + if ((lox & 0x2) !== 0) { + b = 255; + } + if ((hix & 0x1) !== 0) { + g = 127; + } + if ((hix & 0x2) !== 0) { + g = 255; + } + + if ((loy & 0x1) !== 0) { + r = 127; + } + if ((loy & 0x2) !== 0) { + r = 255; + } + if ((hiy & 0x1) !== 0) { + a = 127; + } + if ((hiy & 0x2) !== 0) { + a = 255; + } + + //r = 255; + //g = 255; + //b = 0; + //a = 255; + + const index = y * w + x; + pixelsRgbaBytes[index * 4 + 0] = r; + pixelsRgbaBytes[index * 4 + 1] = g; + pixelsRgbaBytes[index * 4 + 2] = b; + pixelsRgbaBytes[index * 4 + 3] = a; + } + } + return pixelsRgbaBytes; +} + +/** + * Creates a data URI for a PNG image with the given pixels. + * + * @param {number} sizeX The size in x-direction + * @param {number} sizeY The size in y-direction + * @param {number[]} pixelsRgbaBytes The RGBA pixel bytes + * @returns The PNG data URI + */ +function createPngDataUri(sizeX, sizeY, pixelsRgbaBytes) { + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + canvas.width = sizeX; + canvas.height = sizeY; + const dataArray = new Uint8ClampedArray(pixelsRgbaBytes); + const imageData = new ImageData(dataArray, sizeX, sizeY); + context.putImageData(imageData, 0, 0); + const dataUri = canvas.toDataURL("image/png"); + return dataUri; +} + +/** + * Create the PNG data URI for the property texture image used in the specs + * @returns The data URI + */ +function createExamplePropertyTexturePngDataUri() { + const pixelsRgbaBytes = createExamplePropertyTexturePixelsRgbaBytes(); + const dataUri = createPngDataUri(16, 16, pixelsRgbaBytes); + return dataUri; +} + +/** + * Creates a metadata schema for the metadata property texture picking specs. + * + * This is for the 'scalar' test case + * + * @returns The schema + */ +function createExampleSchemaScalar() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_SCALAR: { + name: "Example SCALAR property with UINT8 components", + type: "SCALAR", + componentType: "UINT8", + }, + example_normalized_UINT8_SCALAR: { + name: "Example SCALAR property with normalized UINT8 components", + type: "SCALAR", + componentType: "UINT8", + normalized: true, + }, + }, + }, + }, + }; + return schema; +} + +/** + * Creates the property texture properties definition for the + * metadata property texture picking specs. + * + * This is for the 'scalar' test case + * + * @returns The properties + */ +function createPropertyTexturePropertiesScalar() { + const properties = { + example_UINT8_SCALAR: { + index: 0, + texCoord: 0, + channels: [0], + }, + example_normalized_UINT8_SCALAR: { + index: 0, + texCoord: 0, + channels: [1], + }, + }; + return properties; +} + +/** + * Creates an embedded glTF asset with a property texture. + * + * This creates an assed that represents a unit square and uses + * the `EXT_structural_metadata` extension to assign a single + * property texture to this square. + * + * @param {object} schema The metadata schema + * @param {object} propertyTextureProperties The property texture properties + * @param {string} propertyTexturePngDataUri The PNG data URI of the property texture + * @returns The gltf + */ +function createEmbeddedGltfWithPropertyTexture( + schema, + propertyTextureProperties, + propertyTexturePngDataUri +) { + const result = { + extensions: { + EXT_structural_metadata: { + schema: schema, + propertyTextures: [ + { + class: "exampleClass", + properties: propertyTextureProperties, + }, + ], + }, + }, + extensionsUsed: ["EXT_structural_metadata"], + accessors: [ + { + bufferView: 0, + byteOffset: 0, + componentType: 5123, + count: 6, + type: "SCALAR", + max: [3], + min: [0], + }, + { + bufferView: 1, + byteOffset: 0, + componentType: 5126, + count: 4, + type: "VEC3", + max: [1.0, 1.0, 0.0], + min: [0.0, 0.0, 0.0], + }, + { + bufferView: 1, + byteOffset: 48, + componentType: 5126, + count: 4, + type: "VEC3", + max: [0.0, 0.0, 1.0], + min: [0.0, 0.0, 1.0], + }, + { + bufferView: 1, + byteOffset: 96, + componentType: 5126, + count: 4, + type: "VEC2", + max: [1.0, 1.0], + min: [0.0, 0.0], + }, + ], + asset: { + generator: "JglTF from https://github.com/javagl/JglTF", + version: "2.0", + }, + buffers: [ + { + uri: + "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAA", + byteLength: 156, + }, + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 12, + target: 34963, + }, + { + buffer: 0, + byteOffset: 12, + byteLength: 144, + byteStride: 12, + target: 34962, + }, + ], + images: [ + { + uri: propertyTexturePngDataUri, + mimeType: "image/png", + }, + ], + materials: [ + { + pbrMetallicRoughness: { + baseColorFactor: [1.0, 1.0, 1.0, 1.0], + baseColorTexture: { + index: 0, + texCoord: 0, + }, + metallicFactor: 0.0, + roughnessFactor: 1.0, + }, + alphaMode: "OPAQUE", + doubleSided: true, + }, + ], + meshes: [ + { + primitives: [ + { + extensions: { + EXT_structural_metadata: { + propertyTextures: [0], + }, + }, + attributes: { + POSITION: 1, + NORMAL: 2, + TEXCOORD_0: 3, + }, + indices: 0, + material: 0, + mode: 4, + }, + ], + }, + ], + nodes: [ + { + mesh: 0, + }, + ], + samplers: [ + { + magFilter: 9728, + minFilter: 9728, + }, + ], + scene: 0, + scenes: [ + { + nodes: [0], + }, + ], + textures: [ + { + sampler: 0, + source: 0, + }, + ], + }; + return result; +} + +function createPropertyTextureGltfScalar() { + const schema = createExampleSchemaScalar(); + const properties = createPropertyTexturePropertiesScalar(); + const imageDataUri = createExamplePropertyTexturePngDataUri(); + const gltf = createEmbeddedGltfWithPropertyTexture( + schema, + properties, + imageDataUri + ); + //*/ + console.log("SPEC GLTF:"); + console.log("-".repeat(80)); + console.log(JSON.stringify(gltf, null, 2)); + console.log("-".repeat(80)); + //*/ + return gltf; +} + describe( "Scene/Model/Model", function () { @@ -756,7 +1094,7 @@ describe( const resource = Resource.createIfNeeded( propertyTextureWithTextureTransformUrl ); - // The texture in the example model contains contains 8x8 pixels + // The texture in the example model contains 8x8 pixels // with increasing 'red' component values [0 to 64)*3, interpreted // as a normalized `UINT8` property. // It has a transform with an offset of [0.25, 0.25], and a scale @@ -887,6 +1225,80 @@ describe( }); }); + // eslint-disable-next-line no-restricted-globals + fit("picks metadata from a property texture", async function () { + // Create the gltf with the metadata that is about to be picked + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = 512; + const canvasSizeY = 512; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + // Create the model and wait until it is ready + const basePath = "SPEC_BASE_PATH"; + const model = await Model.fromGltfAsync({ + gltf: gltf, + basePath: basePath, + // This is important to make sure that the property + // texture is fully loaded when the model is rendered! + incrementallyLoadTextures: false, + }); + scene.primitives.add(model); + + await pollToPromise( + function () { + scene.renderForSpecs(); + return model.ready; + }, + { timeout: 10000 } + ); + + // Move the camera so that the unit square exactly + // fills the canvas + const camera = scene.camera; + const fov = CesiumMath.PI_OVER_THREE; + camera.frustum.fov = fov; + camera.frustum.near = 0.01; + camera.frustum.far = 100.0; + const distance = 1.0 / (2.0 * Math.tan(fov * 0.5)); + camera.position = new Cartesian3(distance, 0.5, 0.5); + camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.clone(Cartesian3.UNIT_Y); + + scene.initializeFrame(); + scene.render(defaultDate); + + console.log("position ", camera.position); + console.log("direction ", camera.direction); + console.log("up ", camera.up); + console.log("right ", camera.right); + + for (let x = 0; x < 16; x++) { + for (let y = 0; y < 16; y++) { + const screenX = x * 32 + 4; + const screenY = y * 32 + 4; + const screenPosition = new Cartesian2(screenX, screenY); + const actualMetadataValue = scene.pickMetadata( + screenPosition, + schemaId, + className, + propertyName + ); + console.log( + `actualMetadataValue at ${x} ${y} screen ${screenPosition} is `, + actualMetadataValue + ); + } + } + console.log("done"); + }); + it("renders model with morph targets", async function () { // This model gets clipped if log depth is disabled, so zoom out // the camera just a little From ebddfe1c39e6de8ca70cadd7f9f775be078277c2 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 29 Aug 2024 15:31:52 +0200 Subject: [PATCH 25/63] Revert some experiments. Cleanups. --- .../engine/Source/Scene/DerivedCommand.js | 35 +- .../engine/Specs/Scene/Model/ModelSpec.js | 414 +-------------- .../Specs/Scene/Model/pickMetadataSpec.js | 472 ++++++++++++++++++ 3 files changed, 479 insertions(+), 442 deletions(-) create mode 100644 packages/engine/Specs/Scene/Model/pickMetadataSpec.js diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 278bc2aff51c..7d3b29b4288a 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -268,15 +268,11 @@ DerivedCommand.createLogDepthCommand = function (command, context, result) { return result; }; -function getPickShaderProgram(context, shaderProgram, pickId, pickingMetadata) { - const keyword = `pick-pickingMetadata-${pickingMetadata}`; +function getPickShaderProgram(context, shaderProgram, pickId) { let shader = context.shaderCache.getDerivedShaderProgram( shaderProgram, - keyword + "pick" ); - //console.log("getPickShaderProgram with pickId ", pickId); - //console.log("getPickShaderProgram with pickingMetadata ", pickingMetadata); - //console.log("getPickShaderProgram with shader ", shader); if (!defined(shader)) { const attributeLocations = shaderProgram._attributeLocations; let fs = shaderProgram.fragmentShaderSource; @@ -290,18 +286,7 @@ function getPickShaderProgram(context, shaderProgram, pickId, pickingMetadata) { const outputColorVariable = hasFragData ? "out_FragData_0" : "out_FragColor"; - - // The output fragment should be discarded only when NOT - // trying to pick metadata - let newMain; - if (pickingMetadata === true) { - newMain = `void main () -{ - czm_non_pick_main(); - ${outputColorVariable} = ${pickId}; -} `; - } else { - newMain = `void main () + const newMain = `void main () { czm_non_pick_main(); if (${outputColorVariable}.a == 0.0) { @@ -309,7 +294,6 @@ function getPickShaderProgram(context, shaderProgram, pickId, pickingMetadata) { } ${outputColorVariable} = ${pickId}; } `; - } const newSources = new Array(length + 1); for (let i = 0; i < length; ++i) { @@ -322,7 +306,7 @@ function getPickShaderProgram(context, shaderProgram, pickId, pickingMetadata) { }); shader = context.shaderCache.createDerivedShaderProgram( shaderProgram, - keyword, + "pick", { vertexShaderSource: shaderProgram.vertexShaderSource, fragmentShaderSource: fs, @@ -374,18 +358,11 @@ DerivedCommand.createPickDerivedCommand = function ( result.pickCommand = DrawCommand.shallowClone(command, result.pickCommand); - const frameState = scene._frameState; - const pickingMetadata = frameState.pickingMetadata; - if ( - !defined(shader) || - pickingMetadata || - result.shaderProgramId !== command.shaderProgram.id - ) { + if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) { result.pickCommand.shaderProgram = getPickShaderProgram( context, command.shaderProgram, - command.pickId, - pickingMetadata + command.pickId ); result.pickCommand.renderState = getPickRenderState( scene, diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 41d9fb42dbf0..f91aab5bd9a5 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -52,347 +52,9 @@ import { WireframeIndexGenerator, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; -import createCanvas from "../../../../../Specs/createCanvas.js"; import pollToPromise from "../../../../../Specs/pollToPromise.js"; import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; -/** - * Creates the RGBA bytes of a property texture image for the specs. - * - * The result will be the RGBA bytes of a 16x16 texture, with - * the upper 9x9 pixels filled with the pattern that contains - * all combinations of (0, 127, 255) for the RGBA components. - * - * @returns The pixels - */ -function createExamplePropertyTexturePixelsRgbaBytes() { - const w = 16; - const h = 16; - const pixelsRgbaBytes = Array(w * h * 4); - for (let y = 0; y < h; y++) { - for (let x = 0; x < w; x++) { - let r = 0; - let g = 0; - let b = 0; - let a = 0; - - if (x > 8 || y > 8) { - continue; - } - - const lox = x % 3; - const hix = Math.floor(x / 3); - const loy = y % 3; - const hiy = Math.floor(y / 3); - - if ((lox & 0x1) !== 0) { - b = 127; - } - if ((lox & 0x2) !== 0) { - b = 255; - } - if ((hix & 0x1) !== 0) { - g = 127; - } - if ((hix & 0x2) !== 0) { - g = 255; - } - - if ((loy & 0x1) !== 0) { - r = 127; - } - if ((loy & 0x2) !== 0) { - r = 255; - } - if ((hiy & 0x1) !== 0) { - a = 127; - } - if ((hiy & 0x2) !== 0) { - a = 255; - } - - //r = 255; - //g = 255; - //b = 0; - //a = 255; - - const index = y * w + x; - pixelsRgbaBytes[index * 4 + 0] = r; - pixelsRgbaBytes[index * 4 + 1] = g; - pixelsRgbaBytes[index * 4 + 2] = b; - pixelsRgbaBytes[index * 4 + 3] = a; - } - } - return pixelsRgbaBytes; -} - -/** - * Creates a data URI for a PNG image with the given pixels. - * - * @param {number} sizeX The size in x-direction - * @param {number} sizeY The size in y-direction - * @param {number[]} pixelsRgbaBytes The RGBA pixel bytes - * @returns The PNG data URI - */ -function createPngDataUri(sizeX, sizeY, pixelsRgbaBytes) { - const canvas = document.createElement("canvas"); - const context = canvas.getContext("2d"); - canvas.width = sizeX; - canvas.height = sizeY; - const dataArray = new Uint8ClampedArray(pixelsRgbaBytes); - const imageData = new ImageData(dataArray, sizeX, sizeY); - context.putImageData(imageData, 0, 0); - const dataUri = canvas.toDataURL("image/png"); - return dataUri; -} - -/** - * Create the PNG data URI for the property texture image used in the specs - * @returns The data URI - */ -function createExamplePropertyTexturePngDataUri() { - const pixelsRgbaBytes = createExamplePropertyTexturePixelsRgbaBytes(); - const dataUri = createPngDataUri(16, 16, pixelsRgbaBytes); - return dataUri; -} - -/** - * Creates a metadata schema for the metadata property texture picking specs. - * - * This is for the 'scalar' test case - * - * @returns The schema - */ -function createExampleSchemaScalar() { - const schema = { - id: "ExampleSchema", - classes: { - exampleClass: { - name: "Example class", - properties: { - example_UINT8_SCALAR: { - name: "Example SCALAR property with UINT8 components", - type: "SCALAR", - componentType: "UINT8", - }, - example_normalized_UINT8_SCALAR: { - name: "Example SCALAR property with normalized UINT8 components", - type: "SCALAR", - componentType: "UINT8", - normalized: true, - }, - }, - }, - }, - }; - return schema; -} - -/** - * Creates the property texture properties definition for the - * metadata property texture picking specs. - * - * This is for the 'scalar' test case - * - * @returns The properties - */ -function createPropertyTexturePropertiesScalar() { - const properties = { - example_UINT8_SCALAR: { - index: 0, - texCoord: 0, - channels: [0], - }, - example_normalized_UINT8_SCALAR: { - index: 0, - texCoord: 0, - channels: [1], - }, - }; - return properties; -} - -/** - * Creates an embedded glTF asset with a property texture. - * - * This creates an assed that represents a unit square and uses - * the `EXT_structural_metadata` extension to assign a single - * property texture to this square. - * - * @param {object} schema The metadata schema - * @param {object} propertyTextureProperties The property texture properties - * @param {string} propertyTexturePngDataUri The PNG data URI of the property texture - * @returns The gltf - */ -function createEmbeddedGltfWithPropertyTexture( - schema, - propertyTextureProperties, - propertyTexturePngDataUri -) { - const result = { - extensions: { - EXT_structural_metadata: { - schema: schema, - propertyTextures: [ - { - class: "exampleClass", - properties: propertyTextureProperties, - }, - ], - }, - }, - extensionsUsed: ["EXT_structural_metadata"], - accessors: [ - { - bufferView: 0, - byteOffset: 0, - componentType: 5123, - count: 6, - type: "SCALAR", - max: [3], - min: [0], - }, - { - bufferView: 1, - byteOffset: 0, - componentType: 5126, - count: 4, - type: "VEC3", - max: [1.0, 1.0, 0.0], - min: [0.0, 0.0, 0.0], - }, - { - bufferView: 1, - byteOffset: 48, - componentType: 5126, - count: 4, - type: "VEC3", - max: [0.0, 0.0, 1.0], - min: [0.0, 0.0, 1.0], - }, - { - bufferView: 1, - byteOffset: 96, - componentType: 5126, - count: 4, - type: "VEC2", - max: [1.0, 1.0], - min: [0.0, 0.0], - }, - ], - asset: { - generator: "JglTF from https://github.com/javagl/JglTF", - version: "2.0", - }, - buffers: [ - { - uri: - "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAA", - byteLength: 156, - }, - ], - bufferViews: [ - { - buffer: 0, - byteOffset: 0, - byteLength: 12, - target: 34963, - }, - { - buffer: 0, - byteOffset: 12, - byteLength: 144, - byteStride: 12, - target: 34962, - }, - ], - images: [ - { - uri: propertyTexturePngDataUri, - mimeType: "image/png", - }, - ], - materials: [ - { - pbrMetallicRoughness: { - baseColorFactor: [1.0, 1.0, 1.0, 1.0], - baseColorTexture: { - index: 0, - texCoord: 0, - }, - metallicFactor: 0.0, - roughnessFactor: 1.0, - }, - alphaMode: "OPAQUE", - doubleSided: true, - }, - ], - meshes: [ - { - primitives: [ - { - extensions: { - EXT_structural_metadata: { - propertyTextures: [0], - }, - }, - attributes: { - POSITION: 1, - NORMAL: 2, - TEXCOORD_0: 3, - }, - indices: 0, - material: 0, - mode: 4, - }, - ], - }, - ], - nodes: [ - { - mesh: 0, - }, - ], - samplers: [ - { - magFilter: 9728, - minFilter: 9728, - }, - ], - scene: 0, - scenes: [ - { - nodes: [0], - }, - ], - textures: [ - { - sampler: 0, - source: 0, - }, - ], - }; - return result; -} - -function createPropertyTextureGltfScalar() { - const schema = createExampleSchemaScalar(); - const properties = createPropertyTexturePropertiesScalar(); - const imageDataUri = createExamplePropertyTexturePngDataUri(); - const gltf = createEmbeddedGltfWithPropertyTexture( - schema, - properties, - imageDataUri - ); - //*/ - console.log("SPEC GLTF:"); - console.log("-".repeat(80)); - console.log(JSON.stringify(gltf, null, 2)); - console.log("-".repeat(80)); - //*/ - return gltf; -} - describe( "Scene/Model/Model", function () { @@ -1094,7 +756,7 @@ describe( const resource = Resource.createIfNeeded( propertyTextureWithTextureTransformUrl ); - // The texture in the example model contains 8x8 pixels + // The texture in the example model contains contains 8x8 pixels // with increasing 'red' component values [0 to 64)*3, interpreted // as a normalized `UINT8` property. // It has a transform with an offset of [0.25, 0.25], and a scale @@ -1225,80 +887,6 @@ describe( }); }); - // eslint-disable-next-line no-restricted-globals - fit("picks metadata from a property texture", async function () { - // Create the gltf with the metadata that is about to be picked - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = 512; - const canvasSizeY = 512; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - // Create the model and wait until it is ready - const basePath = "SPEC_BASE_PATH"; - const model = await Model.fromGltfAsync({ - gltf: gltf, - basePath: basePath, - // This is important to make sure that the property - // texture is fully loaded when the model is rendered! - incrementallyLoadTextures: false, - }); - scene.primitives.add(model); - - await pollToPromise( - function () { - scene.renderForSpecs(); - return model.ready; - }, - { timeout: 10000 } - ); - - // Move the camera so that the unit square exactly - // fills the canvas - const camera = scene.camera; - const fov = CesiumMath.PI_OVER_THREE; - camera.frustum.fov = fov; - camera.frustum.near = 0.01; - camera.frustum.far = 100.0; - const distance = 1.0 / (2.0 * Math.tan(fov * 0.5)); - camera.position = new Cartesian3(distance, 0.5, 0.5); - camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); - camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); - camera.right = Cartesian3.clone(Cartesian3.UNIT_Y); - - scene.initializeFrame(); - scene.render(defaultDate); - - console.log("position ", camera.position); - console.log("direction ", camera.direction); - console.log("up ", camera.up); - console.log("right ", camera.right); - - for (let x = 0; x < 16; x++) { - for (let y = 0; y < 16; y++) { - const screenX = x * 32 + 4; - const screenY = y * 32 + 4; - const screenPosition = new Cartesian2(screenX, screenY); - const actualMetadataValue = scene.pickMetadata( - screenPosition, - schemaId, - className, - propertyName - ); - console.log( - `actualMetadataValue at ${x} ${y} screen ${screenPosition} is `, - actualMetadataValue - ); - } - } - console.log("done"); - }); - it("renders model with morph targets", async function () { // This model gets clipped if log depth is disabled, so zoom out // the camera just a little diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js new file mode 100644 index 000000000000..98fb943c1714 --- /dev/null +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -0,0 +1,472 @@ +import { + Cartesian2, + Cartesian3, + JulianDate, + Math as CesiumMath, + Model, + ResourceCache, +} from "../../../index.js"; +import createScene from "../../../../../Specs/createScene.js"; +import createCanvas from "../../../../../Specs/createCanvas.js"; +import pollToPromise from "../../../../../Specs/pollToPromise.js"; + +const propertyTextureSizeX = 16; +const propertyTextureSizeY = 16; + +/** + * Creates the RGBA bytes of a property texture image for the specs. + * + * The result will be the RGBA bytes of a 16x16 texture, with + * the upper 9x9 pixels filled with the pattern that contains + * all combinations of (0, 127, 255) for the RGBA components. + * + * @returns The pixels + */ +function createExamplePropertyTexturePixelsRgbaBytes() { + const sizeX = propertyTextureSizeX; + const sizeY = propertyTextureSizeY; + const pixelsRgbaBytes = Array(sizeX * sizeY * 4); + for (let y = 0; y < sizeY; y++) { + for (let x = 0; x < sizeX; x++) { + let r = 0; + let g = 0; + let b = 0; + let a = 0; + + if (x > 8 || y > 8) { + continue; + } + + const lox = x % 3; + const hix = Math.floor(x / 3); + const loy = y % 3; + const hiy = Math.floor(y / 3); + + if ((lox & 0x1) !== 0) { + b = 127; + } + if ((lox & 0x2) !== 0) { + b = 255; + } + if ((hix & 0x1) !== 0) { + g = 127; + } + if ((hix & 0x2) !== 0) { + g = 255; + } + + if ((loy & 0x1) !== 0) { + r = 127; + } + if ((loy & 0x2) !== 0) { + r = 255; + } + if ((hiy & 0x1) !== 0) { + a = 127; + } + if ((hiy & 0x2) !== 0) { + a = 255; + } + + //r = 255; + //g = 255; + //b = 0; + //a = 255; + + const index = y * sizeX + x; + pixelsRgbaBytes[index * 4 + 0] = r; + pixelsRgbaBytes[index * 4 + 1] = g; + pixelsRgbaBytes[index * 4 + 2] = b; + pixelsRgbaBytes[index * 4 + 3] = a; + } + } + return pixelsRgbaBytes; +} + +/** + * Creates a data URI for a PNG image with the given pixels. + * + * @param {number} sizeX The size in x-direction + * @param {number} sizeY The size in y-direction + * @param {number[]} pixelsRgbaBytes The RGBA pixel bytes + * @returns The PNG data URI + */ +function createPngDataUri(sizeX, sizeY, pixelsRgbaBytes) { + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + canvas.width = sizeX; + canvas.height = sizeY; + const dataArray = new Uint8ClampedArray(pixelsRgbaBytes); + const imageData = new ImageData(dataArray, sizeX, sizeY); + context.putImageData(imageData, 0, 0); + const dataUri = canvas.toDataURL("image/png"); + return dataUri; +} + +/** + * Create the PNG data URI for the property texture image used in the specs + * @returns The data URI + */ +function createExamplePropertyTexturePngDataUri() { + const pixelsRgbaBytes = createExamplePropertyTexturePixelsRgbaBytes(); + const dataUri = createPngDataUri( + propertyTextureSizeX, + propertyTextureSizeY, + pixelsRgbaBytes + ); + return dataUri; +} + +/** + * Creates a metadata schema for the metadata property texture picking specs. + * + * This is for the 'scalar' test case + * + * @returns The schema + */ +function createExampleSchemaScalar() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_SCALAR: { + name: "Example SCALAR property with UINT8 components", + type: "SCALAR", + componentType: "UINT8", + }, + example_normalized_UINT8_SCALAR: { + name: "Example SCALAR property with normalized UINT8 components", + type: "SCALAR", + componentType: "UINT8", + normalized: true, + }, + }, + }, + }, + }; + return schema; +} + +/** + * Creates the property texture properties definition for the + * metadata property texture picking specs. + * + * This is for the 'scalar' test case + * + * @returns The properties + */ +function createPropertyTexturePropertiesScalar() { + const properties = { + example_UINT8_SCALAR: { + index: 0, + texCoord: 0, + channels: [0], + }, + example_normalized_UINT8_SCALAR: { + index: 0, + texCoord: 0, + channels: [1], + }, + }; + return properties; +} + +/** + * Creates an embedded glTF asset with a property texture. + * + * This creates an assed that represents a unit square and uses + * the `EXT_structural_metadata` extension to assign a single + * property texture to this square. + * + * @param {object} schema The metadata schema + * @param {object} propertyTextureProperties The property texture properties + * @param {string} propertyTexturePngDataUri The PNG data URI of the property texture + * @returns The gltf + */ +function createEmbeddedGltfWithPropertyTexture( + schema, + propertyTextureProperties, + propertyTexturePngDataUri +) { + const result = { + extensions: { + EXT_structural_metadata: { + schema: schema, + propertyTextures: [ + { + class: "exampleClass", + properties: propertyTextureProperties, + }, + ], + }, + }, + extensionsUsed: ["EXT_structural_metadata"], + accessors: [ + { + bufferView: 0, + byteOffset: 0, + componentType: 5123, + count: 6, + type: "SCALAR", + max: [3], + min: [0], + }, + { + bufferView: 1, + byteOffset: 0, + componentType: 5126, + count: 4, + type: "VEC3", + max: [1.0, 1.0, 0.0], + min: [0.0, 0.0, 0.0], + }, + { + bufferView: 1, + byteOffset: 48, + componentType: 5126, + count: 4, + type: "VEC3", + max: [0.0, 0.0, 1.0], + min: [0.0, 0.0, 1.0], + }, + { + bufferView: 1, + byteOffset: 96, + componentType: 5126, + count: 4, + type: "VEC2", + max: [1.0, 1.0], + min: [0.0, 0.0], + }, + ], + asset: { + generator: "JglTF from https://github.com/javagl/JglTF", + version: "2.0", + }, + buffers: [ + { + uri: + "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAA", + byteLength: 156, + }, + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 12, + target: 34963, + }, + { + buffer: 0, + byteOffset: 12, + byteLength: 144, + byteStride: 12, + target: 34962, + }, + ], + images: [ + { + uri: propertyTexturePngDataUri, + mimeType: "image/png", + }, + ], + materials: [ + { + pbrMetallicRoughness: { + baseColorFactor: [1.0, 1.0, 1.0, 1.0], + baseColorTexture: { + index: 0, + texCoord: 0, + }, + metallicFactor: 0.0, + roughnessFactor: 1.0, + }, + alphaMode: "OPAQUE", + doubleSided: true, + }, + ], + meshes: [ + { + primitives: [ + { + extensions: { + EXT_structural_metadata: { + propertyTextures: [0], + }, + }, + attributes: { + POSITION: 1, + NORMAL: 2, + TEXCOORD_0: 3, + }, + indices: 0, + material: 0, + mode: 4, + }, + ], + }, + ], + nodes: [ + { + mesh: 0, + }, + ], + samplers: [ + { + magFilter: 9728, + minFilter: 9728, + }, + ], + scene: 0, + scenes: [ + { + nodes: [0], + }, + ], + textures: [ + { + sampler: 0, + source: 0, + }, + ], + }; + return result; +} + +function createPropertyTextureGltfScalar() { + const schema = createExampleSchemaScalar(); + const properties = createPropertyTexturePropertiesScalar(); + const imageDataUri = createExamplePropertyTexturePngDataUri(); + const gltf = createEmbeddedGltfWithPropertyTexture( + schema, + properties, + imageDataUri + ); + //*/ + console.log("SPEC GLTF:"); + console.log("-".repeat(80)); + console.log(JSON.stringify(gltf, null, 2)); + console.log("-".repeat(80)); + //*/ + return gltf; +} + +describe( + "Scene/pickMetadata", + function () { + const defaultDate = JulianDate.fromDate( + new Date("January 1, 2014 12:00:00 UTC") + ); + + let scene; + let scene2D; + let sceneCV; + + beforeAll(function () { + scene = createScene(); + + scene2D = createScene(); + scene2D.morphTo2D(0.0); + + sceneCV = createScene(); + sceneCV.morphToColumbusView(0.0); + }); + + afterAll(function () { + scene.destroyForSpecs(); + scene2D.destroyForSpecs(); + sceneCV.destroyForSpecs(); + }); + + afterEach(function () { + scene.primitives.removeAll(); + scene2D.primitives.removeAll(); + sceneCV.primitives.removeAll(); + scene.verticalExaggeration = 1.0; + ResourceCache.clearForSpecs(); + }); + + // eslint-disable-next-line no-restricted-globals + fit("picks metadata from a property texture", async function () { + // Create the gltf with the metadata that is about to be picked + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const scaling = 32; + const canvasSizeX = propertyTextureSizeX * scaling; + const canvasSizeY = propertyTextureSizeY * scaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + // Create the model and wait until it is ready + const basePath = "SPEC_BASE_PATH"; + const model = await Model.fromGltfAsync({ + gltf: gltf, + basePath: basePath, + // This is important to make sure that the property + // texture is fully loaded when the model is rendered! + incrementallyLoadTextures: false, + }); + scene.primitives.add(model); + + await pollToPromise( + function () { + scene.renderForSpecs(); + return model.ready; + }, + { timeout: 10000 } + ); + + scene.camera.flyToBoundingSphere(model.boundingSphere, { + duration: 0, + }); + + // Move the camera so that the unit square exactly + // fills the canvas + const camera = scene.camera; + const fov = CesiumMath.PI_OVER_THREE; + camera.frustum.fov = fov; + camera.frustum.near = 0.01; + camera.frustum.far = 100.0; + const distance = 1.0 / (2.0 * Math.tan(fov * 0.5)); + camera.position = new Cartesian3(distance, 0.5, 0.5); + camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.clone(Cartesian3.UNIT_Y); + + scene.initializeFrame(); + scene.render(defaultDate); + + console.log("position ", camera.position); + console.log("direction ", camera.direction); + console.log("up ", camera.up); + console.log("right ", camera.right); + + for (let x = 0; x < 16; x++) { + for (let y = 0; y < 16; y++) { + const screenX = Math.floor(x * scaling + scaling / 2); + const screenY = Math.floor(y * scaling + scaling / 2); + const screenPosition = new Cartesian2(screenX, screenY); + const actualMetadataValue = scene.pickMetadata( + screenPosition, + schemaId, + className, + propertyName + ); + console.log( + `actualMetadataValue at ${x} ${y} screen ${screenPosition} is `, + actualMetadataValue + ); + } + } + console.log("done"); + }); + }, + "WebGL" +); From c0eaf7ba23f4e9611ff5ad6937281273adb1cb95 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 29 Aug 2024 23:55:58 +0200 Subject: [PATCH 26/63] Remove debug log --- packages/engine/Source/Scene/Scene.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 3209f661593b..2d4af7458db3 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4331,7 +4331,6 @@ Scene.prototype.pickMetadata = function ( const pickedObject = this.pick(windowPosition); if (!defined(pickedObject)) { - console.log("pickedObject ", pickedObject); return undefined; } From e732550e3e884fdf21b9678eed2e2a3ed7af7f0c Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 29 Aug 2024 23:56:28 +0200 Subject: [PATCH 27/63] Cleanups for metadata picking specs --- .../Specs/Scene/Model/pickMetadataSpec.js | 387 ++++++++++++------ 1 file changed, 259 insertions(+), 128 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 98fb943c1714..1c3ec2212bf4 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -10,8 +10,9 @@ import createScene from "../../../../../Specs/createScene.js"; import createCanvas from "../../../../../Specs/createCanvas.js"; import pollToPromise from "../../../../../Specs/pollToPromise.js"; -const propertyTextureSizeX = 16; -const propertyTextureSizeY = 16; +const textureSizeX = 16; +const textureSizeY = 16; +const canvasScaling = 32; /** * Creates the RGBA bytes of a property texture image for the specs. @@ -23,8 +24,8 @@ const propertyTextureSizeY = 16; * @returns The pixels */ function createExamplePropertyTexturePixelsRgbaBytes() { - const sizeX = propertyTextureSizeX; - const sizeY = propertyTextureSizeY; + const sizeX = textureSizeX; + const sizeY = textureSizeY; const pixelsRgbaBytes = Array(sizeX * sizeY * 4); for (let y = 0; y < sizeY; y++) { for (let x = 0; x < sizeX; x++) { @@ -68,11 +69,6 @@ function createExamplePropertyTexturePixelsRgbaBytes() { a = 255; } - //r = 255; - //g = 255; - //b = 0; - //a = 255; - const index = y * sizeX + x; pixelsRgbaBytes[index * 4 + 0] = r; pixelsRgbaBytes[index * 4 + 1] = g; @@ -83,6 +79,27 @@ function createExamplePropertyTexturePixelsRgbaBytes() { return pixelsRgbaBytes; } +/** + * Creates the RGBA bytes of a base color texture for the specs. + * + * The result will be the the same as for createExamplePropertyTexturePixelsRgbaBytes, + * but with all alpha component bytes being 255. + * + * @returns The pixels + */ +function createExampleBaseColorTexturePixelsRgbaBytes() { + const sizeX = textureSizeX; + const sizeY = textureSizeY; + const pixelsRgbaBytes = createExamplePropertyTexturePixelsRgbaBytes(); + for (let y = 0; y < sizeY; y++) { + for (let x = 0; x < sizeX; x++) { + const index = y * sizeX + x; + pixelsRgbaBytes[index * 4 + 3] = 255; + } + } + return pixelsRgbaBytes; +} + /** * Creates a data URI for a PNG image with the given pixels. * @@ -103,76 +120,6 @@ function createPngDataUri(sizeX, sizeY, pixelsRgbaBytes) { return dataUri; } -/** - * Create the PNG data URI for the property texture image used in the specs - * @returns The data URI - */ -function createExamplePropertyTexturePngDataUri() { - const pixelsRgbaBytes = createExamplePropertyTexturePixelsRgbaBytes(); - const dataUri = createPngDataUri( - propertyTextureSizeX, - propertyTextureSizeY, - pixelsRgbaBytes - ); - return dataUri; -} - -/** - * Creates a metadata schema for the metadata property texture picking specs. - * - * This is for the 'scalar' test case - * - * @returns The schema - */ -function createExampleSchemaScalar() { - const schema = { - id: "ExampleSchema", - classes: { - exampleClass: { - name: "Example class", - properties: { - example_UINT8_SCALAR: { - name: "Example SCALAR property with UINT8 components", - type: "SCALAR", - componentType: "UINT8", - }, - example_normalized_UINT8_SCALAR: { - name: "Example SCALAR property with normalized UINT8 components", - type: "SCALAR", - componentType: "UINT8", - normalized: true, - }, - }, - }, - }, - }; - return schema; -} - -/** - * Creates the property texture properties definition for the - * metadata property texture picking specs. - * - * This is for the 'scalar' test case - * - * @returns The properties - */ -function createPropertyTexturePropertiesScalar() { - const properties = { - example_UINT8_SCALAR: { - index: 0, - texCoord: 0, - channels: [0], - }, - example_normalized_UINT8_SCALAR: { - index: 0, - texCoord: 0, - channels: [1], - }, - }; - return properties; -} - /** * Creates an embedded glTF asset with a property texture. * @@ -183,12 +130,14 @@ function createPropertyTexturePropertiesScalar() { * @param {object} schema The metadata schema * @param {object} propertyTextureProperties The property texture properties * @param {string} propertyTexturePngDataUri The PNG data URI of the property texture + * @param {string} baseColorTexturePngDataUri The PNG data URI of the base color texture * @returns The gltf */ function createEmbeddedGltfWithPropertyTexture( schema, propertyTextureProperties, - propertyTexturePngDataUri + propertyTexturePngDataUri, + baseColorTexturePngDataUri ) { const result = { extensions: { @@ -272,13 +221,17 @@ function createEmbeddedGltfWithPropertyTexture( uri: propertyTexturePngDataUri, mimeType: "image/png", }, + { + uri: baseColorTexturePngDataUri, + mimeType: "image/png", + }, ], materials: [ { pbrMetallicRoughness: { baseColorFactor: [1.0, 1.0, 1.0, 1.0], baseColorTexture: { - index: 0, + index: 1, texCoord: 0, }, metallicFactor: 0.0, @@ -331,19 +284,93 @@ function createEmbeddedGltfWithPropertyTexture( sampler: 0, source: 0, }, + { + sampler: 0, + source: 1, + }, ], }; return result; } +/** + * Creates a metadata schema for the metadata property texture picking specs. + * + * This is for the 'scalar' test case + * + * @returns The schema + */ +function createExampleSchemaScalar() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_SCALAR: { + name: "Example SCALAR property with UINT8 components", + type: "SCALAR", + componentType: "UINT8", + }, + example_normalized_UINT8_SCALAR: { + name: "Example SCALAR property with normalized UINT8 components", + type: "SCALAR", + componentType: "UINT8", + normalized: true, + }, + }, + }, + }, + }; + return schema; +} + +/** + * Creates the property texture properties definition for the + * metadata property texture picking specs. + * + * This is for the 'scalar' test case + * + * @returns The properties + */ +function createPropertyTexturePropertiesScalar() { + const properties = { + example_UINT8_SCALAR: { + index: 0, + texCoord: 0, + channels: [0], + }, + example_normalized_UINT8_SCALAR: { + index: 0, + texCoord: 0, + channels: [1], + }, + }; + return properties; +} + function createPropertyTextureGltfScalar() { const schema = createExampleSchemaScalar(); const properties = createPropertyTexturePropertiesScalar(); - const imageDataUri = createExamplePropertyTexturePngDataUri(); + + const propertyTexturePixelsRgbaBytes = createExamplePropertyTexturePixelsRgbaBytes(); + const propertyTextureDataUri = createPngDataUri( + textureSizeX, + textureSizeY, + propertyTexturePixelsRgbaBytes + ); + const baseColorTexturePixelsRgbaBytes = createExampleBaseColorTexturePixelsRgbaBytes(); + const baseColorTextureDataUri = createPngDataUri( + textureSizeX, + textureSizeY, + baseColorTexturePixelsRgbaBytes + ); + const gltf = createEmbeddedGltfWithPropertyTexture( schema, properties, - imageDataUri + propertyTextureDataUri, + baseColorTextureDataUri ); //*/ console.log("SPEC GLTF:"); @@ -354,6 +381,78 @@ function createPropertyTextureGltfScalar() { return gltf; } +/** + * Create a model from the given glTF, add it as a primitive + * to the given scene, and wait until it is fully loaded. + * + * @param {Scene} scene The scene + * @param {object} gltf The gltf + */ +async function loadAsModel(scene, gltf) { + const basePath = "SPEC_BASE_PATH"; + const model = await Model.fromGltfAsync({ + gltf: gltf, + basePath: basePath, + // This is important to make sure that the property + // texture is fully loaded when the model is rendered! + incrementallyLoadTextures: false, + }); + scene.primitives.add(model); + + await pollToPromise( + function () { + scene.renderForSpecs(); + return model.ready; + }, + { timeout: 10000 } + ); +} + +/** + * Move the camera to exactly look at the unit square along -X + * + * @param {Camera} camera + */ +function fitCameraToUnitSquare(camera) { + const fov = CesiumMath.PI_OVER_THREE; + camera.frustum.fov = fov; + camera.frustum.near = 0.01; + camera.frustum.far = 100.0; + const distance = 1.0 / (2.0 * Math.tan(fov * 0.5)); + camera.position = new Cartesian3(distance, 0.5, 0.5); + camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.clone(Cartesian3.UNIT_Y); +} + +/** + * Pick the specified metadata value from the screen that is contained in + * the property texture at the given coordinates. + * + * (This assumes that the property texture is on a unit square, and + * fitCameraToUnitSquare was called) + * + * @param {Scene} scene The scene + * @param {string|undefined} schemaId The schema ID + * @param {string} className The class name + * @param {string} propertyName The property name + * @param {number} x The x-coordinate in the texture + * @param {number} y The y-coordinate in the texture + * @returns The metadata value + */ +function pickMetadataAt(scene, schemaId, className, propertyName, x, y) { + const screenX = Math.floor(x * canvasScaling + canvasScaling / 2); + const screenY = Math.floor(y * canvasScaling + canvasScaling / 2); + const screenPosition = new Cartesian2(screenX, screenY); + const metadataValue = scene.pickMetadata( + screenPosition, + schemaId, + className, + propertyName + ); + return metadataValue; +} + describe( "Scene/pickMetadata", function () { @@ -397,70 +496,102 @@ describe( const propertyName = "example_UINT8_SCALAR"; const gltf = createPropertyTextureGltfScalar(); - const scaling = 32; - const canvasSizeX = propertyTextureSizeX * scaling; - const canvasSizeY = propertyTextureSizeY * scaling; + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), }); - // Create the model and wait until it is ready - const basePath = "SPEC_BASE_PATH"; - const model = await Model.fromGltfAsync({ - gltf: gltf, - basePath: basePath, - // This is important to make sure that the property - // texture is fully loaded when the model is rendered! - incrementallyLoadTextures: false, - }); - scene.primitives.add(model); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); - await pollToPromise( - function () { - scene.renderForSpecs(); - return model.ready; - }, - { timeout: 10000 } + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 3 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 4 ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 5 + ); + const expectedMetadataValue0 = 0; + const expectedMetadataValue1 = 127; + const expectedMetadataValue2 = 255; + + expect( + CesiumMath.equalsEpsilon( + actualMetadataValue0, + expectedMetadataValue0, + 1 + ) + ).toBe(true); + expect( + CesiumMath.equalsEpsilon( + actualMetadataValue1, + expectedMetadataValue1, + 1 + ) + ).toBe(true); + expect( + CesiumMath.equalsEpsilon( + actualMetadataValue2, + expectedMetadataValue2, + 1 + ) + ).toBe(true); + }); - scene.camera.flyToBoundingSphere(model.boundingSphere, { - duration: 0, + // eslint-disable-next-line no-restricted-globals + fit("picks metadata from a property texture quarry", async function () { + // Create the gltf with the metadata that is about to be picked + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - // Move the camera so that the unit square exactly - // fills the canvas - const camera = scene.camera; - const fov = CesiumMath.PI_OVER_THREE; - camera.frustum.fov = fov; - camera.frustum.near = 0.01; - camera.frustum.far = 100.0; - const distance = 1.0 / (2.0 * Math.tan(fov * 0.5)); - camera.position = new Cartesian3(distance, 0.5, 0.5); - camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); - camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); - camera.right = Cartesian3.clone(Cartesian3.UNIT_Y); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); scene.initializeFrame(); scene.render(defaultDate); - console.log("position ", camera.position); - console.log("direction ", camera.direction); - console.log("up ", camera.up); - console.log("right ", camera.right); - for (let x = 0; x < 16; x++) { for (let y = 0; y < 16; y++) { - const screenX = Math.floor(x * scaling + scaling / 2); - const screenY = Math.floor(y * scaling + scaling / 2); - const screenPosition = new Cartesian2(screenX, screenY); - const actualMetadataValue = scene.pickMetadata( - screenPosition, + const actualMetadataValue = pickMetadataAt( + scene, schemaId, className, - propertyName + propertyName, + x, + y ); + console.log( - `actualMetadataValue at ${x} ${y} screen ${screenPosition} is `, + `actualMetadataValue at ${x} ${y} is `, actualMetadataValue ); } From 775113da6d64fd3e26edd7921969829e6c706376 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 31 Aug 2024 01:19:11 +0200 Subject: [PATCH 28/63] Basic specs and debugging --- packages/engine/Source/Scene/Picking.js | 4 + .../Specs/Scene/Model/pickMetadataSpec.js | 211 +++++++++++++++++- 2 files changed, 209 insertions(+), 6 deletions(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index ade2b8a9e8eb..55c9902e9c4b 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -501,6 +501,10 @@ Picking.prototype.pickMetadata = function ( ); context.endFrame(); + // XXX_DEBUG + console.log("rawMetadataPixel ", rawMetadataPixel); + // XXX_DEBUG + frameState.pickingMetadata = false; const metadataValue = MetadataPicking.decodeMetadataValues( diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 1c3ec2212bf4..19eff349b5a6 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-globals */ import { Cartesian2, Cartesian3, @@ -34,6 +35,7 @@ function createExamplePropertyTexturePixelsRgbaBytes() { let b = 0; let a = 0; + /* if (x > 8 || y > 8) { continue; } @@ -68,6 +70,20 @@ function createExamplePropertyTexturePixelsRgbaBytes() { if ((hiy & 0x2) !== 0) { a = 255; } + */ + + r = 255; + g = 255; + b = 255; + if (y >= 4) { + a = 64; + } + if (y >= 8) { + a = 128; + } + if (y >= 12) { + a = 255; + } const index = y * sizeX + x; pixelsRgbaBytes[index * 4 + 0] = r; @@ -488,9 +504,97 @@ describe( ResourceCache.clearForSpecs(); }); - // eslint-disable-next-line no-restricted-globals - fit("picks metadata from a property texture", async function () { - // Create the gltf with the metadata that is about to be picked + fit("throws without windowPosition", async function () { + const windowPosition = undefined; // For spec + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + }); + + fit("throws without className", async function () { + const windowPosition = new Cartesian2(); + const schemaId = undefined; + const className = undefined; // For spec + const propertyName = "example_UINT8_SCALAR"; + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + }); + + fit("throws without propertyName", async function () { + const windowPosition = new Cartesian2(); + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = undefined; // For spec + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + }); + + fit("returns undefined for class name that does not exist", async function () { + const schemaId = undefined; + const className = "exampleClass_THAT_DOES_NOT_EXIST"; // For spec + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const actualMetadataValue = scene.pickMetadata( + windowPosition, + schemaId, + className, + propertyName + ); + expect(actualMetadataValue).toBeUndefined(); + }); + + fit("returns undefined when there is no object with metadata", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const actualMetadataValue = scene.pickMetadata( + windowPosition, + schemaId, + className, + propertyName + ); + expect(actualMetadataValue).toBeUndefined(); + }); + + fit("picks UINT8 SCALAR from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_SCALAR"; @@ -540,23 +644,118 @@ describe( CesiumMath.equalsEpsilon( actualMetadataValue0, expectedMetadataValue0, - 1 + 0.0, + 1.0 + ) + ).toBe(true); + expect( + CesiumMath.equalsEpsilon( + actualMetadataValue1, + expectedMetadataValue1, + 0.0, + 1.0 + ) + ).toBe(true); + expect( + CesiumMath.equalsEpsilon( + actualMetadataValue2, + expectedMetadataValue2, + 0.0, + 1.0 + ) + ).toBe(true); + }); + + // eslint-disable-next-line no-restricted-globals + fit("picks normalized UINT8 SCALAR from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_normalized_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 3 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 3, + 3 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 6, + 3 + ); + const expectedMetadataValue0 = 0.0; + const expectedMetadataValue1 = 0.5; + const expectedMetadataValue2 = 1.0; + + expect( + CesiumMath.equalsEpsilon( + actualMetadataValue0, + expectedMetadataValue0, + 0.0, + 0.01 ) ).toBe(true); expect( CesiumMath.equalsEpsilon( actualMetadataValue1, expectedMetadataValue1, - 1 + 0.0, + 0.01 ) ).toBe(true); expect( CesiumMath.equalsEpsilon( actualMetadataValue2, expectedMetadataValue2, - 1 + 0.0, + 0.01 ) ).toBe(true); + + for (let x = 0; x < 16; x++) { + for (let y = 0; y < 16; y++) { + const actualMetadataValue = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + x, + y + ); + + console.log( + `actualMetadataValue at ${x} ${y} is `, + actualMetadataValue + ); + } + } + console.log("done"); }); // eslint-disable-next-line no-restricted-globals From 2056da08d93099984a71857b95d8b9c614bbeb8c Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 31 Aug 2024 15:43:42 +0200 Subject: [PATCH 29/63] Extend metadata picking specs --- .../Specs/Scene/Model/pickMetadataSpec.js | 774 +++++++++++++++--- 1 file changed, 648 insertions(+), 126 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 19eff349b5a6..10fca5f1087a 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -6,15 +6,29 @@ import { Math as CesiumMath, Model, ResourceCache, + Cartesian4, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; import createCanvas from "../../../../../Specs/createCanvas.js"; import pollToPromise from "../../../../../Specs/pollToPromise.js"; +// The size of the property texture const textureSizeX = 16; const textureSizeY = 16; + +// A scaling factor (to be applied to the texture size) for +// determining the size of the ("debug") canvas that shows +// the scene where the picking takes place const canvasScaling = 32; +// The 'toEqualEpsilon' matcher (which is which is defined +// in `Specs/addDefaultMatchers.js`, by the way...) uses +// the epsilon as a relative epsilon, and there is no way +// to pass in an absolute epsilon. For comparing the elements +// of a Cartesian2 that stores UINT8 values, an absolute +// epsilon of 1.0 would be handy. But... here we go: +const propertyValueEpsilon = 0.01; + /** * Creates the RGBA bytes of a property texture image for the specs. * @@ -24,7 +38,7 @@ const canvasScaling = 32; * * @returns The pixels */ -function createExamplePropertyTexturePixelsRgbaBytes() { +function createSpecPropertyTexturePixelsRgbaBytes() { const sizeX = textureSizeX; const sizeY = textureSizeY; const pixelsRgbaBytes = Array(sizeX * sizeY * 4); @@ -35,7 +49,6 @@ function createExamplePropertyTexturePixelsRgbaBytes() { let b = 0; let a = 0; - /* if (x > 8 || y > 8) { continue; } @@ -46,10 +59,10 @@ function createExamplePropertyTexturePixelsRgbaBytes() { const hiy = Math.floor(y / 3); if ((lox & 0x1) !== 0) { - b = 127; + r = 127; } if ((lox & 0x2) !== 0) { - b = 255; + r = 255; } if ((hix & 0x1) !== 0) { g = 127; @@ -59,10 +72,10 @@ function createExamplePropertyTexturePixelsRgbaBytes() { } if ((loy & 0x1) !== 0) { - r = 127; + b = 127; } if ((loy & 0x2) !== 0) { - r = 255; + b = 255; } if ((hiy & 0x1) !== 0) { a = 127; @@ -70,20 +83,14 @@ function createExamplePropertyTexturePixelsRgbaBytes() { if ((hiy & 0x2) !== 0) { a = 255; } - */ - r = 255; - g = 255; - b = 255; - if (y >= 4) { - a = 64; - } - if (y >= 8) { - a = 128; - } - if (y >= 12) { - a = 255; - } + // XXX_ALPHA NOTE: There seems to be a bug in the metadata handling + // that causes metadata values in the shader to be affected by + // the alpha value in the property texture, even when they are + // read from other channels. + // Fix the alpha value to 255 here: + a = 255; + // Also see the places marked with XXX_ALPHA below const index = y * sizeX + x; pixelsRgbaBytes[index * 4 + 0] = r; @@ -98,15 +105,15 @@ function createExamplePropertyTexturePixelsRgbaBytes() { /** * Creates the RGBA bytes of a base color texture for the specs. * - * The result will be the the same as for createExamplePropertyTexturePixelsRgbaBytes, + * The result will be the the same as for createSpecPropertyTexturePixelsRgbaBytes, * but with all alpha component bytes being 255. * * @returns The pixels */ -function createExampleBaseColorTexturePixelsRgbaBytes() { +function createSpecBaseColorTexturePixelsRgbaBytes() { const sizeX = textureSizeX; const sizeY = textureSizeY; - const pixelsRgbaBytes = createExamplePropertyTexturePixelsRgbaBytes(); + const pixelsRgbaBytes = createSpecPropertyTexturePixelsRgbaBytes(); for (let y = 0; y < sizeY; y++) { for (let x = 0; x < sizeX; x++) { const index = y * sizeX + x; @@ -309,6 +316,45 @@ function createEmbeddedGltfWithPropertyTexture( return result; } +/** + * Create an embedded glTF with the default property texture, + * and the given schema and property texture properties. + * + * @param {object} schema The JSON form of the metadata schema + * @param {object[]} properties The JSON form of the property texture properties + * @returns The glTF + */ +function createPropertyTextureGltf(schema, properties) { + const propertyTexturePixelsRgbaBytes = createSpecPropertyTexturePixelsRgbaBytes(); + const propertyTextureDataUri = createPngDataUri( + textureSizeX, + textureSizeY, + propertyTexturePixelsRgbaBytes + ); + const baseColorTexturePixelsRgbaBytes = createSpecBaseColorTexturePixelsRgbaBytes(); + const baseColorTextureDataUri = createPngDataUri( + textureSizeX, + textureSizeY, + baseColorTexturePixelsRgbaBytes + ); + + const gltf = createEmbeddedGltfWithPropertyTexture( + schema, + properties, + propertyTextureDataUri, + baseColorTextureDataUri + ); + + //*/ + // Copy-and-paste this into a file to have the actual glTF: + console.log("SPEC GLTF:"); + console.log("-".repeat(80)); + console.log(JSON.stringify(gltf, null, 2)); + console.log("-".repeat(80)); + //*/ + return gltf; +} + /** * Creates a metadata schema for the metadata property texture picking specs. * @@ -316,7 +362,7 @@ function createEmbeddedGltfWithPropertyTexture( * * @returns The schema */ -function createExampleSchemaScalar() { +function createSpecSchemaScalar() { const schema = { id: "ExampleSchema", classes: { @@ -365,36 +411,253 @@ function createPropertyTexturePropertiesScalar() { return properties; } +/** + * Creates the glTF for the 'scalar' test case + * + * @returns The glTF + */ function createPropertyTextureGltfScalar() { - const schema = createExampleSchemaScalar(); + const schema = createSpecSchemaScalar(); const properties = createPropertyTexturePropertiesScalar(); + return createPropertyTextureGltf(schema, properties); +} - const propertyTexturePixelsRgbaBytes = createExamplePropertyTexturePixelsRgbaBytes(); - const propertyTextureDataUri = createPngDataUri( - textureSizeX, - textureSizeY, - propertyTexturePixelsRgbaBytes - ); - const baseColorTexturePixelsRgbaBytes = createExampleBaseColorTexturePixelsRgbaBytes(); - const baseColorTextureDataUri = createPngDataUri( - textureSizeX, - textureSizeY, - baseColorTexturePixelsRgbaBytes - ); +/** + * Creates a metadata schema for the metadata property texture picking specs. + * + * This is for the 'scalar array' test case + * + * @returns The schema + */ +function createSpecSchemaScalarArray() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_fixed_length_UINT8_SCALAR_array: { + name: + "Example fixed-length SCALAR array property with UINT8 components", + type: "SCALAR", + componentType: "UINT8", + array: true, + count: 3, + }, + }, + }, + }, + }; + return schema; +} - const gltf = createEmbeddedGltfWithPropertyTexture( - schema, - properties, - propertyTextureDataUri, - baseColorTextureDataUri - ); - //*/ - console.log("SPEC GLTF:"); - console.log("-".repeat(80)); - console.log(JSON.stringify(gltf, null, 2)); - console.log("-".repeat(80)); - //*/ - return gltf; +/** + * Creates the property texture properties definition for the + * metadata property texture picking specs. + * + * This is for the 'scalar array' test case + * + * @returns The properties + */ +function createPropertyTexturePropertiesScalarArray() { + const properties = { + example_fixed_length_UINT8_SCALAR_array: { + index: 0, + texCoord: 0, + channels: [0, 1, 2], + }, + }; + return properties; +} + +/** + * Creates the glTF for the 'scalar array' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfScalarArray() { + const schema = createSpecSchemaScalarArray(); + const properties = createPropertyTexturePropertiesScalarArray(); + return createPropertyTextureGltf(schema, properties); +} + +/** + * Creates a metadata schema for the metadata property texture picking specs. + * + * This is for the 'vec2' test case + * + * @returns The schema + */ +function createSpecSchemaVec2() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_VEC2: { + name: "Example VEC2 property with UINT8 components", + type: "VEC2", + componentType: "UINT8", + }, + example_normalized_UINT8_VEC2: { + name: "Example VEC2 property with normalized UINT8 components", + type: "VEC2", + componentType: "UINT8", + normalized: true, + }, + }, + }, + }, + }; + return schema; +} + +/** + * Creates the property texture properties definition for the + * metadata property texture picking specs. + * + * This is for the 'vec2' test case + * + * @returns The properties + */ +function createPropertyTexturePropertiesVec2() { + const properties = { + example_UINT8_VEC2: { + index: 0, + texCoord: 0, + channels: [0, 1], + }, + example_normalized_UINT8_VEC2: { + index: 0, + texCoord: 0, + channels: [2, 3], + }, + }; + return properties; +} + +/** + * Creates the glTF for the 'vec2' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfVec2() { + const schema = createSpecSchemaVec2(); + const properties = createPropertyTexturePropertiesVec2(); + return createPropertyTextureGltf(schema, properties); +} + +/** + * Creates a metadata schema for the metadata property texture picking specs. + * + * This is for the 'vec3' test case + * + * @returns The schema + */ +function createSpecSchemaVec3() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_VEC3: { + name: "Example VEC3 property with UINT8 components", + type: "VEC3", + componentType: "UINT8", + }, + }, + }, + }, + }; + return schema; +} + +/** + * Creates the property texture properties definition for the + * metadata property texture picking specs. + * + * This is for the 'vec3' test case + * + * @returns The properties + */ +function createPropertyTexturePropertiesVec3() { + const properties = { + example_UINT8_VEC3: { + index: 0, + texCoord: 0, + channels: [0, 1, 2], + }, + }; + return properties; +} + +/** + * Creates the glTF for the 'vec3' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfVec3() { + const schema = createSpecSchemaVec3(); + const properties = createPropertyTexturePropertiesVec3(); + return createPropertyTextureGltf(schema, properties); +} + +/** + * Creates a metadata schema for the metadata property texture picking specs. + * + * This is for the 'vec4' test case + * + * @returns The schema + */ +function createSpecSchemaVec4() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_VEC4: { + name: "Example VEC4 property with UINT8 components", + type: "VEC4", + componentType: "UINT8", + }, + }, + }, + }, + }; + return schema; +} + +/** + * Creates the property texture properties definition for the + * metadata property texture picking specs. + * + * This is for the 'vec4' test case + * + * @returns The properties + */ +function createPropertyTexturePropertiesVec4() { + const properties = { + example_UINT8_VEC4: { + index: 0, + texCoord: 0, + channels: [0, 1, 2, 3], + }, + }; + return properties; +} + +/** + * Creates the glTF for the 'vec4' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfVec4() { + const schema = createSpecSchemaVec4(); + const properties = createPropertyTexturePropertiesVec4(); + return createPropertyTextureGltf(schema, properties); } /** @@ -618,55 +881,42 @@ describe( className, propertyName, 0, - 3 + 0 ); const actualMetadataValue1 = pickMetadataAt( scene, schemaId, className, propertyName, - 0, - 4 + 1, + 0 ); const actualMetadataValue2 = pickMetadataAt( scene, schemaId, className, propertyName, - 0, - 5 + 2, + 0 ); const expectedMetadataValue0 = 0; const expectedMetadataValue1 = 127; const expectedMetadataValue2 = 255; - expect( - CesiumMath.equalsEpsilon( - actualMetadataValue0, - expectedMetadataValue0, - 0.0, - 1.0 - ) - ).toBe(true); - expect( - CesiumMath.equalsEpsilon( - actualMetadataValue1, - expectedMetadataValue1, - 0.0, - 1.0 - ) - ).toBe(true); - expect( - CesiumMath.equalsEpsilon( - actualMetadataValue2, - expectedMetadataValue2, - 0.0, - 1.0 - ) - ).toBe(true); + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); }); - // eslint-disable-next-line no-restricted-globals fit("picks normalized UINT8 SCALAR from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; @@ -691,7 +941,7 @@ describe( className, propertyName, 0, - 3 + 0 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -699,7 +949,7 @@ describe( className, propertyName, 3, - 3 + 0 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -707,60 +957,332 @@ describe( className, propertyName, 6, - 3 + 0 ); const expectedMetadataValue0 = 0.0; const expectedMetadataValue1 = 0.5; const expectedMetadataValue2 = 1.0; - expect( - CesiumMath.equalsEpsilon( - actualMetadataValue0, - expectedMetadataValue0, - 0.0, - 0.01 - ) - ).toBe(true); - expect( - CesiumMath.equalsEpsilon( - actualMetadataValue1, - expectedMetadataValue1, - 0.0, - 0.01 - ) - ).toBe(true); - expect( - CesiumMath.equalsEpsilon( - actualMetadataValue2, - expectedMetadataValue2, - 0.0, - 0.01 - ) - ).toBe(true); + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); - for (let x = 0; x < 16; x++) { - for (let y = 0; y < 16; y++) { - const actualMetadataValue = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - x, - y - ); + fit("picks fixed length UINT8 SCALAR array from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_fixed_length_UINT8_SCALAR_array"; + const gltf = createPropertyTextureGltfScalarArray(); - console.log( - `actualMetadataValue at ${x} ${y} is `, - actualMetadataValue - ); - } - } - console.log("done"); + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = [0, 0, 0]; + const expectedMetadataValue1 = [127, 0, 127]; + const expectedMetadataValue2 = [255, 0, 255]; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + fit("picks UINT8 VEC2 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC2"; + const gltf = createPropertyTextureGltfVec2(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = new Cartesian2(0, 0); + const expectedMetadataValue1 = new Cartesian2(127, 0); + const expectedMetadataValue2 = new Cartesian2(255, 0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + fit("picks normalized UINT8 VEC2 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_normalized_UINT8_VEC2"; + const gltf = createPropertyTextureGltfVec2(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + + // XXX_ALPHA The second component should be 0.0!!! + const expectedMetadataValue0 = new Cartesian2(0.0, 1.0); + const expectedMetadataValue1 = new Cartesian2(0.5, 1.0); + const expectedMetadataValue2 = new Cartesian2(1.0, 1.0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + fit("picks UINT8 VEC3 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC3"; + const gltf = createPropertyTextureGltfVec3(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = new Cartesian3(0, 0, 0); + const expectedMetadataValue1 = new Cartesian3(127, 0, 127); + const expectedMetadataValue2 = new Cartesian3(255, 0, 255); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + fit("picks UINT8 VEC4 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC4"; + const gltf = createPropertyTextureGltfVec4(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + + // XXX_ALPHA: The last component should be 0! + const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 255); + const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 255); + const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 255); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); }); - // eslint-disable-next-line no-restricted-globals - fit("picks metadata from a property texture quarry", async function () { - // Create the gltf with the metadata that is about to be picked + // XXX For debugging: + fit("picks metadata from a property texture quarry - TO BE REMOVED", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_SCALAR"; From 0e6b7f6d08eac7acb26fd2e775ace57189c7fc94 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 31 Aug 2024 15:45:29 +0200 Subject: [PATCH 30/63] Remove debug output --- packages/engine/Source/Scene/Picking.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 55c9902e9c4b..ade2b8a9e8eb 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -501,10 +501,6 @@ Picking.prototype.pickMetadata = function ( ); context.endFrame(); - // XXX_DEBUG - console.log("rawMetadataPixel ", rawMetadataPixel); - // XXX_DEBUG - frameState.pickingMetadata = false; const metadataValue = MetadataPicking.decodeMetadataValues( From 45727e85b9035eb698af1f685e5e2ff91dfcbb24 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 7 Sep 2024 20:28:18 +0200 Subject: [PATCH 31/63] Remove inlined image creation --- .../Specs/Scene/Model/pickMetadataSpec.js | 177 +++--------------- 1 file changed, 22 insertions(+), 155 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 10fca5f1087a..841303e5f56d 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -1,4 +1,3 @@ -/* eslint-disable no-restricted-globals */ import { Cartesian2, Cartesian3, @@ -29,120 +28,6 @@ const canvasScaling = 32; // epsilon of 1.0 would be handy. But... here we go: const propertyValueEpsilon = 0.01; -/** - * Creates the RGBA bytes of a property texture image for the specs. - * - * The result will be the RGBA bytes of a 16x16 texture, with - * the upper 9x9 pixels filled with the pattern that contains - * all combinations of (0, 127, 255) for the RGBA components. - * - * @returns The pixels - */ -function createSpecPropertyTexturePixelsRgbaBytes() { - const sizeX = textureSizeX; - const sizeY = textureSizeY; - const pixelsRgbaBytes = Array(sizeX * sizeY * 4); - for (let y = 0; y < sizeY; y++) { - for (let x = 0; x < sizeX; x++) { - let r = 0; - let g = 0; - let b = 0; - let a = 0; - - if (x > 8 || y > 8) { - continue; - } - - const lox = x % 3; - const hix = Math.floor(x / 3); - const loy = y % 3; - const hiy = Math.floor(y / 3); - - if ((lox & 0x1) !== 0) { - r = 127; - } - if ((lox & 0x2) !== 0) { - r = 255; - } - if ((hix & 0x1) !== 0) { - g = 127; - } - if ((hix & 0x2) !== 0) { - g = 255; - } - - if ((loy & 0x1) !== 0) { - b = 127; - } - if ((loy & 0x2) !== 0) { - b = 255; - } - if ((hiy & 0x1) !== 0) { - a = 127; - } - if ((hiy & 0x2) !== 0) { - a = 255; - } - - // XXX_ALPHA NOTE: There seems to be a bug in the metadata handling - // that causes metadata values in the shader to be affected by - // the alpha value in the property texture, even when they are - // read from other channels. - // Fix the alpha value to 255 here: - a = 255; - // Also see the places marked with XXX_ALPHA below - - const index = y * sizeX + x; - pixelsRgbaBytes[index * 4 + 0] = r; - pixelsRgbaBytes[index * 4 + 1] = g; - pixelsRgbaBytes[index * 4 + 2] = b; - pixelsRgbaBytes[index * 4 + 3] = a; - } - } - return pixelsRgbaBytes; -} - -/** - * Creates the RGBA bytes of a base color texture for the specs. - * - * The result will be the the same as for createSpecPropertyTexturePixelsRgbaBytes, - * but with all alpha component bytes being 255. - * - * @returns The pixels - */ -function createSpecBaseColorTexturePixelsRgbaBytes() { - const sizeX = textureSizeX; - const sizeY = textureSizeY; - const pixelsRgbaBytes = createSpecPropertyTexturePixelsRgbaBytes(); - for (let y = 0; y < sizeY; y++) { - for (let x = 0; x < sizeX; x++) { - const index = y * sizeX + x; - pixelsRgbaBytes[index * 4 + 3] = 255; - } - } - return pixelsRgbaBytes; -} - -/** - * Creates a data URI for a PNG image with the given pixels. - * - * @param {number} sizeX The size in x-direction - * @param {number} sizeY The size in y-direction - * @param {number[]} pixelsRgbaBytes The RGBA pixel bytes - * @returns The PNG data URI - */ -function createPngDataUri(sizeX, sizeY, pixelsRgbaBytes) { - const canvas = document.createElement("canvas"); - const context = canvas.getContext("2d"); - canvas.width = sizeX; - canvas.height = sizeY; - const dataArray = new Uint8ClampedArray(pixelsRgbaBytes); - const imageData = new ImageData(dataArray, sizeX, sizeY); - context.putImageData(imageData, 0, 0); - const dataUri = canvas.toDataURL("image/png"); - return dataUri; -} - /** * Creates an embedded glTF asset with a property texture. * @@ -152,15 +37,11 @@ function createPngDataUri(sizeX, sizeY, pixelsRgbaBytes) { * * @param {object} schema The metadata schema * @param {object} propertyTextureProperties The property texture properties - * @param {string} propertyTexturePngDataUri The PNG data URI of the property texture - * @param {string} baseColorTexturePngDataUri The PNG data URI of the base color texture * @returns The gltf */ function createEmbeddedGltfWithPropertyTexture( schema, - propertyTextureProperties, - propertyTexturePngDataUri, - baseColorTexturePngDataUri + propertyTextureProperties ) { const result = { extensions: { @@ -241,11 +122,16 @@ function createEmbeddedGltfWithPropertyTexture( ], images: [ { - uri: propertyTexturePngDataUri, + // A 16x16 pixels image that contains all combinations of + // (0, 127, 255) in its upper-left 9x9 pixels + uri: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAi0lEQVR42u2RUQ6AMAhDd3OO/qQt8VP8NRHjNpf0leI5ruqXbNVL4c9Dn+E8ljV+iLaXaoAY1YDaADaynBg2gFZLR1+wAdJEWZpW1AIVqmjCruqybw4qnEJbbQBHdWoS2XIUXdp+F8DNUOpM0tIZCusQJrzHNTnsOy2pFTZ7xpKhYFUu4M1v+OvrdQGABqEpS2kSLgAAAABJRU5ErkJggg==", mimeType: "image/png", }, { - uri: baseColorTexturePngDataUri, + // A 1x1 image of a white pixel + uri: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mP4DwQACfsD/Wj6HMwAAAAASUVORK5CYII=", mimeType: "image/png", }, ], @@ -325,26 +211,7 @@ function createEmbeddedGltfWithPropertyTexture( * @returns The glTF */ function createPropertyTextureGltf(schema, properties) { - const propertyTexturePixelsRgbaBytes = createSpecPropertyTexturePixelsRgbaBytes(); - const propertyTextureDataUri = createPngDataUri( - textureSizeX, - textureSizeY, - propertyTexturePixelsRgbaBytes - ); - const baseColorTexturePixelsRgbaBytes = createSpecBaseColorTexturePixelsRgbaBytes(); - const baseColorTextureDataUri = createPngDataUri( - textureSizeX, - textureSizeY, - baseColorTexturePixelsRgbaBytes - ); - - const gltf = createEmbeddedGltfWithPropertyTexture( - schema, - properties, - propertyTextureDataUri, - baseColorTextureDataUri - ); - + const gltf = createEmbeddedGltfWithPropertyTexture(schema, properties); //*/ // Copy-and-paste this into a file to have the actual glTF: console.log("SPEC GLTF:"); @@ -767,7 +634,7 @@ describe( ResourceCache.clearForSpecs(); }); - fit("throws without windowPosition", async function () { + it("throws without windowPosition", async function () { const windowPosition = undefined; // For spec const schemaId = undefined; const className = "exampleClass"; @@ -779,7 +646,7 @@ describe( }).toThrowDeveloperError(); }); - fit("throws without className", async function () { + it("throws without className", async function () { const windowPosition = new Cartesian2(); const schemaId = undefined; const className = undefined; // For spec @@ -791,7 +658,7 @@ describe( }).toThrowDeveloperError(); }); - fit("throws without propertyName", async function () { + it("throws without propertyName", async function () { const windowPosition = new Cartesian2(); const schemaId = undefined; const className = "exampleClass"; @@ -803,7 +670,7 @@ describe( }).toThrowDeveloperError(); }); - fit("returns undefined for class name that does not exist", async function () { + it("returns undefined for class name that does not exist", async function () { const schemaId = undefined; const className = "exampleClass_THAT_DOES_NOT_EXIST"; // For spec const propertyName = "example_UINT8_SCALAR"; @@ -831,7 +698,7 @@ describe( expect(actualMetadataValue).toBeUndefined(); }); - fit("returns undefined when there is no object with metadata", async function () { + it("returns undefined when there is no object with metadata", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_SCALAR"; @@ -857,7 +724,7 @@ describe( expect(actualMetadataValue).toBeUndefined(); }); - fit("picks UINT8 SCALAR from a property texture", async function () { + it("picks UINT8 SCALAR from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_SCALAR"; @@ -917,7 +784,7 @@ describe( ); }); - fit("picks normalized UINT8 SCALAR from a property texture", async function () { + it("picks normalized UINT8 SCALAR from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_normalized_UINT8_SCALAR"; @@ -977,7 +844,7 @@ describe( ); }); - fit("picks fixed length UINT8 SCALAR array from a property texture", async function () { + it("picks fixed length UINT8 SCALAR array from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_fixed_length_UINT8_SCALAR_array"; @@ -1037,7 +904,7 @@ describe( ); }); - fit("picks UINT8 VEC2 from a property texture", async function () { + it("picks UINT8 VEC2 from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_VEC2"; @@ -1097,7 +964,7 @@ describe( ); }); - fit("picks normalized UINT8 VEC2 from a property texture", async function () { + it("picks normalized UINT8 VEC2 from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_normalized_UINT8_VEC2"; @@ -1159,7 +1026,7 @@ describe( ); }); - fit("picks UINT8 VEC3 from a property texture", async function () { + it("picks UINT8 VEC3 from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_VEC3"; @@ -1219,7 +1086,7 @@ describe( ); }); - fit("picks UINT8 VEC4 from a property texture", async function () { + it("picks UINT8 VEC4 from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_VEC4"; @@ -1282,7 +1149,7 @@ describe( }); // XXX For debugging: - fit("picks metadata from a property texture quarry - TO BE REMOVED", async function () { + it("picks metadata from a property texture quarry - TO BE REMOVED", async function () { const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_SCALAR"; From ddd5fb82385d618dbd56867cdfd6957c335ed097 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 10 Sep 2024 18:22:15 +0200 Subject: [PATCH 32/63] Wrap up specs for metadata picking --- .../Specs/Scene/Model/pickMetadataSpec.js | 389 +++++------------- 1 file changed, 94 insertions(+), 295 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 841303e5f56d..9b680c31a7f3 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -5,7 +5,6 @@ import { Math as CesiumMath, Model, ResourceCache, - Cartesian4, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; import createCanvas from "../../../../../Specs/createCanvas.js"; @@ -128,21 +127,11 @@ function createEmbeddedGltfWithPropertyTexture( "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAi0lEQVR42u2RUQ6AMAhDd3OO/qQt8VP8NRHjNpf0leI5ruqXbNVL4c9Dn+E8ljV+iLaXaoAY1YDaADaynBg2gFZLR1+wAdJEWZpW1AIVqmjCruqybw4qnEJbbQBHdWoS2XIUXdp+F8DNUOpM0tIZCusQJrzHNTnsOy2pFTZ7xpKhYFUu4M1v+OvrdQGABqEpS2kSLgAAAABJRU5ErkJggg==", mimeType: "image/png", }, - { - // A 1x1 image of a white pixel - uri: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mP4DwQACfsD/Wj6HMwAAAAASUVORK5CYII=", - mimeType: "image/png", - }, ], materials: [ { pbrMetallicRoughness: { baseColorFactor: [1.0, 1.0, 1.0, 1.0], - baseColorTexture: { - index: 1, - texCoord: 0, - }, metallicFactor: 0.0, roughnessFactor: 1.0, }, @@ -212,7 +201,7 @@ function createEmbeddedGltfWithPropertyTexture( */ function createPropertyTextureGltf(schema, properties) { const gltf = createEmbeddedGltfWithPropertyTexture(schema, properties); - //*/ + /*/ // Copy-and-paste this into a file to have the actual glTF: console.log("SPEC GLTF:"); console.log("-".repeat(80)); @@ -223,13 +212,11 @@ function createPropertyTextureGltf(schema, properties) { } /** - * Creates a metadata schema for the metadata property texture picking specs. - * - * This is for the 'scalar' test case + * Creates the glTF for the 'scalar' test case * - * @returns The schema + * @returns The glTF */ -function createSpecSchemaScalar() { +function createPropertyTextureGltfScalar() { const schema = { id: "ExampleSchema", classes: { @@ -251,18 +238,6 @@ function createSpecSchemaScalar() { }, }, }; - return schema; -} - -/** - * Creates the property texture properties definition for the - * metadata property texture picking specs. - * - * This is for the 'scalar' test case - * - * @returns The properties - */ -function createPropertyTexturePropertiesScalar() { const properties = { example_UINT8_SCALAR: { index: 0, @@ -275,28 +250,15 @@ function createPropertyTexturePropertiesScalar() { channels: [1], }, }; - return properties; -} - -/** - * Creates the glTF for the 'scalar' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfScalar() { - const schema = createSpecSchemaScalar(); - const properties = createPropertyTexturePropertiesScalar(); return createPropertyTextureGltf(schema, properties); } /** - * Creates a metadata schema for the metadata property texture picking specs. - * - * This is for the 'scalar array' test case + * Creates the glTF for the 'scalar array' test case * - * @returns The schema + * @returns The glTF */ -function createSpecSchemaScalarArray() { +function createPropertyTextureGltfScalarArray() { const schema = { id: "ExampleSchema", classes: { @@ -315,18 +277,6 @@ function createSpecSchemaScalarArray() { }, }, }; - return schema; -} - -/** - * Creates the property texture properties definition for the - * metadata property texture picking specs. - * - * This is for the 'scalar array' test case - * - * @returns The properties - */ -function createPropertyTexturePropertiesScalarArray() { const properties = { example_fixed_length_UINT8_SCALAR_array: { index: 0, @@ -334,28 +284,15 @@ function createPropertyTexturePropertiesScalarArray() { channels: [0, 1, 2], }, }; - return properties; -} - -/** - * Creates the glTF for the 'scalar array' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfScalarArray() { - const schema = createSpecSchemaScalarArray(); - const properties = createPropertyTexturePropertiesScalarArray(); return createPropertyTextureGltf(schema, properties); } /** - * Creates a metadata schema for the metadata property texture picking specs. - * - * This is for the 'vec2' test case + * Creates the glTF for the 'vec2' test case * - * @returns The schema + * @returns The glTF */ -function createSpecSchemaVec2() { +function createPropertyTextureGltfVec2() { const schema = { id: "ExampleSchema", classes: { @@ -367,97 +304,50 @@ function createSpecSchemaVec2() { type: "VEC2", componentType: "UINT8", }, - example_normalized_UINT8_VEC2: { - name: "Example VEC2 property with normalized UINT8 components", - type: "VEC2", - componentType: "UINT8", - normalized: true, - }, }, }, }, }; - return schema; -} - -/** - * Creates the property texture properties definition for the - * metadata property texture picking specs. - * - * This is for the 'vec2' test case - * - * @returns The properties - */ -function createPropertyTexturePropertiesVec2() { const properties = { example_UINT8_VEC2: { index: 0, texCoord: 0, channels: [0, 1], }, - example_normalized_UINT8_VEC2: { - index: 0, - texCoord: 0, - channels: [2, 3], - }, }; - return properties; -} - -/** - * Creates the glTF for the 'vec2' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfVec2() { - const schema = createSpecSchemaVec2(); - const properties = createPropertyTexturePropertiesVec2(); return createPropertyTextureGltf(schema, properties); } /** - * Creates a metadata schema for the metadata property texture picking specs. + * Creates the glTF for the normalized 'vec2' test case * - * This is for the 'vec3' test case - * - * @returns The schema + * @returns The glTF */ -function createSpecSchemaVec3() { +function createPropertyTextureGltfNormalizedVec2() { const schema = { id: "ExampleSchema", classes: { exampleClass: { name: "Example class", properties: { - example_UINT8_VEC3: { - name: "Example VEC3 property with UINT8 components", - type: "VEC3", + example_normalized_UINT8_VEC2: { + name: "Example VEC2 property with normalized UINT8 components", + type: "VEC2", componentType: "UINT8", + normalized: true, }, }, }, }, }; - return schema; -} - -/** - * Creates the property texture properties definition for the - * metadata property texture picking specs. - * - * This is for the 'vec3' test case - * - * @returns The properties - */ -function createPropertyTexturePropertiesVec3() { const properties = { - example_UINT8_VEC3: { + example_normalized_UINT8_VEC2: { index: 0, texCoord: 0, - channels: [0, 1, 2], + channels: [0, 1], }, }; - return properties; + return createPropertyTextureGltf(schema, properties); } /** @@ -466,64 +356,28 @@ function createPropertyTexturePropertiesVec3() { * @returns The glTF */ function createPropertyTextureGltfVec3() { - const schema = createSpecSchemaVec3(); - const properties = createPropertyTexturePropertiesVec3(); - return createPropertyTextureGltf(schema, properties); -} - -/** - * Creates a metadata schema for the metadata property texture picking specs. - * - * This is for the 'vec4' test case - * - * @returns The schema - */ -function createSpecSchemaVec4() { const schema = { id: "ExampleSchema", classes: { exampleClass: { name: "Example class", properties: { - example_UINT8_VEC4: { - name: "Example VEC4 property with UINT8 components", - type: "VEC4", + example_UINT8_VEC3: { + name: "Example VEC3 property with UINT8 components", + type: "VEC3", componentType: "UINT8", }, }, }, }, }; - return schema; -} - -/** - * Creates the property texture properties definition for the - * metadata property texture picking specs. - * - * This is for the 'vec4' test case - * - * @returns The properties - */ -function createPropertyTexturePropertiesVec4() { const properties = { - example_UINT8_VEC4: { + example_UINT8_VEC3: { index: 0, texCoord: 0, - channels: [0, 1, 2, 3], + channels: [0, 1, 2], }, }; - return properties; -} - -/** - * Creates the glTF for the 'vec4' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfVec4() { - const schema = createSpecSchemaVec4(); - const properties = createPropertyTexturePropertiesVec4(); return createPropertyTextureGltf(schema, properties); } @@ -724,6 +578,53 @@ describe( expect(actualMetadataValue).toBeUndefined(); }); + it("pickMetadataSchema returns undefined when there is no object with metadata", async function () { + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const metadataSchema = scene.pickMetadataSchema(windowPosition); + + expect(metadataSchema).toBeUndefined(); + }); + + it("pickMetadataSchema picks the metadata schema object", async function () { + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + + // The pickMetadataSchema call should return the schema that + // was defined in createPropertyTextureGltfScalar + const metadataSchema = scene.pickMetadataSchema(windowPosition); + + expect(metadataSchema).toBeDefined(); + expect(metadataSchema.id).toEqual("ExampleSchema"); + expect(metadataSchema.classes).toBeDefined(); + }); + it("picks UINT8 SCALAR from a property texture", async function () { const schemaId = undefined; const className = "exampleClass"; @@ -748,7 +649,7 @@ describe( className, propertyName, 0, - 0 + 3 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -756,7 +657,7 @@ describe( className, propertyName, 1, - 0 + 3 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -764,7 +665,7 @@ describe( className, propertyName, 2, - 0 + 3 ); const expectedMetadataValue0 = 0; const expectedMetadataValue1 = 127; @@ -808,7 +709,7 @@ describe( className, propertyName, 0, - 0 + 3 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -816,7 +717,7 @@ describe( className, propertyName, 3, - 0 + 3 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -824,7 +725,7 @@ describe( className, propertyName, 6, - 0 + 3 ); const expectedMetadataValue0 = 0.0; const expectedMetadataValue1 = 0.5; @@ -868,7 +769,7 @@ describe( className, propertyName, 0, - 0 + 3 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -876,7 +777,7 @@ describe( className, propertyName, 1, - 1 + 4 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -884,7 +785,7 @@ describe( className, propertyName, 2, - 2 + 5 ); const expectedMetadataValue0 = [0, 0, 0]; const expectedMetadataValue1 = [127, 0, 127]; @@ -928,7 +829,7 @@ describe( className, propertyName, 0, - 0 + 3 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -936,7 +837,7 @@ describe( className, propertyName, 1, - 1 + 4 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -944,7 +845,7 @@ describe( className, propertyName, 2, - 2 + 5 ); const expectedMetadataValue0 = new Cartesian2(0, 0); const expectedMetadataValue1 = new Cartesian2(127, 0); @@ -968,7 +869,7 @@ describe( const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_normalized_UINT8_VEC2"; - const gltf = createPropertyTextureGltfVec2(); + const gltf = createPropertyTextureGltfNormalizedVec2(); const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; @@ -988,7 +889,7 @@ describe( className, propertyName, 0, - 0 + 3 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -996,7 +897,7 @@ describe( className, propertyName, 1, - 1 + 4 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -1004,13 +905,12 @@ describe( className, propertyName, 2, - 2 + 5 ); - // XXX_ALPHA The second component should be 0.0!!! - const expectedMetadataValue0 = new Cartesian2(0.0, 1.0); - const expectedMetadataValue1 = new Cartesian2(0.5, 1.0); - const expectedMetadataValue2 = new Cartesian2(1.0, 1.0); + const expectedMetadataValue0 = new Cartesian2(0.0, 0.0); + const expectedMetadataValue1 = new Cartesian2(0.5, 0.0); + const expectedMetadataValue2 = new Cartesian2(1.0, 0.0); expect(actualMetadataValue0).toEqualEpsilon( expectedMetadataValue0, @@ -1050,7 +950,7 @@ describe( className, propertyName, 0, - 0 + 3 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -1058,7 +958,7 @@ describe( className, propertyName, 1, - 1 + 4 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -1066,7 +966,7 @@ describe( className, propertyName, 2, - 2 + 5 ); const expectedMetadataValue0 = new Cartesian3(0, 0, 0); const expectedMetadataValue1 = new Cartesian3(127, 0, 127); @@ -1085,107 +985,6 @@ describe( propertyValueEpsilon ); }); - - it("picks UINT8 VEC4 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC4"; - const gltf = createPropertyTextureGltfVec4(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - - // XXX_ALPHA: The last component should be 0! - const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 255); - const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 255); - const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 255); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); - - // XXX For debugging: - it("picks metadata from a property texture quarry - TO BE REMOVED", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - for (let x = 0; x < 16; x++) { - for (let y = 0; y < 16; y++) { - const actualMetadataValue = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - x, - y - ); - - console.log( - `actualMetadataValue at ${x} ${y} is `, - actualMetadataValue - ); - } - } - console.log("done"); - }); }, "WebGL" ); From d813dba7ecc61b43858d9f193b26e405e3e36e09 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 10 Sep 2024 18:39:29 +0200 Subject: [PATCH 33/63] Update spec for new pipeline stage --- packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js index 03f0b3d3015e..985f7a5f5ad9 100644 --- a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js @@ -1039,6 +1039,7 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, + MetadataPickingPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, From 0ad2bc576756bf2622cad57a1f2ef3fbc7bb7e8c Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 10 Sep 2024 18:59:04 +0200 Subject: [PATCH 34/63] Revive parts of the metadata picking specs --- .../Specs/Scene/Model/pickMetadataSpec.js | 172 ++++++++++++++++-- 1 file changed, 152 insertions(+), 20 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 9b680c31a7f3..16e8adb1a018 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -5,6 +5,7 @@ import { Math as CesiumMath, Model, ResourceCache, + Cartesian4, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; import createCanvas from "../../../../../Specs/createCanvas.js"; @@ -381,6 +382,37 @@ function createPropertyTextureGltfVec3() { return createPropertyTextureGltf(schema, properties); } +/** + * Creates the glTF for the 'vec4' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfVec4() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_VEC4: { + name: "Example VEC4 property with UINT8 components", + type: "VEC4", + componentType: "UINT8", + }, + }, + }, + }, + }; + const properties = { + example_UINT8_VEC4: { + index: 0, + texCoord: 0, + channels: [0, 1, 2, 3], + }, + }; + return createPropertyTextureGltf(schema, properties); +} + /** * Create a model from the given glTF, add it as a primitive * to the given scene, and wait until it is fully loaded. @@ -649,23 +681,23 @@ describe( className, propertyName, 0, - 3 + 0 ); const actualMetadataValue1 = pickMetadataAt( scene, schemaId, className, propertyName, - 1, - 3 + 0, + 1 ); const actualMetadataValue2 = pickMetadataAt( scene, schemaId, className, propertyName, - 2, - 3 + 0, + 2 ); const expectedMetadataValue0 = 0; const expectedMetadataValue1 = 127; @@ -709,7 +741,7 @@ describe( className, propertyName, 0, - 3 + 0 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -717,7 +749,7 @@ describe( className, propertyName, 3, - 3 + 0 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -725,7 +757,7 @@ describe( className, propertyName, 6, - 3 + 0 ); const expectedMetadataValue0 = 0.0; const expectedMetadataValue1 = 0.5; @@ -769,7 +801,7 @@ describe( className, propertyName, 0, - 3 + 0 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -777,7 +809,7 @@ describe( className, propertyName, 1, - 4 + 1 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -785,7 +817,7 @@ describe( className, propertyName, 2, - 5 + 2 ); const expectedMetadataValue0 = [0, 0, 0]; const expectedMetadataValue1 = [127, 0, 127]; @@ -829,7 +861,7 @@ describe( className, propertyName, 0, - 3 + 0 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -837,7 +869,7 @@ describe( className, propertyName, 1, - 4 + 1 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -845,7 +877,7 @@ describe( className, propertyName, 2, - 5 + 2 ); const expectedMetadataValue0 = new Cartesian2(0, 0); const expectedMetadataValue1 = new Cartesian2(127, 0); @@ -889,7 +921,7 @@ describe( className, propertyName, 0, - 3 + 0 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -897,7 +929,7 @@ describe( className, propertyName, 1, - 4 + 1 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -905,7 +937,7 @@ describe( className, propertyName, 2, - 5 + 2 ); const expectedMetadataValue0 = new Cartesian2(0.0, 0.0); @@ -950,7 +982,7 @@ describe( className, propertyName, 0, - 3 + 0 ); const actualMetadataValue1 = pickMetadataAt( scene, @@ -958,7 +990,7 @@ describe( className, propertyName, 1, - 4 + 1 ); const actualMetadataValue2 = pickMetadataAt( scene, @@ -966,7 +998,7 @@ describe( className, propertyName, 2, - 5 + 2 ); const expectedMetadataValue0 = new Cartesian3(0, 0, 0); const expectedMetadataValue1 = new Cartesian3(127, 0, 127); @@ -985,6 +1017,106 @@ describe( propertyValueEpsilon ); }); + + it("picks UINT8 VEC4 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC4"; + const gltf = createPropertyTextureGltfVec4(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + + const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 0); + const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 0); + const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + // XXX For debugging: + it("picks metadata from a property texture quarry - TO BE REMOVED", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + for (let x = 0; x < 16; x++) { + for (let y = 0; y < 16; y++) { + const actualMetadataValue = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + x, + y + ); + + console.log( + `actualMetadataValue at ${x} ${y} is `, + actualMetadataValue + ); + } + } + console.log("done"); + }); }, "WebGL" ); From aa759220ee9f20b99fbebf84a6e58ab6f4996ac0 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Tue, 10 Sep 2024 19:13:48 +0200 Subject: [PATCH 35/63] Try declaring metadata picking specs as not-WebGL --- .../Specs/Scene/Model/pickMetadataSpec.js | 1145 ++++++++--------- 1 file changed, 551 insertions(+), 594 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 16e8adb1a018..1624b228966d 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -485,638 +485,595 @@ function pickMetadataAt(scene, schemaId, className, propertyName, x, y) { return metadataValue; } -describe( - "Scene/pickMetadata", - function () { - const defaultDate = JulianDate.fromDate( - new Date("January 1, 2014 12:00:00 UTC") - ); +describe("Scene/pickMetadata", function () { + const defaultDate = JulianDate.fromDate( + new Date("January 1, 2014 12:00:00 UTC") + ); - let scene; - let scene2D; - let sceneCV; + let scene; + let scene2D; + let sceneCV; - beforeAll(function () { - scene = createScene(); + beforeAll(function () { + scene = createScene(); - scene2D = createScene(); - scene2D.morphTo2D(0.0); + scene2D = createScene(); + scene2D.morphTo2D(0.0); - sceneCV = createScene(); - sceneCV.morphToColumbusView(0.0); - }); + sceneCV = createScene(); + sceneCV.morphToColumbusView(0.0); + }); - afterAll(function () { - scene.destroyForSpecs(); - scene2D.destroyForSpecs(); - sceneCV.destroyForSpecs(); - }); + afterAll(function () { + scene.destroyForSpecs(); + scene2D.destroyForSpecs(); + sceneCV.destroyForSpecs(); + }); - afterEach(function () { - scene.primitives.removeAll(); - scene2D.primitives.removeAll(); - sceneCV.primitives.removeAll(); - scene.verticalExaggeration = 1.0; - ResourceCache.clearForSpecs(); - }); + afterEach(function () { + scene.primitives.removeAll(); + scene2D.primitives.removeAll(); + sceneCV.primitives.removeAll(); + scene.verticalExaggeration = 1.0; + ResourceCache.clearForSpecs(); + }); - it("throws without windowPosition", async function () { - const windowPosition = undefined; // For spec - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); - }); + it("throws without windowPosition", async function () { + const windowPosition = undefined; // For spec + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + }); - it("throws without className", async function () { - const windowPosition = new Cartesian2(); - const schemaId = undefined; - const className = undefined; // For spec - const propertyName = "example_UINT8_SCALAR"; - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); - }); + it("throws without className", async function () { + const windowPosition = new Cartesian2(); + const schemaId = undefined; + const className = undefined; // For spec + const propertyName = "example_UINT8_SCALAR"; + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + }); + + it("throws without propertyName", async function () { + const windowPosition = new Cartesian2(); + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = undefined; // For spec + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + }); + + it("returns undefined for class name that does not exist", async function () { + const schemaId = undefined; + const className = "exampleClass_THAT_DOES_NOT_EXIST"; // For spec + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); - it("throws without propertyName", async function () { - const windowPosition = new Cartesian2(); - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = undefined; // For spec - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("returns undefined for class name that does not exist", async function () { - const schemaId = undefined; - const className = "exampleClass_THAT_DOES_NOT_EXIST"; // For spec - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2) - ); - const actualMetadataValue = scene.pickMetadata( - windowPosition, - schemaId, - className, - propertyName - ); - expect(actualMetadataValue).toBeUndefined(); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const actualMetadataValue = scene.pickMetadata( + windowPosition, + schemaId, + className, + propertyName + ); + expect(actualMetadataValue).toBeUndefined(); + }); + + it("returns undefined when there is no object with metadata", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("returns undefined when there is no object with metadata", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - fitCameraToUnitSquare(scene.camera); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2) - ); - const actualMetadataValue = scene.pickMetadata( - windowPosition, - schemaId, - className, - propertyName - ); - expect(actualMetadataValue).toBeUndefined(); + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const actualMetadataValue = scene.pickMetadata( + windowPosition, + schemaId, + className, + propertyName + ); + expect(actualMetadataValue).toBeUndefined(); + }); + + it("pickMetadataSchema returns undefined when there is no object with metadata", async function () { + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("pickMetadataSchema returns undefined when there is no object with metadata", async function () { - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); + fitCameraToUnitSquare(scene.camera); - fitCameraToUnitSquare(scene.camera); + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const metadataSchema = scene.pickMetadataSchema(windowPosition); + + expect(metadataSchema).toBeUndefined(); + }); - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2) - ); - const metadataSchema = scene.pickMetadataSchema(windowPosition); + it("pickMetadataSchema picks the metadata schema object", async function () { + const gltf = createPropertyTextureGltfScalar(); - expect(metadataSchema).toBeUndefined(); + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("pickMetadataSchema picks the metadata schema object", async function () { - const gltf = createPropertyTextureGltfScalar(); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); + scene.initializeFrame(); + scene.render(defaultDate); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); - scene.initializeFrame(); - scene.render(defaultDate); + // The pickMetadataSchema call should return the schema that + // was defined in createPropertyTextureGltfScalar + const metadataSchema = scene.pickMetadataSchema(windowPosition); - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2) - ); + expect(metadataSchema).toBeDefined(); + expect(metadataSchema.id).toEqual("ExampleSchema"); + expect(metadataSchema.classes).toBeDefined(); + }); - // The pickMetadataSchema call should return the schema that - // was defined in createPropertyTextureGltfScalar - const metadataSchema = scene.pickMetadataSchema(windowPosition); + it("picks UINT8 SCALAR from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); - expect(metadataSchema).toBeDefined(); - expect(metadataSchema.id).toEqual("ExampleSchema"); - expect(metadataSchema.classes).toBeDefined(); + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("picks UINT8 SCALAR from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 2 - ); - const expectedMetadataValue0 = 0; - const expectedMetadataValue1 = 127; - const expectedMetadataValue2 = 255; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); - it("picks normalized UINT8 SCALAR from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_normalized_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 3, - 0 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 6, - 0 - ); - const expectedMetadataValue0 = 0.0; - const expectedMetadataValue1 = 0.5; - const expectedMetadataValue2 = 1.0; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 2 + ); + const expectedMetadataValue0 = 0; + const expectedMetadataValue1 = 127; + const expectedMetadataValue2 = 255; - it("picks fixed length UINT8 SCALAR array from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_fixed_length_UINT8_SCALAR_array"; - const gltf = createPropertyTextureGltfScalarArray(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - const expectedMetadataValue0 = [0, 0, 0]; - const expectedMetadataValue1 = [127, 0, 127]; - const expectedMetadataValue2 = [255, 0, 255]; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + it("picks normalized UINT8 SCALAR from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_normalized_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("picks UINT8 VEC2 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC2"; - const gltf = createPropertyTextureGltfVec2(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - const expectedMetadataValue0 = new Cartesian2(0, 0); - const expectedMetadataValue1 = new Cartesian2(127, 0); - const expectedMetadataValue2 = new Cartesian2(255, 0); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 3, + 0 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 6, + 0 + ); + const expectedMetadataValue0 = 0.0; + const expectedMetadataValue1 = 0.5; + const expectedMetadataValue2 = 1.0; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + it("picks fixed length UINT8 SCALAR array from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_fixed_length_UINT8_SCALAR_array"; + const gltf = createPropertyTextureGltfScalarArray(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("picks normalized UINT8 VEC2 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_normalized_UINT8_VEC2"; - const gltf = createPropertyTextureGltfNormalizedVec2(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - - const expectedMetadataValue0 = new Cartesian2(0.0, 0.0); - const expectedMetadataValue1 = new Cartesian2(0.5, 0.0); - const expectedMetadataValue2 = new Cartesian2(1.0, 0.0); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = [0, 0, 0]; + const expectedMetadataValue1 = [127, 0, 127]; + const expectedMetadataValue2 = [255, 0, 255]; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + it("picks UINT8 VEC2 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC2"; + const gltf = createPropertyTextureGltfVec2(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("picks UINT8 VEC3 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC3"; - const gltf = createPropertyTextureGltfVec3(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - const expectedMetadataValue0 = new Cartesian3(0, 0, 0); - const expectedMetadataValue1 = new Cartesian3(127, 0, 127); - const expectedMetadataValue2 = new Cartesian3(255, 0, 255); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = new Cartesian2(0, 0); + const expectedMetadataValue1 = new Cartesian2(127, 0); + const expectedMetadataValue2 = new Cartesian2(255, 0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + it("picks normalized UINT8 VEC2 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_normalized_UINT8_VEC2"; + const gltf = createPropertyTextureGltfNormalizedVec2(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - it("picks UINT8 VEC4 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC4"; - const gltf = createPropertyTextureGltfVec4(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - - const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 0); - const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 0); - const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 0); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + + const expectedMetadataValue0 = new Cartesian2(0.0, 0.0); + const expectedMetadataValue1 = new Cartesian2(0.5, 0.0); + const expectedMetadataValue2 = new Cartesian2(1.0, 0.0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + it("picks UINT8 VEC3 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC3"; + const gltf = createPropertyTextureGltfVec3(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - // XXX For debugging: - it("picks metadata from a property texture quarry - TO BE REMOVED", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - for (let x = 0; x < 16; x++) { - for (let y = 0; y < 16; y++) { - const actualMetadataValue = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - x, - y - ); - - console.log( - `actualMetadataValue at ${x} ${y} is `, - actualMetadataValue - ); - } - } - console.log("done"); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = new Cartesian3(0, 0, 0); + const expectedMetadataValue1 = new Cartesian3(127, 0, 127); + const expectedMetadataValue2 = new Cartesian3(255, 0, 255); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + + it("picks UINT8 VEC4 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC4"; + const gltf = createPropertyTextureGltfVec4(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), }); - }, - "WebGL" -); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + + const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 0); + const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 0); + const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); +}); From 9c3c08121d9523a817373483a6a54a193fbab2b7 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 11 Sep 2024 15:08:59 +0200 Subject: [PATCH 36/63] Classify metadata picking spec as WebGL --- .../Specs/Scene/Model/pickMetadataSpec.js | 1108 +++++++++-------- 1 file changed, 556 insertions(+), 552 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 1624b228966d..8a7a9aa88d28 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -485,595 +485,599 @@ function pickMetadataAt(scene, schemaId, className, propertyName, x, y) { return metadataValue; } -describe("Scene/pickMetadata", function () { - const defaultDate = JulianDate.fromDate( - new Date("January 1, 2014 12:00:00 UTC") - ); - - let scene; - let scene2D; - let sceneCV; - - beforeAll(function () { - scene = createScene(); - - scene2D = createScene(); - scene2D.morphTo2D(0.0); - - sceneCV = createScene(); - sceneCV.morphToColumbusView(0.0); - }); - - afterAll(function () { - scene.destroyForSpecs(); - scene2D.destroyForSpecs(); - sceneCV.destroyForSpecs(); - }); - - afterEach(function () { - scene.primitives.removeAll(); - scene2D.primitives.removeAll(); - sceneCV.primitives.removeAll(); - scene.verticalExaggeration = 1.0; - ResourceCache.clearForSpecs(); - }); - - it("throws without windowPosition", async function () { - const windowPosition = undefined; // For spec - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); - }); +describe( + "Scene/pickMetadata", + function () { + const defaultDate = JulianDate.fromDate( + new Date("January 1, 2014 12:00:00 UTC") + ); - it("throws without className", async function () { - const windowPosition = new Cartesian2(); - const schemaId = undefined; - const className = undefined; // For spec - const propertyName = "example_UINT8_SCALAR"; - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); - }); + let scene; + let scene2D; + let sceneCV; - it("throws without propertyName", async function () { - const windowPosition = new Cartesian2(); - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = undefined; // For spec - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); - }); + beforeAll(function () { + scene = createScene(); - it("returns undefined for class name that does not exist", async function () { - const schemaId = undefined; - const className = "exampleClass_THAT_DOES_NOT_EXIST"; // For spec - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); + scene2D = createScene(); + scene2D.morphTo2D(0.0); - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + sceneCV = createScene(); + sceneCV.morphToColumbusView(0.0); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2) - ); - const actualMetadataValue = scene.pickMetadata( - windowPosition, - schemaId, - className, - propertyName - ); - expect(actualMetadataValue).toBeUndefined(); - }); - - it("returns undefined when there is no object with metadata", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + afterAll(function () { + scene.destroyForSpecs(); + scene2D.destroyForSpecs(); + sceneCV.destroyForSpecs(); }); - fitCameraToUnitSquare(scene.camera); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2) - ); - const actualMetadataValue = scene.pickMetadata( - windowPosition, - schemaId, - className, - propertyName - ); - expect(actualMetadataValue).toBeUndefined(); - }); - - it("pickMetadataSchema returns undefined when there is no object with metadata", async function () { - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + afterEach(function () { + scene.primitives.removeAll(); + scene2D.primitives.removeAll(); + sceneCV.primitives.removeAll(); + scene.verticalExaggeration = 1.0; + ResourceCache.clearForSpecs(); }); - fitCameraToUnitSquare(scene.camera); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2) - ); - const metadataSchema = scene.pickMetadataSchema(windowPosition); - - expect(metadataSchema).toBeUndefined(); - }); - - it("pickMetadataSchema picks the metadata schema object", async function () { - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + it("throws without windowPosition", async function () { + const windowPosition = undefined; // For spec + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2) - ); - - // The pickMetadataSchema call should return the schema that - // was defined in createPropertyTextureGltfScalar - const metadataSchema = scene.pickMetadataSchema(windowPosition); - - expect(metadataSchema).toBeDefined(); - expect(metadataSchema.id).toEqual("ExampleSchema"); - expect(metadataSchema.classes).toBeDefined(); - }); - - it("picks UINT8 SCALAR from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + it("throws without className", async function () { + const windowPosition = new Cartesian2(); + const schemaId = undefined; + const className = undefined; // For spec + const propertyName = "example_UINT8_SCALAR"; + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 2 - ); - const expectedMetadataValue0 = 0; - const expectedMetadataValue1 = 127; - const expectedMetadataValue2 = 255; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); - - it("picks normalized UINT8 SCALAR from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_normalized_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + it("throws without propertyName", async function () { + const windowPosition = new Cartesian2(); + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = undefined; // For spec + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 3, - 0 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 6, - 0 - ); - const expectedMetadataValue0 = 0.0; - const expectedMetadataValue1 = 0.5; - const expectedMetadataValue2 = 1.0; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); - - it("picks fixed length UINT8 SCALAR array from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_fixed_length_UINT8_SCALAR_array"; - const gltf = createPropertyTextureGltfScalarArray(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + it("returns undefined for class name that does not exist", async function () { + const schemaId = undefined; + const className = "exampleClass_THAT_DOES_NOT_EXIST"; // For spec + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const actualMetadataValue = scene.pickMetadata( + windowPosition, + schemaId, + className, + propertyName + ); + expect(actualMetadataValue).toBeUndefined(); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - const expectedMetadataValue0 = [0, 0, 0]; - const expectedMetadataValue1 = [127, 0, 127]; - const expectedMetadataValue2 = [255, 0, 255]; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); - - it("picks UINT8 VEC2 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC2"; - const gltf = createPropertyTextureGltfVec2(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + it("returns undefined when there is no object with metadata", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const actualMetadataValue = scene.pickMetadata( + windowPosition, + schemaId, + className, + propertyName + ); + expect(actualMetadataValue).toBeUndefined(); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - const expectedMetadataValue0 = new Cartesian2(0, 0); - const expectedMetadataValue1 = new Cartesian2(127, 0); - const expectedMetadataValue2 = new Cartesian2(255, 0); + it("pickMetadataSchema returns undefined when there is no object with metadata", async function () { + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); + fitCameraToUnitSquare(scene.camera); - it("picks normalized UINT8 VEC2 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_normalized_UINT8_VEC2"; - const gltf = createPropertyTextureGltfNormalizedVec2(); + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); + const metadataSchema = scene.pickMetadataSchema(windowPosition); - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + expect(metadataSchema).toBeUndefined(); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); + it("pickMetadataSchema picks the metadata schema object", async function () { + const gltf = createPropertyTextureGltfScalar(); - scene.initializeFrame(); - scene.render(defaultDate); + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); - const expectedMetadataValue0 = new Cartesian2(0.0, 0.0); - const expectedMetadataValue1 = new Cartesian2(0.5, 0.0); - const expectedMetadataValue2 = new Cartesian2(1.0, 0.0); + scene.initializeFrame(); + scene.render(defaultDate); - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2) + ); - it("picks UINT8 VEC3 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC3"; - const gltf = createPropertyTextureGltfVec3(); + // The pickMetadataSchema call should return the schema that + // was defined in createPropertyTextureGltfScalar + const metadataSchema = scene.pickMetadataSchema(windowPosition); - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + expect(metadataSchema).toBeDefined(); + expect(metadataSchema.id).toEqual("ExampleSchema"); + expect(metadataSchema.classes).toBeDefined(); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); - const expectedMetadataValue0 = new Cartesian3(0, 0, 0); - const expectedMetadataValue1 = new Cartesian3(127, 0, 127); - const expectedMetadataValue2 = new Cartesian3(255, 0, 255); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); - - it("picks UINT8 VEC4 from a property texture", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC4"; - const gltf = createPropertyTextureGltfVec4(); + it("picks UINT8 SCALAR from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 2 + ); + const expectedMetadataValue0 = 0; + const expectedMetadataValue1 = 127; + const expectedMetadataValue2 = 255; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), + it("picks normalized UINT8 SCALAR from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_normalized_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 3, + 0 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 6, + 0 + ); + const expectedMetadataValue0 = 0.0; + const expectedMetadataValue1 = 0.5; + const expectedMetadataValue2 = 1.0; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); }); - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); + it("picks fixed length UINT8 SCALAR array from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_fixed_length_UINT8_SCALAR_array"; + const gltf = createPropertyTextureGltfScalarArray(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = [0, 0, 0]; + const expectedMetadataValue1 = [127, 0, 127]; + const expectedMetadataValue2 = [255, 0, 255]; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); - scene.initializeFrame(); - scene.render(defaultDate); + it("picks UINT8 VEC2 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC2"; + const gltf = createPropertyTextureGltfVec2(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = new Cartesian2(0, 0); + const expectedMetadataValue1 = new Cartesian2(127, 0); + const expectedMetadataValue2 = new Cartesian2(255, 0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0 - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1 - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2 - ); + it("picks normalized UINT8 VEC2 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_normalized_UINT8_VEC2"; + const gltf = createPropertyTextureGltfNormalizedVec2(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + + const expectedMetadataValue0 = new Cartesian2(0.0, 0.0); + const expectedMetadataValue1 = new Cartesian2(0.5, 0.0); + const expectedMetadataValue2 = new Cartesian2(1.0, 0.0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); - const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 0); - const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 0); - const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 0); + it("picks UINT8 VEC3 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC3"; + const gltf = createPropertyTextureGltfVec3(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + const expectedMetadataValue0 = new Cartesian3(0, 0, 0); + const expectedMetadataValue1 = new Cartesian3(127, 0, 127); + const expectedMetadataValue2 = new Cartesian3(255, 0, 255); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon - ); - }); -}); + it("picks UINT8 VEC4 from a property texture", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC4"; + const gltf = createPropertyTextureGltfVec4(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0 + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1 + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2 + ); + + const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 0); + const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 0); + const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon + ); + }); + }, + "WebGL" +); From c854235fc1feaf99888d98435de8f2a0216aa3b5 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 11 Sep 2024 15:42:45 +0200 Subject: [PATCH 37/63] Create WebGL 1 context for metadata picking specs --- .../Specs/Scene/Model/pickMetadataSpec.js | 85 ++++++++++++++----- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 8a7a9aa88d28..f2cc1e558035 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -493,30 +493,10 @@ describe( ); let scene; - let scene2D; - let sceneCV; - - beforeAll(function () { - scene = createScene(); - - scene2D = createScene(); - scene2D.morphTo2D(0.0); - - sceneCV = createScene(); - sceneCV.morphToColumbusView(0.0); - }); - - afterAll(function () { - scene.destroyForSpecs(); - scene2D.destroyForSpecs(); - sceneCV.destroyForSpecs(); - }); afterEach(function () { scene.primitives.removeAll(); - scene2D.primitives.removeAll(); - sceneCV.primitives.removeAll(); - scene.verticalExaggeration = 1.0; + scene.destroyForSpecs(); ResourceCache.clearForSpecs(); }); @@ -525,6 +505,16 @@ describe( const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_SCALAR"; + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + scene.initializeFrame(); scene.render(defaultDate); expect(() => { @@ -537,6 +527,16 @@ describe( const schemaId = undefined; const className = undefined; // For spec const propertyName = "example_UINT8_SCALAR"; + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + scene.initializeFrame(); scene.render(defaultDate); expect(() => { @@ -549,6 +549,16 @@ describe( const schemaId = undefined; const className = "exampleClass"; const propertyName = undefined; // For spec + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + scene.initializeFrame(); scene.render(defaultDate); expect(() => { @@ -566,6 +576,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); @@ -593,6 +606,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); fitCameraToUnitSquare(scene.camera); @@ -615,6 +631,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); fitCameraToUnitSquare(scene.camera); @@ -635,6 +654,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); @@ -667,6 +689,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); @@ -727,6 +752,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); @@ -787,6 +815,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); @@ -847,6 +878,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); @@ -907,6 +941,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); @@ -968,6 +1005,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); @@ -1028,6 +1068,9 @@ describe( const canvasSizeY = textureSizeY * canvasScaling; scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, }); await loadAsModel(scene, gltf); From 384b2fa20f7c58c29db82291811de1bb3179a412 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 11 Sep 2024 16:34:25 +0200 Subject: [PATCH 38/63] Skip metadata picking specs on WebGL stubs --- .../Specs/Scene/Model/pickMetadataSpec.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index f2cc1e558035..0272abced455 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -488,6 +488,11 @@ function pickMetadataAt(scene, schemaId, className, propertyName, x, y) { describe( "Scene/pickMetadata", function () { + // When using a WebGL stub, the functionality of reading metadata + // values back from the frame buffer is not supported. So nearly + // all the tests have to be skipped. + const webglStub = !!window.webglStub; + const defaultDate = JulianDate.fromDate( new Date("January 1, 2014 12:00:00 UTC") ); @@ -648,6 +653,10 @@ describe( }); it("pickMetadataSchema picks the metadata schema object", async function () { + if (webglStub) { + return; + } + const gltf = createPropertyTextureGltfScalar(); const canvasSizeX = textureSizeX * canvasScaling; @@ -680,6 +689,10 @@ describe( }); it("picks UINT8 SCALAR from a property texture", async function () { + if (webglStub) { + return; + } + const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_SCALAR"; @@ -743,6 +756,9 @@ describe( }); it("picks normalized UINT8 SCALAR from a property texture", async function () { + if (webglStub) { + return; + } const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_normalized_UINT8_SCALAR"; @@ -806,6 +822,9 @@ describe( }); it("picks fixed length UINT8 SCALAR array from a property texture", async function () { + if (webglStub) { + return; + } const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_fixed_length_UINT8_SCALAR_array"; @@ -869,6 +888,10 @@ describe( }); it("picks UINT8 VEC2 from a property texture", async function () { + if (webglStub) { + return; + } + const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_VEC2"; @@ -932,6 +955,10 @@ describe( }); it("picks normalized UINT8 VEC2 from a property texture", async function () { + if (webglStub) { + return; + } + const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_normalized_UINT8_VEC2"; @@ -996,6 +1023,10 @@ describe( }); it("picks UINT8 VEC3 from a property texture", async function () { + if (webglStub) { + return; + } + const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_VEC3"; @@ -1059,6 +1090,10 @@ describe( }); it("picks UINT8 VEC4 from a property texture", async function () { + if (webglStub) { + return; + } + const schemaId = undefined; const className = "exampleClass"; const propertyName = "example_UINT8_VEC4"; From 3c1b4972dbc5fcbac680470de2e85ee939f59a08 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 11 Sep 2024 16:48:58 +0200 Subject: [PATCH 39/63] Make sure that scene is defined in specs --- packages/engine/Specs/Scene/Model/pickMetadataSpec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 0272abced455..b61b6bd02b70 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -500,8 +500,10 @@ describe( let scene; afterEach(function () { - scene.primitives.removeAll(); - scene.destroyForSpecs(); + if (scene) { + scene.primitives.removeAll(); + scene.destroyForSpecs(); + } ResourceCache.clearForSpecs(); }); From a005521520e3521638acda72fa41926295884dc0 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 11 Sep 2024 16:56:26 +0200 Subject: [PATCH 40/63] Do not try to remove the primitives --- packages/engine/Specs/Scene/Model/pickMetadataSpec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index b61b6bd02b70..92122fed4028 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -501,7 +501,6 @@ describe( afterEach(function () { if (scene) { - scene.primitives.removeAll(); scene.destroyForSpecs(); } ResourceCache.clearForSpecs(); From 30e80b1c0761613a4c969202124a3c76069a4d54 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 11 Sep 2024 17:20:13 +0200 Subject: [PATCH 41/63] Do not try to keep the scene --- .../Specs/Scene/Model/pickMetadataSpec.js | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js index 92122fed4028..bd9f08e942b0 100644 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js @@ -4,7 +4,6 @@ import { JulianDate, Math as CesiumMath, Model, - ResourceCache, Cartesian4, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; @@ -497,15 +496,6 @@ describe( new Date("January 1, 2014 12:00:00 UTC") ); - let scene; - - afterEach(function () { - if (scene) { - scene.destroyForSpecs(); - } - ResourceCache.clearForSpecs(); - }); - it("throws without windowPosition", async function () { const windowPosition = undefined; // For spec const schemaId = undefined; @@ -514,7 +504,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -526,6 +516,7 @@ describe( expect(() => { scene.pickMetadata(windowPosition, schemaId, className, propertyName); }).toThrowDeveloperError(); + scene.destroyForSpecs(); }); it("throws without className", async function () { @@ -536,7 +527,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -548,6 +539,7 @@ describe( expect(() => { scene.pickMetadata(windowPosition, schemaId, className, propertyName); }).toThrowDeveloperError(); + scene.destroyForSpecs(); }); it("throws without propertyName", async function () { @@ -558,7 +550,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -570,6 +562,7 @@ describe( expect(() => { scene.pickMetadata(windowPosition, schemaId, className, propertyName); }).toThrowDeveloperError(); + scene.destroyForSpecs(); }); it("returns undefined for class name that does not exist", async function () { @@ -580,7 +573,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -601,6 +594,7 @@ describe( propertyName ); expect(actualMetadataValue).toBeUndefined(); + scene.destroyForSpecs(); }); it("returns undefined when there is no object with metadata", async function () { @@ -610,7 +604,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -630,12 +624,13 @@ describe( propertyName ); expect(actualMetadataValue).toBeUndefined(); + scene.destroyForSpecs(); }); it("pickMetadataSchema returns undefined when there is no object with metadata", async function () { const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -651,6 +646,7 @@ describe( const metadataSchema = scene.pickMetadataSchema(windowPosition); expect(metadataSchema).toBeUndefined(); + scene.destroyForSpecs(); }); it("pickMetadataSchema picks the metadata schema object", async function () { @@ -662,7 +658,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -687,6 +683,7 @@ describe( expect(metadataSchema).toBeDefined(); expect(metadataSchema.id).toEqual("ExampleSchema"); expect(metadataSchema.classes).toBeDefined(); + scene.destroyForSpecs(); }); it("picks UINT8 SCALAR from a property texture", async function () { @@ -701,7 +698,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -754,6 +751,7 @@ describe( expectedMetadataValue2, propertyValueEpsilon ); + scene.destroyForSpecs(); }); it("picks normalized UINT8 SCALAR from a property texture", async function () { @@ -767,7 +765,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -820,6 +818,7 @@ describe( expectedMetadataValue2, propertyValueEpsilon ); + scene.destroyForSpecs(); }); it("picks fixed length UINT8 SCALAR array from a property texture", async function () { @@ -833,7 +832,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -886,6 +885,7 @@ describe( expectedMetadataValue2, propertyValueEpsilon ); + scene.destroyForSpecs(); }); it("picks UINT8 VEC2 from a property texture", async function () { @@ -900,7 +900,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -953,6 +953,7 @@ describe( expectedMetadataValue2, propertyValueEpsilon ); + scene.destroyForSpecs(); }); it("picks normalized UINT8 VEC2 from a property texture", async function () { @@ -967,7 +968,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -1021,6 +1022,7 @@ describe( expectedMetadataValue2, propertyValueEpsilon ); + scene.destroyForSpecs(); }); it("picks UINT8 VEC3 from a property texture", async function () { @@ -1035,7 +1037,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -1088,6 +1090,7 @@ describe( expectedMetadataValue2, propertyValueEpsilon ); + scene.destroyForSpecs(); }); it("picks UINT8 VEC4 from a property texture", async function () { @@ -1102,7 +1105,7 @@ describe( const canvasSizeX = textureSizeX * canvasScaling; const canvasSizeY = textureSizeY * canvasScaling; - scene = createScene({ + const scene = createScene({ canvas: createCanvas(canvasSizeX, canvasSizeY), contextOptions: { requestWebgl1: true, @@ -1156,6 +1159,7 @@ describe( expectedMetadataValue2, propertyValueEpsilon ); + scene.destroyForSpecs(); }); }, "WebGL" From 9007e85d50d1573e40f2f0e2849fef270d3a4b11 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 16:41:15 +0200 Subject: [PATCH 42/63] Remove debug log --- packages/engine/Source/Scene/Scene.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index de9b646ac9f2..eff324bc8ff3 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4340,30 +4340,12 @@ Scene.prototype.pickMetadata = function ( const structuralMetadata = model?.structuralMetadata; const schema = structuralMetadata?.schema; - const XXX_METADATA_PICKING_DEBUG_LOG = false; - if (XXX_METADATA_PICKING_DEBUG_LOG) { - console.log("pickedObject ", pickedObject); - console.log("model ", model); - console.log("structuralMetadata ", structuralMetadata); - console.log("schema ", schema); - } - const classProperty = getMetadataClassProperty( schema, schemaId, className, propertyName ); - if (classProperty === undefined) { - if (XXX_METADATA_PICKING_DEBUG_LOG) { - console.log("The metadata property was not found"); - console.log("schema ", schema); - console.log("schemaId ", schemaId); - console.log("className ", className); - console.log("propertyName ", propertyName); - } - return undefined; - } const pickedMetadataInfo = { schemaId: schemaId, @@ -4378,10 +4360,6 @@ Scene.prototype.pickMetadata = function ( pickedMetadataInfo ); - if (XXX_METADATA_PICKING_DEBUG_LOG) { - console.log("pickedMetadataValues ", pickedMetadataValues); - } - return pickedMetadataValues; }; From 2a92b943c9a874bcdab7fcd491e9b5cb90ef2946 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:09:27 +0200 Subject: [PATCH 43/63] Cleaner handling for dirty commands No pun intended --- packages/engine/Source/Scene/Scene.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index eff324bc8ff3..1076791101ed 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -1750,16 +1750,14 @@ function updateDerivedCommands(scene, command, shadowsDirty) { ); } if (frameState.pickingMetadata && command.pickMetadataAllowed) { - if (pickedMetadataInfoChanged(command, frameState)) { - command.pickedMetadataInfo = frameState.pickedMetadataInfo; - if (defined(command.pickedMetadataInfo)) { - derivedCommands.pickingMetadata = DerivedCommand.createPickMetadataDerivedCommand( - scene, - command, - context, - derivedCommands.pickingMetadata - ); - } + command.pickedMetadataInfo = frameState.pickedMetadataInfo; + if (defined(command.pickedMetadataInfo)) { + derivedCommands.pickingMetadata = DerivedCommand.createPickMetadataDerivedCommand( + scene, + command, + context, + derivedCommands.pickingMetadata + ); } } if (!command.pickOnly) { @@ -1844,14 +1842,18 @@ Scene.prototype.updateDerivedCommands = function (command) { useLogDepth && !hasLogDepthDerivedCommands; const needsHdrCommands = useHdr && !hasHdrCommands; const needsDerivedCommands = (!useLogDepth || !useHdr) && !hasDerivedCommands; + const needsUpdateForMetadataPicking = + frameState.pickingMetadata && + pickedMetadataInfoChanged(command, frameState); command.dirty = command.dirty || needsLogDepthDerivedCommands || needsHdrCommands || - needsDerivedCommands; + needsDerivedCommands || + needsUpdateForMetadataPicking; - if (command.dirty || frameState.pickingMetadata) { + if (command.dirty) { command.dirty = false; const shadowMaps = frameState.shadowState.shadowMaps; From 8b02ec4dbbcb2baaa3d89297a7d2e8d67dc37edb Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:10:25 +0200 Subject: [PATCH 44/63] Clarification for undefined schema ID --- packages/engine/Source/Scene/Scene.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 1076791101ed..2932463cb759 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4308,7 +4308,8 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. * @param {string|undefined} schemaId The ID of the metadata schema to pick values - * from, or `undefined` to pick values from any schema. + * from. If this is `undefined`, then it will pick the values from the object + * that match the given class- and property name, regardless of the schema ID. * @param {string} className The name of the metadata class to pick * values from * @param {string} propertyName The name of the metadata property to pick From 77351118d1e94d4938d5b56989a5707330d7f8c8 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:11:08 +0200 Subject: [PATCH 45/63] Optional chaining. Fix from removed debug log. --- packages/engine/Source/Scene/Scene.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 2932463cb759..62155df85460 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4338,17 +4338,19 @@ Scene.prototype.pickMetadata = function ( // Check if the picked object is a model that has structural // metadata, with a schema that contains the specified // property. - const detail = pickedObject?.detail; - const model = detail?.model; - const structuralMetadata = model?.structuralMetadata; - const schema = structuralMetadata?.schema; - + // Check if the picked object is a model that has structural + // metadata, with a schema that contains the specified + // property. + const schema = pickedObject.detail?.model?.structuralMetadata?.schema; const classProperty = getMetadataClassProperty( schema, schemaId, className, propertyName ); + if (!defined(classProperty)) { + return undefined; + } const pickedMetadataInfo = { schemaId: schemaId, From d81641846bc75df905b939528a24f727b6ab4fdb Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:12:40 +0200 Subject: [PATCH 46/63] Optional chaining. Comment cleanups. --- packages/engine/Source/Scene/Scene.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 62155df85460..3c0dc7bc1777 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4335,9 +4335,6 @@ Scene.prototype.pickMetadata = function ( return undefined; } - // Check if the picked object is a model that has structural - // metadata, with a schema that contains the specified - // property. // Check if the picked object is a model that has structural // metadata, with a schema that contains the specified // property. @@ -4372,7 +4369,7 @@ Scene.prototype.pickMetadata = function ( * Pick the schema of the metadata of the object at the given position * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. - * @returns The metadata schema, or `undefined` if there is no object with + * @returns {MetadataSchema} The metadata schema, or `undefined` if there is no object with * associated metadata at the given position. * * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy. @@ -4386,11 +4383,7 @@ Scene.prototype.pickMetadataSchema = function (windowPosition) { if (!defined(pickedObject)) { return undefined; } - - const detail = pickedObject?.detail; - const model = detail?.model; - const structuralMetadata = model?.structuralMetadata; - const schema = structuralMetadata?.schema; + const schema = pickedObject.detail?.model?.structuralMetadata?.schema; return schema; }; From 7d34d0dc1e43864b11ab3005b6cb09c71f2d24c6 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:13:30 +0200 Subject: [PATCH 47/63] Return type in JSDoc --- packages/engine/Source/Scene/Picking.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index ade2b8a9e8eb..2a9902347886 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -245,7 +245,7 @@ const scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0); * be an odd integer number, default: 3.0 * @param {number|undefined} height - The height of the rectangle, default: width * @param {BoundingRectangle} result The result rectangle - * @returns The result rectangle + * @returns {BoundingRectangle} The result rectangle */ function computePickingDrawingBufferRectangle( drawingBufferHeight, From 8af87b2134995e332096cf78fe2c21f07d502d43 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:26:28 +0200 Subject: [PATCH 48/63] Pull PickedMetadataInfo into class --- .../engine/Source/Scene/PickedMetadataInfo.js | 39 +++++++++++++++++++ packages/engine/Source/Scene/Scene.js | 13 ++++--- 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 packages/engine/Source/Scene/PickedMetadataInfo.js diff --git a/packages/engine/Source/Scene/PickedMetadataInfo.js b/packages/engine/Source/Scene/PickedMetadataInfo.js new file mode 100644 index 000000000000..1e8de1902b17 --- /dev/null +++ b/packages/engine/Source/Scene/PickedMetadataInfo.js @@ -0,0 +1,39 @@ +/** + * Information about metadata that is supposed to be picked. + * + * This is initialized in the `Scene.pickMetadata` function, and passed to + * the `FrameState`. It is used to configure the draw commands that render + * the metadata values of an object into the picking frame buffer. The + * raw values are read from that buffer, and are then translated back into + * proper metadata values in `Picking.pickMetadata`, using the structural + * information about the metadata `classProperty` that is stored here. + * + * @private + */ +function PickedMetadataInfo(schemaId, className, propertyName, classProperty) { + /** + * The optional ID of the metadata schema + * + * @type {string|undefined} + */ + this.schemaId = schemaId; + /** + * The name of the metadata class + * + * @type {string} + */ + this.className = className; + /** + * The name of the metadata property + * + * @type {string} + */ + this.propertyName = propertyName; + /** + * The optional ID of the metadata schema + * + * @type {MetadataClassProperty} + */ + this.classProperty = classProperty; +} +export default PickedMetadataInfo; diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 3c0dc7bc1777..4339b7f400ca 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -78,6 +78,7 @@ import DebugInspector from "./DebugInspector.js"; import VoxelCell from "./VoxelCell.js"; import VoxelPrimitive from "./VoxelPrimitive.js"; import getMetadataClassProperty from "./getMetadataClassProperty.js"; +import PickedMetadataInfo from "./PickedMetadataInfo.js"; const requestRenderAfterFrame = function (scene) { return function () { @@ -4349,12 +4350,12 @@ Scene.prototype.pickMetadata = function ( return undefined; } - const pickedMetadataInfo = { - schemaId: schemaId, - className: className, - propertyName: propertyName, - classProperty: classProperty, - }; + const pickedMetadataInfo = new PickedMetadataInfo( + schemaId, + className, + propertyName, + classProperty + ); const pickedMetadataValues = this._picking.pickMetadata( this, From 6054373107981151779ac55406c11604826df222 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:35:49 +0200 Subject: [PATCH 49/63] Add type information in JSDoc --- packages/engine/Source/Scene/Picking.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 2a9902347886..ddb97d77a05a 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -414,9 +414,20 @@ Picking.prototype.pickVoxelCoordinate = function ( * The given `pickedMetadataInfo` defines the metadata value that is * supposed to be picked. * + * The return type will depend on the type of the metadata property + * that is picked. Given the current limitations of the types that + * are supported for metadata picking, the return type will be one + * of the following: + * + * - For `SCALAR`, the return type will be a `number` + * - For `SCALAR` arrays, the return type will be a `number[]` + * - For `VEC2`, the return type will be a `Cartesian2` + * - For `VEC3`, the return type will be a `Cartesian3` + * - For `VEC4`, the return type will be a `Cartesian4` + * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. * @param {PickedMetadataInfo} pickedMetadataInfo Information about the picked metadata. - * @returns The metadata values + * @returns {object} The metadata values * * @private */ From e1e2ae6600dc193a1d9c7c7becbdc36e08b9a43c Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:36:22 +0200 Subject: [PATCH 50/63] Fix return type in JSDoc --- packages/engine/Source/Scene/Picking.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index ddb97d77a05a..f9f9fc9596ae 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -427,7 +427,7 @@ Picking.prototype.pickVoxelCoordinate = function ( * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. * @param {PickedMetadataInfo} pickedMetadataInfo Information about the picked metadata. - * @returns {object} The metadata values + * @returns {any} The metadata values * * @private */ From 6f7f16215d94f3b6a9e5439f913bf88d65bfa55e Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:52:41 +0200 Subject: [PATCH 51/63] Added return types in JSDoc --- packages/engine/Source/Scene/DerivedCommand.js | 2 +- packages/engine/Source/Scene/MetadataPicking.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 7d3b29b4288a..8e05e13aefca 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -415,7 +415,7 @@ function replaceDefine(defines, defineName, newDefineValue) { * or the array length if it is an array. * * @param {MetadataClassProperty} classProperty The class property - * @returns The component count + * @returns {number} The component count * @private */ function getComponentCount(classProperty) { diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index 55a377555592..fc1f2f170ce5 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -78,7 +78,7 @@ MetadataPicking.decodeRawMetadataValue = function ( * @param {DataView} dataView The data view containing the raw metadata values * @param {number} dataViewOffset The byte offset within the data view from * which the component should be read - * @returns The metadata value component + * @returns {number|bigint|undefined} The metadata value component */ MetadataPicking.decodeRawMetadataValueComponent = function ( classProperty, @@ -115,7 +115,7 @@ MetadataPicking.decodeRawMetadataValueComponent = function ( * @param {DataView} dataView The data view containing the raw metadata values * @param {number} elementIndex The index of the element. This is the index * inside the array for array-typed properties, and 0 for non-array types. - * @returns The decoded metadata value element + * @returns {number|number[]|bigint|bigint[]|undefined} The decoded metadata value element */ MetadataPicking.decodeRawMetadataValueElement = function ( classProperty, @@ -232,7 +232,7 @@ MetadataPicking.decodeRawMetadataValues = function ( * * @param {string} type The `ClassProperty` type * @param {number|bigint|number[]|bigint[]|undefined} value The input value - * @returns The object representation + * @returns {any} The object representation */ MetadataPicking.convertToObjectType = function (type, value) { if (!defined(value)) { From 651a2b2282c1b8e9fdc9e2c665da4d850005ec30 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:58:34 +0200 Subject: [PATCH 52/63] Add return types in JSDoc --- packages/engine/Source/Scene/DerivedCommand.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Scene/DerivedCommand.js b/packages/engine/Source/Scene/DerivedCommand.js index 8e05e13aefca..7e0029fc1d0b 100644 --- a/packages/engine/Source/Scene/DerivedCommand.js +++ b/packages/engine/Source/Scene/DerivedCommand.js @@ -432,7 +432,7 @@ function getComponentCount(classProperty) { * for a property texture property with the given class property * * @param {MetadataClassProperty} classProperty The class property - * @returns The GLSL shader type string for the property + * @returns {string} The GLSL shader type string for the property */ function getGlslType(classProperty) { const componentCount = getComponentCount(classProperty); @@ -464,7 +464,7 @@ function getGlslType(classProperty) { * @param {Context} context The context * @param {ShaderProgram} shaderProgram The shader program * @param {PickedMetadataInfo} pickedMetadataInfo The picked metadata info - * @returns The new shader program + * @returns {ShaderProgram} The new shader program * @private */ function getPickMetadataShaderProgram( From a7a0ece3199995fc3e937480981ec04aba5fba30 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 27 Sep 2024 17:58:54 +0200 Subject: [PATCH 53/63] Make functions local, as per review comment... --- .../Source/Scene/Model/ModelDrawCommands.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommands.js b/packages/engine/Source/Scene/Model/ModelDrawCommands.js index 34508267a110..bdedec155a06 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommands.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommands.js @@ -40,13 +40,13 @@ ModelDrawCommands.buildModelDrawCommand = function ( frameState ) { const shaderBuilder = primitiveRenderResources.shaderBuilder; - const shaderProgram = ModelDrawCommands.createShaderProgram( + const shaderProgram = createShaderProgram( primitiveRenderResources, shaderBuilder, frameState ); - const command = ModelDrawCommands.buildDrawCommandForModel( + const command = buildDrawCommandForModel( primitiveRenderResources, shaderProgram, frameState @@ -70,7 +70,7 @@ ModelDrawCommands.buildModelDrawCommand = function ( /** * @private */ -ModelDrawCommands.createShaderProgram = function ( +function createShaderProgram( primitiveRenderResources, shaderBuilder, frameState @@ -82,7 +82,7 @@ ModelDrawCommands.createShaderProgram = function ( const shaderProgram = shaderBuilder.buildShaderProgram(frameState.context); model._pipelineResources.push(shaderProgram); return shaderProgram; -}; +} /** * Builds the {@link DrawCommand} that serves as the basis for either creating @@ -97,14 +97,12 @@ ModelDrawCommands.createShaderProgram = function ( * * @private */ -ModelDrawCommands.buildDrawCommandForModel = function ( +function buildDrawCommandForModel( primitiveRenderResources, shaderProgram, frameState ) { - const indexBuffer = ModelDrawCommands.getIndexBuffer( - primitiveRenderResources - ); + const indexBuffer = getIndexBuffer(primitiveRenderResources); const vertexArray = new VertexArray({ context: frameState.context, @@ -192,12 +190,12 @@ ModelDrawCommands.buildDrawCommandForModel = function ( receiveShadows: receiveShadows, }); return command; -}; +} /** * @private */ -ModelDrawCommands.getIndexBuffer = function (primitiveRenderResources) { +function getIndexBuffer(primitiveRenderResources) { const wireframeIndexBuffer = primitiveRenderResources.wireframeIndexBuffer; if (defined(wireframeIndexBuffer)) { return wireframeIndexBuffer; @@ -215,6 +213,6 @@ ModelDrawCommands.getIndexBuffer = function (primitiveRenderResources) { //>>includeEnd('debug'); return indices.buffer; -}; +} export default ModelDrawCommands; From 942a178ad10918488bbe56568487f55c97740cc9 Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 30 Sep 2024 12:58:56 -0400 Subject: [PATCH 54/63] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 57e24aa40a4b..145d84df492c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Added `enableVerticalExaggeration` option to models. Set this value to `false` to prevent model exaggeration when `Scene.verticalExaggeration` is set to a value other than `1.0`. [#12141](https://github.com/CesiumGS/cesium/pull/12141) - Added `CallbackPositionProperty` to allow lazy entity position evaluation. [#12170](https://github.com/CesiumGS/cesium/pull/12170) +- Added `Scene.prototype.pickMetadata` and `Scene.prototype.pickMetadataSchema`, enabling experimental support for picking property textures or property attributes. ##### Fixes :wrench: From efe3510e9e9c3bc10038feee8cfc604dc2ed25d4 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 19:37:54 +0200 Subject: [PATCH 55/63] Removed obsolete comment --- packages/engine/Source/Scene/MetadataPicking.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index b38fd5f387c7..c7cfc3ec308a 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -16,8 +16,6 @@ import MetadataType from "./MetadataType.js"; * into the actual metadata values, according to the structure * defined by the `MetadataClassProperty`. * - * This is marked as 'private', but supposed to be used in Picking.js. - * * @private */ const MetadataPicking = {}; From 35bdc13a7ddfff742fdc83347f5d43649fa82b0e Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 19:39:08 +0200 Subject: [PATCH 56/63] Throw error for invalid component types --- packages/engine/Source/Scene/MetadataPicking.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index c7cfc3ec308a..b85c64a69275 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -5,6 +5,7 @@ import defined from "../Core/defined.js"; import Matrix2 from "../Core/Matrix2.js"; import Matrix3 from "../Core/Matrix3.js"; import Matrix4 from "../Core/Matrix4.js"; +import RuntimeError from "../Core/RuntimeError.js"; import MetadataComponentType from "./MetadataComponentType.js"; import MetadataType from "./MetadataType.js"; @@ -58,8 +59,7 @@ MetadataPicking.decodeRawMetadataValue = function ( case MetadataComponentType.FLOAT64: return dataView.getFloat64(index); } - // Appropriate error handling? - return undefined; + throw new RuntimeError(`Invalid component type: ${componentType}`); }; /** From 93b994a72dcc308d0fe57d3c3dd5520661c2e5bd Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 19:40:49 +0200 Subject: [PATCH 57/63] Use predefined constants --- .../engine/Source/Scene/MetadataPicking.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/engine/Source/Scene/MetadataPicking.js b/packages/engine/Source/Scene/MetadataPicking.js index b85c64a69275..87f33a91d7d3 100644 --- a/packages/engine/Source/Scene/MetadataPicking.js +++ b/packages/engine/Source/Scene/MetadataPicking.js @@ -236,26 +236,26 @@ MetadataPicking.convertToObjectType = function (type, value) { return value; } if ( - type === "SCALAR" || - type === "STRING" || - type === "BOOLEAN" || - type === "ENUM" + type === MetadataType.SCALAR || + type === MetadataType.STRING || + type === MetadataType.BOOLEAN || + type === MetadataType.ENUM ) { return value; } const numbers = value.map((n) => Number(n)); switch (type) { - case "VEC2": + case MetadataType.VEC2: return Cartesian2.unpack(numbers, 0, new Cartesian2()); - case "VEC3": + case MetadataType.VEC3: return Cartesian3.unpack(numbers, 0, new Cartesian3()); - case "VEC4": + case MetadataType.VEC4: return Cartesian4.unpack(numbers, 0, new Cartesian3()); - case "MAT2": + case MetadataType.MAT2: return Matrix2.unpack(numbers, 0, new Matrix2()); - case "MAT3": + case MetadataType.MAT3: return Matrix3.unpack(numbers, 0, new Matrix3()); - case "MAT4": + case MetadataType.MAT4: return Matrix4.unpack(numbers, 0, new Matrix4()); } // Should never happen: From 17bc38bd259f7491b227bef28946c4ae05eb3f51 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 19:41:36 +0200 Subject: [PATCH 58/63] Update comment in Picking.js via review suggestion Co-authored-by: Gabby Getz --- packages/engine/Source/Scene/Picking.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 9c1fb3eebf65..7b8ffbf949e3 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -239,11 +239,11 @@ const scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0); * Compute the rectangle that describes the part of the drawing buffer * that is relevant for picking. * - * @param {number} drawingBufferHeight - The height of the drawing buffer - * @param {Cartesian2} position - The position inside the drawing buffer - * @param {number|undefined} width - The width of the rectangle, asssumed to - * be an odd integer number, default: 3.0 - * @param {number|undefined} height - The height of the rectangle, default: width + * @param {number} drawingBufferHeight The height of the drawing buffer + * @param {Cartesian2} position The position inside the drawing buffer + * @param {number} [width=3.0] The width of the rectangle, assumed to + * be an odd integer number. + * @param {number} [height] The height of the rectangle. If unspecified, height will default to the value of width * @param {BoundingRectangle} result The result rectangle * @returns {BoundingRectangle} The result rectangle */ From 4f99d1b9f156bc7e29fa33379254c45c8ee4ea12 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 19:42:48 +0200 Subject: [PATCH 59/63] Update comment in Scene.js from review suggestion Co-authored-by: Gabby Getz --- packages/engine/Source/Scene/Scene.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index e751dda94087..dd346924961c 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4395,7 +4395,7 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { * Pick a metadata value at the given window position. * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. - * @param {string|undefined} schemaId The ID of the metadata schema to pick values + * @param {string} [schemaId] The ID of the metadata schema to pick values * from. If this is `undefined`, then it will pick the values from the object * that match the given class- and property name, regardless of the schema ID. * @param {string} className The name of the metadata class to pick From 7300ae101e7051742833fe6eeb523ac2388f96b7 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 19:43:07 +0200 Subject: [PATCH 60/63] Update comment from review suggestion Co-authored-by: Gabby Getz --- packages/engine/Source/Scene/getMetadataClassProperty.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/getMetadataClassProperty.js b/packages/engine/Source/Scene/getMetadataClassProperty.js index 3fb88ad7367e..2119e9742066 100644 --- a/packages/engine/Source/Scene/getMetadataClassProperty.js +++ b/packages/engine/Source/Scene/getMetadataClassProperty.js @@ -14,7 +14,7 @@ import defined from "../Core/defined.js"; * Otherwise, the `MetadataClassProperty` is returned. * * @param {object} schema The schema object - * @param {string|undefined} schemaId The ID of the metadata schema + * @param {string} [schemaId] The ID of the metadata schema * @param {string} className The name of the metadata class * @param {string} propertyName The name of the metadata property * @returns {MetadataClassProperty|undefined} From 59b4787d8f667a3485d46f49b64107d698d7912e Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 20:07:23 +0200 Subject: [PATCH 61/63] Do not declare required parameters as optional --- packages/engine/Source/Scene/Scene.js | 2 +- packages/engine/Source/Scene/getMetadataClassProperty.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index dd346924961c..e751dda94087 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -4395,7 +4395,7 @@ Scene.prototype.pickVoxel = function (windowPosition, width, height) { * Pick a metadata value at the given window position. * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. - * @param {string} [schemaId] The ID of the metadata schema to pick values + * @param {string|undefined} schemaId The ID of the metadata schema to pick values * from. If this is `undefined`, then it will pick the values from the object * that match the given class- and property name, regardless of the schema ID. * @param {string} className The name of the metadata class to pick diff --git a/packages/engine/Source/Scene/getMetadataClassProperty.js b/packages/engine/Source/Scene/getMetadataClassProperty.js index 2119e9742066..3fb88ad7367e 100644 --- a/packages/engine/Source/Scene/getMetadataClassProperty.js +++ b/packages/engine/Source/Scene/getMetadataClassProperty.js @@ -14,7 +14,7 @@ import defined from "../Core/defined.js"; * Otherwise, the `MetadataClassProperty` is returned. * * @param {object} schema The schema object - * @param {string} [schemaId] The ID of the metadata schema + * @param {string|undefined} schemaId The ID of the metadata schema * @param {string} className The name of the metadata class * @param {string} propertyName The name of the metadata property * @returns {MetadataClassProperty|undefined} From 2ee6366d3316ed7edbf18df77e4b0e57967b91a3 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 20:17:16 +0200 Subject: [PATCH 62/63] Do not declare parameters as optional --- packages/engine/Source/Scene/Picking.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/Scene/Picking.js b/packages/engine/Source/Scene/Picking.js index 7b8ffbf949e3..fc38a4a90955 100644 --- a/packages/engine/Source/Scene/Picking.js +++ b/packages/engine/Source/Scene/Picking.js @@ -241,9 +241,10 @@ const scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0); * * @param {number} drawingBufferHeight The height of the drawing buffer * @param {Cartesian2} position The position inside the drawing buffer - * @param {number} [width=3.0] The width of the rectangle, assumed to - * be an odd integer number. - * @param {number} [height] The height of the rectangle. If unspecified, height will default to the value of width + * @param {number|undefined} width The width of the rectangle, assumed to + * be an odd integer number, default : 3.0 + * @param {number|undefined} height The height of the rectangle. If unspecified, + * height will default to the value of width * @param {BoundingRectangle} result The result rectangle * @returns {BoundingRectangle} The result rectangle */ From 280e8d9f145d3265d047d3c8072ae51728181c9f Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Mon, 30 Sep 2024 21:13:27 +0200 Subject: [PATCH 63/63] Move metadata picking specs into SceneSpec --- .../Specs/Scene/Model/pickMetadataSpec.js | 1163 ----------------- packages/engine/Specs/Scene/SceneSpec.js | 1148 ++++++++++++++++ 2 files changed, 1148 insertions(+), 1163 deletions(-) delete mode 100644 packages/engine/Specs/Scene/Model/pickMetadataSpec.js diff --git a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js b/packages/engine/Specs/Scene/Model/pickMetadataSpec.js deleted file mode 100644 index 466c8651c916..000000000000 --- a/packages/engine/Specs/Scene/Model/pickMetadataSpec.js +++ /dev/null @@ -1,1163 +0,0 @@ -import { - Cartesian2, - Cartesian3, - JulianDate, - Math as CesiumMath, - Model, - Cartesian4, -} from "../../../index.js"; -import createScene from "../../../../../Specs/createScene.js"; -import createCanvas from "../../../../../Specs/createCanvas.js"; -import pollToPromise from "../../../../../Specs/pollToPromise.js"; - -// The size of the property texture -const textureSizeX = 16; -const textureSizeY = 16; - -// A scaling factor (to be applied to the texture size) for -// determining the size of the ("debug") canvas that shows -// the scene where the picking takes place -const canvasScaling = 32; - -// The 'toEqualEpsilon' matcher (which is which is defined -// in `Specs/addDefaultMatchers.js`, by the way...) uses -// the epsilon as a relative epsilon, and there is no way -// to pass in an absolute epsilon. For comparing the elements -// of a Cartesian2 that stores UINT8 values, an absolute -// epsilon of 1.0 would be handy. But... here we go: -const propertyValueEpsilon = 0.01; - -/** - * Creates an embedded glTF asset with a property texture. - * - * This creates an assed that represents a unit square and uses - * the `EXT_structural_metadata` extension to assign a single - * property texture to this square. - * - * @param {object} schema The metadata schema - * @param {object} propertyTextureProperties The property texture properties - * @returns The gltf - */ -function createEmbeddedGltfWithPropertyTexture( - schema, - propertyTextureProperties, -) { - const result = { - extensions: { - EXT_structural_metadata: { - schema: schema, - propertyTextures: [ - { - class: "exampleClass", - properties: propertyTextureProperties, - }, - ], - }, - }, - extensionsUsed: ["EXT_structural_metadata"], - accessors: [ - { - bufferView: 0, - byteOffset: 0, - componentType: 5123, - count: 6, - type: "SCALAR", - max: [3], - min: [0], - }, - { - bufferView: 1, - byteOffset: 0, - componentType: 5126, - count: 4, - type: "VEC3", - max: [1.0, 1.0, 0.0], - min: [0.0, 0.0, 0.0], - }, - { - bufferView: 1, - byteOffset: 48, - componentType: 5126, - count: 4, - type: "VEC3", - max: [0.0, 0.0, 1.0], - min: [0.0, 0.0, 1.0], - }, - { - bufferView: 1, - byteOffset: 96, - componentType: 5126, - count: 4, - type: "VEC2", - max: [1.0, 1.0], - min: [0.0, 0.0], - }, - ], - asset: { - generator: "JglTF from https://github.com/javagl/JglTF", - version: "2.0", - }, - buffers: [ - { - uri: "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAA", - byteLength: 156, - }, - ], - bufferViews: [ - { - buffer: 0, - byteOffset: 0, - byteLength: 12, - target: 34963, - }, - { - buffer: 0, - byteOffset: 12, - byteLength: 144, - byteStride: 12, - target: 34962, - }, - ], - images: [ - { - // A 16x16 pixels image that contains all combinations of - // (0, 127, 255) in its upper-left 9x9 pixels - uri: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAi0lEQVR42u2RUQ6AMAhDd3OO/qQt8VP8NRHjNpf0leI5ruqXbNVL4c9Dn+E8ljV+iLaXaoAY1YDaADaynBg2gFZLR1+wAdJEWZpW1AIVqmjCruqybw4qnEJbbQBHdWoS2XIUXdp+F8DNUOpM0tIZCusQJrzHNTnsOy2pFTZ7xpKhYFUu4M1v+OvrdQGABqEpS2kSLgAAAABJRU5ErkJggg==", - mimeType: "image/png", - }, - ], - materials: [ - { - pbrMetallicRoughness: { - baseColorFactor: [1.0, 1.0, 1.0, 1.0], - metallicFactor: 0.0, - roughnessFactor: 1.0, - }, - alphaMode: "OPAQUE", - doubleSided: true, - }, - ], - meshes: [ - { - primitives: [ - { - extensions: { - EXT_structural_metadata: { - propertyTextures: [0], - }, - }, - attributes: { - POSITION: 1, - NORMAL: 2, - TEXCOORD_0: 3, - }, - indices: 0, - material: 0, - mode: 4, - }, - ], - }, - ], - nodes: [ - { - mesh: 0, - }, - ], - samplers: [ - { - magFilter: 9728, - minFilter: 9728, - }, - ], - scene: 0, - scenes: [ - { - nodes: [0], - }, - ], - textures: [ - { - sampler: 0, - source: 0, - }, - { - sampler: 0, - source: 1, - }, - ], - }; - return result; -} - -/** - * Create an embedded glTF with the default property texture, - * and the given schema and property texture properties. - * - * @param {object} schema The JSON form of the metadata schema - * @param {object[]} properties The JSON form of the property texture properties - * @returns The glTF - */ -function createPropertyTextureGltf(schema, properties) { - const gltf = createEmbeddedGltfWithPropertyTexture(schema, properties); - /*/ - // Copy-and-paste this into a file to have the actual glTF: - console.log("SPEC GLTF:"); - console.log("-".repeat(80)); - console.log(JSON.stringify(gltf, null, 2)); - console.log("-".repeat(80)); - //*/ - return gltf; -} - -/** - * Creates the glTF for the 'scalar' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfScalar() { - const schema = { - id: "ExampleSchema", - classes: { - exampleClass: { - name: "Example class", - properties: { - example_UINT8_SCALAR: { - name: "Example SCALAR property with UINT8 components", - type: "SCALAR", - componentType: "UINT8", - }, - example_normalized_UINT8_SCALAR: { - name: "Example SCALAR property with normalized UINT8 components", - type: "SCALAR", - componentType: "UINT8", - normalized: true, - }, - }, - }, - }, - }; - const properties = { - example_UINT8_SCALAR: { - index: 0, - texCoord: 0, - channels: [0], - }, - example_normalized_UINT8_SCALAR: { - index: 0, - texCoord: 0, - channels: [1], - }, - }; - return createPropertyTextureGltf(schema, properties); -} - -/** - * Creates the glTF for the 'scalar array' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfScalarArray() { - const schema = { - id: "ExampleSchema", - classes: { - exampleClass: { - name: "Example class", - properties: { - example_fixed_length_UINT8_SCALAR_array: { - name: "Example fixed-length SCALAR array property with UINT8 components", - type: "SCALAR", - componentType: "UINT8", - array: true, - count: 3, - }, - }, - }, - }, - }; - const properties = { - example_fixed_length_UINT8_SCALAR_array: { - index: 0, - texCoord: 0, - channels: [0, 1, 2], - }, - }; - return createPropertyTextureGltf(schema, properties); -} - -/** - * Creates the glTF for the 'vec2' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfVec2() { - const schema = { - id: "ExampleSchema", - classes: { - exampleClass: { - name: "Example class", - properties: { - example_UINT8_VEC2: { - name: "Example VEC2 property with UINT8 components", - type: "VEC2", - componentType: "UINT8", - }, - }, - }, - }, - }; - const properties = { - example_UINT8_VEC2: { - index: 0, - texCoord: 0, - channels: [0, 1], - }, - }; - return createPropertyTextureGltf(schema, properties); -} - -/** - * Creates the glTF for the normalized 'vec2' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfNormalizedVec2() { - const schema = { - id: "ExampleSchema", - classes: { - exampleClass: { - name: "Example class", - properties: { - example_normalized_UINT8_VEC2: { - name: "Example VEC2 property with normalized UINT8 components", - type: "VEC2", - componentType: "UINT8", - normalized: true, - }, - }, - }, - }, - }; - const properties = { - example_normalized_UINT8_VEC2: { - index: 0, - texCoord: 0, - channels: [0, 1], - }, - }; - return createPropertyTextureGltf(schema, properties); -} - -/** - * Creates the glTF for the 'vec3' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfVec3() { - const schema = { - id: "ExampleSchema", - classes: { - exampleClass: { - name: "Example class", - properties: { - example_UINT8_VEC3: { - name: "Example VEC3 property with UINT8 components", - type: "VEC3", - componentType: "UINT8", - }, - }, - }, - }, - }; - const properties = { - example_UINT8_VEC3: { - index: 0, - texCoord: 0, - channels: [0, 1, 2], - }, - }; - return createPropertyTextureGltf(schema, properties); -} - -/** - * Creates the glTF for the 'vec4' test case - * - * @returns The glTF - */ -function createPropertyTextureGltfVec4() { - const schema = { - id: "ExampleSchema", - classes: { - exampleClass: { - name: "Example class", - properties: { - example_UINT8_VEC4: { - name: "Example VEC4 property with UINT8 components", - type: "VEC4", - componentType: "UINT8", - }, - }, - }, - }, - }; - const properties = { - example_UINT8_VEC4: { - index: 0, - texCoord: 0, - channels: [0, 1, 2, 3], - }, - }; - return createPropertyTextureGltf(schema, properties); -} - -/** - * Create a model from the given glTF, add it as a primitive - * to the given scene, and wait until it is fully loaded. - * - * @param {Scene} scene The scene - * @param {object} gltf The gltf - */ -async function loadAsModel(scene, gltf) { - const basePath = "SPEC_BASE_PATH"; - const model = await Model.fromGltfAsync({ - gltf: gltf, - basePath: basePath, - // This is important to make sure that the property - // texture is fully loaded when the model is rendered! - incrementallyLoadTextures: false, - }); - scene.primitives.add(model); - - await pollToPromise( - function () { - scene.renderForSpecs(); - return model.ready; - }, - { timeout: 10000 }, - ); -} - -/** - * Move the camera to exactly look at the unit square along -X - * - * @param {Camera} camera - */ -function fitCameraToUnitSquare(camera) { - const fov = CesiumMath.PI_OVER_THREE; - camera.frustum.fov = fov; - camera.frustum.near = 0.01; - camera.frustum.far = 100.0; - const distance = 1.0 / (2.0 * Math.tan(fov * 0.5)); - camera.position = new Cartesian3(distance, 0.5, 0.5); - camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); - camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); - camera.right = Cartesian3.clone(Cartesian3.UNIT_Y); -} - -/** - * Pick the specified metadata value from the screen that is contained in - * the property texture at the given coordinates. - * - * (This assumes that the property texture is on a unit square, and - * fitCameraToUnitSquare was called) - * - * @param {Scene} scene The scene - * @param {string|undefined} schemaId The schema ID - * @param {string} className The class name - * @param {string} propertyName The property name - * @param {number} x The x-coordinate in the texture - * @param {number} y The y-coordinate in the texture - * @returns The metadata value - */ -function pickMetadataAt(scene, schemaId, className, propertyName, x, y) { - const screenX = Math.floor(x * canvasScaling + canvasScaling / 2); - const screenY = Math.floor(y * canvasScaling + canvasScaling / 2); - const screenPosition = new Cartesian2(screenX, screenY); - const metadataValue = scene.pickMetadata( - screenPosition, - schemaId, - className, - propertyName, - ); - return metadataValue; -} - -describe( - "Scene/pickMetadata", - function () { - // When using a WebGL stub, the functionality of reading metadata - // values back from the frame buffer is not supported. So nearly - // all the tests have to be skipped. - const webglStub = !!window.webglStub; - - const defaultDate = JulianDate.fromDate( - new Date("January 1, 2014 12:00:00 UTC"), - ); - - it("throws without windowPosition", async function () { - const windowPosition = undefined; // For spec - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); - scene.destroyForSpecs(); - }); - - it("throws without className", async function () { - const windowPosition = new Cartesian2(); - const schemaId = undefined; - const className = undefined; // For spec - const propertyName = "example_UINT8_SCALAR"; - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); - scene.destroyForSpecs(); - }); - - it("throws without propertyName", async function () { - const windowPosition = new Cartesian2(); - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = undefined; // For spec - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - scene.initializeFrame(); - scene.render(defaultDate); - expect(() => { - scene.pickMetadata(windowPosition, schemaId, className, propertyName); - }).toThrowDeveloperError(); - scene.destroyForSpecs(); - }); - - it("returns undefined for class name that does not exist", async function () { - const schemaId = undefined; - const className = "exampleClass_THAT_DOES_NOT_EXIST"; // For spec - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2), - ); - const actualMetadataValue = scene.pickMetadata( - windowPosition, - schemaId, - className, - propertyName, - ); - expect(actualMetadataValue).toBeUndefined(); - scene.destroyForSpecs(); - }); - - it("returns undefined when there is no object with metadata", async function () { - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - fitCameraToUnitSquare(scene.camera); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2), - ); - const actualMetadataValue = scene.pickMetadata( - windowPosition, - schemaId, - className, - propertyName, - ); - expect(actualMetadataValue).toBeUndefined(); - scene.destroyForSpecs(); - }); - - it("pickMetadataSchema returns undefined when there is no object with metadata", async function () { - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - fitCameraToUnitSquare(scene.camera); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2), - ); - const metadataSchema = scene.pickMetadataSchema(windowPosition); - - expect(metadataSchema).toBeUndefined(); - scene.destroyForSpecs(); - }); - - it("pickMetadataSchema picks the metadata schema object", async function () { - if (webglStub) { - return; - } - - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const windowPosition = new Cartesian2( - Math.floor(canvasSizeX / 2), - Math.floor(canvasSizeY / 2), - ); - - // The pickMetadataSchema call should return the schema that - // was defined in createPropertyTextureGltfScalar - const metadataSchema = scene.pickMetadataSchema(windowPosition); - - expect(metadataSchema).toBeDefined(); - expect(metadataSchema.id).toEqual("ExampleSchema"); - expect(metadataSchema.classes).toBeDefined(); - scene.destroyForSpecs(); - }); - - it("picks UINT8 SCALAR from a property texture", async function () { - if (webglStub) { - return; - } - - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0, - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 1, - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 2, - ); - const expectedMetadataValue0 = 0; - const expectedMetadataValue1 = 127; - const expectedMetadataValue2 = 255; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon, - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon, - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon, - ); - scene.destroyForSpecs(); - }); - - it("picks normalized UINT8 SCALAR from a property texture", async function () { - if (webglStub) { - return; - } - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_normalized_UINT8_SCALAR"; - const gltf = createPropertyTextureGltfScalar(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0, - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 3, - 0, - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 6, - 0, - ); - const expectedMetadataValue0 = 0.0; - const expectedMetadataValue1 = 0.5; - const expectedMetadataValue2 = 1.0; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon, - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon, - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon, - ); - scene.destroyForSpecs(); - }); - - it("picks fixed length UINT8 SCALAR array from a property texture", async function () { - if (webglStub) { - return; - } - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_fixed_length_UINT8_SCALAR_array"; - const gltf = createPropertyTextureGltfScalarArray(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0, - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1, - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2, - ); - const expectedMetadataValue0 = [0, 0, 0]; - const expectedMetadataValue1 = [127, 0, 127]; - const expectedMetadataValue2 = [255, 0, 255]; - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon, - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon, - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon, - ); - scene.destroyForSpecs(); - }); - - it("picks UINT8 VEC2 from a property texture", async function () { - if (webglStub) { - return; - } - - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC2"; - const gltf = createPropertyTextureGltfVec2(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0, - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1, - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2, - ); - const expectedMetadataValue0 = new Cartesian2(0, 0); - const expectedMetadataValue1 = new Cartesian2(127, 0); - const expectedMetadataValue2 = new Cartesian2(255, 0); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon, - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon, - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon, - ); - scene.destroyForSpecs(); - }); - - it("picks normalized UINT8 VEC2 from a property texture", async function () { - if (webglStub) { - return; - } - - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_normalized_UINT8_VEC2"; - const gltf = createPropertyTextureGltfNormalizedVec2(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0, - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1, - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2, - ); - - const expectedMetadataValue0 = new Cartesian2(0.0, 0.0); - const expectedMetadataValue1 = new Cartesian2(0.5, 0.0); - const expectedMetadataValue2 = new Cartesian2(1.0, 0.0); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon, - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon, - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon, - ); - scene.destroyForSpecs(); - }); - - it("picks UINT8 VEC3 from a property texture", async function () { - if (webglStub) { - return; - } - - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC3"; - const gltf = createPropertyTextureGltfVec3(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0, - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1, - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2, - ); - const expectedMetadataValue0 = new Cartesian3(0, 0, 0); - const expectedMetadataValue1 = new Cartesian3(127, 0, 127); - const expectedMetadataValue2 = new Cartesian3(255, 0, 255); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon, - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon, - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon, - ); - scene.destroyForSpecs(); - }); - - it("picks UINT8 VEC4 from a property texture", async function () { - if (webglStub) { - return; - } - - const schemaId = undefined; - const className = "exampleClass"; - const propertyName = "example_UINT8_VEC4"; - const gltf = createPropertyTextureGltfVec4(); - - const canvasSizeX = textureSizeX * canvasScaling; - const canvasSizeY = textureSizeY * canvasScaling; - const scene = createScene({ - canvas: createCanvas(canvasSizeX, canvasSizeY), - contextOptions: { - requestWebgl1: true, - }, - }); - - await loadAsModel(scene, gltf); - fitCameraToUnitSquare(scene.camera); - - scene.initializeFrame(); - scene.render(defaultDate); - - const actualMetadataValue0 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 0, - 0, - ); - const actualMetadataValue1 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 1, - 1, - ); - const actualMetadataValue2 = pickMetadataAt( - scene, - schemaId, - className, - propertyName, - 2, - 2, - ); - - const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 0); - const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 0); - const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 0); - - expect(actualMetadataValue0).toEqualEpsilon( - expectedMetadataValue0, - propertyValueEpsilon, - ); - expect(actualMetadataValue1).toEqualEpsilon( - expectedMetadataValue1, - propertyValueEpsilon, - ); - expect(actualMetadataValue2).toEqualEpsilon( - expectedMetadataValue2, - propertyValueEpsilon, - ); - scene.destroyForSpecs(); - }); - }, - "WebGL", -); diff --git a/packages/engine/Specs/Scene/SceneSpec.js b/packages/engine/Specs/Scene/SceneSpec.js index e305f094ce3d..a2f21c789697 100644 --- a/packages/engine/Specs/Scene/SceneSpec.js +++ b/packages/engine/Specs/Scene/SceneSpec.js @@ -53,6 +53,478 @@ import createCanvas from "../../../../Specs/createCanvas.js"; import createScene from "../../../../Specs/createScene.js"; import pollToPromise from "../../../../Specs/pollToPromise.js"; import render from "../../../../Specs/render.js"; +import { Cartesian4, Model } from "@cesium/engine"; + +// The size of the property texture +const textureSizeX = 16; +const textureSizeY = 16; + +// A scaling factor (to be applied to the texture size) for +// determining the size of the ("debug") canvas that shows +// the scene where the picking takes place +const canvasScaling = 32; + +// The 'toEqualEpsilon' matcher (which is which is defined +// in `Specs/addDefaultMatchers.js`, by the way...) uses +// the epsilon as a relative epsilon, and there is no way +// to pass in an absolute epsilon. For comparing the elements +// of a Cartesian2 that stores UINT8 values, an absolute +// epsilon of 1.0 would be handy. But... here we go: +const propertyValueEpsilon = 0.01; + +/** + * Creates an embedded glTF asset with a property texture. + * + * This creates an assed that represents a unit square and uses + * the `EXT_structural_metadata` extension to assign a single + * property texture to this square. + * + * @param {object} schema The metadata schema + * @param {object} propertyTextureProperties The property texture properties + * @returns The gltf + */ +function createEmbeddedGltfWithPropertyTexture( + schema, + propertyTextureProperties, +) { + const result = { + extensions: { + EXT_structural_metadata: { + schema: schema, + propertyTextures: [ + { + class: "exampleClass", + properties: propertyTextureProperties, + }, + ], + }, + }, + extensionsUsed: ["EXT_structural_metadata"], + accessors: [ + { + bufferView: 0, + byteOffset: 0, + componentType: 5123, + count: 6, + type: "SCALAR", + max: [3], + min: [0], + }, + { + bufferView: 1, + byteOffset: 0, + componentType: 5126, + count: 4, + type: "VEC3", + max: [1.0, 1.0, 0.0], + min: [0.0, 0.0, 0.0], + }, + { + bufferView: 1, + byteOffset: 48, + componentType: 5126, + count: 4, + type: "VEC3", + max: [0.0, 0.0, 1.0], + min: [0.0, 0.0, 1.0], + }, + { + bufferView: 1, + byteOffset: 96, + componentType: 5126, + count: 4, + type: "VEC2", + max: [1.0, 1.0], + min: [0.0, 0.0], + }, + ], + asset: { + generator: "JglTF from https://github.com/javagl/JglTF", + version: "2.0", + }, + buffers: [ + { + uri: "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAA", + byteLength: 156, + }, + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 12, + target: 34963, + }, + { + buffer: 0, + byteOffset: 12, + byteLength: 144, + byteStride: 12, + target: 34962, + }, + ], + images: [ + { + // A 16x16 pixels image that contains all combinations of + // (0, 127, 255) in its upper-left 9x9 pixels + uri: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAi0lEQVR42u2RUQ6AMAhDd3OO/qQt8VP8NRHjNpf0leI5ruqXbNVL4c9Dn+E8ljV+iLaXaoAY1YDaADaynBg2gFZLR1+wAdJEWZpW1AIVqmjCruqybw4qnEJbbQBHdWoS2XIUXdp+F8DNUOpM0tIZCusQJrzHNTnsOy2pFTZ7xpKhYFUu4M1v+OvrdQGABqEpS2kSLgAAAABJRU5ErkJggg==", + mimeType: "image/png", + }, + ], + materials: [ + { + pbrMetallicRoughness: { + baseColorFactor: [1.0, 1.0, 1.0, 1.0], + metallicFactor: 0.0, + roughnessFactor: 1.0, + }, + alphaMode: "OPAQUE", + doubleSided: true, + }, + ], + meshes: [ + { + primitives: [ + { + extensions: { + EXT_structural_metadata: { + propertyTextures: [0], + }, + }, + attributes: { + POSITION: 1, + NORMAL: 2, + TEXCOORD_0: 3, + }, + indices: 0, + material: 0, + mode: 4, + }, + ], + }, + ], + nodes: [ + { + mesh: 0, + }, + ], + samplers: [ + { + magFilter: 9728, + minFilter: 9728, + }, + ], + scene: 0, + scenes: [ + { + nodes: [0], + }, + ], + textures: [ + { + sampler: 0, + source: 0, + }, + { + sampler: 0, + source: 1, + }, + ], + }; + return result; +} + +/** + * Create an embedded glTF with the default property texture, + * and the given schema and property texture properties. + * + * @param {object} schema The JSON form of the metadata schema + * @param {object[]} properties The JSON form of the property texture properties + * @returns The glTF + */ +function createPropertyTextureGltf(schema, properties) { + const gltf = createEmbeddedGltfWithPropertyTexture(schema, properties); + /*/ + // Copy-and-paste this into a file to have the actual glTF: + console.log("SPEC GLTF:"); + console.log("-".repeat(80)); + console.log(JSON.stringify(gltf, null, 2)); + console.log("-".repeat(80)); + //*/ + return gltf; +} + +/** + * Creates the glTF for the 'scalar' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfScalar() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_SCALAR: { + name: "Example SCALAR property with UINT8 components", + type: "SCALAR", + componentType: "UINT8", + }, + example_normalized_UINT8_SCALAR: { + name: "Example SCALAR property with normalized UINT8 components", + type: "SCALAR", + componentType: "UINT8", + normalized: true, + }, + }, + }, + }, + }; + const properties = { + example_UINT8_SCALAR: { + index: 0, + texCoord: 0, + channels: [0], + }, + example_normalized_UINT8_SCALAR: { + index: 0, + texCoord: 0, + channels: [1], + }, + }; + return createPropertyTextureGltf(schema, properties); +} + +/** + * Creates the glTF for the 'scalar array' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfScalarArray() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_fixed_length_UINT8_SCALAR_array: { + name: "Example fixed-length SCALAR array property with UINT8 components", + type: "SCALAR", + componentType: "UINT8", + array: true, + count: 3, + }, + }, + }, + }, + }; + const properties = { + example_fixed_length_UINT8_SCALAR_array: { + index: 0, + texCoord: 0, + channels: [0, 1, 2], + }, + }; + return createPropertyTextureGltf(schema, properties); +} + +/** + * Creates the glTF for the 'vec2' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfVec2() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_VEC2: { + name: "Example VEC2 property with UINT8 components", + type: "VEC2", + componentType: "UINT8", + }, + }, + }, + }, + }; + const properties = { + example_UINT8_VEC2: { + index: 0, + texCoord: 0, + channels: [0, 1], + }, + }; + return createPropertyTextureGltf(schema, properties); +} + +/** + * Creates the glTF for the normalized 'vec2' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfNormalizedVec2() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_normalized_UINT8_VEC2: { + name: "Example VEC2 property with normalized UINT8 components", + type: "VEC2", + componentType: "UINT8", + normalized: true, + }, + }, + }, + }, + }; + const properties = { + example_normalized_UINT8_VEC2: { + index: 0, + texCoord: 0, + channels: [0, 1], + }, + }; + return createPropertyTextureGltf(schema, properties); +} + +/** + * Creates the glTF for the 'vec3' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfVec3() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_VEC3: { + name: "Example VEC3 property with UINT8 components", + type: "VEC3", + componentType: "UINT8", + }, + }, + }, + }, + }; + const properties = { + example_UINT8_VEC3: { + index: 0, + texCoord: 0, + channels: [0, 1, 2], + }, + }; + return createPropertyTextureGltf(schema, properties); +} + +/** + * Creates the glTF for the 'vec4' test case + * + * @returns The glTF + */ +function createPropertyTextureGltfVec4() { + const schema = { + id: "ExampleSchema", + classes: { + exampleClass: { + name: "Example class", + properties: { + example_UINT8_VEC4: { + name: "Example VEC4 property with UINT8 components", + type: "VEC4", + componentType: "UINT8", + }, + }, + }, + }, + }; + const properties = { + example_UINT8_VEC4: { + index: 0, + texCoord: 0, + channels: [0, 1, 2, 3], + }, + }; + return createPropertyTextureGltf(schema, properties); +} + +/** + * Create a model from the given glTF, add it as a primitive + * to the given scene, and wait until it is fully loaded. + * + * @param {Scene} scene The scene + * @param {object} gltf The gltf + */ +async function loadAsModel(scene, gltf) { + const basePath = "SPEC_BASE_PATH"; + const model = await Model.fromGltfAsync({ + gltf: gltf, + basePath: basePath, + // This is important to make sure that the property + // texture is fully loaded when the model is rendered! + incrementallyLoadTextures: false, + }); + scene.primitives.add(model); + + await pollToPromise( + function () { + scene.renderForSpecs(); + return model.ready; + }, + { timeout: 10000 }, + ); +} + +/** + * Move the camera to exactly look at the unit square along -X + * + * @param {Camera} camera + */ +function fitCameraToUnitSquare(camera) { + const fov = CesiumMath.PI_OVER_THREE; + camera.frustum.fov = fov; + camera.frustum.near = 0.01; + camera.frustum.far = 100.0; + const distance = 1.0 / (2.0 * Math.tan(fov * 0.5)); + camera.position = new Cartesian3(distance, 0.5, 0.5); + camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.clone(Cartesian3.UNIT_Y); +} + +/** + * Pick the specified metadata value from the screen that is contained in + * the property texture at the given coordinates. + * + * (This assumes that the property texture is on a unit square, and + * fitCameraToUnitSquare was called) + * + * @param {Scene} scene The scene + * @param {string|undefined} schemaId The schema ID + * @param {string} className The class name + * @param {string} propertyName The property name + * @param {number} x The x-coordinate in the texture + * @param {number} y The y-coordinate in the texture + * @returns The metadata value + */ +function pickMetadataAt(scene, schemaId, className, propertyName, x, y) { + const screenX = Math.floor(x * canvasScaling + canvasScaling / 2); + const screenY = Math.floor(y * canvasScaling + canvasScaling / 2); + const screenPosition = new Cartesian2(screenX, screenY); + const metadataValue = scene.pickMetadata( + screenPosition, + schemaId, + className, + propertyName, + ); + return metadataValue; +} describe( "Scene/Scene", @@ -2367,5 +2839,681 @@ describe( }); }, + describe("pickMetadata", () => { + // When using a WebGL stub, the functionality of reading metadata + // values back from the frame buffer is not supported. So nearly + // all the tests have to be skipped. + const webglStub = !!window.webglStub; + + const defaultDate = JulianDate.fromDate( + new Date("January 1, 2014 12:00:00 UTC"), + ); + + it("throws without windowPosition", async function () { + const windowPosition = undefined; // For spec + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + scene.destroyForSpecs(); + }); + + it("throws without className", async function () { + const windowPosition = new Cartesian2(); + const schemaId = undefined; + const className = undefined; // For spec + const propertyName = "example_UINT8_SCALAR"; + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + scene.destroyForSpecs(); + }); + + it("throws without propertyName", async function () { + const windowPosition = new Cartesian2(); + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = undefined; // For spec + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + scene.initializeFrame(); + scene.render(defaultDate); + expect(() => { + scene.pickMetadata(windowPosition, schemaId, className, propertyName); + }).toThrowDeveloperError(); + scene.destroyForSpecs(); + }); + + it("returns undefined for class name that does not exist", async function () { + const schemaId = undefined; + const className = "exampleClass_THAT_DOES_NOT_EXIST"; // For spec + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2), + ); + const actualMetadataValue = scene.pickMetadata( + windowPosition, + schemaId, + className, + propertyName, + ); + expect(actualMetadataValue).toBeUndefined(); + scene.destroyForSpecs(); + }); + + it("returns undefined when there is no object with metadata", async function () { + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2), + ); + const actualMetadataValue = scene.pickMetadata( + windowPosition, + schemaId, + className, + propertyName, + ); + expect(actualMetadataValue).toBeUndefined(); + scene.destroyForSpecs(); + }); + + it("pickMetadataSchema returns undefined when there is no object with metadata", async function () { + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + fitCameraToUnitSquare(scene.camera); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2), + ); + const metadataSchema = scene.pickMetadataSchema(windowPosition); + + expect(metadataSchema).toBeUndefined(); + scene.destroyForSpecs(); + }); + + it("pickMetadataSchema picks the metadata schema object", async function () { + if (webglStub) { + return; + } + + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const windowPosition = new Cartesian2( + Math.floor(canvasSizeX / 2), + Math.floor(canvasSizeY / 2), + ); + + // The pickMetadataSchema call should return the schema that + // was defined in createPropertyTextureGltfScalar + const metadataSchema = scene.pickMetadataSchema(windowPosition); + + expect(metadataSchema).toBeDefined(); + expect(metadataSchema.id).toEqual("ExampleSchema"); + expect(metadataSchema.classes).toBeDefined(); + scene.destroyForSpecs(); + }); + + it("picks UINT8 SCALAR from a property texture", async function () { + if (webglStub) { + return; + } + + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0, + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 1, + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 2, + ); + const expectedMetadataValue0 = 0; + const expectedMetadataValue1 = 127; + const expectedMetadataValue2 = 255; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon, + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon, + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon, + ); + scene.destroyForSpecs(); + }); + + it("picks normalized UINT8 SCALAR from a property texture", async function () { + if (webglStub) { + return; + } + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_normalized_UINT8_SCALAR"; + const gltf = createPropertyTextureGltfScalar(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0, + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 3, + 0, + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 6, + 0, + ); + const expectedMetadataValue0 = 0.0; + const expectedMetadataValue1 = 0.5; + const expectedMetadataValue2 = 1.0; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon, + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon, + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon, + ); + scene.destroyForSpecs(); + }); + + it("picks fixed length UINT8 SCALAR array from a property texture", async function () { + if (webglStub) { + return; + } + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_fixed_length_UINT8_SCALAR_array"; + const gltf = createPropertyTextureGltfScalarArray(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0, + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1, + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2, + ); + const expectedMetadataValue0 = [0, 0, 0]; + const expectedMetadataValue1 = [127, 0, 127]; + const expectedMetadataValue2 = [255, 0, 255]; + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon, + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon, + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon, + ); + scene.destroyForSpecs(); + }); + + it("picks UINT8 VEC2 from a property texture", async function () { + if (webglStub) { + return; + } + + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC2"; + const gltf = createPropertyTextureGltfVec2(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0, + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1, + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2, + ); + const expectedMetadataValue0 = new Cartesian2(0, 0); + const expectedMetadataValue1 = new Cartesian2(127, 0); + const expectedMetadataValue2 = new Cartesian2(255, 0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon, + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon, + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon, + ); + scene.destroyForSpecs(); + }); + + it("picks normalized UINT8 VEC2 from a property texture", async function () { + if (webglStub) { + return; + } + + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_normalized_UINT8_VEC2"; + const gltf = createPropertyTextureGltfNormalizedVec2(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0, + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1, + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2, + ); + + const expectedMetadataValue0 = new Cartesian2(0.0, 0.0); + const expectedMetadataValue1 = new Cartesian2(0.5, 0.0); + const expectedMetadataValue2 = new Cartesian2(1.0, 0.0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon, + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon, + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon, + ); + scene.destroyForSpecs(); + }); + + it("picks UINT8 VEC3 from a property texture", async function () { + if (webglStub) { + return; + } + + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC3"; + const gltf = createPropertyTextureGltfVec3(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0, + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1, + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2, + ); + const expectedMetadataValue0 = new Cartesian3(0, 0, 0); + const expectedMetadataValue1 = new Cartesian3(127, 0, 127); + const expectedMetadataValue2 = new Cartesian3(255, 0, 255); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon, + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon, + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon, + ); + scene.destroyForSpecs(); + }); + + it("picks UINT8 VEC4 from a property texture", async function () { + if (webglStub) { + return; + } + + const schemaId = undefined; + const className = "exampleClass"; + const propertyName = "example_UINT8_VEC4"; + const gltf = createPropertyTextureGltfVec4(); + + const canvasSizeX = textureSizeX * canvasScaling; + const canvasSizeY = textureSizeY * canvasScaling; + const scene = createScene({ + canvas: createCanvas(canvasSizeX, canvasSizeY), + contextOptions: { + requestWebgl1: true, + }, + }); + + await loadAsModel(scene, gltf); + fitCameraToUnitSquare(scene.camera); + + scene.initializeFrame(); + scene.render(defaultDate); + + const actualMetadataValue0 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 0, + 0, + ); + const actualMetadataValue1 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 1, + 1, + ); + const actualMetadataValue2 = pickMetadataAt( + scene, + schemaId, + className, + propertyName, + 2, + 2, + ); + + const expectedMetadataValue0 = new Cartesian4(0, 0, 0, 0); + const expectedMetadataValue1 = new Cartesian4(127, 0, 127, 0); + const expectedMetadataValue2 = new Cartesian4(255, 0, 255, 0); + + expect(actualMetadataValue0).toEqualEpsilon( + expectedMetadataValue0, + propertyValueEpsilon, + ); + expect(actualMetadataValue1).toEqualEpsilon( + expectedMetadataValue1, + propertyValueEpsilon, + ); + expect(actualMetadataValue2).toEqualEpsilon( + expectedMetadataValue2, + propertyValueEpsilon, + ); + scene.destroyForSpecs(); + }); + }), "WebGL", );