@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
535 lines (467 loc) • 15 kB
JavaScript
import addAllToArray from "../../Core/addAllToArray.js";
import BoundingSphere from "../../Core/BoundingSphere.js";
import Check from "../../Core/Check.js";
import Frozen from "../../Core/Frozen.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 = options ?? Frozen.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) {
addAllToArray(result, this._commandListDebugWireframe);
return;
}
if (this._classifiesTerrain) {
addAllToArray(result, this._commandListTerrain);
}
if (this._classifies3DTiles) {
addAllToArray(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,
);
}
addAllToArray(result, this._commandListIgnoreShow);
}
}
if (passes.pick) {
if (this._classifiesTerrain) {
addAllToArray(result, this._commandListTerrainPicking);
}
if (this._classifies3DTiles) {
addAllToArray(result, this._commandList3DTilesPicking);
}
}
return result;
};
export default ClassificationModelDrawCommand;