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

498 lines
15 KiB
JavaScript

import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian3 from "../Core/Cartesian3.js";
import CesiumMath from "../Core/Math.js";
import Check from "../Core/Check.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
import defaultValue from "../Core/defaultValue.js";
/**
* A box {@link VoxelShape}.
*
* @alias VoxelBoxShape
* @constructor
*
* @see VoxelShape
* @see VoxelEllipsoidShape
* @see VoxelCylinderShape
* @see VoxelShapeType
*
* @private
*/
function VoxelBoxShape() {
/**
* An oriented bounding box containing the bounded shape.
* The update function must be called before accessing this value.
* @type {OrientedBoundingBox}
* @readonly
*/
this.orientedBoundingBox = new OrientedBoundingBox();
/**
* A bounding sphere containing the bounded shape.
* The update function must be called before accessing this value.
* @type {BoundingSphere}
* @readonly
*/
this.boundingSphere = new BoundingSphere();
/**
* A transformation matrix containing the bounded shape.
* The update function must be called before accessing this value.
* @type {Matrix4}
* @readonly
*/
this.boundTransform = new Matrix4();
/**
* A transformation matrix containing the shape, ignoring the bounds.
* The update function must be called before accessing this value.
* @type {Matrix4}
* @readonly
*/
this.shapeTransform = new Matrix4();
/**
* @type {Cartesian3}
* @private
*/
this._minBounds = Cartesian3.clone(
VoxelBoxShape.DefaultMinBounds,
new Cartesian3()
);
/**
* @type {Cartesian3}
* @private
*/
this._maxBounds = Cartesian3.clone(
VoxelBoxShape.DefaultMaxBounds,
new Cartesian3()
);
/**
* @type {Object<string, any>}
* @readonly
*/
this.shaderUniforms = {
renderMinBounds: new Cartesian3(),
renderMaxBounds: new Cartesian3(),
boxUvToShapeUvScale: new Cartesian3(),
boxUvToShapeUvTranslate: new Cartesian3(),
};
/**
* @type {Object<string, any>}
* @readonly
*/
this.shaderDefines = {
BOX_INTERSECTION_INDEX: undefined,
BOX_HAS_SHAPE_BOUNDS: undefined,
};
/**
* The maximum number of intersections against the shape for any ray direction.
* @type {number}
* @readonly
*/
this.shaderMaximumIntersectionsLength = 0; // not known until update
}
const scratchCenter = new Cartesian3();
const scratchScale = new Cartesian3();
const scratchRotation = new Matrix3();
const scratchClipMinBounds = new Cartesian3();
const scratchClipMaxBounds = new Cartesian3();
const scratchRenderMinBounds = new Cartesian3();
const scratchRenderMaxBounds = new Cartesian3();
const transformLocalToUv = Matrix4.fromRotationTranslation(
Matrix3.fromUniformScale(0.5, new Matrix3()),
new Cartesian3(0.5, 0.5, 0.5),
new Matrix4()
);
/**
* Update the shape's state.
*
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
* @param {Cartesian3} [clipMinBounds=VoxelBoxShape.DefaultMinBounds] The minimum clip bounds.
* @param {Cartesian3} [clipMaxBounds=VoxelBoxShape.DefaultMaxBounds] The maximum clip bounds.
* @returns {boolean} Whether the shape is visible.
*/
VoxelBoxShape.prototype.update = function (
modelMatrix,
minBounds,
maxBounds,
clipMinBounds,
clipMaxBounds
) {
clipMinBounds = defaultValue(clipMinBounds, VoxelBoxShape.DefaultMinBounds);
clipMaxBounds = defaultValue(clipMaxBounds, VoxelBoxShape.DefaultMaxBounds);
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("modelMatrix", modelMatrix);
Check.typeOf.object("minBounds", minBounds);
Check.typeOf.object("maxBounds", maxBounds);
//>>includeEnd('debug');
const defaultMinBounds = VoxelBoxShape.DefaultMinBounds;
const defaultMaxBounds = VoxelBoxShape.DefaultMaxBounds;
minBounds = this._minBounds = Cartesian3.clamp(
minBounds,
defaultMinBounds,
defaultMaxBounds,
this._minBounds
);
maxBounds = this._maxBounds = Cartesian3.clamp(
maxBounds,
defaultMinBounds,
defaultMaxBounds,
this._maxBounds
);
clipMinBounds = Cartesian3.clamp(
clipMinBounds,
defaultMinBounds,
defaultMaxBounds,
scratchClipMinBounds
);
clipMaxBounds = Cartesian3.clamp(
clipMaxBounds,
defaultMinBounds,
defaultMaxBounds,
scratchClipMaxBounds
);
const renderMinBounds = Cartesian3.clamp(
minBounds,
clipMinBounds,
clipMaxBounds,
scratchRenderMinBounds
);
const renderMaxBounds = Cartesian3.clamp(
maxBounds,
clipMinBounds,
clipMaxBounds,
scratchRenderMaxBounds
);
const scale = Matrix4.getScale(modelMatrix, scratchScale);
// Box is not visible if:
// - any of the min render bounds exceed the max render bounds
// - two or more of the min bounds equal the max bounds (line / point)
// - any of the min clip bounds exceed the max clip bounds
// - scale is 0 for any component (too annoying to reconstruct rotation matrix)
if (
renderMinBounds.x > renderMaxBounds.x ||
renderMinBounds.y > renderMaxBounds.y ||
renderMinBounds.z > renderMaxBounds.z ||
(renderMinBounds.x === renderMaxBounds.x) +
(renderMinBounds.y === renderMaxBounds.y) +
(renderMinBounds.z === renderMaxBounds.z) >=
2 ||
clipMinBounds.x > clipMaxBounds.x ||
clipMinBounds.y > clipMaxBounds.y ||
clipMinBounds.z > clipMaxBounds.z ||
scale.x === 0.0 ||
scale.y === 0.0 ||
scale.z === 0.0
) {
return false;
}
this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
this.orientedBoundingBox = getBoxChunkObb(
renderMinBounds,
renderMaxBounds,
this.shapeTransform,
this.orientedBoundingBox
);
// All of the box bounds go from -1 to +1, so the model matrix scale can be
// used as the oriented bounding box half axes.
this.boundTransform = Matrix4.fromRotationTranslation(
this.orientedBoundingBox.halfAxes,
this.orientedBoundingBox.center,
this.boundTransform
);
this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
this.orientedBoundingBox,
this.boundingSphere
);
const { shaderUniforms, shaderDefines } = this;
// To keep things simple, clear the defines every time
for (const key in shaderDefines) {
if (shaderDefines.hasOwnProperty(key)) {
shaderDefines[key] = undefined;
}
}
const hasShapeBounds =
!Cartesian3.equals(minBounds, defaultMinBounds) ||
!Cartesian3.equals(maxBounds, defaultMaxBounds);
// Keep track of how many intersections there are going to be.
let intersectionCount = 0;
shaderDefines["BOX_INTERSECTION_INDEX"] = intersectionCount;
intersectionCount += 1;
shaderUniforms.renderMinBounds = Matrix4.multiplyByPoint(
transformLocalToUv,
renderMinBounds,
shaderUniforms.renderMinBounds
);
shaderUniforms.renderMaxBounds = Matrix4.multiplyByPoint(
transformLocalToUv,
renderMaxBounds,
shaderUniforms.renderMaxBounds
);
if (hasShapeBounds) {
shaderDefines["BOX_HAS_SHAPE_BOUNDS"] = true;
const min = minBounds;
const max = maxBounds;
// Go from UV space to bounded UV space:
// delerp(posUv, minBoundsUv, maxBoundsUv)
// (posUv - minBoundsUv) / (maxBoundsUv - minBoundsUv)
// posUv / (maxBoundsUv - minBoundsUv) - minBoundsUv / (maxBoundsUv - minBoundsUv)
// scale = 1.0 / (maxBoundsUv - minBoundsUv)
// scale = 1.0 / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
// scale = 2.0 / (maxBounds - minBounds)
// offset = -minBoundsUv / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
// offset = -2.0 * (minBounds * 0.5 + 0.5) / (maxBounds - minBounds)
// offset = -scale * (minBounds * 0.5 + 0.5)
shaderUniforms.boxUvToShapeUvScale = Cartesian3.fromElements(
2.0 / (min.x === max.x ? 1.0 : max.x - min.x),
2.0 / (min.y === max.y ? 1.0 : max.y - min.y),
2.0 / (min.z === max.z ? 1.0 : max.z - min.z),
shaderUniforms.boxUvToShapeUvScale
);
shaderUniforms.boxUvToShapeUvTranslate = Cartesian3.fromElements(
-shaderUniforms.boxUvToShapeUvScale.x * (min.x * 0.5 + 0.5),
-shaderUniforms.boxUvToShapeUvScale.y * (min.y * 0.5 + 0.5),
-shaderUniforms.boxUvToShapeUvScale.z * (min.z * 0.5 + 0.5),
shaderUniforms.boxUvToShapeUvTranslate
);
}
this.shaderMaximumIntersectionsLength = intersectionCount;
return true;
};
const scratchTileMinBounds = new Cartesian3();
const scratchTileMaxBounds = new Cartesian3();
/**
* Computes an oriented bounding box for a specified tile.
* The update function must be called before calling this function.
*
* @param {number} tileLevel The tile's level.
* @param {number} tileX The tile's x coordinate.
* @param {number} tileY The tile's y coordinate.
* @param {number} tileZ The tile's z coordinate.
* @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
* @returns {OrientedBoundingBox} The oriented bounding box.
*/
VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
tileLevel,
tileX,
tileY,
tileZ,
result
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("tileLevel", tileLevel);
Check.typeOf.number("tileX", tileX);
Check.typeOf.number("tileY", tileY);
Check.typeOf.number("tileZ", tileZ);
Check.typeOf.object("result", result);
//>>includeEnd('debug');
const minBounds = this._minBounds;
const maxBounds = this._maxBounds;
const sizeAtLevel = 1.0 / Math.pow(2, tileLevel);
const tileMinBounds = Cartesian3.fromElements(
CesiumMath.lerp(minBounds.x, maxBounds.x, sizeAtLevel * tileX),
CesiumMath.lerp(minBounds.y, maxBounds.y, sizeAtLevel * tileY),
CesiumMath.lerp(minBounds.z, maxBounds.z, sizeAtLevel * tileZ),
scratchTileMinBounds
);
const tileMaxBounds = Cartesian3.fromElements(
CesiumMath.lerp(minBounds.x, maxBounds.x, sizeAtLevel * (tileX + 1)),
CesiumMath.lerp(minBounds.y, maxBounds.y, sizeAtLevel * (tileY + 1)),
CesiumMath.lerp(minBounds.z, maxBounds.z, sizeAtLevel * (tileZ + 1)),
scratchTileMaxBounds
);
return getBoxChunkObb(
tileMinBounds,
tileMaxBounds,
this.shapeTransform,
result
);
};
const sampleSizeScratch = new Cartesian3();
/**
* Computes an oriented bounding box for a specified sample within a specified tile.
* The update function must be called before calling this function.
*
* @param {SpatialNode} spatialNode The spatial node containing the sample
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
* @param {Cartesian3} tileUv The sample coordinate within the tile
* @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified sample
* @returns {OrientedBoundingBox} The oriented bounding box.
*/
VoxelBoxShape.prototype.computeOrientedBoundingBoxForSample = function (
spatialNode,
tileDimensions,
tileUv,
result
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("spatialNode", spatialNode);
Check.typeOf.object("tileDimensions", tileDimensions);
Check.typeOf.object("tileUv", tileUv);
Check.typeOf.object("result", result);
//>>includeEnd('debug');
const tileSizeAtLevel = 1.0 / Math.pow(2, spatialNode.level);
const sampleSize = Cartesian3.divideComponents(
Cartesian3.ONE,
tileDimensions,
sampleSizeScratch
);
const sampleSizeAtLevel = Cartesian3.multiplyByScalar(
sampleSize,
tileSizeAtLevel,
sampleSizeScratch
);
const minLerp = Cartesian3.multiplyByScalar(
Cartesian3.fromElements(
spatialNode.x + tileUv.x,
spatialNode.y + tileUv.y,
spatialNode.z + tileUv.z,
scratchTileMinBounds
),
tileSizeAtLevel,
scratchTileMinBounds
);
const maxLerp = Cartesian3.add(
minLerp,
sampleSizeAtLevel,
scratchTileMaxBounds
);
const minBounds = this._minBounds;
const maxBounds = this._maxBounds;
const sampleMinBounds = Cartesian3.fromElements(
CesiumMath.lerp(minBounds.x, maxBounds.x, minLerp.x),
CesiumMath.lerp(minBounds.y, maxBounds.y, minLerp.y),
CesiumMath.lerp(minBounds.z, maxBounds.z, minLerp.z),
scratchTileMinBounds
);
const sampleMaxBounds = Cartesian3.fromElements(
CesiumMath.lerp(minBounds.x, maxBounds.x, maxLerp.x),
CesiumMath.lerp(minBounds.y, maxBounds.y, maxLerp.y),
CesiumMath.lerp(minBounds.z, maxBounds.z, maxLerp.z),
scratchTileMaxBounds
);
return getBoxChunkObb(
sampleMinBounds,
sampleMaxBounds,
this.shapeTransform,
result
);
};
/**
* Defines the minimum bounds of the shape. Corresponds to minimum X, Y, Z.
*
* @type {Cartesian3}
* @constant
* @readonly
*/
VoxelBoxShape.DefaultMinBounds = Object.freeze(
new Cartesian3(-1.0, -1.0, -1.0)
);
/**
* Defines the maximum bounds of the shape. Corresponds to maximum X, Y, Z.
*
* @type {Cartesian3}
* @constant
* @readonly
*/
VoxelBoxShape.DefaultMaxBounds = Object.freeze(
new Cartesian3(+1.0, +1.0, +1.0)
);
/**
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
*
* @function
*
* @param {Cartesian3} minimumBounds The minimum bounds, in the local coordinates of the shape.
* @param {Cartesian3} maximumBounds The maximum bounds, in the local coordinates of the shape.
* @param {Matrix4} matrix The matrix to transform the points.
* @param {OrientedBoundingBox} result The object onto which to store the result.
* @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
*
* @private
*/
function getBoxChunkObb(minimumBounds, maximumBounds, matrix, result) {
const defaultMinBounds = VoxelBoxShape.DefaultMinBounds;
const defaultMaxBounds = VoxelBoxShape.DefaultMaxBounds;
const isDefaultBounds =
Cartesian3.equals(minimumBounds, defaultMinBounds) &&
Cartesian3.equals(maximumBounds, defaultMaxBounds);
if (isDefaultBounds) {
result.center = Matrix4.getTranslation(matrix, result.center);
result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
} else {
let scale = Matrix4.getScale(matrix, scratchScale);
const localCenter = Cartesian3.midpoint(
minimumBounds,
maximumBounds,
scratchCenter
);
result.center = Matrix4.multiplyByPoint(matrix, localCenter, result.center);
scale = Cartesian3.fromElements(
scale.x * 0.5 * (maximumBounds.x - minimumBounds.x),
scale.y * 0.5 * (maximumBounds.y - minimumBounds.y),
scale.z * 0.5 * (maximumBounds.z - minimumBounds.z),
scratchScale
);
const rotation = Matrix4.getRotation(matrix, scratchRotation);
result.halfAxes = Matrix3.setScale(rotation, scale, result.halfAxes);
}
return result;
}
export default VoxelBoxShape;