import BoundingSphere from "../../Core/BoundingSphere.js"; import Check from "../../Core/Check.js"; import defaultValue from "../../Core/defaultValue.js"; import Matrix4 from "../../Core/Matrix4.js"; import DrawCommand from "../../Renderer/DrawCommand.js"; import Pass from "../../Renderer/Pass.js"; import RenderState from "../../Renderer/RenderState.js"; import BlendingState from "../BlendingState.js"; import ClassificationType from "../ClassificationType.js"; import DepthFunction from "../DepthFunction.js"; import StencilConstants from "../StencilConstants.js"; import StencilFunction from "../StencilFunction.js"; import StencilOperation from "../StencilOperation.js"; /** * A wrapper around the draw commands used to render a classification model, * i.e. a {@link Model} that classifies another asset. This manages the * derived commands and returns only the necessary commands depending on the * given frame state. * * @param {object} options An object containing the following options: * @param {DrawCommand} options.command The draw command from which to derive other commands from. * @param {PrimitiveRenderResources} options.primitiveRenderResources The render resources of the primitive associated with the command. * * @alias ClassificationModelDrawCommand * @constructor * * @private */ function ClassificationModelDrawCommand(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const command = options.command; const renderResources = options.primitiveRenderResources; //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.command", command); Check.typeOf.object("options.primitiveRenderResources", renderResources); //>>includeEnd('debug'); const model = renderResources.model; this._command = command; this._model = model; this._runtimePrimitive = renderResources.runtimePrimitive; // Classification models aren't supported in 2D mode, so there's no need to // duplicate the model matrix for each derived command. this._modelMatrix = command.modelMatrix; this._boundingVolume = command.boundingVolume; this._cullFace = command.renderState.cull.face; const type = model.classificationType; this._classificationType = type; // ClassificationType has three values: terrain only, 3D Tiles only, or both. this._classifiesTerrain = type !== ClassificationType.CESIUM_3D_TILE; this._classifies3DTiles = type !== ClassificationType.TERRAIN; this._useDebugWireframe = model._enableDebugWireframe && model.debugWireframe; this._pickId = renderResources.pickId; this._commandListTerrain = []; this._commandList3DTiles = []; this._commandListIgnoreShow = []; // Used for inverted classification. this._commandListDebugWireframe = []; this._commandListTerrainPicking = []; this._commandList3DTilesPicking = []; initialize(this); } function getStencilDepthRenderState(stencilFunction) { return { colorMask: { red: false, green: false, blue: false, alpha: false, }, stencilTest: { enabled: true, frontFunction: stencilFunction, frontOperation: { fail: StencilOperation.KEEP, zFail: StencilOperation.DECREMENT_WRAP, zPass: StencilOperation.KEEP, }, backFunction: stencilFunction, backOperation: { fail: StencilOperation.KEEP, zFail: StencilOperation.INCREMENT_WRAP, zPass: StencilOperation.KEEP, }, reference: StencilConstants.CESIUM_3D_TILE_MASK, mask: StencilConstants.CESIUM_3D_TILE_MASK, }, stencilMask: StencilConstants.CLASSIFICATION_MASK, depthTest: { enabled: true, func: DepthFunction.LESS_OR_EQUAL, }, depthMask: false, }; } const colorRenderState = { stencilTest: { enabled: true, frontFunction: StencilFunction.NOT_EQUAL, frontOperation: { fail: StencilOperation.ZERO, zFail: StencilOperation.ZERO, zPass: StencilOperation.ZERO, }, backFunction: StencilFunction.NOT_EQUAL, backOperation: { fail: StencilOperation.ZERO, zFail: StencilOperation.ZERO, zPass: StencilOperation.ZERO, }, reference: 0, mask: StencilConstants.CLASSIFICATION_MASK, }, stencilMask: StencilConstants.CLASSIFICATION_MASK, depthTest: { enabled: false, }, depthMask: false, blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, }; const pickRenderState = { stencilTest: { enabled: true, frontFunction: StencilFunction.NOT_EQUAL, frontOperation: { fail: StencilOperation.ZERO, zFail: StencilOperation.ZERO, zPass: StencilOperation.ZERO, }, backFunction: StencilFunction.NOT_EQUAL, backOperation: { fail: StencilOperation.ZERO, zFail: StencilOperation.ZERO, zPass: StencilOperation.ZERO, }, reference: 0, mask: StencilConstants.CLASSIFICATION_MASK, }, stencilMask: StencilConstants.CLASSIFICATION_MASK, depthTest: { enabled: false, }, depthMask: false, }; const scratchDerivedCommands = []; function initialize(drawCommand) { const command = drawCommand._command; const derivedCommands = scratchDerivedCommands; // If debug wireframe is enabled, don't derive any new commands. // Render normally in the opaque pass. if (drawCommand._useDebugWireframe) { command.pass = Pass.OPAQUE; derivedCommands.length = 0; derivedCommands.push(command); drawCommand._commandListDebugWireframe = createBatchCommands( drawCommand, derivedCommands, drawCommand._commandListDebugWireframe ); const commandList = drawCommand._commandListDebugWireframe; const length = commandList.length; for (let i = 0; i < length; i++) { // The lengths / offsets of the batches have to be adjusted for wireframe. // Only PrimitiveType.TRIANGLES is allowed for classification, so this // just requires doubling the values for the batches. const command = commandList[i]; command.count *= 2; command.offset *= 2; } return; } const model = drawCommand.model; const allowPicking = model.allowPicking; if (drawCommand._classifiesTerrain) { const pass = Pass.TERRAIN_CLASSIFICATION; const stencilDepthCommand = deriveStencilDepthCommand(command, pass); const colorCommand = deriveColorCommand(command, pass); derivedCommands.length = 0; derivedCommands.push(stencilDepthCommand, colorCommand); drawCommand._commandListTerrain = createBatchCommands( drawCommand, derivedCommands, drawCommand._commandListTerrain ); if (allowPicking) { drawCommand._commandListTerrainPicking = createPickCommands( drawCommand, derivedCommands, drawCommand._commandListTerrainPicking ); } } if (drawCommand._classifies3DTiles) { const pass = Pass.CESIUM_3D_TILE_CLASSIFICATION; const stencilDepthCommand = deriveStencilDepthCommand(command, pass); const colorCommand = deriveColorCommand(command, pass); derivedCommands.length = 0; derivedCommands.push(stencilDepthCommand, colorCommand); drawCommand._commandList3DTiles = createBatchCommands( drawCommand, derivedCommands, drawCommand._commandList3DTiles ); if (allowPicking) { drawCommand._commandList3DTilesPicking = createPickCommands( drawCommand, derivedCommands, drawCommand._commandList3DTilesPicking ); } } } function createBatchCommands(drawCommand, derivedCommands, result) { const runtimePrimitive = drawCommand._runtimePrimitive; const batchLengths = runtimePrimitive.batchLengths; const batchOffsets = runtimePrimitive.batchOffsets; const numBatches = batchLengths.length; const numDerivedCommands = derivedCommands.length; for (let i = 0; i < numBatches; i++) { const batchLength = batchLengths[i]; const batchOffset = batchOffsets[i]; // For multiple derived commands (e.g. stencil and color commands), // they must be added in a certain order even within the batches. for (let j = 0; j < numDerivedCommands; j++) { const derivedCommand = derivedCommands[j]; const batchCommand = DrawCommand.shallowClone(derivedCommand); batchCommand.count = batchLength; batchCommand.offset = batchOffset; result.push(batchCommand); } } return result; } function deriveStencilDepthCommand(command, pass) { const stencilDepthCommand = DrawCommand.shallowClone(command); stencilDepthCommand.cull = false; stencilDepthCommand.pass = pass; const stencilFunction = pass === Pass.TERRAIN_CLASSIFICATION ? StencilFunction.ALWAYS : StencilFunction.EQUAL; const renderState = getStencilDepthRenderState(stencilFunction); stencilDepthCommand.renderState = RenderState.fromCache(renderState); return stencilDepthCommand; } function deriveColorCommand(command, pass) { const colorCommand = DrawCommand.shallowClone(command); colorCommand.cull = false; colorCommand.pass = pass; colorCommand.renderState = RenderState.fromCache(colorRenderState); return colorCommand; } const scratchPickCommands = []; function createPickCommands(drawCommand, derivedCommands, commandList) { const renderState = RenderState.fromCache(pickRenderState); const stencilDepthCommand = derivedCommands[0]; const colorCommand = derivedCommands[1]; const pickStencilDepthCommand = DrawCommand.shallowClone(stencilDepthCommand); pickStencilDepthCommand.cull = true; pickStencilDepthCommand.pickOnly = true; const pickColorCommand = DrawCommand.shallowClone(colorCommand); pickColorCommand.cull = true; pickColorCommand.pickOnly = true; pickColorCommand.renderState = renderState; pickColorCommand.pickId = drawCommand._pickId; const pickCommands = scratchPickCommands; pickCommands.length = 0; pickCommands.push(pickStencilDepthCommand, pickColorCommand); return createBatchCommands(drawCommand, pickCommands, commandList); } Object.defineProperties(ClassificationModelDrawCommand.prototype, { /** * The main draw command that the other commands are derived from. * * @memberof ClassificationModelDrawCommand.prototype * @type {DrawCommand} * * @readonly * @private */ command: { get: function () { return this._command; }, }, /** * The runtime primitive that the draw command belongs to. * * @memberof ClassificationModelDrawCommand.prototype * @type {ModelRuntimePrimitive} * * @readonly * @private */ runtimePrimitive: { get: function () { return this._runtimePrimitive; }, }, /** * The batch lengths used to generate multiple draw commands. * * @memberof ClassificationModelDrawCommand.prototype * @type {number[]} * * @readonly * @private */ batchLengths: { get: function () { return this._runtimePrimitive.batchLengths; }, }, /** * The batch offsets used to generate multiple draw commands. * * @memberof ClassificationModelDrawCommand.prototype * @type {number[]} * * @readonly * @private */ batchOffsets: { get: function () { return this._runtimePrimitive.batchOffsets; }, }, /** * The model that the draw command belongs to. * * @memberof ClassificationModelDrawCommand.prototype * @type {Model} * * @readonly * @private */ model: { get: function () { return this._model; }, }, /** * The classification type of the model that this draw command belongs to. * * @memberof ClassificationModelDrawCommand.prototype * @type {ClassificationType} * * @readonly * @private */ classificationType: { get: function () { return this._classificationType; }, }, /** * The current model matrix applied to the draw commands. * * @memberof ClassificationModelDrawCommand.prototype * @type {Matrix4} * * @readonly * @private */ modelMatrix: { get: function () { return this._modelMatrix; }, set: function (value) { this._modelMatrix = Matrix4.clone(value, this._modelMatrix); const boundingSphere = this._runtimePrimitive.boundingSphere; this._boundingVolume = BoundingSphere.transform( boundingSphere, this._modelMatrix, this._boundingVolume ); }, }, /** * The bounding volume of the main draw command. This is equivalent * to the primitive's bounding sphere transformed by the draw * command's model matrix. * * @memberof ClassificationModelDrawCommand.prototype * @type {BoundingSphere} * * @readonly * @private */ boundingVolume: { get: function () { return this._boundingVolume; }, }, /** * Culling is disabled for classification models, so this has no effect on * how the model renders. This only exists to match the interface of * {@link ModelDrawCommand}. * * @memberof ClassificationModelDrawCommand.prototype * @type {CullFace} * * @private */ cullFace: { get: function () { return this._cullFace; }, set: function (value) { this._cullFace = value; }, }, }); /** * Pushes the draw commands necessary to render the primitive. * * @param {FrameState} frameState The frame state. * @param {DrawCommand[]} result The array to push the draw commands to. * * @returns {DrawCommand[]} The modified result parameter. * * @private */ ClassificationModelDrawCommand.prototype.pushCommands = function ( frameState, result ) { const passes = frameState.passes; if (passes.render) { if (this._useDebugWireframe) { result.push.apply(result, this._commandListDebugWireframe); return; } if (this._classifiesTerrain) { result.push.apply(result, this._commandListTerrain); } if (this._classifies3DTiles) { result.push.apply(result, this._commandList3DTiles); } const useIgnoreShowCommands = frameState.invertClassification && this._classifies3DTiles; if (useIgnoreShowCommands) { if (this._commandListIgnoreShow.length === 0) { const pass = Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW; const command = deriveStencilDepthCommand(this._command, pass); const derivedCommands = scratchDerivedCommands; derivedCommands.length = 0; derivedCommands.push(command); this._commandListIgnoreShow = createBatchCommands( this, derivedCommands, this._commandListIgnoreShow ); } result.push.apply(result, this._commandListIgnoreShow); } } if (passes.pick) { if (this._classifiesTerrain) { result.push.apply(result, this._commandListTerrainPicking); } if (this._classifies3DTiles) { result.push.apply(result, this._commandList3DTilesPicking); } } return result; }; export default ClassificationModelDrawCommand;