diff --git a/Source/Scene/ModelExperimental/GeometryPipelineStage.js b/Source/Scene/ModelExperimental/GeometryPipelineStage.js index 436b39f295e0..445b469fcc60 100644 --- a/Source/Scene/ModelExperimental/GeometryPipelineStage.js +++ b/Source/Scene/ModelExperimental/GeometryPipelineStage.js @@ -1,15 +1,8 @@ import AttributeType from "../AttributeType.js"; -import BoundingSphere from "../../Core/BoundingSphere.js"; -import Buffer from "../../Renderer/Buffer.js"; -import BufferUsage from "../../Renderer/BufferUsage.js"; -import Cartesian3 from "../../Core/Cartesian3.js"; -import clone from "../../Core/clone.js"; -import combine from "../../Core/combine.js"; import ComponentDatatype from "../../Core/ComponentDatatype.js"; import defined from "../../Core/defined.js"; import GeometryStageFS from "../../Shaders/ModelExperimental/GeometryStageFS.js"; import GeometryStageVS from "../../Shaders/ModelExperimental/GeometryStageVS.js"; -import Matrix4 from "../../Core/Matrix4.js"; import ModelExperimentalUtility from "./ModelExperimentalUtility.js"; import ModelExperimentalType from "./ModelExperimentalType.js"; import PrimitiveType from "../../Core/PrimitiveType.js"; @@ -17,7 +10,6 @@ import SceneMode from "../SceneMode.js"; import SelectedFeatureIdPipelineStage from "./SelectedFeatureIdPipelineStage.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; -import SceneTransforms from "../SceneTransforms.js"; /** * The geometry pipeline stage processes the vertex attributes of a primitive. @@ -58,9 +50,7 @@ GeometryPipelineStage.FUNCTION_SIGNATURE_SET_DYNAMIC_VARYINGS = * * If the scene is not 3D only, this stage also: * * * @param {PrimitiveRenderResources} renderResources The render resources for this primitive. @@ -146,10 +136,6 @@ GeometryPipelineStage.process = function ( const mode = frameState.mode; const use2D = mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW; - // The reference point, a.k.a. the center of the projected bounding sphere, - // will be computed and stored when the position attribute is processed. This is - // used for the 2D model matrix uniform. - let referencePoint2D; for (let i = 0; i < primitive.attributes.length; i++) { const attribute = primitive.attributes[i]; const attributeLocationCount = AttributeType.getAttributeLocationCount( @@ -178,10 +164,6 @@ GeometryPipelineStage.process = function ( frameState, use2D ); - - if (isPositionAttribute) { - referencePoint2D = attribute.referencePoint2D; - } } handleBitangents(shaderBuilder, primitive.attributes); @@ -192,38 +174,6 @@ GeometryPipelineStage.process = function ( shaderBuilder.addVertexLines([GeometryStageVS]); shaderBuilder.addFragmentLines([GeometryStageFS]); - - // Handle the shader define and model matrix uniform for 2D - if (use2D) { - shaderBuilder.addDefine( - "USE_2D_POSITIONS", - undefined, - ShaderDestination.VERTEX - ); - shaderBuilder.addUniform("mat4", "u_modelView2D", ShaderDestination.VERTEX); - - const modelMatrix = Matrix4.fromTranslation( - referencePoint2D, - new Matrix4() - ); - const modelView = new Matrix4(); - - const camera = frameState.camera; - const uniformMap = { - u_modelView2D: function () { - return Matrix4.multiplyTransformation( - camera.viewMatrix, - modelMatrix, - modelView - ); - }, - }; - - renderResources.uniformMap = combine( - uniformMap, - renderResources.uniformMap - ); - } }; function processAttribute( @@ -250,7 +200,6 @@ function processAttribute( renderResources, attribute, attributeIndex, - frameState, use2D ); } @@ -295,7 +244,6 @@ function addAttributeToRenderResources( renderResources, attribute, attributeIndex, - frameState, use2D ) { const quantization = attribute.quantization; @@ -337,29 +285,19 @@ function addAttributeToRenderResources( renderResources.attributes.push(vertexAttribute); - if (!isPositionAttribute) { - return; - } - - // If the position typed array exists, project the positions and - // store them in a separate GPU buffer in the attribute. - if (defined(attribute.typedArray)) { - modifyAttributeFor2D(attribute, renderResources, frameState); - attribute.typedArray = undefined; - } - - if (!use2D) { + if (!isPositionAttribute || !use2D) { return; } // Add an additional attribute for the projected positions in 2D / CV. + const buffer2D = renderResources.runtimePrimitive.positionBuffer2D; const positionAttribute2D = { index: attributeIndex, - value: defined(attribute.buffer2D) ? undefined : attribute.constant, - vertexBuffer: attribute.buffer2D, + value: buffer2D ? undefined : attribute.constant, + vertexBuffer: buffer2D, count: attribute.count, componentsPerAttribute: componentsPerAttribute, - componentDatatype: ComponentDatatype.FLOAT, // The positions will always be projected as floats. + componentDatatype: ComponentDatatype.FLOAT, // The projected positions will always be floats. offsetInBytes: attribute.byteOffset, strideInBytes: attribute.byteStride, normalize: attribute.normalized, @@ -592,183 +530,4 @@ function handleBitangents(shaderBuilder, attributes) { ); } -const scratchPosition = new Cartesian3(); - -function dequantizePositionsTypedArray(typedArray, quantization) { - // Draco compression is normally handled in the shader, but it must - // be decoded here to project the positions to 2D / CV. - const length = typedArray.length; - const dequantizedArray = new Float32Array(length); - const quantizedVolumeOffset = quantization.quantizedVolumeOffset; - const quantizedVolumeStepSize = quantization.quantizedVolumeStepSize; - for (let i = 0; i < length; i += 3) { - const initialPosition = Cartesian3.fromArray( - typedArray, - i, - scratchPosition - ); - const scaledPosition = Cartesian3.multiplyComponents( - initialPosition, - quantizedVolumeStepSize, - initialPosition - ); - const dequantizedPosition = Cartesian3.add( - scaledPosition, - quantizedVolumeOffset, - scaledPosition - ); - - dequantizedArray[i] = dequantizedPosition.x; - dequantizedArray[i + 1] = dequantizedPosition.y; - dequantizedArray[i + 2] = dequantizedPosition.z; - } - - return dequantizedArray; -} - -function createPositionsTypedArrayFor2D( - attribute, - modelMatrix, - frameState, - referencePoint -) { - const typedArray = attribute.typedArray; - - let result; - if (defined(attribute.quantization)) { - // Dequantize the positions if necessary. - result = dequantizePositionsTypedArray( - attribute.typedArray, - attribute.quantization - ); - } else { - result = new Float32Array( - typedArray.buffer, - typedArray.byteOffset, - typedArray.byteLength / Float32Array.BYTES_PER_ELEMENT - ); - } - - const startIndex = attribute.byteOffset / Float32Array.BYTES_PER_ELEMENT; - const length = result.length; - for (let i = startIndex; i < length; i += 3) { - const initialPosition = Cartesian3.fromArray(result, i, scratchPosition); - - const transformedPosition = Matrix4.multiplyByPoint( - modelMatrix, - initialPosition, - initialPosition - ); - - const projectedPosition = SceneTransforms.computeActualWgs84Position( - frameState, - transformedPosition, - transformedPosition - ); - - // To prevent jitter, the positions are defined relative to - // a common reference point. For convenience, this is the center - // of the primitive's bounding sphere. - const relativePosition = Cartesian3.subtract( - projectedPosition, - referencePoint, - projectedPosition - ); - - result[i] = relativePosition.x; - result[i + 1] = relativePosition.y; - result[i + 2] = relativePosition.z; - } - - return result; -} - -const scratchMatrix = new Matrix4(); -const scratchProjectedMin = new Cartesian3(); -const scratchProjectedMax = new Cartesian3(); -const scratchBoundingSphere = new BoundingSphere(); - -function computeReferencePoint( - positionMin, - positionMax, - modelMatrix, - frameState -) { - // Compute the bounding sphere in 2D. - const transformedPositionMin = Matrix4.multiplyByPoint( - modelMatrix, - positionMin, - scratchProjectedMin - ); - - const projectedMin = SceneTransforms.computeActualWgs84Position( - frameState, - transformedPositionMin, - transformedPositionMin - ); - - const transformedPositionMax = Matrix4.multiplyByPoint( - modelMatrix, - positionMax, - scratchProjectedMax - ); - - const projectedMax = SceneTransforms.computeActualWgs84Position( - frameState, - transformedPositionMax, - transformedPositionMax - ); - - const boundingSphere = BoundingSphere.fromCornerPoints( - projectedMin, - projectedMax, - scratchBoundingSphere - ); - - // Use the center of the 2D bounding sphere as the reference point. - return Cartesian3.clone(boundingSphere.center, new Cartesian3()); -} - -function modifyAttributeFor2D(attribute, renderResources, frameState) { - const sceneGraph = renderResources.model.sceneGraph; - const modelMatrix = sceneGraph.computedModelMatrix; - const nodeComputedTransform = renderResources.runtimeNode.computedTransform; - const computedModelMatrix = Matrix4.multiplyTransformation( - modelMatrix, - nodeComputedTransform, - scratchMatrix - ); - - // Force the scene mode to be CV. In 2D, the projected position will have an - // x-coordinate of 0, which will be problematic when switching to CV mode. - const frameStateCV = clone(frameState); - frameStateCV.mode = SceneMode.COLUMBUS_VIEW; - - // Project positions relative to the 2D bounding sphere's center. - const referencePoint = computeReferencePoint( - renderResources.positionMin, - renderResources.positionMax, - computedModelMatrix, - frameStateCV - ); - const projectedPositions = createPositionsTypedArrayFor2D( - attribute, - computedModelMatrix, - frameStateCV, - referencePoint - ); - - // Put the resulting data in a GPU buffer. - const buffer = Buffer.createVertexBuffer({ - context: frameState.context, - typedArray: projectedPositions, - usage: BufferUsage.STATIC_DRAW, - }); - buffer.vertexArrayDestroyable = false; - - // Store the reference point for repeated future use. - attribute.buffer2D = buffer; - attribute.referencePoint2D = referencePoint; -} - export default GeometryPipelineStage; diff --git a/Source/Scene/ModelExperimental/ModelExperimental.js b/Source/Scene/ModelExperimental/ModelExperimental.js index 7e3df4f296d9..96f78d26a7e3 100644 --- a/Source/Scene/ModelExperimental/ModelExperimental.js +++ b/Source/Scene/ModelExperimental/ModelExperimental.js @@ -207,9 +207,12 @@ export default function ModelExperimental(options) { this._featureTableId = undefined; this._featureTableIdDirty = true; - // Keeps track of resources that need to be destroyed when the Model is destroyed. + // Keeps track of resources that need to be destroyed when the draw commands are reset. this._resources = []; + // Keeps track of resources that need to be destroyed when the Model is destroyed. + this._modelResources = []; + // Computation of the model's bounding sphere and its initial radius is done in ModelExperimentalSceneGraph this._boundingSphere = new BoundingSphere(); this._initialRadius = undefined; @@ -1462,6 +1465,7 @@ ModelExperimental.prototype.destroy = function () { } this.destroyResources(); + this.destroyModelResources(); // Only destroy the ClippingPlaneCollection if this is the owner. const clippingPlaneCollection = this._clippingPlanes; @@ -1498,6 +1502,18 @@ ModelExperimental.prototype.destroyResources = function () { this._resources = []; }; +/** + * Destroys resources generated for the model. + * @private + */ +ModelExperimental.prototype.destroyModelResources = function () { + const resources = this._modelResources; + for (let i = 0; i < resources.length; i++) { + resources[i].destroy(); + } + this._modelResources = []; +}; + /** *

