@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
302 lines (262 loc) • 9.02 kB
JavaScript
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 ModelUtility from "./ModelUtility.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();
const scratchModelView2D = new Matrix4();
/**
* The scene mode 2D stage generates resources for rendering a primitive in 2D / CV mode.
*
* @namespace SceneMode2DPipelineStage
*
* @private
*/
const SceneMode2DPipelineStage = {
name: "SceneMode2DPipelineStage", // Helps with debugging
};
/**
* This pipeline stage processes the position attribute of a primitive and adds the relevant
* define and uniform matrix to the shader. It also generates new resources for the primitive
* in 2D. These resources persist in the runtime primitive so that the typed array used to
* store the positional data can be freed.
*
* This stage must go before the GeometryPipelineStage in the primitive pipeline.
*
* Processes a primitive. This stage modifies the following parts of the render resources:
* <ul>
* <li> creates a vertex buffer for the positions of the primitive projected to 2D
* <li> creates the bounding sphere for the primitive in 2D
* <li> adds a flag to the shader to use 2D positions
* <li> adds a uniform for the view model matrix in 2D
* </ul>
*
* @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 positionAttribute = ModelUtility.getAttributeBySemantic(
primitive,
VertexAttributeSemantic.POSITION,
);
const shaderBuilder = renderResources.shaderBuilder;
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,
);
const runtimePrimitive = renderResources.runtimePrimitive;
runtimePrimitive.boundingSphere2D = boundingSphere2D;
// If the model is instanced, 2D projection will be handled in the
// InstancingPipelineStage.
const instances = renderResources.runtimeNode.node.instances;
if (defined(instances)) {
return;
}
// 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,
);
// Since this buffer will persist even if the pipeline is re-run,
// its memory will be counted in PrimitiveStatisticsPipelineStage
runtimePrimitive.positionBuffer2D = buffer2D;
model._modelResources.push(buffer2D);
// Unload the typed array. This is just a pointer to the array in
// the vertex buffer loader, so if the typed array is shared by
// multiple primitives (i.e. multiple instances of the same mesh),
// this will not affect the other primitives.
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 context = frameState.context;
const uniformMap = {
u_modelView2D: function () {
return Matrix4.multiplyTransformation(
context.uniformState.view,
modelMatrix2D,
scratchModelView2D,
);
},
};
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.computeActualEllipsoidPosition(
frameState,
transformedPositionMin,
transformedPositionMin,
);
const transformedPositionMax = Matrix4.multiplyByPoint(
modelMatrix,
renderResources.positionMax,
scratchProjectedMax,
);
const projectedMax = SceneTransforms.computeActualEllipsoidPosition(
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,
) {
let result;
if (defined(attribute.quantization)) {
// Dequantize the positions if necessary.
result = dequantizePositionsTypedArray(
attribute.typedArray,
attribute.quantization,
);
} else {
result = attribute.typedArray.slice();
}
const startIndex = attribute.byteOffset / Float32Array.BYTES_PER_ELEMENT;
const length = result.length;
const stride = defined(attribute.byteStride)
? attribute.byteStride / Float32Array.BYTES_PER_ELEMENT
: 3;
for (let i = startIndex; i < length; i += stride) {
const initialPosition = Cartesian3.fromArray(result, i, scratchPosition);
if (
isNaN(initialPosition.x) ||
isNaN(initialPosition.y) ||
isNaN(initialPosition.z)
) {
continue;
}
const transformedPosition = Matrix4.multiplyByPoint(
modelMatrix,
initialPosition,
initialPosition,
);
const projectedPosition = SceneTransforms.computeActualEllipsoidPosition(
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;