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:
*
- * - creates a vertex buffer for the projected positions of the primitive in 2D
*
- adds an attribute for the 2D positions
- *
- adds a uniform for the view model matrix in 2D
*
*
* @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:
+ *
+ * - creates a vertex buffer for the projected positions of the primitive in 2D
+ *
- creates the bounding sphere for the primitive in 2D
+ *
- adds a uniform for the view model matrix in 2D
+ *
+ *
+ * @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);
+ });
+ });
+});