123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- import binarySearch from "../Core/binarySearch.js";
- import Cartesian3 from "../Core/Cartesian3.js";
- import CesiumMath from "../Core/Math.js";
- import defined from "../Core/defined.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import KeyframeNode from "./KeyframeNode.js";
- import Matrix3 from "../Core/Matrix3.js";
- import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
- /**
- * @alias SpatialNode
- * @constructor
- *
- * @param {number} level
- * @param {number} x
- * @param {number} y
- * @param {number} z
- * @param {SpatialNode} parent
- * @param {VoxelShape} shape
- * @param {Cartesian3} voxelDimensions
- *
- * @private
- */
- function SpatialNode(level, x, y, z, parent, shape, voxelDimensions) {
- /**
- * @ignore
- * @type {SpatialNode[]}
- */
- this.children = undefined;
- this.parent = parent;
- this.level = level;
- this.x = x;
- this.y = y;
- this.z = z;
- /**
- * @ignore
- * @type {KeyframeNode[]}
- */
- this.keyframeNodes = [];
- /**
- * @ignore
- * @type {KeyframeNode[]}
- */
- this.renderableKeyframeNodes = [];
- this.renderableKeyframeNodeLerp = 0.0;
- /**
- * @ignore
- * @type {KeyframeNode}
- */
- this.renderableKeyframeNodePrevious = undefined;
- /**
- * @ignore
- * @type {KeyframeNode}
- */
- this.renderableKeyframeNodeNext = undefined;
- this.orientedBoundingBox = new OrientedBoundingBox();
- this.approximateVoxelSize = 0.0;
- this.screenSpaceError = 0.0;
- this.visitedFrameNumber = -1;
- this.computeBoundingVolumes(shape, voxelDimensions);
- }
- const scratchObbHalfScale = new Cartesian3();
- /**
- * @param {VoxelShape} shape
- * @param {Cartesian3} voxelDimensions
- */
- SpatialNode.prototype.computeBoundingVolumes = function (
- shape,
- voxelDimensions
- ) {
- this.orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
- this.level,
- this.x,
- this.y,
- this.z,
- this.orientedBoundingBox
- );
- const halfScale = Matrix3.getScale(
- this.orientedBoundingBox.halfAxes,
- scratchObbHalfScale
- );
- const maximumScale = 2.0 * Cartesian3.maximumComponent(halfScale);
- this.approximateVoxelSize =
- maximumScale / Cartesian3.minimumComponent(voxelDimensions);
- };
- /**
- * @param {VoxelShape} shape The shape of the parent VoxelPrimitive
- * @param {Cartesian3} voxelDimensions
- * @private
- */
- SpatialNode.prototype.constructChildNodes = function (shape, voxelDimensions) {
- const { level, x, y, z } = this;
- const xMin = x * 2;
- const yMin = y * 2;
- const zMin = z * 2;
- const yMax = yMin + 1;
- const xMax = xMin + 1;
- const zMax = zMin + 1;
- const childLevel = level + 1;
- const childCoords = [
- [childLevel, xMin, yMin, zMin],
- [childLevel, xMax, yMin, zMin],
- [childLevel, xMin, yMax, zMin],
- [childLevel, xMax, yMax, zMin],
- [childLevel, xMin, yMin, zMax],
- [childLevel, xMax, yMin, zMax],
- [childLevel, xMin, yMax, zMax],
- [childLevel, xMax, yMax, zMax],
- ];
- this.children = childCoords.map(([level, x, y, z]) => {
- return new SpatialNode(level, x, y, z, this, shape, voxelDimensions);
- });
- };
- /**
- * @param {FrameState} frameState
- * @param {number} visibilityPlaneMask
- * @returns {number} A plane mask as described in {@link CullingVolume#computeVisibilityWithPlaneMask}.
- */
- SpatialNode.prototype.visibility = function (frameState, visibilityPlaneMask) {
- const obb = this.orientedBoundingBox;
- const cullingVolume = frameState.cullingVolume;
- return cullingVolume.computeVisibilityWithPlaneMask(obb, visibilityPlaneMask);
- };
- /**
- * @param {Cartesian3} cameraPosition
- * @param {number} screenSpaceErrorMultiplier
- */
- SpatialNode.prototype.computeScreenSpaceError = function (
- cameraPosition,
- screenSpaceErrorMultiplier
- ) {
- const obb = this.orientedBoundingBox;
- let distance = Math.sqrt(obb.distanceSquaredTo(cameraPosition));
- // Avoid divide-by-zero when viewer is inside the tile.
- distance = Math.max(distance, CesiumMath.EPSILON7);
- const approximateVoxelSize = this.approximateVoxelSize;
- const error = screenSpaceErrorMultiplier * (approximateVoxelSize / distance);
- this.screenSpaceError = error;
- };
- // This object imitates a KeyframeNode. Only used for binary search function.
- const scratchBinarySearchKeyframeNode = {
- keyframe: 0,
- };
- /**
- * Find the index of a given key frame position within an array of KeyframeNodes,
- * or the complement (~) of the index where it would be in the sorted array.
- * @param {number} keyframe
- * @param {KeyframeNode[]} keyframeNodes
- * @returns {number}
- * @private
- */
- function findKeyframeIndex(keyframe, keyframeNodes) {
- scratchBinarySearchKeyframeNode.keyframe = keyframe;
- return binarySearch(
- keyframeNodes,
- scratchBinarySearchKeyframeNode,
- KeyframeNode.searchComparator
- );
- }
- /**
- * Computes the most suitable keyframes for rendering, balancing between temporal and visual quality.
- *
- * @param {number} keyframeLocation
- */
- SpatialNode.prototype.computeSurroundingRenderableKeyframeNodes = function (
- keyframeLocation
- ) {
- let spatialNode = this;
- const startLevel = spatialNode.level;
- const targetKeyframePrev = Math.floor(keyframeLocation);
- const targetKeyframeNext = Math.ceil(keyframeLocation);
- let bestKeyframeNodePrev;
- let bestKeyframeNodeNext;
- let minimumDistancePrev = +Number.MAX_VALUE;
- let minimumDistanceNext = +Number.MAX_VALUE;
- while (defined(spatialNode)) {
- const { renderableKeyframeNodes } = spatialNode;
- if (renderableKeyframeNodes.length >= 1) {
- const indexPrev = getKeyframeIndexPrev(
- targetKeyframePrev,
- renderableKeyframeNodes
- );
- const keyframeNodePrev = renderableKeyframeNodes[indexPrev];
- const indexNext =
- targetKeyframeNext === targetKeyframePrev ||
- targetKeyframePrev < keyframeNodePrev.keyframe
- ? indexPrev
- : Math.min(indexPrev + 1, renderableKeyframeNodes.length - 1);
- const keyframeNodeNext = renderableKeyframeNodes[indexNext];
- const distancePrev = targetKeyframePrev - keyframeNodePrev.keyframe;
- const weightedDistancePrev = getWeightedKeyframeDistance(
- startLevel - spatialNode.level,
- distancePrev
- );
- if (weightedDistancePrev < minimumDistancePrev) {
- minimumDistancePrev = weightedDistancePrev;
- bestKeyframeNodePrev = keyframeNodePrev;
- }
- const distanceNext = keyframeNodeNext.keyframe - targetKeyframeNext;
- const weightedDistanceNext = getWeightedKeyframeDistance(
- startLevel - spatialNode.level,
- distanceNext
- );
- if (weightedDistanceNext < minimumDistanceNext) {
- minimumDistanceNext = weightedDistanceNext;
- bestKeyframeNodeNext = keyframeNodeNext;
- }
- if (distancePrev === 0 && distanceNext === 0) {
- // Nothing higher up will be better, so break early.
- break;
- }
- }
- spatialNode = spatialNode.parent;
- }
- this.renderableKeyframeNodePrevious = bestKeyframeNodePrev;
- this.renderableKeyframeNodeNext = bestKeyframeNodeNext;
- if (!defined(bestKeyframeNodePrev) || !defined(bestKeyframeNodeNext)) {
- return;
- }
- const bestKeyframePrev = bestKeyframeNodePrev.keyframe;
- const bestKeyframeNext = bestKeyframeNodeNext.keyframe;
- this.renderableKeyframeNodeLerp =
- bestKeyframePrev === bestKeyframeNext
- ? 0.0
- : CesiumMath.clamp(
- (keyframeLocation - bestKeyframePrev) /
- (bestKeyframeNext - bestKeyframePrev),
- 0.0,
- 1.0
- );
- };
- function getKeyframeIndexPrev(targetKeyframe, keyframeNodes) {
- const keyframeIndex = findKeyframeIndex(targetKeyframe, keyframeNodes);
- return keyframeIndex < 0
- ? CesiumMath.clamp(~keyframeIndex - 1, 0, keyframeNodes.length - 1)
- : keyframeIndex;
- }
- function getWeightedKeyframeDistance(levelDistance, keyframeDistance) {
- // Balance quality between visual (levelDistance) and temporal (keyframeDistance)
- const levelWeight = Math.exp(levelDistance * 4.0);
- // Keyframes on the opposite of the desired direction are deprioritized.
- const keyframeWeight = keyframeDistance >= 0 ? 1.0 : -200.0;
- return levelDistance * levelWeight + keyframeDistance * keyframeWeight;
- }
- /**
- * @param {number} frameNumber
- * @returns {boolean}
- */
- SpatialNode.prototype.isVisited = function (frameNumber) {
- return this.visitedFrameNumber === frameNumber;
- };
- /**
- * @param {number} keyframe
- */
- SpatialNode.prototype.createKeyframeNode = function (keyframe) {
- let index = findKeyframeIndex(keyframe, this.keyframeNodes);
- if (index < 0) {
- index = ~index; // convert to insertion index
- const keyframeNode = new KeyframeNode(this, keyframe);
- this.keyframeNodes.splice(index, 0, keyframeNode);
- }
- };
- /**
- * @param {KeyframeNode} keyframeNode
- * @param {Megatexture[]} megatextures
- */
- SpatialNode.prototype.destroyKeyframeNode = function (
- keyframeNode,
- megatextures
- ) {
- const keyframe = keyframeNode.keyframe;
- const keyframeIndex = findKeyframeIndex(keyframe, this.keyframeNodes);
- if (keyframeIndex < 0) {
- throw new DeveloperError("Keyframe node does not exist.");
- }
- this.keyframeNodes.splice(keyframeIndex, 1);
- if (keyframeNode.megatextureIndex !== -1) {
- for (let i = 0; i < megatextures.length; i++) {
- megatextures[i].remove(keyframeNode.megatextureIndex);
- }
- const renderableKeyframeNodeIndex = findKeyframeIndex(
- keyframe,
- this.renderableKeyframeNodes
- );
- if (renderableKeyframeNodeIndex < 0) {
- throw new DeveloperError("Renderable keyframe node does not exist.");
- }
- this.renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 1);
- }
- keyframeNode.spatialNode = undefined;
- keyframeNode.state = KeyframeNode.LoadState.UNLOADED;
- keyframeNode.metadatas = {};
- keyframeNode.megatextureIndex = -1;
- keyframeNode.priority = -Number.MAX_VALUE;
- keyframeNode.highPriorityFrameNumber = -1;
- };
- /**
- * @param {KeyframeNode} keyframeNode
- * @param {Megatexture[]} megatextures
- */
- SpatialNode.prototype.addKeyframeNodeToMegatextures = function (
- keyframeNode,
- megatextures
- ) {
- if (
- keyframeNode.state !== KeyframeNode.LoadState.RECEIVED ||
- keyframeNode.megatextureIndex !== -1 ||
- keyframeNode.metadatas.length !== megatextures.length
- ) {
- throw new DeveloperError("Keyframe node cannot be added to megatexture");
- }
- for (let i = 0; i < megatextures.length; i++) {
- const megatexture = megatextures[i];
- keyframeNode.megatextureIndex = megatexture.add(keyframeNode.metadatas[i]);
- keyframeNode.metadatas[i] = undefined; // data is in megatexture so no need to hold onto it
- }
- keyframeNode.state = KeyframeNode.LoadState.LOADED;
- const renderableKeyframeNodes = this.renderableKeyframeNodes;
- let renderableKeyframeNodeIndex = findKeyframeIndex(
- keyframeNode.keyframe,
- renderableKeyframeNodes
- );
- if (renderableKeyframeNodeIndex >= 0) {
- throw new DeveloperError("Keyframe already renderable");
- }
- renderableKeyframeNodeIndex = ~renderableKeyframeNodeIndex;
- renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 0, keyframeNode);
- };
- /**
- * @param {number} frameNumber
- * @returns {boolean}
- */
- SpatialNode.prototype.isRenderable = function (frameNumber) {
- const previousNode = this.renderableKeyframeNodePrevious;
- const nextNode = this.renderableKeyframeNodeNext;
- const level = this.level;
- return (
- defined(previousNode) &&
- defined(nextNode) &&
- (previousNode.spatialNode.level === level ||
- nextNode.spatialNode.level === level) &&
- this.visitedFrameNumber === frameNumber
- );
- };
- export default SpatialNode;
|