From f18b2dd3d2e43d995db1beef74f0d31e78475f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Sun, 24 Aug 2025 10:03:37 -0300 Subject: [PATCH 01/11] Fix error of not resetting when adding and removing the same quantity (without changing the length) --- packages/engine/Source/Scene/Model/Model.js | 9 ++++----- .../engine/Source/Scene/Model/ModelInstanceCollection.js | 6 ++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index ad484109de43..a2e1e69d5203 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -317,7 +317,7 @@ function Model(options) { } this._instanceFeatureIdLabel = instanceFeatureIdLabel; - this._runtimeInstancesLength = 0; + this._runtimeInstancesDirty = false; this._featureTables = []; this._featureTableId = undefined; @@ -2260,12 +2260,11 @@ function updateVerticalExaggeration(model, frameState) { } function updateRuntimeModelInstances(model) { - if ( - model.sceneGraph.modelInstances.length !== model._runtimeInstancesLength - ) { + if (model._runtimeInstancesDirty) { model.resetDrawCommands(); - model._runtimeInstancesLength = model.sceneGraph.modelInstances.length; + model._runtimeInstancesDirty = false; } + let instance; for (let i = 0; i < model.sceneGraph.modelInstances.length; i++) { instance = model.sceneGraph.modelInstances.get(i); diff --git a/packages/engine/Source/Scene/Model/ModelInstanceCollection.js b/packages/engine/Source/Scene/Model/ModelInstanceCollection.js index ff95e5fa1319..bdf3184f3414 100644 --- a/packages/engine/Source/Scene/Model/ModelInstanceCollection.js +++ b/packages/engine/Source/Scene/Model/ModelInstanceCollection.js @@ -113,6 +113,9 @@ ModelInstanceCollection.prototype.add = function (transform) { const instance = new ModelInstance(transform, this); this._instances.push(instance); + + this._model._runtimeInstancesDirty = true; + return instance; }; @@ -151,6 +154,7 @@ ModelInstanceCollection.prototype.remove = function (instance) { } this._instances.splice(index, 1); + this._model._runtimeInstancesDirty = true; return true; }; @@ -172,6 +176,8 @@ ModelInstanceCollection.prototype.remove = function (instance) { ModelInstanceCollection.prototype.removeAll = function () { const instances = this._instances; instances.length = 0; + + this._model._runtimeInstancesDirty = true; }; /** From c918bb96f57aebcdb859989a002b502be11ec9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Sun, 24 Aug 2025 10:06:20 -0300 Subject: [PATCH 02/11] Keep the ModelInstance pickId private --- packages/engine/Source/Scene/Model/ModelInstance.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelInstance.js b/packages/engine/Source/Scene/Model/ModelInstance.js index 5130c78a5703..4a04e56afb52 100644 --- a/packages/engine/Source/Scene/Model/ModelInstance.js +++ b/packages/engine/Source/Scene/Model/ModelInstance.js @@ -117,11 +117,6 @@ class ModelInstance { return this._relativeScaledTransform; } - /** - * The Pick Id of the instance. - * @type {string|undefined} - * @readonly - */ get pickId() { return this._pickId; } From cff2fd5674cdc045b779ebbdbfe2252a10eca688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Sun, 24 Aug 2025 10:18:18 -0300 Subject: [PATCH 03/11] Added a color option for each ModelInstance --- packages/engine/Source/Scene/Model/Model.js | 20 ++++-- .../Source/Scene/Model/ModelInstance.js | 24 +++++++ .../Scene/Model/ModelInstancesUpdateStage.js | 6 +- .../RuntimeModelInstancingPipelineStage.js | 67 +++++++++++++++++-- .../engine/Source/Shaders/Model/ModelFS.glsl | 4 ++ ...RuntimeModelInstancingPipelineStageFS.glsl | 7 ++ ...RuntimeModelInstancingPipelineStageVS.glsl | 2 + 7 files changed, 115 insertions(+), 15 deletions(-) create mode 100644 packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index a2e1e69d5203..f1ecfb0dde5c 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2260,20 +2260,30 @@ function updateVerticalExaggeration(model, frameState) { } function updateRuntimeModelInstances(model) { - if (model._runtimeInstancesDirty) { - model.resetDrawCommands(); - model._runtimeInstancesDirty = false; - } - let instance; for (let i = 0; i < model.sceneGraph.modelInstances.length; i++) { instance = model.sceneGraph.modelInstances.get(i); + if (instance._dirty) { if (!model.sceneGraph.modelInstances._dirty) { model.sceneGraph.modelInstances._dirty = true; } + instance._dirty = false; } + + if (instance._dirtyDraw) { + if (!model._runtimeInstancesDirty) { + model._runtimeInstancesDirty = true; + } + + instance._dirtyDraw = false; + } + } + + if (model._runtimeInstancesDirty) { + model.resetDrawCommands(); + model._runtimeInstancesDirty = false; } } diff --git a/packages/engine/Source/Scene/Model/ModelInstance.js b/packages/engine/Source/Scene/Model/ModelInstance.js index 4a04e56afb52..d5dcf9e2406f 100644 --- a/packages/engine/Source/Scene/Model/ModelInstance.js +++ b/packages/engine/Source/Scene/Model/ModelInstance.js @@ -5,6 +5,7 @@ import Matrix3 from "../../Core/Matrix3.js"; import Matrix4 from "../../Core/Matrix4.js"; import TranslationRotationScale from "../../Core/TranslationRotationScale.js"; import Quaternion from "../../Core/Quaternion.js"; +import Color from "../../Core/Color.js"; const scratchTranslationRotationScale = new TranslationRotationScale(); const scratchRotation = new Matrix3(); @@ -48,9 +49,11 @@ class ModelInstance { this._relativeTransform = new Matrix4(); this._relativeScaledTransform = new Matrix4(); this._pickId = undefined; + this._color = Color.WHITE; this._updateTransform(transform); this._dirty = false; + this._dirtyDraw = false; } /** @@ -121,6 +124,27 @@ class ModelInstance { return this._pickId; } + /** + * The Color of the instance. + * @type {Color} + */ + get color() { + return this._color; + } + + set color(value) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("color", value); + //>>includeEnd('debug'); + + if (this._color.equals(value)) { + return; + } + + this._color = value; + this._dirtyDraw = true; + } + _updateTransform(transform) { // Get center from the transform this._center = Matrix4.getTranslation(transform, this._center); diff --git a/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js b/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js index 5d898aa5ead9..403ea57faf78 100644 --- a/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js +++ b/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js @@ -49,7 +49,6 @@ ModelInstancesUpdateStage.update = function ( */ function updateRuntimeNode(runtimeNode, sceneGraph, frameState) { const modelInstances = sceneGraph.modelInstances._instances; - const buffer = runtimeNode.instancingTransformsBuffer; const transformsTypedArray = RuntimeModelInstancingPipelineStage._getTransformsTypedArray( @@ -61,8 +60,9 @@ function updateRuntimeNode(runtimeNode, sceneGraph, frameState) { runtimeNode.instancingTransformsBuffer.copyFromArrayView( transformsTypedArray, ); - - runtimeNode.instancingTransformsBuffer = buffer; + + const colorsTypedArray = RuntimeModelInstancingPipelineStage._getColorsTypedArray(modelInstances); + runtimeNode.instanceColorsBuffer.copyFromArrayView(colorsTypedArray); const childrenLength = runtimeNode.children.length; diff --git a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js index d00911b2daa3..1a2db3271fcd 100644 --- a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js +++ b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js @@ -9,6 +9,8 @@ import BufferUsage from "../../Renderer/BufferUsage.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import InstancingStageCommon from "../../Shaders/Model/InstancingStageCommon.js"; import RuntimeModelInstancingPipelineStageVS from "../../Shaders/Model/RuntimeModelInstancingPipelineStageVS.js"; +import RuntimeModelInstancingPipelineStageFS from "../../Shaders/Model/RuntimeModelInstancingPipelineStageFS.js"; +import ColorBlendMode from "../ColorBlendMode.js"; const nodeTransformScratch = new Matrix4(); const relativeScaledTransformScratch = new Matrix4(); @@ -43,17 +45,18 @@ RuntimeModelInstancingPipelineStage.process = function ( const shaderBuilder = renderResources.shaderBuilder; shaderBuilder.addDefine("HAS_INSTANCING"); shaderBuilder.addDefine("HAS_INSTANCE_MATRICES"); - shaderBuilder.addDefine( - "USE_API_INSTANCING", - undefined, - ShaderDestination.VERTEX, - ); + shaderBuilder.addDefine("USE_API_INSTANCING", undefined, ShaderDestination.VERTEX); + shaderBuilder.addDefine("USE_API_INSTANCING", undefined, ShaderDestination.FRAGMENT); + shaderBuilder.addVertexLines(InstancingStageCommon); shaderBuilder.addVertexLines(RuntimeModelInstancingPipelineStageVS); + shaderBuilder.addVarying("vec4", "v_gex_instanceColor"); + shaderBuilder.addFragmentLines(RuntimeModelInstancingPipelineStageFS); + const model = renderResources.model; const sceneGraph = model.sceneGraph; - + /** * @type {ModelInstance[]} */ @@ -122,6 +125,24 @@ RuntimeModelInstancingPipelineStage._getTransformsTypedArray = function ( return transformsTypedArray; }; +RuntimeModelInstancingPipelineStage._getColorsTypedArray = function ( + modelInstances +) { + const colorsTypedArray = new Uint8Array(modelInstances.length * 4); + + for (let i = 0; i < modelInstances.length; i++) { + const color = modelInstances[i]?.color; + const o = i * 4; + + colorsTypedArray[o + 0] = Math.round(((color?.red ?? 1.0) * 255)); + colorsTypedArray[o + 1] = Math.round(((color?.green ?? 1.0) * 255)); + colorsTypedArray[o + 2] = Math.round(((color?.blue ?? 1.0) * 255)); + colorsTypedArray[o + 3] = Math.round(((color?.alpha ?? 1.0) * 255)); + } + + return colorsTypedArray; +}; + RuntimeModelInstancingPipelineStage._createAttributes = function ( frameState, renderResources, @@ -143,11 +164,20 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( typedArray: transformsTypedArray, }); + const colorsTypedArray = RuntimeModelInstancingPipelineStage._getColorsTypedArray(modelInstances); + const colorsBuffer = Buffer.createVertexBuffer({ + context, + usage: BufferUsage.DYNAMIC_DRAW, + typedArray: colorsTypedArray, + }); + renderResources.runtimeNode.instancingTransformsBuffer = transformsBuffer; + renderResources.runtimeNode.instanceColorsBuffer = colorsBuffer; // Destruction of resources allocated by the Model // is handled by Model.destroy(). transformsBuffer.vertexArrayDestroyable = false; + colorsBuffer.vertexArrayDestroyable = false; // Add attribute declarations const shaderBuilder = renderResources.shaderBuilder; @@ -158,6 +188,8 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( shaderBuilder.addAttribute("vec3", `a_instancingPositionHigh`); shaderBuilder.addAttribute("vec3", `a_instancingPositionLow`); + shaderBuilder.addAttribute("vec4", "a_gex_instanceColor"); + // Create attributes const vertexSizeInFloats = 18; const componentByteSize = ComponentDatatype.getSizeInBytes( @@ -216,6 +248,16 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( strideInBytes: strideInBytes, instanceDivisor: 1, }, + { + index: renderResources.attributeIndex++, + vertexBuffer: colorsBuffer, + componentsPerAttribute: 4, + componentDatatype: ComponentDatatype.UNSIGNED_BYTE, + normalize: true, + offsetInBytes: 0, + strideInBytes: 4, + instanceDivisor: 1, + } ]; return attributes; @@ -226,11 +268,18 @@ RuntimeModelInstancingPipelineStage._createUniforms = function ( sceneGraph, ) { const shaderBuilder = renderResources.shaderBuilder; + shaderBuilder.addUniform( "mat4", "u_instance_nodeTransform", ShaderDestination.VERTEX, ); + + shaderBuilder.addUniform( + "float", + "gex_instanceColorBlend", + ShaderDestination.FRAGMENT, + ); const runtimeNode = renderResources.runtimeNode; @@ -251,8 +300,12 @@ RuntimeModelInstancingPipelineStage._createUniforms = function ( nodeTransformScratch, ); }, - }; + gex_instanceColorBlend: () => { + return ColorBlendMode.getColorBlend(renderResources.model.colorBlendMode, renderResources.model.colorBlendAmount); + } + }; + return uniformMap; }; diff --git a/packages/engine/Source/Shaders/Model/ModelFS.glsl b/packages/engine/Source/Shaders/Model/ModelFS.glsl index 33f106ea02b9..8f53a31ac1c4 100644 --- a/packages/engine/Source/Shaders/Model/ModelFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelFS.glsl @@ -83,6 +83,10 @@ void main() primitiveOutlineStage(material); #endif + #ifdef USE_API_INSTANCING + RuntimeModelInstancingStage(material); + #endif + vec4 color = handleAlpha(material.diffuse, material.alpha); // When not picking metadata END diff --git a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl new file mode 100644 index 000000000000..c264b208a0ab --- /dev/null +++ b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl @@ -0,0 +1,7 @@ +void RuntimeModelInstancingStage(inout czm_modelMaterial material) +{ + material.diffuse = mix(material.diffuse, v_gex_instanceColor.rgb, gex_instanceColorBlend); + float highlight = ceil(gex_instanceColorBlend); + material.diffuse *= mix(v_gex_instanceColor.rgb, vec3(1.0), highlight); + material.alpha *= v_gex_instanceColor.a; +} \ No newline at end of file diff --git a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl index 7e77449e8d47..84fc8a9b546f 100644 --- a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl +++ b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl @@ -15,4 +15,6 @@ void RuntimeModelInstancingStage( instanceModelView = czm_modelViewRelativeToEye; instanceModelViewInverseTranspose = mat3(czm_modelViewRelativeToEye * instanceModel); + + v_gex_instanceColor = a_gex_instanceColor; } From 7273c321af979bbea7f3ac2a1f6feaa909864dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Sun, 24 Aug 2025 10:57:58 -0300 Subject: [PATCH 04/11] Allow in ModelInstance color = undefined --- packages/engine/Source/Scene/Model/ModelInstance.js | 12 +++++++++--- .../Model/RuntimeModelInstancingPipelineStageFS.glsl | 7 +++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelInstance.js b/packages/engine/Source/Scene/Model/ModelInstance.js index d5dcf9e2406f..b91476f94ea5 100644 --- a/packages/engine/Source/Scene/Model/ModelInstance.js +++ b/packages/engine/Source/Scene/Model/ModelInstance.js @@ -6,6 +6,7 @@ import Matrix4 from "../../Core/Matrix4.js"; import TranslationRotationScale from "../../Core/TranslationRotationScale.js"; import Quaternion from "../../Core/Quaternion.js"; import Color from "../../Core/Color.js"; +import defined from "../../Core/defined.js"; const scratchTranslationRotationScale = new TranslationRotationScale(); const scratchRotation = new Matrix3(); @@ -49,7 +50,7 @@ class ModelInstance { this._relativeTransform = new Matrix4(); this._relativeScaledTransform = new Matrix4(); this._pickId = undefined; - this._color = Color.WHITE; + this._color = undefined; this._updateTransform(transform); this._dirty = false; @@ -134,10 +135,15 @@ class ModelInstance { set color(value) { //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("color", value); + if (defined(value)) { + Check.typeOf.object("color", value); + } //>>includeEnd('debug'); - if (this._color.equals(value)) { + if ( + this._color === value || + (defined(this._color) && defined(value) && Color.equals(this._color, value)) + ) { return; } diff --git a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl index c264b208a0ab..c65ae1920207 100644 --- a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl @@ -1,5 +1,12 @@ void RuntimeModelInstancingStage(inout czm_modelMaterial material) { + if (v_gex_instanceColor.r == 0.0 && + v_gex_instanceColor.g == 0.0 && + v_gex_instanceColor.b == 0.0 && + v_gex_instanceColor.a == 0.0) { + return; + } + material.diffuse = mix(material.diffuse, v_gex_instanceColor.rgb, gex_instanceColorBlend); float highlight = ceil(gex_instanceColorBlend); material.diffuse *= mix(v_gex_instanceColor.rgb, vec3(1.0), highlight); From 00a22a7363b15f1801effb0761578a2478182b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Sun, 24 Aug 2025 11:03:48 -0300 Subject: [PATCH 05/11] Allow in ModelInstance color = undefined --- .../Model/RuntimeModelInstancingPipelineStage.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js index 1a2db3271fcd..4fee576fe3a6 100644 --- a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js +++ b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js @@ -132,12 +132,17 @@ RuntimeModelInstancingPipelineStage._getColorsTypedArray = function ( for (let i = 0; i < modelInstances.length; i++) { const color = modelInstances[i]?.color; + + if (color === undefined) { + continue; + } + const o = i * 4; - colorsTypedArray[o + 0] = Math.round(((color?.red ?? 1.0) * 255)); - colorsTypedArray[o + 1] = Math.round(((color?.green ?? 1.0) * 255)); - colorsTypedArray[o + 2] = Math.round(((color?.blue ?? 1.0) * 255)); - colorsTypedArray[o + 3] = Math.round(((color?.alpha ?? 1.0) * 255)); + colorsTypedArray[o + 0] = Math.round(color.red * 255); + colorsTypedArray[o + 1] = Math.round(color.green * 255); + colorsTypedArray[o + 2] = Math.round(color.blue * 255); + colorsTypedArray[o + 3] = Math.round(color.alpha * 255); } return colorsTypedArray; From bdb937cc78d056f4ae65e3088498800b6bfc4f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Sun, 24 Aug 2025 11:45:59 -0300 Subject: [PATCH 06/11] Adjustments --- .../Scene/Model/ModelInstanceCollection.js | 19 ++++++++-- .../RuntimeModelInstancingPipelineStage.js | 38 ++++++++++++------- ...RuntimeModelInstancingPipelineStageSpec.js | 7 +++- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelInstanceCollection.js b/packages/engine/Source/Scene/Model/ModelInstanceCollection.js index bdf3184f3414..906eb73ca41c 100644 --- a/packages/engine/Source/Scene/Model/ModelInstanceCollection.js +++ b/packages/engine/Source/Scene/Model/ModelInstanceCollection.js @@ -79,6 +79,10 @@ ModelInstanceCollection.prototype.initialize = function (transforms) { const instance = new ModelInstance(transform); this._instances.push(instance); } + + if (transforms.length > 0 && this._model !== undefined) { + this._model._runtimeInstancesDirty = true; + } }; /** @@ -114,7 +118,9 @@ ModelInstanceCollection.prototype.add = function (transform) { const instance = new ModelInstance(transform, this); this._instances.push(instance); - this._model._runtimeInstancesDirty = true; + if (this._model !== undefined) { + this._model._runtimeInstancesDirty = true; + } return instance; }; @@ -154,7 +160,10 @@ ModelInstanceCollection.prototype.remove = function (instance) { } this._instances.splice(index, 1); - this._model._runtimeInstancesDirty = true; + + if (this._model !== undefined) { + this._model._runtimeInstancesDirty = true; + } return true; }; @@ -176,8 +185,10 @@ ModelInstanceCollection.prototype.remove = function (instance) { ModelInstanceCollection.prototype.removeAll = function () { const instances = this._instances; instances.length = 0; - - this._model._runtimeInstancesDirty = true; + + if (this._model !== undefined) { + this._model._runtimeInstancesDirty = true; + } }; /** diff --git a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js index 4fee576fe3a6..3d713498ab8e 100644 --- a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js +++ b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js @@ -45,9 +45,17 @@ RuntimeModelInstancingPipelineStage.process = function ( const shaderBuilder = renderResources.shaderBuilder; shaderBuilder.addDefine("HAS_INSTANCING"); shaderBuilder.addDefine("HAS_INSTANCE_MATRICES"); - shaderBuilder.addDefine("USE_API_INSTANCING", undefined, ShaderDestination.VERTEX); - shaderBuilder.addDefine("USE_API_INSTANCING", undefined, ShaderDestination.FRAGMENT); - + shaderBuilder.addDefine( + "USE_API_INSTANCING", + undefined, + ShaderDestination.VERTEX, + ); + shaderBuilder.addDefine( + "USE_API_INSTANCING", + undefined, + ShaderDestination.FRAGMENT, + ); + shaderBuilder.addVertexLines(InstancingStageCommon); shaderBuilder.addVertexLines(RuntimeModelInstancingPipelineStageVS); @@ -56,7 +64,7 @@ RuntimeModelInstancingPipelineStage.process = function ( const model = renderResources.model; const sceneGraph = model.sceneGraph; - + /** * @type {ModelInstance[]} */ @@ -126,7 +134,7 @@ RuntimeModelInstancingPipelineStage._getTransformsTypedArray = function ( }; RuntimeModelInstancingPipelineStage._getColorsTypedArray = function ( - modelInstances + modelInstances, ) { const colorsTypedArray = new Uint8Array(modelInstances.length * 4); @@ -169,13 +177,14 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( typedArray: transformsTypedArray, }); - const colorsTypedArray = RuntimeModelInstancingPipelineStage._getColorsTypedArray(modelInstances); + const colorsTypedArray = + RuntimeModelInstancingPipelineStage._getColorsTypedArray(modelInstances); const colorsBuffer = Buffer.createVertexBuffer({ context, - usage: BufferUsage.DYNAMIC_DRAW, + usage, typedArray: colorsTypedArray, }); - + renderResources.runtimeNode.instancingTransformsBuffer = transformsBuffer; renderResources.runtimeNode.instanceColorsBuffer = colorsBuffer; @@ -262,7 +271,7 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( offsetInBytes: 0, strideInBytes: 4, instanceDivisor: 1, - } + }, ]; return attributes; @@ -279,7 +288,7 @@ RuntimeModelInstancingPipelineStage._createUniforms = function ( "u_instance_nodeTransform", ShaderDestination.VERTEX, ); - + shaderBuilder.addUniform( "float", "gex_instanceColorBlend", @@ -307,10 +316,13 @@ RuntimeModelInstancingPipelineStage._createUniforms = function ( }, gex_instanceColorBlend: () => { - return ColorBlendMode.getColorBlend(renderResources.model.colorBlendMode, renderResources.model.colorBlendAmount); - } + return ColorBlendMode.getColorBlend( + renderResources.model.colorBlendMode, + renderResources.model.colorBlendAmount, + ); + }, }; - + return uniformMap; }; diff --git a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js index 5f4d193d3c80..069b6c5a44a4 100644 --- a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js @@ -152,6 +152,10 @@ describe( ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ "HAS_INSTANCE_MATRICES", "HAS_INSTANCING", + "USE_API_INSTANCING", + ]); + ShaderBuilderTester.expectHasVaryings(shaderBuilder, [ + "vec4 v_gex_instanceColor;", ]); ShaderBuilderTester.expectHasAttributes(shaderBuilder, undefined, [ "in vec4 a_instancingTransformRow0;", @@ -159,10 +163,11 @@ describe( "in vec4 a_instancingTransformRow2;", "in vec3 a_instancingPositionHigh;", "in vec3 a_instancingPositionLow;", + "in vec4 a_gex_instanceColor;", ]); - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, [ "uniform mat4 u_instance_nodeTransform;", + "uniform float gex_instanceColorBlend;", ]); expect(runtimeNode.instancingTransformsBuffer).toBeDefined(); From c959cfd2245b64ef4c4e3a093562a1e4c27710b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Sun, 24 Aug 2025 12:10:09 -0300 Subject: [PATCH 07/11] prettier, update test --- packages/engine/Source/Scene/Model/Model.js | 2 -- packages/engine/Source/Scene/Model/ModelInstance.js | 5 +++-- .../engine/Source/Scene/Model/ModelInstancesUpdateStage.js | 4 ++-- .../Scene/Model/RuntimeModelInstancingPipelineStageSpec.js | 2 ++ 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index f1ecfb0dde5c..f9e7963166ee 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2268,7 +2268,6 @@ function updateRuntimeModelInstances(model) { if (!model.sceneGraph.modelInstances._dirty) { model.sceneGraph.modelInstances._dirty = true; } - instance._dirty = false; } @@ -2276,7 +2275,6 @@ function updateRuntimeModelInstances(model) { if (!model._runtimeInstancesDirty) { model._runtimeInstancesDirty = true; } - instance._dirtyDraw = false; } } diff --git a/packages/engine/Source/Scene/Model/ModelInstance.js b/packages/engine/Source/Scene/Model/ModelInstance.js index b91476f94ea5..b725ec88125f 100644 --- a/packages/engine/Source/Scene/Model/ModelInstance.js +++ b/packages/engine/Source/Scene/Model/ModelInstance.js @@ -132,7 +132,6 @@ class ModelInstance { get color() { return this._color; } - set color(value) { //>>includeStart('debug', pragmas.debug); if (defined(value)) { @@ -142,7 +141,9 @@ class ModelInstance { if ( this._color === value || - (defined(this._color) && defined(value) && Color.equals(this._color, value)) + (defined(this._color) && + defined(value) && + Color.equals(this._color, value)) ) { return; } diff --git a/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js b/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js index 403ea57faf78..5051d39c0469 100644 --- a/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js +++ b/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js @@ -60,8 +60,8 @@ function updateRuntimeNode(runtimeNode, sceneGraph, frameState) { runtimeNode.instancingTransformsBuffer.copyFromArrayView( transformsTypedArray, ); - - const colorsTypedArray = RuntimeModelInstancingPipelineStage._getColorsTypedArray(modelInstances); + const colorsTypedArray = + RuntimeModelInstancingPipelineStage._getColorsTypedArray(modelInstances); runtimeNode.instanceColorsBuffer.copyFromArrayView(colorsTypedArray); const childrenLength = runtimeNode.children.length; diff --git a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js index 069b6c5a44a4..23ddefe76df8 100644 --- a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js @@ -167,6 +167,8 @@ describe( ]); ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, [ "uniform mat4 u_instance_nodeTransform;", + ]); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ "uniform float gex_instanceColorBlend;", ]); From f41f5779a71b316b312d8c21efd89d71fd41f59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Sun, 24 Aug 2025 12:26:21 -0300 Subject: [PATCH 08/11] spec fixes --- .../Scene/Model/RuntimeModelInstancingPipelineStageSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js index 23ddefe76df8..1ccddc27a2ae 100644 --- a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js @@ -141,7 +141,7 @@ describe( scene.frameState, ); - expect(renderResources.attributes.length).toBe(5); + expect(renderResources.attributes.length).toBe(6); const shaderBuilder = renderResources.shaderBuilder; ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ From 7b243f0c41fb147bc9a960432c2d3d348e04d49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Mon, 25 Aug 2025 20:10:20 -0300 Subject: [PATCH 09/11] add show in modelinstance --- packages/engine/Source/Scene/Model/Model.js | 4 +- .../Source/Scene/Model/ModelInstance.js | 28 +++++++++- .../Scene/Model/ModelInstancesUpdateStage.js | 4 ++ .../RuntimeModelInstancingPipelineStage.js | 56 ++++++++++++++++++- ...RuntimeModelInstancingPipelineStageFS.glsl | 4 ++ ...RuntimeModelInstancingPipelineStageVS.glsl | 2 + ...RuntimeModelInstancingPipelineStageSpec.js | 4 +- 7 files changed, 94 insertions(+), 8 deletions(-) diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index f9e7963166ee..7ce93a967d65 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2271,11 +2271,11 @@ function updateRuntimeModelInstances(model) { instance._dirty = false; } - if (instance._dirtyDraw) { + if (instance._drawDirty) { if (!model._runtimeInstancesDirty) { model._runtimeInstancesDirty = true; } - instance._dirtyDraw = false; + instance._drawDirty = false; } } diff --git a/packages/engine/Source/Scene/Model/ModelInstance.js b/packages/engine/Source/Scene/Model/ModelInstance.js index b725ec88125f..9777ddbd3071 100644 --- a/packages/engine/Source/Scene/Model/ModelInstance.js +++ b/packages/engine/Source/Scene/Model/ModelInstance.js @@ -50,11 +50,12 @@ class ModelInstance { this._relativeTransform = new Matrix4(); this._relativeScaledTransform = new Matrix4(); this._pickId = undefined; + this._show = true; this._color = undefined; this._updateTransform(transform); this._dirty = false; - this._dirtyDraw = false; + this._drawDirty = false; } /** @@ -125,6 +126,29 @@ class ModelInstance { return this._pickId; } + /** + * Whether or not to render the model instance. + * + * @type {boolean} + * + * @default true + */ + get show() { + return this._show; + } + set show(value) { + //>>includeStart('debug', pragmas.debug); + //Check.typeOf.object("show", value); + //>>includeEnd('debug'); + + if (this._show === value) { + return; + } + + this._show = value; + this._dirty = true; + } + /** * The Color of the instance. * @type {Color} @@ -149,7 +173,7 @@ class ModelInstance { } this._color = value; - this._dirtyDraw = true; + this._drawDirty = true; } _updateTransform(transform) { diff --git a/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js b/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js index 5051d39c0469..6bc85e3e54fa 100644 --- a/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js +++ b/packages/engine/Source/Scene/Model/ModelInstancesUpdateStage.js @@ -50,6 +50,10 @@ ModelInstancesUpdateStage.update = function ( function updateRuntimeNode(runtimeNode, sceneGraph, frameState) { const modelInstances = sceneGraph.modelInstances._instances; + const showsTypedArray = + RuntimeModelInstancingPipelineStage._getShowsTypedArray(modelInstances); + runtimeNode.instanceShowsBuffer.copyFromArrayView(showsTypedArray); + const transformsTypedArray = RuntimeModelInstancingPipelineStage._getTransformsTypedArray( modelInstances, diff --git a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js index 3d713498ab8e..956611de8579 100644 --- a/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js +++ b/packages/engine/Source/Scene/Model/RuntimeModelInstancingPipelineStage.js @@ -43,6 +43,9 @@ RuntimeModelInstancingPipelineStage.process = function ( frameState, ) { const shaderBuilder = renderResources.shaderBuilder; + shaderBuilder.addVarying("float", "v_gex_show"); + shaderBuilder.addVarying("vec4", "v_gex_instanceColor"); + shaderBuilder.addDefine("HAS_INSTANCING"); shaderBuilder.addDefine("HAS_INSTANCE_MATRICES"); shaderBuilder.addDefine( @@ -59,7 +62,6 @@ RuntimeModelInstancingPipelineStage.process = function ( shaderBuilder.addVertexLines(InstancingStageCommon); shaderBuilder.addVertexLines(RuntimeModelInstancingPipelineStageVS); - shaderBuilder.addVarying("vec4", "v_gex_instanceColor"); shaderBuilder.addFragmentLines(RuntimeModelInstancingPipelineStageFS); const model = renderResources.model; @@ -86,6 +88,23 @@ RuntimeModelInstancingPipelineStage.process = function ( renderResources.uniformMap = combine(uniformMap, renderResources.uniformMap); }; +RuntimeModelInstancingPipelineStage._getShowsTypedArray = function ( + modelInstances, +) { + const showsTypedArray = new Uint8Array(modelInstances.length); + + for (let i = 0; i < modelInstances.length; i++) { + const modelInstance = modelInstances[i]; + if (!defined(modelInstance)) { + continue; + } + + showsTypedArray[i] = modelInstance.show ? 255 : 0; + } + + return showsTypedArray; +}; + RuntimeModelInstancingPipelineStage._getTransformsTypedArray = function ( modelInstances, model, @@ -139,9 +158,14 @@ RuntimeModelInstancingPipelineStage._getColorsTypedArray = function ( const colorsTypedArray = new Uint8Array(modelInstances.length * 4); for (let i = 0; i < modelInstances.length; i++) { - const color = modelInstances[i]?.color; + const modelInstance = modelInstances[i]; + if (!defined(modelInstance)) { + continue; + } + + const color = modelInstance.color; - if (color === undefined) { + if (!defined(color)) { continue; } @@ -165,6 +189,18 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( const usage = BufferUsage.STATIC_DRAW; // Create typed array and buffer + const showsTypedArray = + RuntimeModelInstancingPipelineStage._getShowsTypedArray( + modelInstances, + renderResources.model, + frameState, + ); + const showsBuffer = Buffer.createVertexBuffer({ + context, + usage, + typedArray: showsTypedArray, + }); + const transformsTypedArray = RuntimeModelInstancingPipelineStage._getTransformsTypedArray( modelInstances, @@ -185,16 +221,20 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( typedArray: colorsTypedArray, }); + renderResources.runtimeNode.instanceShowsBuffer = showsBuffer; renderResources.runtimeNode.instancingTransformsBuffer = transformsBuffer; renderResources.runtimeNode.instanceColorsBuffer = colorsBuffer; // Destruction of resources allocated by the Model // is handled by Model.destroy(). + showsBuffer.vertexArrayDestroyable = false; transformsBuffer.vertexArrayDestroyable = false; colorsBuffer.vertexArrayDestroyable = false; // Add attribute declarations const shaderBuilder = renderResources.shaderBuilder; + shaderBuilder.addAttribute("float", "a_gex_show"); + shaderBuilder.addAttribute("vec4", `a_instancingTransformRow0`); shaderBuilder.addAttribute("vec4", `a_instancingTransformRow1`); shaderBuilder.addAttribute("vec4", `a_instancingTransformRow2`); @@ -212,6 +252,16 @@ RuntimeModelInstancingPipelineStage._createAttributes = function ( const strideInBytes = componentByteSize * vertexSizeInFloats; const attributes = [ + { + index: renderResources.attributeIndex++, + vertexBuffer: showsBuffer, + componentsPerAttribute: 1, + componentDatatype: ComponentDatatype.BYTE, + normalize: true, + offsetInBytes: 0, + strideInBytes: 1, + instanceDivisor: 1, + }, { index: renderResources.attributeIndex++, vertexBuffer: transformsBuffer, diff --git a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl index c65ae1920207..061c8ad3a3a3 100644 --- a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageFS.glsl @@ -1,5 +1,9 @@ void RuntimeModelInstancingStage(inout czm_modelMaterial material) { + if (v_gex_show == 0.0) { + discard; + } + if (v_gex_instanceColor.r == 0.0 && v_gex_instanceColor.g == 0.0 && v_gex_instanceColor.b == 0.0 && diff --git a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl index 84fc8a9b546f..dae69a019bd0 100644 --- a/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl +++ b/packages/engine/Source/Shaders/Model/RuntimeModelInstancingPipelineStageVS.glsl @@ -3,6 +3,8 @@ void RuntimeModelInstancingStage( out mat4 instanceModelView, out mat3 instanceModelViewInverseTranspose) { + v_gex_show = a_gex_show; + vec3 positionMC = attributes.positionMC; mat4 instancingTransform = getInstancingTransform(); diff --git a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js index 1ccddc27a2ae..36ae9cc5940c 100644 --- a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js @@ -141,7 +141,7 @@ describe( scene.frameState, ); - expect(renderResources.attributes.length).toBe(6); + expect(renderResources.attributes.length).toBe(7); const shaderBuilder = renderResources.shaderBuilder; ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ @@ -155,9 +155,11 @@ describe( "USE_API_INSTANCING", ]); ShaderBuilderTester.expectHasVaryings(shaderBuilder, [ + "float v_gex_show;", "vec4 v_gex_instanceColor;", ]); ShaderBuilderTester.expectHasAttributes(shaderBuilder, undefined, [ + "in float a_gex_show;", "in vec4 a_instancingTransformRow0;", "in vec4 a_instancingTransformRow1;", "in vec4 a_instancingTransformRow2;", From b9dc17ab9c0f6daedb4abc9b2147dbc28bf87f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Tue, 26 Aug 2025 16:04:33 -0300 Subject: [PATCH 10/11] Update options new ModelInstance --- .../Source/Scene/Model/ModelInstance.js | 26 ++++++---- .../Scene/Model/ModelInstanceCollection.js | 35 +++++++------ .../Model/ModelInstanceCollectionSpec.js | 20 ++++++-- .../Specs/Scene/Model/ModelInstanceSpec.js | 50 ++++++++++++++++--- 4 files changed, 97 insertions(+), 34 deletions(-) diff --git a/packages/engine/Source/Scene/Model/ModelInstance.js b/packages/engine/Source/Scene/Model/ModelInstance.js index 9777ddbd3071..5995d9262610 100644 --- a/packages/engine/Source/Scene/Model/ModelInstance.js +++ b/packages/engine/Source/Scene/Model/ModelInstance.js @@ -23,7 +23,12 @@ class ModelInstance { * Constructs a {@link ModelInstance}, a copy of a {@link Model} mesh, for efficiently rendering a large number of copies the same model using GPU mesh instancing. * The position, orientation, and scale of the instance is determined by the specified {@link Matrix4}. * @constructor - * @param {Matrix4} transform Matrix4 describing the transform of the instance + * + * @param {object} options Object with the following properties: + * @param {Matrix4} options.transform Matrix4 describing the transform of the instance. + * @param {boolean} [options.show=true] Determines if the instance in the collection will be shown. + * @param {Color} [options.color] A color that blends with the instance rendered color + * * @example * const position = Cesium.Cartesian3.fromDegrees(-75.1652, 39.9526); * @@ -38,22 +43,25 @@ class ModelInstance { * Cesium.Ellipsoid.WGS84, * fixedFrameTransform, * ); - * const modelInstance = new Cesium.ModelInstance(instanceModelMatrix); + * const modelInstance = new Cesium.ModelInstance({ + * transform: instanceModelMatrix + * }); */ - constructor(transform) { + constructor(options) { //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("transform", transform); + Check.typeOf.object("options", options); + Check.typeOf.object("options.transform", options.transform); //>>includeEnd('debug'); - this._transform = transform; + this._transform = options.transform; this._center = new Cartesian3(); this._relativeTransform = new Matrix4(); this._relativeScaledTransform = new Matrix4(); this._pickId = undefined; - this._show = true; - this._color = undefined; + this.show = options.show ?? true; + this.color = options.color; - this._updateTransform(transform); + this._updateTransform(options.transform); this._dirty = false; this._drawDirty = false; } @@ -138,7 +146,7 @@ class ModelInstance { } set show(value) { //>>includeStart('debug', pragmas.debug); - //Check.typeOf.object("show", value); + Check.typeOf.bool("show", value); //>>includeEnd('debug'); if (this._show === value) { diff --git a/packages/engine/Source/Scene/Model/ModelInstanceCollection.js b/packages/engine/Source/Scene/Model/ModelInstanceCollection.js index 906eb73ca41c..4ae8c2a462ee 100644 --- a/packages/engine/Source/Scene/Model/ModelInstanceCollection.js +++ b/packages/engine/Source/Scene/Model/ModelInstanceCollection.js @@ -1,4 +1,3 @@ -import Check from "../../Core/Check.js"; import defined from "../../Core/defined.js"; import DeveloperError from "../../Core/DeveloperError.js"; import ModelInstance from "./ModelInstance.js"; @@ -36,7 +35,9 @@ import RuntimeError from "../../Core/RuntimeError.js"; * * // Add an instance at the specified transform to a collection * const collection = new Cesium.ModelInstanceCollection(); - * collection.add(instanceModelMatrix); + * collection.add({ + * transform: instanceModelMatrix + * }); * * // Add an instance to a model * const model = await Cesium.Model.fromGltfAsync({ @@ -44,7 +45,9 @@ import RuntimeError from "../../Core/RuntimeError.js"; * minimumPixelSize: 64, * }); * viewer.scene.primitives.add(model); - * model.instances.add(instanceModelMatrix); + * model.instances.add({ + * transform: instanceModelMatrix + * }); */ function ModelInstanceCollection(options) { this._instances = []; @@ -75,8 +78,9 @@ ModelInstanceCollection.prototype.initialize = function (transforms) { } for (let i = 0; i < transforms.length; i++) { - const transform = transforms[i]; - const instance = new ModelInstance(transform); + const instance = new ModelInstance({ + transform: transforms[i], + }); this._instances.push(instance); } @@ -89,7 +93,10 @@ ModelInstanceCollection.prototype.initialize = function (transforms) { * Creates and adds an instance with the specified transform to the collection. * The added instance is returned so it can be modified or removed from the collection later. * - * @param {Matrix4} transform A transform that represents an instance of a Model + * @param {object} options Object with the following properties: + * @param {Matrix4} options.transform Matrix4 describing the transform of the instance. + * @param {boolean} [options.show=true] Determines if the billboards in the collection will be shown. + * @param {Color} [options.color] Determines if the billboards in the collection will be shown. * @returns {ModelInstance} The model instance that was added to the collection. * * @performance Calling add is expected constant time. However, the collection's vertex buffer @@ -99,23 +106,21 @@ ModelInstanceCollection.prototype.initialize = function (transforms) { * @example * // Example: Provide a transform to add a model instance to the collection * const collection = new ModelInstanceCollection() - * const instance = collection.add(transform) + * const instance = collection.add({ + * transform: instanceTransform + * }) * * @see ModelInstanceCollection#remove * @see ModelInstanceCollection#removeAll */ -ModelInstanceCollection.prototype.add = function (transform) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object("transform", transform); - //>>includeEnd('debug'); - +ModelInstanceCollection.prototype.add = function (options) { if (this._model?._loader._hasMeshGpuInstancing) { throw new RuntimeError( "Models with the EXT_mesh_gpu_instancing extension cannot use the ModelInstanceCollection class.", ); } - const instance = new ModelInstance(transform, this); + const instance = new ModelInstance(options); this._instances.push(instance); if (this._model !== undefined) { @@ -141,7 +146,9 @@ ModelInstanceCollection.prototype.add = function (transform) { * * * @example - * const instance = collection.add(transform); + * const instance = collection.add({ + * transform: transform + * }); * collection.remove(instance); // Returns true * * @see ModelInstanceCollection#add diff --git a/packages/engine/Specs/Scene/Model/ModelInstanceCollectionSpec.js b/packages/engine/Specs/Scene/Model/ModelInstanceCollectionSpec.js index 046d9e2a1601..844c86172575 100644 --- a/packages/engine/Specs/Scene/Model/ModelInstanceCollectionSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelInstanceCollectionSpec.js @@ -49,15 +49,21 @@ describe("Scene/Model/ModelInstanceCollection", function () { }); it("can add an instance", function () { - const instance = collection.add(sampleTransform1); + const instance = collection.add({ + transform: sampleTransform1, + }); expect(collection.length).toEqual(1); expect(collection.get(0)).toBe(instance); }); it("can remove an instance", function () { - const sampleInstance1 = collection.add(sampleTransform1); - collection.add(sampleTransform2); + const sampleInstance1 = collection.add({ + transform: sampleTransform1, + }); + collection.add({ + transform: sampleTransform2, + }); const removed = collection.remove(sampleInstance1); @@ -67,8 +73,12 @@ describe("Scene/Model/ModelInstanceCollection", function () { }); it("can remove all instances", function () { - collection.add(sampleTransform1); - collection.add(sampleTransform2); + collection.add({ + transform: sampleTransform1, + }); + collection.add({ + transform: sampleTransform2, + }); expect(collection.length).toEqual(2); collection.removeAll(); expect(collection.length).toEqual(0); diff --git a/packages/engine/Specs/Scene/Model/ModelInstanceSpec.js b/packages/engine/Specs/Scene/Model/ModelInstanceSpec.js index 1b06a51d5721..a602f04ddfec 100644 --- a/packages/engine/Specs/Scene/Model/ModelInstanceSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelInstanceSpec.js @@ -7,6 +7,7 @@ import { Cartesian3, Ellipsoid, BoundingSphere, + Color, } from "../../../index.js"; import createScene from "../../../../../Specs/createScene.js"; import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; @@ -47,11 +48,15 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); expect(instance.transform).toEqual(instanceModelMatrix); expect(instance.center).toEqual(new Cartesian3()); - expect(instance.relativeTransform).toEqual(instanceModelMatrix); + expect(instance.transform).toEqual(instanceModelMatrix); + expect(instance.show).toEqual(true); + expect(instance.color).toEqual(undefined); }); it("creates an instance with translation", async function () { @@ -68,7 +73,9 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); expect(instance.transform).toEqual(instanceModelMatrix); const center = new Cartesian3(10, 10, 10); @@ -108,7 +115,9 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); const sampleModelMatrix = Matrix4.IDENTITY; const sampleRootNodeTransform = Matrix4.IDENTITY; @@ -224,7 +233,9 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); // values based on the Primitive for the "Wheels" Node in the CesiumMilkTruck const sampleModel = { @@ -309,7 +320,9 @@ describe( fixedFrameTransform, ); - const instance = new ModelInstance(instanceModelMatrix); + const instance = new ModelInstance({ + transform: instanceModelMatrix, + }); // values based on the Primitive for the "Wheels" Node in the CesiumMilkTruck const sampleModel = { @@ -385,6 +398,31 @@ describe( expect(primitiveBoundingSphere.center).toEqual(boundingSphereCenter); expect(primitiveBoundingSphere.radius).toEqual(boundingSphereRadius); }); + + it("creates an instance with show and color", async function () { + const position = new Cartesian3(0, 0, 0); + + const headingPositionRoll = new HeadingPitchRoll(); + const fixedFrameTransform = Transforms.localFrameToFixedFrameGenerator( + "north", + "west", + ); + const instanceModelMatrix = new Transforms.headingPitchRollToFixedFrame( + position, + headingPositionRoll, + Ellipsoid.WGS84, + fixedFrameTransform, + ); + + const instance = new ModelInstance({ + transform: instanceModelMatrix, + show: false, + color: Color.RED, + }); + + expect(instance.show).toEqual(false); + expect(instance.color).toEqual(Color.RED); + }); }, "WebGL", ); From e802e60a228333c906e79ee3f18354b4a09bd24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rud=C3=A1=20Cunha?= Date: Tue, 26 Aug 2025 16:36:26 -0300 Subject: [PATCH 11/11] fix spec --- .../RuntimeModelInstancingPipelineStageSpec.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js index 36ae9cc5940c..15077a2ea2c3 100644 --- a/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/RuntimeModelInstancingPipelineStageSpec.js @@ -79,8 +79,12 @@ describe( fixedFrameTransform, ); - const sampleInstance1 = new ModelInstance(instanceModelMatrix1); - const sampleInstance2 = new ModelInstance(instanceModelMatrix2); + const sampleInstance1 = new ModelInstance({ + transform: instanceModelMatrix1, + }); + const sampleInstance2 = new ModelInstance({ + transform: instanceModelMatrix2, + }); function mockRenderResources(node) { return { @@ -292,8 +296,12 @@ describe( instanceModelMatrix4, ); - const sampleInstance3 = new ModelInstance(instanceModelMatrix3); - const sampleInstance4 = new ModelInstance(instanceModelMatrix4); + const sampleInstance3 = new ModelInstance({ + transform: instanceModelMatrix3, + }); + const sampleInstance4 = new ModelInstance({ + transform: instanceModelMatrix4, + }); // mock resources for ModelInstancesUpdateStage sceneGraph.modelInstances._instances = [sampleInstance3, sampleInstance4];