// @ts-check import Frozen from "../Core/Frozen.js"; import defined from "../Core/defined.js"; import PrimitiveType from "../Core/PrimitiveType.js"; /** @import Context from "./Context.js"; */ /** @import Framebuffer from "./Framebuffer.js"; */ /** @import Matrix4 from "../Core/Matrix4.js"; */ /** @import OrientedBoundingBox from "../Core/OrientedBoundingBox.js"; */ /** @import Pass from "./Pass.js"; */ /** @import PassState from "./PassState.js"; */ /** @import PickedMetadataInfo from "../Scene/PickedMetadataInfo.js"; */ /** @import RenderState from "./RenderState.js"; */ /** @import ShaderProgram from "./ShaderProgram.js"; */ /** @import VertexArray from "./VertexArray.js"; */ /** * @enum {number} * @ignore */ const Flags = { CULL: 1, OCCLUDE: 2, EXECUTE_IN_CLOSEST_FRUSTUM: 4, DEBUG_SHOW_BOUNDING_VOLUME: 8, CAST_SHADOWS: 16, RECEIVE_SHADOWS: 32, PICK_ONLY: 64, DEPTH_FOR_TRANSLUCENT_CLASSIFICATION: 128, }; /** * @typedef {object} DrawCommandOptions * @property {object} [boundingVolume] * @property {OrientedBoundingBox} [orientedBoundingBox] * @property {Matrix4} [modelMatrix] * @property {PrimitiveType} [primitiveType=PrimitiveType.TRIANGLES] * @property {VertexArray} [vertexArray] * @property {number} [count] * @property {number} [offset] * @property {number} [instanceCount] * @property {ShaderProgram} [shaderProgram] * @property {object} [uniformMap] * @property {RenderState} [renderState] * @property {Framebuffer} [framebuffer] * @property {Pass} [pass] * @property {object} [owner] * @property {string} [pickId] * @property {boolean} [pickMetadataAllowed=false] * @property {boolean} [cull=true] * @property {boolean} [occlude=true] * @property {boolean} [executeInClosestFrustum=false] * @property {boolean} [debugShowBoundingVolume=false] * @property {boolean} [castShadows=false] * @property {boolean} [receiveShadows=false] * @property {boolean} [pickOnly=false] * @property {boolean} [depthForTranslucentClassification=false] * * @ignore */ /** * Represents a command to the renderer for drawing. * * @private */ class DrawCommand { /** * @param {DrawCommandOptions} [options] */ constructor(options = Frozen.EMPTY_OBJECT) { /** @private */ this._boundingVolume = options.boundingVolume; /** @private */ this._orientedBoundingBox = options.orientedBoundingBox; /** @private */ this._modelMatrix = options.modelMatrix; /** @private */ this._primitiveType = options.primitiveType ?? PrimitiveType.TRIANGLES; /** @private */ this._vertexArray = options.vertexArray; /** @private */ this._count = options.count; /** @private */ this._offset = options.offset ?? 0; /** @private */ this._instanceCount = options.instanceCount ?? 0; /** @private */ this._shaderProgram = options.shaderProgram; /** @private */ this._uniformMap = options.uniformMap; /** @private */ this._renderState = options.renderState; /** @private */ this._framebuffer = options.framebuffer; /** @private */ this._pass = options.pass; /** @private */ this._owner = options.owner; /** @private */ this._debugOverlappingFrustums = 0; /** @private */ this._pickId = options.pickId; /** @private */ this._pickMetadataAllowed = options.pickMetadataAllowed === true; /** * @type {PickedMetadataInfo|undefined} * @private */ this._pickedMetadataInfo = undefined; // Set initial flags. this._flags = 0; this.cull = options.cull ?? true; this.occlude = options.occlude ?? true; this.executeInClosestFrustum = options.executeInClosestFrustum ?? false; this.debugShowBoundingVolume = options.debugShowBoundingVolume ?? false; this.castShadows = options.castShadows ?? false; this.receiveShadows = options.receiveShadows ?? false; this.pickOnly = options.pickOnly ?? false; this.depthForTranslucentClassification = options.depthForTranslucentClassification ?? false; this.dirty = true; this.lastDirtyTime = 0; /** * @private */ this.derivedCommands = {}; } /** * The bounding volume of the geometry in world space. This is used for culling and frustum selection. *
* For best rendering performance, use the tightest possible bounding volume. Although
* undefined is allowed, always try to provide a bounding volume to
* allow the tightest possible near and far planes to be computed for the scene, and
* minimize the number of frustums needed.
*
true, the renderer frustum and horizon culls the command based on its {@link DrawCommand#boundingVolume}.
* If the command was already culled, set this to false for a performance improvement.
*
* @type {boolean}
* @default true
*/
get cull() {
return hasFlag(this, Flags.CULL);
}
set cull(value) {
if (hasFlag(this, Flags.CULL) !== value) {
setFlag(this, Flags.CULL, value);
this.dirty = true;
}
}
/**
* When true, the horizon culls the command based on its {@link DrawCommand#boundingVolume}.
* {@link DrawCommand#cull} must also be true in order for the command to be culled.
*
* @type {boolean}
* @default true
*/
get occlude() {
return hasFlag(this, Flags.OCCLUDE);
}
set occlude(value) {
if (hasFlag(this, Flags.OCCLUDE) !== value) {
setFlag(this, Flags.OCCLUDE, value);
this.dirty = true;
}
}
/**
* The transformation from the geometry in model space to world space.
*
* When undefined, the geometry is assumed to be defined in world space.
*
false.
*
* @type {boolean}
* @default false
*/
get executeInClosestFrustum() {
return hasFlag(this, Flags.EXECUTE_IN_CLOSEST_FRUSTUM);
}
set executeInClosestFrustum(value) {
if (hasFlag(this, Flags.EXECUTE_IN_CLOSEST_FRUSTUM) !== value) {
setFlag(this, Flags.EXECUTE_IN_CLOSEST_FRUSTUM, value);
this.dirty = true;
}
}
/**
* The object who created this command. This is useful for debugging command
* execution; it allows us to see who created a command when we only have a
* reference to the command, and can be used to selectively execute commands
* with {@link Scene#debugCommandFilter}.
*
* @type {object}
* @default undefined
*
* @see Scene#debugCommandFilter
*/
get owner() {
return this._owner;
}
set owner(value) {
if (this._owner !== value) {
this._owner = value;
this.dirty = true;
}
}
/**
* This property is for debugging only; it is not for production use nor is it optimized.
* * Draws the {@link DrawCommand#boundingVolume} for this command, assuming it is a sphere, when the command executes. *
* * @type {boolean} * @default false * * @see DrawCommand#boundingVolume */ get debugShowBoundingVolume() { return hasFlag(this, Flags.DEBUG_SHOW_BOUNDING_VOLUME); } set debugShowBoundingVolume(value) { if (hasFlag(this, Flags.DEBUG_SHOW_BOUNDING_VOLUME) !== value) { setFlag(this, Flags.DEBUG_SHOW_BOUNDING_VOLUME, value); this.dirty = true; } } /** * Used to implement Scene.debugShowFrustums. * @ignore */ get debugOverlappingFrustums() { return this._debugOverlappingFrustums; } set debugOverlappingFrustums(value) { if (this._debugOverlappingFrustums !== value) { this._debugOverlappingFrustums = value; this.dirty = true; } } /** * A GLSL string that will evaluate to a pick id. Whenundefined, the command will only draw depth
* during the pick pass.
*
* @type {string|undefined}
* @default undefined
*/
get pickId() {
return this._pickId;
}
set pickId(value) {
if (this._pickId !== value) {
this._pickId = value;
this.dirty = true;
}
}
/**
* Whether metadata picking is allowed.
*
* This is essentially only set to `true` for draw commands that are
* part of a `ModelDrawCommand`, to check whether a derived command
* for metadata picking has to be created.
*
* @type {boolean}
* @default undefined
* @private
*/
get pickMetadataAllowed() {
return this._pickMetadataAllowed;
}
/**
* Information about picked metadata.
*
* @type {PickedMetadataInfo|undefined}
* @default undefined
*/
get pickedMetadataInfo() {
return this._pickedMetadataInfo;
}
set pickedMetadataInfo(value) {
if (this._pickedMetadataInfo !== value) {
this._pickedMetadataInfo = value;
this.dirty = true;
}
}
/**
* Whether this command should be executed in the pick pass only.
*
* @type {boolean}
* @default false
*/
get pickOnly() {
return hasFlag(this, Flags.PICK_ONLY);
}
set pickOnly(value) {
if (hasFlag(this, Flags.PICK_ONLY) !== value) {
setFlag(this, Flags.PICK_ONLY, value);
this.dirty = true;
}
}
/**
* Whether this command should be derived to draw depth for classification of translucent primitives.
*
* @type {boolean}
* @default false
*/
get depthForTranslucentClassification() {
return hasFlag(this, Flags.DEPTH_FOR_TRANSLUCENT_CLASSIFICATION);
}
set depthForTranslucentClassification(value) {
if (hasFlag(this, Flags.DEPTH_FOR_TRANSLUCENT_CLASSIFICATION) !== value) {
setFlag(this, Flags.DEPTH_FOR_TRANSLUCENT_CLASSIFICATION, value);
this.dirty = true;
}
}
/**
* @param {DrawCommand} command
* @param {DrawCommand} result
* @returns {DrawCommand}
* @private
*/
static shallowClone(command, result) {
if (!defined(command)) {
return undefined;
}
if (!defined(result)) {
result = new DrawCommand();
}
result._boundingVolume = command._boundingVolume;
result._orientedBoundingBox = command._orientedBoundingBox;
result._modelMatrix = command._modelMatrix;
result._primitiveType = command._primitiveType;
result._vertexArray = command._vertexArray;
result._count = command._count;
result._offset = command._offset;
result._instanceCount = command._instanceCount;
result._shaderProgram = command._shaderProgram;
result._uniformMap = command._uniformMap;
result._renderState = command._renderState;
result._framebuffer = command._framebuffer;
result._pass = command._pass;
result._owner = command._owner;
result._debugOverlappingFrustums = command._debugOverlappingFrustums;
result._pickId = command._pickId;
result._pickMetadataAllowed = command._pickMetadataAllowed;
result._pickedMetadataInfo = command._pickedMetadataInfo;
result._flags = command._flags;
result.dirty = true;
result.lastDirtyTime = 0;
return result;
}
/**
* Executes the draw command.
*
* @param {Context} context The renderer context in which to draw.
* @param {PassState} [passState] The state for the current render pass.
*/
execute(context, passState) {
context.draw(this, passState);
}
}
/**
* @param {DrawCommand} command
* @param {Flags} flag
* @returns {boolean}
* @ignore
*/
function hasFlag(command, flag) {
return (command._flags & flag) === flag;
}
/**
* @param {DrawCommand} command
* @param {Flags} flag
* @param {boolean} value
* @ignore
*/
function setFlag(command, flag, value) {
if (value) {
command._flags |= flag;
} else {
command._flags &= ~flag;
}
}
export default DrawCommand;