* Creates a model from a glTF asset. When the model is ready to render, i.e., when the external binary, image, diff --git a/Source/Scene/ModelExperimental/ModelExperimentalPrimitive.js b/Source/Scene/ModelExperimental/ModelExperimentalPrimitive.js index 7f5f8ad38676..1aaf8297be1e 100644 --- a/Source/Scene/ModelExperimental/ModelExperimentalPrimitive.js +++ b/Source/Scene/ModelExperimental/ModelExperimentalPrimitive.js @@ -17,6 +17,8 @@ import ModelExperimentalUtility from "./ModelExperimentalUtility.js"; import MorphTargetsPipelineStage from "./MorphTargetsPipelineStage.js"; import PickingPipelineStage from "./PickingPipelineStage.js"; import PointCloudAttenuationPipelineStage from "./PointCloudAttenuationPipelineStage.js"; +import SceneMode from "../SceneMode.js"; +import SceneMode2DPipelineStage from "./SceneMode2DPipelineStage.js"; import SelectedFeatureIdPipelineStage from "./SelectedFeatureIdPipelineStage.js"; import SkinningPipelineStage from "./SkinningPipelineStage.js"; import WireframePipelineStage from "./WireframePipelineStage.js"; @@ -113,6 +115,17 @@ export default function ModelExperimentalPrimitive(options) { */ this.boundingSphere2D = undefined; + /** + * A buffer containing the primitive's positions projected to 2D world coordinates. + * Used for rendering in 2D / CV mode. + * + * @type {Buffer} + * @readonly + * + * @private + */ + this.positionBuffer2D = undefined; + /** * Update stages to apply to this primitive. * @@ -122,8 +135,8 @@ export default function ModelExperimentalPrimitive(options) { } /** - * Configure the primitive pipeline stages. If the pipeline needs to be re-run, call - * this method again to ensure the correct sequence of pipeline stages are + * Configure the primitive pipeline stages. If the pipeline needs to be re-run, + * call this method again to ensure the correct sequence of pipeline stages are * used. * * @param {FrameState} frameState The frame state. @@ -138,9 +151,10 @@ ModelExperimentalPrimitive.prototype.configurePipeline = function (frameState) { const node = this.node; const model = this.model; const customShader = model.customShader; - const context = frameState.context; - const useWebgl2 = context.webgl2; + const useWebgl2 = frameState.context.webgl2; + const mode = frameState.mode; + const use2D = mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW; const hasMorphTargets = defined(primitive.morphTargets) && primitive.morphTargets.length > 0; const hasSkinning = defined(node.skin); @@ -168,6 +182,9 @@ ModelExperimentalPrimitive.prototype.configurePipeline = function (frameState) { const featureIdFlags = inspectFeatureIds(model, node, primitive); // Start of pipeline ----------------------------------------------------- + if (use2D && !frameState.scene3DOnly) { + pipelineStages.push(SceneMode2DPipelineStage); + } pipelineStages.push(GeometryPipelineStage); diff --git a/Source/Scene/ModelExperimental/SceneMode2DPipelineStage.js b/Source/Scene/ModelExperimental/SceneMode2DPipelineStage.js new file mode 100644 index 000000000000..9407384ae988 --- /dev/null +++ b/Source/Scene/ModelExperimental/SceneMode2DPipelineStage.js @@ -0,0 +1,281 @@ +import BoundingSphere from "../../Core/BoundingSphere.js"; +import Buffer from "../../Renderer/Buffer.js"; +import BufferUsage from "../../Renderer/BufferUsage.js"; +import Cartesian3 from "../../Core/Cartesian3.js"; +import clone from "../../Core/clone.js"; +import combine from "../../Core/combine.js"; +import defined from "../../Core/defined.js"; +import Matrix4 from "../../Core/Matrix4.js"; +import ModelExperimentalUtility from "./ModelExperimentalUtility.js"; +import SceneMode from "../SceneMode.js"; +import ShaderDestination from "../../Renderer/ShaderDestination.js"; +import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; +import SceneTransforms from "../SceneTransforms.js"; + +const scratchModelMatrix = new Matrix4(); + +/** + * The scene mode 2D stage processes the vertex attributes of a primitive. + * + * @namespace SceneMode2DPipelineStage + * + * @private + */ +const SceneMode2DPipelineStage = {}; +SceneMode2DPipelineStage.name = "SceneMode2DPipelineStage"; // Helps with debugging + +/** + * This pipeline stage processes the vertex attributes of a primitive, adding the attribute declarations to the shaders, + * the attribute objects to the render resources and setting the flags as needed. + * + * This must go before GeometryPipelineStage in the primitive pipeline. + * + * Processes a primitive. This stage modifies the following parts of the render resources: + *

+ * + * @param {PrimitiveRenderResources} renderResources The render resources for this primitive. + * @param {ModelComponents.Primitive} primitive The primitive. + * @param {FrameState} frameState The frame state. + * + * @private + */ + +SceneMode2DPipelineStage.process = function ( + renderResources, + primitive, + frameState +) { + const shaderBuilder = renderResources.shaderBuilder; + const runtimePrimitive = renderResources.runtimePrimitive; + + const model = renderResources.model; + const modelMatrix = model.sceneGraph.computedModelMatrix; + const nodeComputedTransform = renderResources.runtimeNode.computedTransform; + const computedModelMatrix = Matrix4.multiplyTransformation( + modelMatrix, + nodeComputedTransform, + scratchModelMatrix + ); + + const boundingSphere2D = computeBoundingSphere2D( + renderResources, + computedModelMatrix, + frameState + ); + + runtimePrimitive.boundingSphere2D = boundingSphere2D; + + const positionAttribute = ModelExperimentalUtility.getAttributeBySemantic( + primitive, + VertexAttributeSemantic.POSITION + ); + + // If the typed array of the position attribute exists, then + // the positions haven't been projected to 2D yet. + if (defined(positionAttribute.typedArray)) { + const buffer2D = createPositionBufferFor2D( + positionAttribute, + computedModelMatrix, + boundingSphere2D, + frameState + ); + + runtimePrimitive.positionBuffer2D = buffer2D; + model._modelResources.push(buffer2D); + + // Unload the typed array + positionAttribute.typedArray = undefined; + } + + shaderBuilder.addDefine( + "USE_2D_POSITIONS", + undefined, + ShaderDestination.VERTEX + ); + + shaderBuilder.addUniform("mat4", "u_modelView2D", ShaderDestination.VERTEX); + + const modelMatrix2D = Matrix4.fromTranslation( + boundingSphere2D.center, + new Matrix4() + ); + const modelView = new Matrix4(); + + const camera = frameState.camera; + const uniformMap = { + u_modelView2D: function () { + return Matrix4.multiplyTransformation( + camera.viewMatrix, + modelMatrix2D, + modelView + ); + }, + }; + + renderResources.uniformMap = combine(uniformMap, renderResources.uniformMap); +}; + +const scratchProjectedMin = new Cartesian3(); +const scratchProjectedMax = new Cartesian3(); + +function computeBoundingSphere2D(renderResources, modelMatrix, frameState) { + // Compute the bounding sphere in 2D. + const transformedPositionMin = Matrix4.multiplyByPoint( + modelMatrix, + renderResources.positionMin, + scratchProjectedMin + ); + + const projectedMin = SceneTransforms.computeActualWgs84Position( + frameState, + transformedPositionMin, + transformedPositionMin + ); + + const transformedPositionMax = Matrix4.multiplyByPoint( + modelMatrix, + renderResources.positionMax, + scratchProjectedMax + ); + + const projectedMax = SceneTransforms.computeActualWgs84Position( + frameState, + transformedPositionMax, + transformedPositionMax + ); + + return BoundingSphere.fromCornerPoints( + projectedMin, + projectedMax, + new BoundingSphere() + ); +} + +const scratchPosition = new Cartesian3(); + +function dequantizePositionsTypedArray(typedArray, quantization) { + // Draco compression is normally handled in the dequantization stage + // in the shader, but it must be decoded here in order to project + // the positions to 2D / CV. + const length = typedArray.length; + const dequantizedArray = new Float32Array(length); + const quantizedVolumeOffset = quantization.quantizedVolumeOffset; + const quantizedVolumeStepSize = quantization.quantizedVolumeStepSize; + for (let i = 0; i < length; i += 3) { + const initialPosition = Cartesian3.fromArray( + typedArray, + i, + scratchPosition + ); + const scaledPosition = Cartesian3.multiplyComponents( + initialPosition, + quantizedVolumeStepSize, + initialPosition + ); + const dequantizedPosition = Cartesian3.add( + scaledPosition, + quantizedVolumeOffset, + scaledPosition + ); + + dequantizedArray[i] = dequantizedPosition.x; + dequantizedArray[i + 1] = dequantizedPosition.y; + dequantizedArray[i + 2] = dequantizedPosition.z; + } + + return dequantizedArray; +} + +function createPositionsTypedArrayFor2D( + attribute, + modelMatrix, + referencePoint, + frameState +) { + const typedArray = attribute.typedArray; + + let result; + if (defined(attribute.quantization)) { + // Dequantize the positions if necessary. + result = dequantizePositionsTypedArray( + attribute.typedArray, + attribute.quantization + ); + } else { + result = new Float32Array( + typedArray.buffer, + typedArray.byteOffset, + typedArray.byteLength / Float32Array.BYTES_PER_ELEMENT + ); + } + + const startIndex = attribute.byteOffset / Float32Array.BYTES_PER_ELEMENT; + const length = result.length; + for (let i = startIndex; i < length; i += 3) { + const initialPosition = Cartesian3.fromArray(result, i, scratchPosition); + + const transformedPosition = Matrix4.multiplyByPoint( + modelMatrix, + initialPosition, + initialPosition + ); + + const projectedPosition = SceneTransforms.computeActualWgs84Position( + frameState, + transformedPosition, + transformedPosition + ); + + const relativePosition = Cartesian3.subtract( + projectedPosition, + referencePoint, + projectedPosition + ); + + result[i] = relativePosition.x; + result[i + 1] = relativePosition.y; + result[i + 2] = relativePosition.z; + } + + return result; +} + +function createPositionBufferFor2D( + positionAttribute, + modelMatrix, + boundingSphere2D, + frameState +) { + // Force the scene mode to be CV. In 2D, projected positions will have + // an x-coordinate of 0, which eliminates the height data that is + // necessary for rendering in CV mode. + const frameStateCV = clone(frameState); + frameStateCV.mode = SceneMode.COLUMBUS_VIEW; + + // To prevent jitter, the positions are defined relative to a common + // reference point. For convenience, this is the center of the + // primitive's bounding sphere in 2D. + const referencePoint = boundingSphere2D.center; + const projectedPositions = createPositionsTypedArrayFor2D( + positionAttribute, + modelMatrix, + referencePoint, + frameStateCV + ); + + // Put the resulting data in a GPU buffer. + const buffer = Buffer.createVertexBuffer({ + context: frameState.context, + typedArray: projectedPositions, + usage: BufferUsage.STATIC_DRAW, + }); + buffer.vertexArrayDestroyable = false; + + return buffer; +} + +export default SceneMode2DPipelineStage; diff --git a/Source/Scene/ModelExperimental/buildDrawCommands.js b/Source/Scene/ModelExperimental/buildDrawCommands.js index fe9ab59ce935..26e303414156 100644 --- a/Source/Scene/ModelExperimental/buildDrawCommands.js +++ b/Source/Scene/ModelExperimental/buildDrawCommands.js @@ -1,7 +1,6 @@ import BlendingState from "../BlendingState.js"; import Buffer from "../../Renderer/Buffer.js"; import BufferUsage from "../../Renderer/BufferUsage.js"; -import Cartesian3 from "../../Core/Cartesian3.js"; import clone from "../../Core/clone.js"; import defined from "../../Core/defined.js"; import DrawCommand from "../../Renderer/DrawCommand.js"; @@ -18,10 +17,6 @@ import BoundingSphere from "../../Core/BoundingSphere.js"; import Matrix4 from "../../Core/Matrix4.js"; import ShadowMode from "../ShadowMode.js"; import SceneMode from "../SceneMode.js"; -import SceneTransforms from "../SceneTransforms.js"; - -const scratchPositionMin = new Cartesian3(); -const scratchPositionMax = new Cartesian3(); /** * Builds the DrawCommands for a {@link ModelExperimentalPrimitive} using its render resources. @@ -80,11 +75,8 @@ export default function buildDrawCommands( let boundingSphere; if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) { - boundingSphere = computeBoundingSphere2D( - primitiveRenderResources, - modelMatrix, - frameState - ); + const runtimePrimitive = primitiveRenderResources.runtimePrimitive; + boundingSphere = runtimePrimitive.boundingSphere2D; } else { boundingSphere = BoundingSphere.transform( primitiveRenderResources.boundingSphere, @@ -205,40 +197,3 @@ function getIndexBuffer(primitiveRenderResources, frameState) { indexDatatype: indexDatatype, }); } - -/** - * @private - */ -function computeBoundingSphere2D( - primitiveRenderResources, - modelMatrix, - frameState -) { - const projectedMin = Matrix4.multiplyByPoint( - modelMatrix, - primitiveRenderResources.positionMin, - scratchPositionMin - ); - const projectedMax = Matrix4.multiplyByPoint( - modelMatrix, - primitiveRenderResources.positionMax, - scratchPositionMax - ); - - SceneTransforms.computeActualWgs84Position( - frameState, - projectedMin, - projectedMin - ); - SceneTransforms.computeActualWgs84Position( - frameState, - projectedMax, - projectedMax - ); - - return BoundingSphere.fromCornerPoints( - projectedMin, - projectedMax, - new BoundingSphere() - ); -} diff --git a/Specs/Scene/ModelExperimental/GeometryPipelineStageSpec.js b/Specs/Scene/ModelExperimental/GeometryPipelineStageSpec.js index 96587fb41656..2691b0267931 100644 --- a/Specs/Scene/ModelExperimental/GeometryPipelineStageSpec.js +++ b/Specs/Scene/ModelExperimental/GeometryPipelineStageSpec.js @@ -1,16 +1,12 @@ import { AttributeType, - Cartesian3, combine, ComponentDatatype, - defaultValue, GeometryPipelineStage, GltfLoader, - Matrix4, ModelExperimentalType, Resource, ResourceCache, - SceneMode, SelectedFeatureIdPipelineStage, ShaderBuilder, VertexAttributeSemantic, @@ -19,11 +15,9 @@ import createScene from "../../createScene.js"; import waitForLoaderProcess from "../../waitForLoaderProcess.js"; import ShaderBuilderTester from "../../ShaderBuilderTester.js"; -describe( +fdescribe( "Scene/ModelExperimental/GeometryPipelineStage", function () { - const scratchMatrix = new Matrix4(); - const positionOnlyPrimitive = { attributes: [ { @@ -82,11 +76,9 @@ describe( const gltfLoaders = []; beforeAll(function () { - scene = createScene({ - scene3DOnly: true, - }); + scene = createScene(); scene2D = createScene(); - scene2D._mode = SceneMode.SCENE2D; + scene2D.morphTo2D(0.0); scene2D.updateFrameState(); }); @@ -134,20 +126,11 @@ describe( } function loadGltf(gltfPath, options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); gltfLoaders.push(gltfLoader); gltfLoader.load(); - // To load the vertex buffers as typed arrays, - // scene3DOnly must be false. - const loadPositionsFor2D = defaultValue( - options.loadPositionsFor2D, - false - ); - const targetScene = loadPositionsFor2D ? scene2D : scene; - - return waitForLoaderProcess(gltfLoader, targetScene); + return waitForLoaderProcess(gltfLoader, scene); } it("processes POSITION attribute from primitive", function () { @@ -163,7 +146,7 @@ describe( GeometryPipelineStage.process( renderResources, positionOnlyPrimitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -242,7 +225,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -344,54 +327,29 @@ describe( }); }); - // TODO: add one for draco compressed typed array - it("processes POSITION typed array from primitive for 2D", function () { - const positionMin = new Cartesian3(-0.5, -0.5, -0.5); - const positionMax = new Cartesian3(0.5, 0.5, 0.5); + it("processes POSITION attribute from primitive for 2D", function () { + const runtimePrimitive = { + positionBuffer2D: {}, + }; const renderResources = { attributes: [], shaderBuilder: new ShaderBuilder(), attributeIndex: 1, model: { type: ModelExperimentalType.TILE_GLTF, - sceneGraph: { - computedModelMatrix: Matrix4.IDENTITY, - }, - }, - runtimeNode: { - computedTransform: Matrix4.IDENTITY, }, - positionMin: positionMin, - positionMax: positionMax, + runtimePrimitive: runtimePrimitive, }; - return loadGltf(boxTextured, { loadPositionsFor2D: true }).then(function ( - gltfLoader - ) { + return loadGltf(boxTextured).then(function (gltfLoader) { const components = gltfLoader.components; const primitive = components.nodes[1].primitives[0]; GeometryPipelineStage.process( renderResources, primitive, - scene2D._frameState - ); - - let referencePoint2D; - // Check that all typed arrays have been unloaded - const primitiveAttributes = primitive.attributes; - const length = primitiveAttributes.length; - for (let i = 0; i < length; i++) { - const attribute = primitiveAttributes[i]; - expect(attribute.typedArray).toBeUndefined(); - - // Expect the 2D positions to be stored in the attribute. - if (attribute.semantic === VertexAttributeSemantic.POSITION) { - expect(attribute.buffer2D).toBeDefined(); - expect(attribute.referencePoint2D).toBeDefined(); - referencePoint2D = attribute.referencePoint2D; - } - } + scene2D.frameState + ); const shaderBuilder = renderResources.shaderBuilder; const attributes = renderResources.attributes; @@ -413,7 +371,9 @@ describe( const position2DAttribute = attributes[2]; expect(position2DAttribute.index).toEqual(2); - expect(position2DAttribute.vertexBuffer).toBeDefined(); + expect(position2DAttribute.vertexBuffer).toBe( + runtimePrimitive.positionBuffer2D + ); expect(position2DAttribute.componentsPerAttribute).toEqual(3); expect(position2DAttribute.componentDatatype).toEqual( ComponentDatatype.FLOAT @@ -449,7 +409,6 @@ describe( ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ "HAS_NORMALS", "HAS_TEXCOORD_0", - "USE_2D_POSITIONS", ]); ShaderBuilderTester.expectHasAttributes( shaderBuilder, @@ -461,17 +420,6 @@ describe( ] ); verifyFeatureStruct(shaderBuilder); - - const translationMatrix = Matrix4.fromTranslation( - referencePoint2D, - scratchMatrix - ); - const expected = Matrix4.multiplyTransformation( - scene2D._frameState.camera.viewMatrix, - translationMatrix, - translationMatrix - ); - expect(renderResources.uniformMap.u_modelView2D()).toEqual(expected); }); }); @@ -492,7 +440,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -642,7 +590,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -769,7 +717,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -918,7 +866,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -1020,7 +968,7 @@ describe( GeometryPipelineStage.process( renderResources, customAttributePrimitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -1117,7 +1065,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -1221,7 +1169,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -1309,7 +1257,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -1410,7 +1358,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; @@ -1519,6 +1467,127 @@ describe( }); }); + it("processes Draco model for 2D", function () { + const runtimePrimitive = { + positionBuffer2D: {}, + }; + const renderResources = { + attributes: [], + shaderBuilder: new ShaderBuilder(), + attributeIndex: 1, + model: { + type: ModelExperimentalType.TILE_GLTF, + }, + runtimePrimitive: runtimePrimitive, + }; + + return loadGltf(dracoMilkTruck).then(function (gltfLoader) { + const components = gltfLoader.components; + const primitive = components.nodes[0].primitives[0]; + + GeometryPipelineStage.process( + renderResources, + primitive, + scene2D.frameState + ); + + const shaderBuilder = renderResources.shaderBuilder; + const attributes = renderResources.attributes; + + expect(attributes.length).toEqual(4); + + const normalAttribute = attributes[0]; + expect(normalAttribute.index).toEqual(1); + expect(normalAttribute.vertexBuffer).toBeDefined(); + expect(normalAttribute.componentsPerAttribute).toEqual(2); + expect(normalAttribute.componentDatatype).toEqual( + ComponentDatatype.UNSIGNED_SHORT + ); + expect(normalAttribute.offsetInBytes).toBe(0); + expect(normalAttribute.strideInBytes).not.toBeDefined(); + + const positionAttribute = attributes[1]; + expect(positionAttribute.index).toEqual(0); + expect(positionAttribute.vertexBuffer).toBeDefined(); + expect(positionAttribute.componentsPerAttribute).toEqual(3); + expect(positionAttribute.componentDatatype).toEqual( + ComponentDatatype.UNSIGNED_SHORT + ); + expect(positionAttribute.offsetInBytes).toBe(0); + expect(positionAttribute.strideInBytes).not.toBeDefined(); + + const positionAttribute2D = attributes[2]; + expect(positionAttribute2D.index).toEqual(2); + expect(positionAttribute2D.vertexBuffer).toBeDefined(); + expect(positionAttribute2D.componentsPerAttribute).toEqual(3); + expect(positionAttribute2D.componentDatatype).toEqual( + ComponentDatatype.FLOAT + ); + expect(positionAttribute2D.offsetInBytes).toBe(0); + expect(positionAttribute2D.strideInBytes).not.toBeDefined(); + + const texCoord0Attribute = attributes[3]; + expect(texCoord0Attribute.index).toEqual(3); + expect(texCoord0Attribute.vertexBuffer).toBeDefined(); + expect(texCoord0Attribute.componentsPerAttribute).toEqual(2); + expect(texCoord0Attribute.componentDatatype).toEqual( + ComponentDatatype.UNSIGNED_SHORT + ); + expect(texCoord0Attribute.offsetInBytes).toBe(0); + expect(texCoord0Attribute.strideInBytes).not.toBeDefined(); + + ShaderBuilderTester.expectHasVertexStruct( + shaderBuilder, + GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_VS, + GeometryPipelineStage.STRUCT_NAME_PROCESSED_ATTRIBUTES, + [ + " vec3 positionMC;", + " vec3 position2D;", + " vec3 normalMC;", + " vec2 texCoord_0;", + ] + ); + ShaderBuilderTester.expectHasFragmentStruct( + shaderBuilder, + GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_FS, + GeometryPipelineStage.STRUCT_NAME_PROCESSED_ATTRIBUTES, + [ + " vec3 positionMC;", + " vec3 positionWC;", + " vec3 positionEC;", + " vec3 normalEC;", + " vec2 texCoord_0;", + ] + ); + + // While initialization is skipped for dequantized attributes, + // the 2D position attribute should still be accounted for + ShaderBuilderTester.expectHasVertexFunctionUnordered( + shaderBuilder, + GeometryPipelineStage.FUNCTION_ID_INITIALIZE_ATTRIBUTES, + GeometryPipelineStage.FUNCTION_SIGNATURE_INITIALIZE_ATTRIBUTES, + [" attributes.position2D = a_position2D;"] + ); + ShaderBuilderTester.expectHasAttributes( + shaderBuilder, + "attribute vec3 a_quantized_positionMC;", + [ + "attribute vec3 a_position2D;", + "attribute vec2 a_quantized_normalMC;", + "attribute vec2 a_quantized_texCoord_0;", + ] + ); + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ + "HAS_NORMALS", + "HAS_TEXCOORD_0", + ]); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_NORMALS", + "HAS_TEXCOORD_0", + ]); + }); + }); + it("processes model with matrix attributes", function () { const renderResources = { attributes: [], @@ -1538,7 +1607,7 @@ describe( GeometryPipelineStage.process( renderResources, primitive, - scene._frameState + scene.frameState ); const shaderBuilder = renderResources.shaderBuilder; diff --git a/Specs/Scene/ModelExperimental/SceneMode2DPipelineStageSpec.js b/Specs/Scene/ModelExperimental/SceneMode2DPipelineStageSpec.js new file mode 100644 index 000000000000..7757fd1dae9b --- /dev/null +++ b/Specs/Scene/ModelExperimental/SceneMode2DPipelineStageSpec.js @@ -0,0 +1,179 @@ +import { + Cartesian3, + combine, + GltfLoader, + Matrix4, + ModelExperimentalType, + ModelExperimentalUtility, + Resource, + ResourceCache, + SceneMode2DPipelineStage, + ShaderBuilder, + VertexAttributeSemantic, +} from "../../../Source/Cesium.js"; +import createScene from "../../createScene.js"; +import waitForLoaderProcess from "../../waitForLoaderProcess.js"; +import ShaderBuilderTester from "../../ShaderBuilderTester.js"; + +describe("Scene/ModelExperimental/SceneMode2DPipelineStage", function () { + const scratchMatrix = new Matrix4(); + + const boxTexturedUrl = + "./Data/Models/GltfLoader/BoxTextured/glTF-Binary/BoxTextured.glb"; + const dracoBoxWithTangentsUrl = + "./Data/Models/DracoCompression/BoxWithTangents/BoxWithTangents.gltf"; + + let scene; + const gltfLoaders = []; + + beforeAll(function () { + scene = createScene(); + scene.morphTo2D(0.0); + scene.updateFrameState(); + }); + + afterAll(function () { + scene.destroyForSpecs(); + }); + + afterEach(function () { + const gltfLoadersLength = gltfLoaders.length; + for (let i = 0; i < gltfLoadersLength; ++i) { + const gltfLoader = gltfLoaders[i]; + if (!gltfLoader.isDestroyed()) { + gltfLoader.destroy(); + } + } + gltfLoaders.length = 0; + ResourceCache.clearForSpecs(); + }); + + function getOptions(gltfPath, options) { + const resource = new Resource({ + url: gltfPath, + }); + + return combine(options, { + gltfResource: resource, + incrementallyLoadTextures: false, // Default to false if not supplied + }); + } + + function loadGltf(gltfPath, options) { + const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); + gltfLoaders.push(gltfLoader); + gltfLoader.load(); + + return waitForLoaderProcess(gltfLoader, scene); + } + + function mockRenderResources() { + return { + attributes: [], + shaderBuilder: new ShaderBuilder(), + attributeIndex: 1, + model: { + type: ModelExperimentalType.TILE_GLTF, + sceneGraph: { + computedModelMatrix: Matrix4.IDENTITY, + }, + _modelResources: [], + }, + runtimeNode: { + computedTransform: Matrix4.IDENTITY, + }, + runtimePrimitive: {}, + positionMin: new Cartesian3(-0.5, -0.5, -0.5), + positionMax: new Cartesian3(0.5, 0.5, 0.5), + }; + } + + it("processes resources for 2D for primitive", function () { + const renderResources = mockRenderResources(); + + return loadGltf(boxTexturedUrl, { + loadPositionsFor2D: true, + }).then(function (gltfLoader) { + const components = gltfLoader.components; + const primitive = components.nodes[1].primitives[0]; + + SceneMode2DPipelineStage.process( + renderResources, + primitive, + scene.frameState + ); + + const runtimePrimitive = renderResources.runtimePrimitive; + expect(runtimePrimitive.boundingSphere2D).toBeDefined(); + expect(runtimePrimitive.positionBuffer2D).toBeDefined(); + + // Check that the position attribute's typed array has been unloaded. + const positionAttribute = ModelExperimentalUtility.getAttributeBySemantic( + primitive, + VertexAttributeSemantic.POSITION + ); + expect(positionAttribute.typedArray).toBeUndefined(); + + const shaderBuilder = renderResources.shaderBuilder; + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ + "USE_2D_POSITIONS", + ]); + + const translationMatrix = Matrix4.fromTranslation( + runtimePrimitive.boundingSphere2D.center, + scratchMatrix + ); + const expected = Matrix4.multiplyTransformation( + scene.frameState.camera.viewMatrix, + translationMatrix, + translationMatrix + ); + expect(renderResources.uniformMap.u_modelView2D()).toEqual(expected); + }); + }); + + it("processes resources for 2D for primitive with draco compression", function () { + const renderResources = mockRenderResources(); + + return loadGltf(dracoBoxWithTangentsUrl, { + loadPositionsFor2D: true, + }).then(function (gltfLoader) { + const components = gltfLoader.components; + const primitive = components.nodes[0].primitives[0]; + + SceneMode2DPipelineStage.process( + renderResources, + primitive, + scene.frameState + ); + + const runtimePrimitive = renderResources.runtimePrimitive; + expect(runtimePrimitive.boundingSphere2D).toBeDefined(); + expect(runtimePrimitive.positionBuffer2D).toBeDefined(); + + // Check that the position attribute's typed array has been unloaded. + const positionAttribute = ModelExperimentalUtility.getAttributeBySemantic( + primitive, + VertexAttributeSemantic.POSITION + ); + expect(positionAttribute.typedArray).toBeUndefined(); + + const shaderBuilder = renderResources.shaderBuilder; + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ + "USE_2D_POSITIONS", + ]); + + const translationMatrix = Matrix4.fromTranslation( + runtimePrimitive.boundingSphere2D.center, + scratchMatrix + ); + const expected = Matrix4.multiplyTransformation( + scene.frameState.camera.viewMatrix, + translationMatrix, + translationMatrix + ); + expect(renderResources.uniformMap.u_modelView2D()).toEqual(expected); + }); + }); +});