Flatlogic Bot 055d24df95 WORKING
2025-10-14 02:37:44 +00:00

1993 lines
54 KiB
JavaScript

import buildVoxelDrawCommands from "./buildVoxelDrawCommands.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import CesiumMath from "../Core/Math.js";
import Check from "../Core/Check.js";
import clone from "../Core/clone.js";
import Color from "../Core/Color.js";
import defaultValue from "../Core/defaultValue.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import Event from "../Core/Event.js";
import JulianDate from "../Core/JulianDate.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import oneTimeWarning from "../Core/oneTimeWarning.js";
import ClippingPlaneCollection from "./ClippingPlaneCollection.js";
import Material from "./Material.js";
import MetadataComponentType from "./MetadataComponentType.js";
import MetadataType from "./MetadataType.js";
import PolylineCollection from "./PolylineCollection.js";
import VoxelShapeType from "./VoxelShapeType.js";
import VoxelTraversal from "./VoxelTraversal.js";
import CustomShader from "./Model/CustomShader.js";
import Cartographic from "../Core/Cartographic.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import VerticalExaggeration from "../Core/VerticalExaggeration.js";
/**
* A primitive that renders voxel data from a {@link VoxelProvider}.
*
* @alias VoxelPrimitive
* @constructor
*
* @param {object} [options] Object with the following properties:
* @param {VoxelProvider} [options.provider] The voxel provider that supplies the primitive with tile data.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix used to transform the primitive.
* @param {CustomShader} [options.customShader] The custom shader used to style the primitive.
* @param {Clock} [options.clock] The clock used to control time dynamic behavior.
*
* @see VoxelProvider
* @see Cesium3DTilesVoxelProvider
* @see VoxelShapeType
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
function VoxelPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* @type {boolean}
* @private
*/
this._ready = false;
/**
* @type {VoxelProvider}
* @private
*/
this._provider = defaultValue(
options.provider,
VoxelPrimitive.DefaultProvider
);
/**
* This member is not created until the provider and shape are ready.
*
* @type {VoxelTraversal}
* @private
*/
this._traversal = undefined;
/**
* This member is not created until the provider is ready.
*
* @type {VoxelShape}
* @private
*/
this._shape = undefined;
/**
* @type {boolean}
* @private
*/
this._shapeVisible = false;
/**
* This member is not created until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._paddingBefore = new Cartesian3();
/**
* This member is not created until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._paddingAfter = new Cartesian3();
/**
* This member is not known until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._minBounds = new Cartesian3();
/**
* Used to detect if the shape is dirty.
* This member is not known until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._minBoundsOld = new Cartesian3();
/**
* This member is not known until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._maxBounds = new Cartesian3();
/**
* Used to detect if the shape is dirty.
* This member is not known until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._maxBoundsOld = new Cartesian3();
/**
* Minimum bounds with vertical exaggeration applied
*
* @type {Cartesian3}
* @private
*/
this._exaggeratedMinBounds = new Cartesian3();
/**
* Used to detect if the shape is dirty.
*
* @type {Cartesian3}
* @private
*/
this._exaggeratedMinBoundsOld = new Cartesian3();
/**
* Maximum bounds with vertical exaggeration applied
*
* @type {Cartesian3}
* @private
*/
this._exaggeratedMaxBounds = new Cartesian3();
/**
* Used to detect if the shape is dirty.
*
* @type {Cartesian3}
* @private
*/
this._exaggeratedMaxBoundsOld = new Cartesian3();
/**
* This member is not known until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._minClippingBounds = new Cartesian3();
/**
* Used to detect if the clipping is dirty.
* This member is not known until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._minClippingBoundsOld = new Cartesian3();
/**
* This member is not known until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._maxClippingBounds = new Cartesian3();
/**
* Used to detect if the clipping is dirty.
* This member is not known until the provider is ready.
*
* @type {Cartesian3}
* @private
*/
this._maxClippingBoundsOld = new Cartesian3();
/**
* Clipping planes on the primitive
*
* @type {ClippingPlaneCollection}
* @private
*/
this._clippingPlanes = undefined;
/**
* Keeps track of when the clipping planes change
*
* @type {number}
* @private
*/
this._clippingPlanesState = 0;
/**
* Keeps track of when the clipping planes are enabled / disabled
*
* @type {boolean}
* @private
*/
this._clippingPlanesEnabled = false;
/**
* The primitive's model matrix.
*
* @type {Matrix4}
* @private
*/
this._modelMatrix = Matrix4.clone(
defaultValue(options.modelMatrix, Matrix4.IDENTITY)
);
/**
* Model matrix with vertical exaggeration applied. Only used for BOX shape type.
*
* @type {Matrix4}
* @private
*/
this._exaggeratedModelMatrix = Matrix4.clone(this._modelMatrix);
/**
* The primitive's model matrix multiplied by the provider's model matrix.
* This member is not known until the provider is ready.
*
* @type {Matrix4}
* @private
*/
this._compoundModelMatrix = new Matrix4();
/**
* Used to detect if the shape is dirty.
* This member is not known until the provider is ready.
*
* @type {Matrix4}
* @private
*/
this._compoundModelMatrixOld = new Matrix4();
/**
* @type {CustomShader}
* @private
*/
this._customShader = defaultValue(
options.customShader,
VoxelPrimitive.DefaultCustomShader
);
/**
* @type {Event}
* @private
*/
this._customShaderCompilationEvent = new Event();
/**
* @type {boolean}
* @private
*/
this._shaderDirty = true;
/**
* @type {DrawCommand}
* @private
*/
this._drawCommand = undefined;
/**
* @type {DrawCommand}
* @private
*/
this._drawCommandPick = undefined;
/**
* @type {object}
* @private
*/
this._pickId = undefined;
/**
* @type {Clock}
* @private
*/
this._clock = options.clock;
// Transforms and other values that are computed when the shape changes
/**
* @type {Matrix4}
* @private
*/
this._transformPositionWorldToUv = new Matrix4();
/**
* @type {Matrix4}
* @private
*/
this._transformPositionUvToWorld = new Matrix4();
/**
* @type {Matrix3}
* @private
*/
this._transformDirectionWorldToLocal = new Matrix3();
/**
* @type {Matrix3}
* @private
*/
this._transformNormalLocalToWorld = new Matrix3();
// Rendering
/**
* @type {boolean}
* @private
*/
this._jitter = false;
/**
* @type {boolean}
* @private
*/
this._nearestSampling = false;
/**
* @type {number}
* @private
*/
this._levelBlendFactor = 0.0;
/**
* @type {number}
* @private
*/
this._stepSizeMultiplier = 1.0;
/**
* @type {boolean}
* @private
*/
this._depthTest = true;
/**
* @type {boolean}
* @private
*/
this._useLogDepth = undefined;
/**
* @type {number}
* @private
*/
this._screenSpaceError = 4.0; // in pixels
// Debug / statistics
/**
* @type {PolylineCollection}
* @private
*/
this._debugPolylines = new PolylineCollection();
/**
* @type {boolean}
* @private
*/
this._debugDraw = false;
/**
* @type {boolean}
* @private
*/
this._disableRender = false;
/**
* @type {boolean}
* @private
*/
this._disableUpdate = false;
/**
* @type {Object<string, any>}
* @private
*/
this._uniforms = {
octreeInternalNodeTexture: undefined,
octreeInternalNodeTilesPerRow: 0,
octreeInternalNodeTexelSizeUv: new Cartesian2(),
octreeLeafNodeTexture: undefined,
octreeLeafNodeTilesPerRow: 0,
octreeLeafNodeTexelSizeUv: new Cartesian2(),
megatextureTextures: [],
megatextureSliceDimensions: new Cartesian2(),
megatextureTileDimensions: new Cartesian2(),
megatextureVoxelSizeUv: new Cartesian2(),
megatextureSliceSizeUv: new Cartesian2(),
megatextureTileSizeUv: new Cartesian2(),
dimensions: new Cartesian3(),
paddingBefore: new Cartesian3(),
paddingAfter: new Cartesian3(),
transformPositionViewToUv: new Matrix4(),
transformPositionUvToView: new Matrix4(),
transformDirectionViewToLocal: new Matrix3(),
transformNormalLocalToWorld: new Matrix3(),
cameraPositionUv: new Cartesian3(),
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
clippingPlanesTexture: undefined,
clippingPlanesMatrix: new Matrix4(),
stepSize: 0,
pickColor: new Color(),
};
/**
* Shape specific shader defines from the previous shape update. Used to detect if the shader needs to be rebuilt.
* @type {Object<string, any>}
* @private
*/
this._shapeDefinesOld = {};
/**
* Map uniform names to functions that return the uniform values.
* @type {Object<string, function():any>}
* @private
*/
this._uniformMap = {};
const uniforms = this._uniforms;
const uniformMap = this._uniformMap;
for (const key in uniforms) {
if (uniforms.hasOwnProperty(key)) {
const name = `u_${key}`;
uniformMap[name] = function () {
return uniforms[key];
};
}
}
// If the provider fails to initialize the primitive will fail too.
const provider = this._provider;
initialize(this, provider);
}
function initialize(primitive, provider) {
// Set the bounds
const {
shape: shapeType,
minBounds = VoxelShapeType.getMinBounds(shapeType),
maxBounds = VoxelShapeType.getMaxBounds(shapeType),
} = provider;
primitive.minBounds = minBounds;
primitive.maxBounds = maxBounds;
primitive.minClippingBounds = VoxelShapeType.getMinBounds(shapeType);
primitive.maxClippingBounds = VoxelShapeType.getMaxBounds(shapeType);
// Initialize the exaggerated versions of bounds and model matrix
primitive._exaggeratedMinBounds = Cartesian3.clone(
primitive._minBounds,
primitive._exaggeratedMinBounds
);
primitive._exaggeratedMaxBounds = Cartesian3.clone(
primitive._maxBounds,
primitive._exaggeratedMaxBounds
);
primitive._exaggeratedModelMatrix = Matrix4.clone(
primitive._modelMatrix,
primitive._exaggeratedModelMatrix
);
checkTransformAndBounds(primitive, provider);
// Create the shape object, and update it so it is valid for VoxelTraversal
const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
primitive._shape = new ShapeConstructor();
primitive._shapeVisible = updateShapeAndTransforms(
primitive,
primitive._shape,
provider
);
}
Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets a value indicating whether or not the primitive is ready for use.
*
* @memberof VoxelPrimitive.prototype
* @type {boolean}
* @readonly
*/
ready: {
get: function () {
return this._ready;
},
},
/**
* Gets the {@link VoxelProvider} associated with this primitive.
*
* @memberof VoxelPrimitive.prototype
* @type {VoxelProvider}
* @readonly
*/
provider: {
get: function () {
return this._provider;
},
},
/**
* Gets the bounding sphere.
*
* @memberof VoxelPrimitive.prototype
* @type {BoundingSphere}
* @readonly
*/
boundingSphere: {
get: function () {
return this._shape.boundingSphere;
},
},
/**
* Gets the oriented bounding box.
*
* @memberof VoxelPrimitive.prototype
* @type {OrientedBoundingBox}
* @readonly
*/
orientedBoundingBox: {
get: function () {
return this._shape.orientedBoundingBox;
},
},
/**
* Gets the model matrix.
*
* @memberof VoxelPrimitive.prototype
* @type {Matrix4}
* @readonly
*/
modelMatrix: {
get: function () {
return this._modelMatrix;
},
set: function (modelMatrix) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("modelMatrix", modelMatrix);
//>>includeEnd('debug');
this._modelMatrix = Matrix4.clone(modelMatrix, this._modelMatrix);
},
},
/**
* Gets the shape type.
*
* @memberof VoxelPrimitive.prototype
* @type {VoxelShapeType}
* @readonly
*/
shape: {
get: function () {
return this._provider.shape;
},
},
/**
* Gets the voxel dimensions.
*
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
* @readonly
*/
dimensions: {
get: function () {
return this._provider.dimensions;
},
},
/**
* Gets the minimum value per channel of the voxel data.
*
* @memberof VoxelPrimitive.prototype
* @type {number[][]}
* @readonly
*/
minimumValues: {
get: function () {
return this._provider.minimumValues;
},
},
/**
* Gets the maximum value per channel of the voxel data.
*
* @memberof VoxelPrimitive.prototype
* @type {number[][]}
* @readonly
*/
maximumValues: {
get: function () {
return this._provider.maximumValues;
},
},
/**
* Gets or sets whether or not this primitive should be displayed.
*
* @memberof VoxelPrimitive.prototype
* @type {boolean}
*/
show: {
get: function () {
return !this._disableRender;
},
set: function (show) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.bool("show", show);
//>>includeEnd('debug');
this._disableRender = !show;
},
},
/**
* Gets or sets whether or not the primitive should update when the view changes.
*
* @memberof VoxelPrimitive.prototype
* @type {boolean}
*/
disableUpdate: {
get: function () {
return this._disableUpdate;
},
set: function (disableUpdate) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.bool("disableUpdate", disableUpdate);
//>>includeEnd('debug');
this._disableUpdate = disableUpdate;
},
},
/**
* Gets or sets whether or not to render debug visualizations.
*
* @memberof VoxelPrimitive.prototype
* @type {boolean}
*/
debugDraw: {
get: function () {
return this._debugDraw;
},
set: function (debugDraw) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.bool("debugDraw", debugDraw);
//>>includeEnd('debug');
this._debugDraw = debugDraw;
},
},
/**
* Gets or sets whether or not to test against depth when rendering.
*
* @memberof VoxelPrimitive.prototype
* @type {boolean}
*/
depthTest: {
get: function () {
return this._depthTest;
},
set: function (depthTest) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.bool("depthTest", depthTest);
//>>includeEnd('debug');
if (this._depthTest !== depthTest) {
this._depthTest = depthTest;
this._shaderDirty = true;
}
},
},
/**
* Gets or sets whether or not to jitter the view ray during the raymarch.
* This reduces stair-step artifacts but introduces noise.
*
* @memberof VoxelPrimitive.prototype
* @type {boolean}
*/
jitter: {
get: function () {
return this._jitter;
},
set: function (jitter) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.bool("jitter", jitter);
//>>includeEnd('debug');
if (this._jitter !== jitter) {
this._jitter = jitter;
this._shaderDirty = true;
}
},
},
/**
* Gets or sets the nearest sampling.
*
* @memberof VoxelPrimitive.prototype
* @type {boolean}
*/
nearestSampling: {
get: function () {
return this._nearestSampling;
},
set: function (nearestSampling) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.bool("nearestSampling", nearestSampling);
//>>includeEnd('debug');
if (this._nearestSampling !== nearestSampling) {
this._nearestSampling = nearestSampling;
this._shaderDirty = true;
}
},
},
/**
* Controls how quickly to blend between different levels of the tree.
* 0.0 means an instantaneous pop.
* 1.0 means a full linear blend.
*
* @memberof VoxelPrimitive.prototype
* @type {number}
* @private
*/
levelBlendFactor: {
get: function () {
return this._levelBlendFactor;
},
set: function (levelBlendFactor) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("levelBlendFactor", levelBlendFactor);
//>>includeEnd('debug');
this._levelBlendFactor = CesiumMath.clamp(levelBlendFactor, 0.0, 1.0);
},
},
/**
* Gets or sets the screen space error in pixels. If the screen space size
* of a voxel is greater than the screen space error, the tile is subdivided.
* Lower screen space error corresponds with higher detail rendering, but could
* result in worse performance and higher memory consumption.
*
* @memberof VoxelPrimitive.prototype
* @type {number}
*/
screenSpaceError: {
get: function () {
return this._screenSpaceError;
},
set: function (screenSpaceError) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("screenSpaceError", screenSpaceError);
//>>includeEnd('debug');
this._screenSpaceError = screenSpaceError;
},
},
/**
* Gets or sets the step size multiplier used during raymarching.
* The lower the value, the higher the rendering quality, but
* also the worse the performance.
*
* @memberof VoxelPrimitive.prototype
* @type {number}
*/
stepSize: {
get: function () {
return this._stepSizeMultiplier;
},
set: function (stepSize) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("stepSize", stepSize);
//>>includeEnd('debug');
this._stepSizeMultiplier = stepSize;
},
},
/**
* Gets or sets the minimum bounds in the shape's local coordinate system.
* Voxel data is stretched or squashed to fit the bounds.
*
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
*/
minBounds: {
get: function () {
return this._minBounds;
},
set: function (minBounds) {
//>>includeStart('debug', pragmas.debug);
Check.defined("minBounds", minBounds);
//>>includeEnd('debug');
this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
},
},
/**
* Gets or sets the maximum bounds in the shape's local coordinate system.
* Voxel data is stretched or squashed to fit the bounds.
*
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
*/
maxBounds: {
get: function () {
return this._maxBounds;
},
set: function (maxBounds) {
//>>includeStart('debug', pragmas.debug);
Check.defined("maxBounds", maxBounds);
//>>includeEnd('debug');
this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
},
},
/**
* Gets or sets the minimum clipping location in the shape's local coordinate system.
* Any voxel content outside the range is clipped.
*
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
*/
minClippingBounds: {
get: function () {
return this._minClippingBounds;
},
set: function (minClippingBounds) {
//>>includeStart('debug', pragmas.debug);
Check.defined("minClippingBounds", minClippingBounds);
//>>includeEnd('debug');
this._minClippingBounds = Cartesian3.clone(
minClippingBounds,
this._minClippingBounds
);
},
},
/**
* Gets or sets the maximum clipping location in the shape's local coordinate system.
* Any voxel content outside the range is clipped.
*
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
*/
maxClippingBounds: {
get: function () {
return this._maxClippingBounds;
},
set: function (maxClippingBounds) {
//>>includeStart('debug', pragmas.debug);
Check.defined("maxClippingBounds", maxClippingBounds);
//>>includeEnd('debug');
this._maxClippingBounds = Cartesian3.clone(
maxClippingBounds,
this._maxClippingBounds
);
},
},
/**
* The {@link ClippingPlaneCollection} used to selectively disable rendering the primitive.
*
* @memberof VoxelPrimitive.prototype
* @type {ClippingPlaneCollection}
*/
clippingPlanes: {
get: function () {
return this._clippingPlanes;
},
set: function (clippingPlanes) {
// Don't need to check if undefined, it's handled in the setOwner function
ClippingPlaneCollection.setOwner(clippingPlanes, this, "_clippingPlanes");
},
},
/**
* Gets or sets the custom shader. If undefined, {@link VoxelPrimitive.DefaultCustomShader} is set.
*
* @memberof VoxelPrimitive.prototype
* @type {CustomShader}
*/
customShader: {
get: function () {
return this._customShader;
},
set: function (customShader) {
if (this._customShader !== customShader) {
// Delete old custom shader entries from the uniform map
const uniformMap = this._uniformMap;
const oldCustomShader = this._customShader;
const oldCustomShaderUniformMap = oldCustomShader.uniformMap;
for (const uniformName in oldCustomShaderUniformMap) {
if (oldCustomShaderUniformMap.hasOwnProperty(uniformName)) {
// If the custom shader was set but the voxel shader was never
// built, the custom shader uniforms wouldn't have been added to
// the uniform map. But it doesn't matter because the delete
// operator ignores if the key doesn't exist.
delete uniformMap[uniformName];
}
}
if (!defined(customShader)) {
this._customShader = VoxelPrimitive.DefaultCustomShader;
} else {
this._customShader = customShader;
}
this._shaderDirty = true;
}
},
},
/**
* Gets an event that is raised whenever a custom shader is compiled.
*
* @memberof VoxelPrimitive.prototype
* @type {Event}
* @readonly
*/
customShaderCompilationEvent: {
get: function () {
return this._customShaderCompilationEvent;
},
},
});
const scratchDimensions = new Cartesian3();
const scratchIntersect = new Cartesian4();
const scratchNdcAabb = new Cartesian4();
const scratchScale = new Cartesian3();
const scratchLocalScale = new Cartesian3();
const scratchRotation = new Matrix3();
const scratchRotationAndLocalScale = new Matrix3();
const scratchTransformPositionWorldToLocal = new Matrix4();
const scratchTransformPositionLocalToWorld = new Matrix4();
const scratchTransformPositionLocalToProjection = new Matrix4();
const transformPositionLocalToUv = Matrix4.fromRotationTranslation(
Matrix3.fromUniformScale(0.5, new Matrix3()),
new Cartesian3(0.5, 0.5, 0.5),
new Matrix4()
);
const transformPositionUvToLocal = Matrix4.fromRotationTranslation(
Matrix3.fromUniformScale(2.0, new Matrix3()),
new Cartesian3(-1.0, -1.0, -1.0),
new Matrix4()
);
/**
* Updates the voxel primitive.
*
* @param {FrameState} frameState
* @private
*/
VoxelPrimitive.prototype.update = function (frameState) {
const provider = this._provider;
// Update the custom shader in case it has texture uniforms.
this._customShader.update(frameState);
// Initialize from the ready provider. This only happens once.
const context = frameState.context;
if (!this._ready) {
initFromProvider(this, provider, context);
// Set the primitive as ready after the first frame render since the user might set up events subscribed to
// the post render event, and the primitive may not be ready for those past the first frame.
frameState.afterRender.push(() => {
this._ready = true;
return true;
});
// Don't render until the next frame after ready is set to true
return;
}
updateVerticalExaggeration(this, frameState);
// Check if the shape is dirty before updating it. This needs to happen every
// frame because the member variables can be modified externally via the
// getters.
const shapeDirty = checkTransformAndBounds(this, provider);
const shape = this._shape;
if (shapeDirty) {
this._shapeVisible = updateShapeAndTransforms(this, shape, provider);
if (checkShapeDefines(this, shape)) {
this._shaderDirty = true;
}
}
if (!this._shapeVisible) {
return;
}
// Update the traversal and prepare for rendering.
const keyframeLocation = getKeyframeLocation(
provider.timeIntervalCollection,
this._clock
);
const traversal = this._traversal;
const sampleCountOld = traversal._sampleCount;
traversal.update(
frameState,
keyframeLocation,
shapeDirty, // recomputeBoundingVolumes
this._disableUpdate // pauseUpdate
);
if (sampleCountOld !== traversal._sampleCount) {
this._shaderDirty = true;
}
if (!traversal.isRenderable(traversal.rootNode)) {
return;
}
if (this._debugDraw) {
// Debug draw bounding boxes and other things. Must go after traversal update
// because that's what updates the tile bounding boxes.
debugDraw(this, frameState);
}
if (this._disableRender) {
return;
}
// Check if log depth changed
if (this._useLogDepth !== frameState.useLogDepth) {
this._useLogDepth = frameState.useLogDepth;
this._shaderDirty = true;
}
// Check if clipping planes changed
const clippingPlanesChanged = updateClippingPlanes(this, frameState);
if (clippingPlanesChanged) {
this._shaderDirty = true;
}
const leafNodeTexture = traversal.leafNodeTexture;
const uniforms = this._uniforms;
if (defined(leafNodeTexture)) {
uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
uniforms.octreeLeafNodeTexelSizeUv = Cartesian2.clone(
traversal.leafNodeTexelSizeUv,
uniforms.octreeLeafNodeTexelSizeUv
);
uniforms.octreeLeafNodeTilesPerRow = traversal.leafNodeTilesPerRow;
}
// Rebuild shaders
if (this._shaderDirty) {
buildVoxelDrawCommands(this, context);
this._shaderDirty = false;
}
// Calculate the NDC-space AABB to "scissor" the fullscreen quad
const transformPositionWorldToProjection =
context.uniformState.viewProjection;
const orientedBoundingBox = shape.orientedBoundingBox;
const ndcAabb = orientedBoundingBoxToNdcAabb(
orientedBoundingBox,
transformPositionWorldToProjection,
scratchNdcAabb
);
// If the object is offscreen, don't render it.
const offscreen =
ndcAabb.x === +1.0 ||
ndcAabb.y === +1.0 ||
ndcAabb.z === -1.0 ||
ndcAabb.w === -1.0;
if (offscreen) {
return;
}
// Prepare to render: update uniforms that can change every frame
// Using a uniform instead of going through RenderState's scissor because the viewport is not accessible here, and the scissor command needs pixel coordinates.
uniforms.ndcSpaceAxisAlignedBoundingBox = Cartesian4.clone(
ndcAabb,
uniforms.ndcSpaceAxisAlignedBoundingBox
);
const transformPositionViewToWorld = context.uniformState.inverseView;
uniforms.transformPositionViewToUv = Matrix4.multiplyTransformation(
this._transformPositionWorldToUv,
transformPositionViewToWorld,
uniforms.transformPositionViewToUv
);
const transformPositionWorldToView = context.uniformState.view;
uniforms.transformPositionUvToView = Matrix4.multiplyTransformation(
transformPositionWorldToView,
this._transformPositionUvToWorld,
uniforms.transformPositionUvToView
);
const transformDirectionViewToWorld =
context.uniformState.inverseViewRotation;
uniforms.transformDirectionViewToLocal = Matrix3.multiply(
this._transformDirectionWorldToLocal,
transformDirectionViewToWorld,
uniforms.transformDirectionViewToLocal
);
uniforms.transformNormalLocalToWorld = Matrix3.clone(
this._transformNormalLocalToWorld,
uniforms.transformNormalLocalToWorld
);
const cameraPositionWorld = frameState.camera.positionWC;
uniforms.cameraPositionUv = Matrix4.multiplyByPoint(
this._transformPositionWorldToUv,
cameraPositionWorld,
uniforms.cameraPositionUv
);
uniforms.stepSize = this._stepSizeMultiplier;
// Render the primitive
const command = frameState.passes.pick
? this._drawCommandPick
: frameState.passes.pickVoxel
? this._drawCommandPickVoxel
: this._drawCommand;
command.boundingVolume = shape.boundingSphere;
frameState.commandList.push(command);
};
const scratchExaggerationScale = new Cartesian3();
const scratchExaggerationCenter = new Cartesian3();
const scratchCartographicCenter = new Cartographic();
const scratchExaggerationTranslation = new Cartesian3();
/**
* Update the exaggerated bounds of a primitive to account for vertical exaggeration
* Currently only applies to Ellipsoid shape type
* @param {VoxelPrimitive} primitive
* @param {FrameState} frameState
* @private
*/
function updateVerticalExaggeration(primitive, frameState) {
primitive._exaggeratedMinBounds = Cartesian3.clone(
primitive._minBounds,
primitive._exaggeratedMinBounds
);
primitive._exaggeratedMaxBounds = Cartesian3.clone(
primitive._maxBounds,
primitive._exaggeratedMaxBounds
);
if (primitive.shape === VoxelShapeType.ELLIPSOID) {
// Apply the exaggeration by stretching the height bounds
const relativeHeight = frameState.verticalExaggerationRelativeHeight;
const exaggeration = frameState.verticalExaggeration;
primitive._exaggeratedMinBounds.z =
(primitive._minBounds.z - relativeHeight) * exaggeration + relativeHeight;
primitive._exaggeratedMaxBounds.z =
(primitive._maxBounds.z - relativeHeight) * exaggeration + relativeHeight;
} else if (primitive.shape === VoxelShapeType.BOX) {
// Apply the exaggeration via the model matrix
const exaggerationScale = Cartesian3.fromElements(
1.0,
1.0,
frameState.verticalExaggeration,
scratchExaggerationScale
);
primitive._exaggeratedModelMatrix = Matrix4.multiplyByScale(
primitive._modelMatrix,
exaggerationScale,
primitive._exaggeratedModelMatrix
);
primitive._exaggeratedModelMatrix = Matrix4.multiplyByTranslation(
primitive._exaggeratedModelMatrix,
computeBoxExaggerationTranslation(primitive, frameState),
primitive._exaggeratedModelMatrix
);
}
}
function computeBoxExaggerationTranslation(primitive, frameState) {
// Compute translation based on box center, relative height, and exaggeration
const {
shapeTransform = Matrix4.IDENTITY,
globalTransform = Matrix4.IDENTITY,
} = primitive._provider;
// Find the Cartesian position of the center of the OBB
const initialCenter = Matrix4.getTranslation(
shapeTransform,
scratchExaggerationCenter
);
const intermediateCenter = Matrix4.multiplyByPoint(
primitive._modelMatrix,
initialCenter,
scratchExaggerationCenter
);
const transformedCenter = Matrix4.multiplyByPoint(
globalTransform,
intermediateCenter,
scratchExaggerationCenter
);
// Find the cartographic height
const ellipsoid = Ellipsoid.WGS84;
const centerCartographic = ellipsoid.cartesianToCartographic(
transformedCenter,
scratchCartographicCenter
);
let centerHeight = 0.0;
if (defined(centerCartographic)) {
centerHeight = centerCartographic.height;
}
// Find the shift that will put the center in the right position relative
// to relativeHeight, after it is scaled by verticalExaggeration
const exaggeratedHeight = VerticalExaggeration.getHeight(
centerHeight,
frameState.verticalExaggeration,
frameState.verticalExaggerationRelativeHeight
);
return Cartesian3.fromElements(
0.0,
0.0,
(exaggeratedHeight - centerHeight) / frameState.verticalExaggeration,
scratchExaggerationTranslation
);
}
/**
* Initialize primitive properties that are derived from the voxel provider
* @param {VoxelPrimitive} primitive
* @param {VoxelProvider} provider
* @param {Context} context
* @private
*/
function initFromProvider(primitive, provider, context) {
const uniforms = primitive._uniforms;
primitive._pickId = context.createPickId({ primitive });
uniforms.pickColor = Color.clone(primitive._pickId.color, uniforms.pickColor);
const { shaderDefines, shaderUniforms: shapeUniforms } = primitive._shape;
primitive._shapeDefinesOld = clone(shaderDefines, true);
// Add shape uniforms to the uniform map
const uniformMap = primitive._uniformMap;
for (const key in shapeUniforms) {
if (shapeUniforms.hasOwnProperty(key)) {
const name = `u_${key}`;
//>>includeStart('debug', pragmas.debug);
if (defined(uniformMap[name])) {
oneTimeWarning(
`VoxelPrimitive: Uniform name "${name}" is already defined`
);
}
//>>includeEnd('debug');
uniformMap[name] = function () {
return shapeUniforms[key];
};
}
}
// Set uniforms that come from the provider.
// Note that minBounds and maxBounds can be set dynamically, so their uniforms aren't set here.
uniforms.dimensions = Cartesian3.clone(
provider.dimensions,
uniforms.dimensions
);
primitive._paddingBefore = Cartesian3.clone(
defaultValue(provider.paddingBefore, Cartesian3.ZERO),
primitive._paddingBefore
);
uniforms.paddingBefore = Cartesian3.clone(
primitive._paddingBefore,
uniforms.paddingBefore
);
primitive._paddingAfter = Cartesian3.clone(
defaultValue(provider.paddingAfter, Cartesian3.ZERO),
primitive._paddingBefore
);
uniforms.paddingAfter = Cartesian3.clone(
primitive._paddingAfter,
uniforms.paddingAfter
);
// Create the VoxelTraversal, and set related uniforms
primitive._traversal = setupTraversal(primitive, provider, context);
setTraversalUniforms(primitive._traversal, uniforms);
}
/**
* Track changes in provider transform and primitive bounds
* @param {VoxelPrimitive} primitive
* @param {VoxelProvider} provider
* @returns {boolean} Whether any of the transform or bounds changed
* @private
*/
function checkTransformAndBounds(primitive, provider) {
const shapeTransform = defaultValue(
provider.shapeTransform,
Matrix4.IDENTITY
);
const globalTransform = defaultValue(
provider.globalTransform,
Matrix4.IDENTITY
);
// Compound model matrix = global transform * model matrix * shape transform
Matrix4.multiplyTransformation(
globalTransform,
primitive._exaggeratedModelMatrix,
primitive._compoundModelMatrix
);
Matrix4.multiplyTransformation(
primitive._compoundModelMatrix,
shapeTransform,
primitive._compoundModelMatrix
);
const numChanges =
updateBound(primitive, "_compoundModelMatrix", "_compoundModelMatrixOld") +
updateBound(primitive, "_minBounds", "_minBoundsOld") +
updateBound(primitive, "_maxBounds", "_maxBoundsOld") +
updateBound(
primitive,
"_exaggeratedMinBounds",
"_exaggeratedMinBoundsOld"
) +
updateBound(
primitive,
"_exaggeratedMaxBounds",
"_exaggeratedMaxBoundsOld"
) +
updateBound(primitive, "_minClippingBounds", "_minClippingBoundsOld") +
updateBound(primitive, "_maxClippingBounds", "_maxClippingBoundsOld");
return numChanges > 0;
}
/**
* Compare old and new values of a bound and update the old if it is different.
* @param {VoxelPrimitive} primitive The primitive with bounds properties
* @param {string} newBoundKey A key pointing to a bounds property of type Cartesian3 or Matrix4
* @param {string} oldBoundKey A key pointing to a bounds property of the same type as the property at newBoundKey
* @returns {number} 1 if the bound value changed, 0 otherwise
*
* @private
*/
function updateBound(primitive, newBoundKey, oldBoundKey) {
const newBound = primitive[newBoundKey];
const oldBound = primitive[oldBoundKey];
const changed = !newBound.equals(oldBound);
if (changed) {
newBound.clone(oldBound);
}
return changed ? 1 : 0;
}
/**
* Update the shape and related transforms
* @param {VoxelPrimitive} primitive
* @param {VoxelShape} shape
* @param {VoxelProvider} provider
* @returns {boolean} True if the shape is visible
* @private
*/
function updateShapeAndTransforms(primitive, shape, provider) {
const visible = shape.update(
primitive._compoundModelMatrix,
primitive._exaggeratedMinBounds,
primitive._exaggeratedMaxBounds,
primitive.minClippingBounds,
primitive.maxClippingBounds
);
if (!visible) {
return false;
}
const transformPositionLocalToWorld = shape.shapeTransform;
const transformPositionWorldToLocal = Matrix4.inverse(
transformPositionLocalToWorld,
scratchTransformPositionWorldToLocal
);
const rotation = Matrix4.getRotation(
transformPositionLocalToWorld,
scratchRotation
);
// Note that inverse(rotation) is the same as transpose(rotation)
const scale = Matrix4.getScale(transformPositionLocalToWorld, scratchScale);
const maximumScaleComponent = Cartesian3.maximumComponent(scale);
const localScale = Cartesian3.divideByScalar(
scale,
maximumScaleComponent,
scratchLocalScale
);
const rotationAndLocalScale = Matrix3.multiplyByScale(
rotation,
localScale,
scratchRotationAndLocalScale
);
// Set member variables when the shape is dirty
primitive._transformPositionWorldToUv = Matrix4.multiplyTransformation(
transformPositionLocalToUv,
transformPositionWorldToLocal,
primitive._transformPositionWorldToUv
);
primitive._transformPositionUvToWorld = Matrix4.multiplyTransformation(
transformPositionLocalToWorld,
transformPositionUvToLocal,
primitive._transformPositionUvToWorld
);
primitive._transformDirectionWorldToLocal = Matrix4.getMatrix3(
transformPositionWorldToLocal,
primitive._transformDirectionWorldToLocal
);
primitive._transformNormalLocalToWorld = Matrix3.inverseTranspose(
rotationAndLocalScale,
primitive._transformNormalLocalToWorld
);
return true;
}
/**
* Set up a VoxelTraversal based on dimensions and types from the primitive and provider
* @param {VoxelPrimitive} primitive
* @param {VoxelProvider} provider
* @param {Context} context
* @returns {VoxelTraversal}
* @private
*/
function setupTraversal(primitive, provider, context) {
const dimensions = Cartesian3.clone(provider.dimensions, scratchDimensions);
Cartesian3.add(dimensions, primitive._paddingBefore, dimensions);
Cartesian3.add(dimensions, primitive._paddingAfter, dimensions);
// It's ok for memory byte length to be undefined.
// The system will choose a default memory size.
const maximumTileCount = provider.maximumTileCount;
const maximumTextureMemoryByteLength = defined(maximumTileCount)
? VoxelTraversal.getApproximateTextureMemoryByteLength(
maximumTileCount,
dimensions,
provider.types,
provider.componentTypes
)
: undefined;
const keyframeCount = defaultValue(provider.keyframeCount, 1);
return new VoxelTraversal(
primitive,
context,
dimensions,
provider.types,
provider.componentTypes,
keyframeCount,
maximumTextureMemoryByteLength
);
}
/**
* Set uniforms that come from the traversal.
* @param {VoxelTraversal} traversal
* @param {object} uniforms
* @private
*/
function setTraversalUniforms(traversal, uniforms) {
uniforms.octreeInternalNodeTexture = traversal.internalNodeTexture;
uniforms.octreeInternalNodeTexelSizeUv = Cartesian2.clone(
traversal.internalNodeTexelSizeUv,
uniforms.octreeInternalNodeTexelSizeUv
);
uniforms.octreeInternalNodeTilesPerRow = traversal.internalNodeTilesPerRow;
const megatextures = traversal.megatextures;
const megatexture = megatextures[0];
const megatextureLength = megatextures.length;
uniforms.megatextureTextures = new Array(megatextureLength);
for (let i = 0; i < megatextureLength; i++) {
uniforms.megatextureTextures[i] = megatextures[i].texture;
}
uniforms.megatextureSliceDimensions = Cartesian2.clone(
megatexture.sliceCountPerRegion,
uniforms.megatextureSliceDimensions
);
uniforms.megatextureTileDimensions = Cartesian2.clone(
megatexture.regionCountPerMegatexture,
uniforms.megatextureTileDimensions
);
uniforms.megatextureVoxelSizeUv = Cartesian2.clone(
megatexture.voxelSizeUv,
uniforms.megatextureVoxelSizeUv
);
uniforms.megatextureSliceSizeUv = Cartesian2.clone(
megatexture.sliceSizeUv,
uniforms.megatextureSliceSizeUv
);
uniforms.megatextureTileSizeUv = Cartesian2.clone(
megatexture.regionSizeUv,
uniforms.megatextureTileSizeUv
);
}
/**
* Track changes in shape-related shader defines
* @param {VoxelPrimitive} primitive
* @param {VoxelShape} shape
* @returns {boolean} True if any of the shape defines changed, requiring a shader rebuild
* @private
*/
function checkShapeDefines(primitive, shape) {
const shapeDefines = shape.shaderDefines;
const shapeDefinesChanged = Object.keys(shapeDefines).some(
(key) => shapeDefines[key] !== primitive._shapeDefinesOld[key]
);
if (shapeDefinesChanged) {
primitive._shapeDefinesOld = clone(shapeDefines, true);
}
return shapeDefinesChanged;
}
/**
* Find the keyframe location to render at. Doesn't need to be a whole number.
* @param {TimeIntervalCollection} timeIntervalCollection
* @param {Clock} clock
* @returns {number}
*
* @private
*/
function getKeyframeLocation(timeIntervalCollection, clock) {
if (!defined(timeIntervalCollection) || !defined(clock)) {
return 0.0;
}
let date = clock.currentTime;
let timeInterval;
let timeIntervalIndex = timeIntervalCollection.indexOf(date);
if (timeIntervalIndex >= 0) {
timeInterval = timeIntervalCollection.get(timeIntervalIndex);
} else {
// Date fell outside the range
timeIntervalIndex = ~timeIntervalIndex;
if (timeIntervalIndex === timeIntervalCollection.length) {
// Date past range
timeIntervalIndex = timeIntervalCollection.length - 1;
timeInterval = timeIntervalCollection.get(timeIntervalIndex);
date = timeInterval.stop;
} else {
// Date before range
timeInterval = timeIntervalCollection.get(timeIntervalIndex);
date = timeInterval.start;
}
}
// De-lerp between the start and end of the interval
const totalSeconds = JulianDate.secondsDifference(
timeInterval.stop,
timeInterval.start
);
const secondsDifferenceStart = JulianDate.secondsDifference(
date,
timeInterval.start
);
const t = secondsDifferenceStart / totalSeconds;
return timeIntervalIndex + t;
}
/**
* Update the clipping planes state and associated uniforms
*
* @param {VoxelPrimitive} primitive
* @param {FrameState} frameState
* @returns {boolean} Whether the clipping planes changed, requiring a shader rebuild
* @private
*/
function updateClippingPlanes(primitive, frameState) {
const clippingPlanes = primitive.clippingPlanes;
if (!defined(clippingPlanes)) {
return false;
}
clippingPlanes.update(frameState);
const { clippingPlanesState, enabled } = clippingPlanes;
if (enabled) {
const uniforms = primitive._uniforms;
uniforms.clippingPlanesTexture = clippingPlanes.texture;
// Compute the clipping plane's transformation to uv space and then take the inverse
// transpose to properly transform the hessian normal form of the plane.
// transpose(inverse(worldToUv * clippingPlaneLocalToWorld))
// transpose(inverse(clippingPlaneLocalToWorld) * inverse(worldToUv))
// transpose(inverse(clippingPlaneLocalToWorld) * uvToWorld)
uniforms.clippingPlanesMatrix = Matrix4.transpose(
Matrix4.multiplyTransformation(
Matrix4.inverse(
clippingPlanes.modelMatrix,
uniforms.clippingPlanesMatrix
),
primitive._transformPositionUvToWorld,
uniforms.clippingPlanesMatrix
),
uniforms.clippingPlanesMatrix
);
}
if (
primitive._clippingPlanesState === clippingPlanesState &&
primitive._clippingPlanesEnabled === enabled
) {
return false;
}
primitive._clippingPlanesState = clippingPlanesState;
primitive._clippingPlanesEnabled = enabled;
return true;
}
/**
* Returns true if this object was destroyed; otherwise, false.
* <br /><br />
* If this object was destroyed, it should not be used; calling any function other than
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
*
* @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
*
* @see VoxelPrimitive#destroy
*/
VoxelPrimitive.prototype.isDestroyed = function () {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
* <br /><br />
* Once an object is destroyed, it should not be used; calling any function other than
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (<code>undefined</code>) to the object as done in the example.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see VoxelPrimitive#isDestroyed
*
* @example
* voxelPrimitive = voxelPrimitive && voxelPrimitive.destroy();
*/
VoxelPrimitive.prototype.destroy = function () {
const drawCommand = this._drawCommand;
if (defined(drawCommand)) {
drawCommand.shaderProgram =
drawCommand.shaderProgram && drawCommand.shaderProgram.destroy();
}
const drawCommandPick = this._drawCommandPick;
if (defined(drawCommandPick)) {
drawCommandPick.shaderProgram =
drawCommandPick.shaderProgram && drawCommandPick.shaderProgram.destroy();
}
this._pickId = this._pickId && this._pickId.destroy();
this._traversal = this._traversal && this._traversal.destroy();
this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy();
return destroyObject(this);
};
const corners = new Array(
new Cartesian4(-1.0, -1.0, -1.0, 1.0),
new Cartesian4(+1.0, -1.0, -1.0, 1.0),
new Cartesian4(-1.0, +1.0, -1.0, 1.0),
new Cartesian4(+1.0, +1.0, -1.0, 1.0),
new Cartesian4(-1.0, -1.0, +1.0, 1.0),
new Cartesian4(+1.0, -1.0, +1.0, 1.0),
new Cartesian4(-1.0, +1.0, +1.0, 1.0),
new Cartesian4(+1.0, +1.0, +1.0, 1.0)
);
const vertexNeighborIndices = new Array(
1,
2,
4,
0,
3,
5,
0,
3,
6,
1,
2,
7,
0,
5,
6,
1,
4,
7,
2,
4,
7,
3,
5,
6
);
const scratchCornersClipSpace = new Array(
new Cartesian4(),
new Cartesian4(),
new Cartesian4(),
new Cartesian4(),
new Cartesian4(),
new Cartesian4(),
new Cartesian4(),
new Cartesian4()
);
/**
* Projects all 8 corners of the oriented bounding box to NDC space and finds the
* resulting NDC axis aligned bounding box. To avoid projecting a vertex that is
* behind the near plane, it uses the intersection point of each of the vertex's
* edges against the near plane as part of the AABB calculation. This is done in
* clip space prior to perspective division.
*
* @function
*
* @param {OrientedBoundingBox} orientedBoundingBox
* @param {Matrix4} worldToProjection
* @param {Cartesian4} result
* @returns {Cartesian4}
*
* @private
*/
function orientedBoundingBoxToNdcAabb(
orientedBoundingBox,
worldToProjection,
result
) {
const transformPositionLocalToWorld = Matrix4.fromRotationTranslation(
orientedBoundingBox.halfAxes,
orientedBoundingBox.center,
scratchTransformPositionLocalToWorld
);
const transformPositionLocalToProjection = Matrix4.multiply(
worldToProjection,
transformPositionLocalToWorld,
scratchTransformPositionLocalToProjection
);
let ndcMinX = +Number.MAX_VALUE;
let ndcMaxX = -Number.MAX_VALUE;
let ndcMinY = +Number.MAX_VALUE;
let ndcMaxY = -Number.MAX_VALUE;
let cornerIndex;
// Convert all points to clip space
const cornersClipSpace = scratchCornersClipSpace;
const cornersLength = corners.length;
for (cornerIndex = 0; cornerIndex < cornersLength; cornerIndex++) {
Matrix4.multiplyByVector(
transformPositionLocalToProjection,
corners[cornerIndex],
cornersClipSpace[cornerIndex]
);
}
for (cornerIndex = 0; cornerIndex < cornersLength; cornerIndex++) {
const position = cornersClipSpace[cornerIndex];
if (position.z >= -position.w) {
// Position is past near plane, so there's no need to clip.
const ndcX = position.x / position.w;
const ndcY = position.y / position.w;
ndcMinX = Math.min(ndcMinX, ndcX);
ndcMaxX = Math.max(ndcMaxX, ndcX);
ndcMinY = Math.min(ndcMinY, ndcY);
ndcMaxY = Math.max(ndcMaxY, ndcY);
} else {
for (let neighborIndex = 0; neighborIndex < 3; neighborIndex++) {
const neighborVertexIndex =
vertexNeighborIndices[cornerIndex * 3 + neighborIndex];
const neighborPosition = cornersClipSpace[neighborVertexIndex];
if (neighborPosition.z >= -neighborPosition.w) {
// Position is behind the near plane and neighbor is after, so get intersection point on the near plane.
const distanceToPlaneFromPosition = position.z + position.w;
const distanceToPlaneFromNeighbor =
neighborPosition.z + neighborPosition.w;
const t =
distanceToPlaneFromPosition /
(distanceToPlaneFromPosition - distanceToPlaneFromNeighbor);
const intersect = Cartesian4.lerp(
position,
neighborPosition,
t,
scratchIntersect
);
const intersectNdcX = intersect.x / intersect.w;
const intersectNdcY = intersect.y / intersect.w;
ndcMinX = Math.min(ndcMinX, intersectNdcX);
ndcMaxX = Math.max(ndcMaxX, intersectNdcX);
ndcMinY = Math.min(ndcMinY, intersectNdcY);
ndcMaxY = Math.max(ndcMaxY, intersectNdcY);
}
}
}
}
// Clamp the NDC values to -1 to +1 range even if they extend much further.
ndcMinX = CesiumMath.clamp(ndcMinX, -1.0, +1.0);
ndcMinY = CesiumMath.clamp(ndcMinY, -1.0, +1.0);
ndcMaxX = CesiumMath.clamp(ndcMaxX, -1.0, +1.0);
ndcMaxY = CesiumMath.clamp(ndcMaxY, -1.0, +1.0);
result = Cartesian4.fromElements(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, result);
return result;
}
const polylineAxisDistance = 30000000.0;
const polylineXAxis = new Cartesian3(polylineAxisDistance, 0.0, 0.0);
const polylineYAxis = new Cartesian3(0.0, polylineAxisDistance, 0.0);
const polylineZAxis = new Cartesian3(0.0, 0.0, polylineAxisDistance);
/**
* Draws the tile bounding boxes and axes.
*
* @function
*
* @param {VoxelPrimitive} that
* @param {FrameState} frameState
*
* @private
*/
function debugDraw(that, frameState) {
const traversal = that._traversal;
const polylines = that._debugPolylines;
polylines.removeAll();
function makePolylineLineSegment(startPos, endPos, color, thickness) {
polylines.add({
positions: [startPos, endPos],
width: thickness,
material: Material.fromType("Color", {
color: color,
}),
});
}
function makePolylineBox(orientedBoundingBox, color, thickness) {
// Normally would want to use a scratch variable to store the corners, but
// polylines don't clone the positions.
const corners = orientedBoundingBox.computeCorners();
makePolylineLineSegment(corners[0], corners[1], color, thickness);
makePolylineLineSegment(corners[2], corners[3], color, thickness);
makePolylineLineSegment(corners[4], corners[5], color, thickness);
makePolylineLineSegment(corners[6], corners[7], color, thickness);
makePolylineLineSegment(corners[0], corners[2], color, thickness);
makePolylineLineSegment(corners[4], corners[6], color, thickness);
makePolylineLineSegment(corners[1], corners[3], color, thickness);
makePolylineLineSegment(corners[5], corners[7], color, thickness);
makePolylineLineSegment(corners[0], corners[4], color, thickness);
makePolylineLineSegment(corners[2], corners[6], color, thickness);
makePolylineLineSegment(corners[1], corners[5], color, thickness);
makePolylineLineSegment(corners[3], corners[7], color, thickness);
}
function drawTile(tile) {
if (!traversal.isRenderable(tile)) {
return;
}
const level = tile.level;
const startThickness = 5.0;
const thickness = Math.max(1.0, startThickness / Math.pow(2.0, level));
const colors = [Color.RED, Color.LIME, Color.BLUE];
const color = colors[level % 3];
makePolylineBox(tile.orientedBoundingBox, color, thickness);
if (defined(tile.children)) {
for (let i = 0; i < 8; i++) {
drawTile(tile.children[i]);
}
}
}
makePolylineBox(that._shape.orientedBoundingBox, Color.WHITE, 5.0);
drawTile(traversal.rootNode);
const axisThickness = 10.0;
makePolylineLineSegment(
Cartesian3.ZERO,
polylineXAxis,
Color.RED,
axisThickness
);
makePolylineLineSegment(
Cartesian3.ZERO,
polylineYAxis,
Color.LIME,
axisThickness
);
makePolylineLineSegment(
Cartesian3.ZERO,
polylineZAxis,
Color.BLUE,
axisThickness
);
polylines.update(frameState);
}
/**
* The default custom shader used by the primitive.
*
* @type {CustomShader}
* @constant
* @readonly
*
* @private
*/
VoxelPrimitive.DefaultCustomShader = new CustomShader({
fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
material.diffuse = vec3(1.0);
material.alpha = 1.0;
}`,
});
function DefaultVoxelProvider() {
this.ready = true;
this.shape = VoxelShapeType.BOX;
this.dimensions = new Cartesian3(1, 1, 1);
this.names = ["data"];
this.types = [MetadataType.SCALAR];
this.componentTypes = [MetadataComponentType.FLOAT32];
this.maximumTileCount = 1;
}
DefaultVoxelProvider.prototype.requestData = function (options) {
const tileLevel = defined(options) ? defaultValue(options.tileLevel, 0) : 0;
if (tileLevel >= 1) {
return undefined;
}
return Promise.resolve([new Float32Array(1)]);
};
VoxelPrimitive.DefaultProvider = new DefaultVoxelProvider();
export default VoxelPrimitive;