UNPKG

@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
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;