SpatialNode.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. import binarySearch from "../Core/binarySearch.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import CesiumMath from "../Core/Math.js";
  4. import defined from "../Core/defined.js";
  5. import DeveloperError from "../Core/DeveloperError.js";
  6. import KeyframeNode from "./KeyframeNode.js";
  7. import Matrix3 from "../Core/Matrix3.js";
  8. import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
  9. /**
  10. * @alias SpatialNode
  11. * @constructor
  12. *
  13. * @param {number} level
  14. * @param {number} x
  15. * @param {number} y
  16. * @param {number} z
  17. * @param {SpatialNode} parent
  18. * @param {VoxelShape} shape
  19. * @param {Cartesian3} voxelDimensions
  20. *
  21. * @private
  22. */
  23. function SpatialNode(level, x, y, z, parent, shape, voxelDimensions) {
  24. /**
  25. * @ignore
  26. * @type {SpatialNode[]}
  27. */
  28. this.children = undefined;
  29. this.parent = parent;
  30. this.level = level;
  31. this.x = x;
  32. this.y = y;
  33. this.z = z;
  34. /**
  35. * @ignore
  36. * @type {KeyframeNode[]}
  37. */
  38. this.keyframeNodes = [];
  39. /**
  40. * @ignore
  41. * @type {KeyframeNode[]}
  42. */
  43. this.renderableKeyframeNodes = [];
  44. this.renderableKeyframeNodeLerp = 0.0;
  45. /**
  46. * @ignore
  47. * @type {KeyframeNode}
  48. */
  49. this.renderableKeyframeNodePrevious = undefined;
  50. /**
  51. * @ignore
  52. * @type {KeyframeNode}
  53. */
  54. this.renderableKeyframeNodeNext = undefined;
  55. this.orientedBoundingBox = new OrientedBoundingBox();
  56. this.approximateVoxelSize = 0.0;
  57. this.screenSpaceError = 0.0;
  58. this.visitedFrameNumber = -1;
  59. this.computeBoundingVolumes(shape, voxelDimensions);
  60. }
  61. const scratchObbHalfScale = new Cartesian3();
  62. /**
  63. * @param {VoxelShape} shape
  64. * @param {Cartesian3} voxelDimensions
  65. */
  66. SpatialNode.prototype.computeBoundingVolumes = function (
  67. shape,
  68. voxelDimensions
  69. ) {
  70. this.orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
  71. this.level,
  72. this.x,
  73. this.y,
  74. this.z,
  75. this.orientedBoundingBox
  76. );
  77. const halfScale = Matrix3.getScale(
  78. this.orientedBoundingBox.halfAxes,
  79. scratchObbHalfScale
  80. );
  81. const maximumScale = 2.0 * Cartesian3.maximumComponent(halfScale);
  82. this.approximateVoxelSize =
  83. maximumScale / Cartesian3.minimumComponent(voxelDimensions);
  84. };
  85. /**
  86. * @param {VoxelShape} shape The shape of the parent VoxelPrimitive
  87. * @param {Cartesian3} voxelDimensions
  88. * @private
  89. */
  90. SpatialNode.prototype.constructChildNodes = function (shape, voxelDimensions) {
  91. const { level, x, y, z } = this;
  92. const xMin = x * 2;
  93. const yMin = y * 2;
  94. const zMin = z * 2;
  95. const yMax = yMin + 1;
  96. const xMax = xMin + 1;
  97. const zMax = zMin + 1;
  98. const childLevel = level + 1;
  99. const childCoords = [
  100. [childLevel, xMin, yMin, zMin],
  101. [childLevel, xMax, yMin, zMin],
  102. [childLevel, xMin, yMax, zMin],
  103. [childLevel, xMax, yMax, zMin],
  104. [childLevel, xMin, yMin, zMax],
  105. [childLevel, xMax, yMin, zMax],
  106. [childLevel, xMin, yMax, zMax],
  107. [childLevel, xMax, yMax, zMax],
  108. ];
  109. this.children = childCoords.map(([level, x, y, z]) => {
  110. return new SpatialNode(level, x, y, z, this, shape, voxelDimensions);
  111. });
  112. };
  113. /**
  114. * @param {FrameState} frameState
  115. * @param {number} visibilityPlaneMask
  116. * @returns {number} A plane mask as described in {@link CullingVolume#computeVisibilityWithPlaneMask}.
  117. */
  118. SpatialNode.prototype.visibility = function (frameState, visibilityPlaneMask) {
  119. const obb = this.orientedBoundingBox;
  120. const cullingVolume = frameState.cullingVolume;
  121. return cullingVolume.computeVisibilityWithPlaneMask(obb, visibilityPlaneMask);
  122. };
  123. /**
  124. * @param {Cartesian3} cameraPosition
  125. * @param {number} screenSpaceErrorMultiplier
  126. */
  127. SpatialNode.prototype.computeScreenSpaceError = function (
  128. cameraPosition,
  129. screenSpaceErrorMultiplier
  130. ) {
  131. const obb = this.orientedBoundingBox;
  132. let distance = Math.sqrt(obb.distanceSquaredTo(cameraPosition));
  133. // Avoid divide-by-zero when viewer is inside the tile.
  134. distance = Math.max(distance, CesiumMath.EPSILON7);
  135. const approximateVoxelSize = this.approximateVoxelSize;
  136. const error = screenSpaceErrorMultiplier * (approximateVoxelSize / distance);
  137. this.screenSpaceError = error;
  138. };
  139. // This object imitates a KeyframeNode. Only used for binary search function.
  140. const scratchBinarySearchKeyframeNode = {
  141. keyframe: 0,
  142. };
  143. /**
  144. * Find the index of a given key frame position within an array of KeyframeNodes,
  145. * or the complement (~) of the index where it would be in the sorted array.
  146. * @param {number} keyframe
  147. * @param {KeyframeNode[]} keyframeNodes
  148. * @returns {number}
  149. * @private
  150. */
  151. function findKeyframeIndex(keyframe, keyframeNodes) {
  152. scratchBinarySearchKeyframeNode.keyframe = keyframe;
  153. return binarySearch(
  154. keyframeNodes,
  155. scratchBinarySearchKeyframeNode,
  156. KeyframeNode.searchComparator
  157. );
  158. }
  159. /**
  160. * Computes the most suitable keyframes for rendering, balancing between temporal and visual quality.
  161. *
  162. * @param {number} keyframeLocation
  163. */
  164. SpatialNode.prototype.computeSurroundingRenderableKeyframeNodes = function (
  165. keyframeLocation
  166. ) {
  167. let spatialNode = this;
  168. const startLevel = spatialNode.level;
  169. const targetKeyframePrev = Math.floor(keyframeLocation);
  170. const targetKeyframeNext = Math.ceil(keyframeLocation);
  171. let bestKeyframeNodePrev;
  172. let bestKeyframeNodeNext;
  173. let minimumDistancePrev = +Number.MAX_VALUE;
  174. let minimumDistanceNext = +Number.MAX_VALUE;
  175. while (defined(spatialNode)) {
  176. const { renderableKeyframeNodes } = spatialNode;
  177. if (renderableKeyframeNodes.length >= 1) {
  178. const indexPrev = getKeyframeIndexPrev(
  179. targetKeyframePrev,
  180. renderableKeyframeNodes
  181. );
  182. const keyframeNodePrev = renderableKeyframeNodes[indexPrev];
  183. const indexNext =
  184. targetKeyframeNext === targetKeyframePrev ||
  185. targetKeyframePrev < keyframeNodePrev.keyframe
  186. ? indexPrev
  187. : Math.min(indexPrev + 1, renderableKeyframeNodes.length - 1);
  188. const keyframeNodeNext = renderableKeyframeNodes[indexNext];
  189. const distancePrev = targetKeyframePrev - keyframeNodePrev.keyframe;
  190. const weightedDistancePrev = getWeightedKeyframeDistance(
  191. startLevel - spatialNode.level,
  192. distancePrev
  193. );
  194. if (weightedDistancePrev < minimumDistancePrev) {
  195. minimumDistancePrev = weightedDistancePrev;
  196. bestKeyframeNodePrev = keyframeNodePrev;
  197. }
  198. const distanceNext = keyframeNodeNext.keyframe - targetKeyframeNext;
  199. const weightedDistanceNext = getWeightedKeyframeDistance(
  200. startLevel - spatialNode.level,
  201. distanceNext
  202. );
  203. if (weightedDistanceNext < minimumDistanceNext) {
  204. minimumDistanceNext = weightedDistanceNext;
  205. bestKeyframeNodeNext = keyframeNodeNext;
  206. }
  207. if (distancePrev === 0 && distanceNext === 0) {
  208. // Nothing higher up will be better, so break early.
  209. break;
  210. }
  211. }
  212. spatialNode = spatialNode.parent;
  213. }
  214. this.renderableKeyframeNodePrevious = bestKeyframeNodePrev;
  215. this.renderableKeyframeNodeNext = bestKeyframeNodeNext;
  216. if (!defined(bestKeyframeNodePrev) || !defined(bestKeyframeNodeNext)) {
  217. return;
  218. }
  219. const bestKeyframePrev = bestKeyframeNodePrev.keyframe;
  220. const bestKeyframeNext = bestKeyframeNodeNext.keyframe;
  221. this.renderableKeyframeNodeLerp =
  222. bestKeyframePrev === bestKeyframeNext
  223. ? 0.0
  224. : CesiumMath.clamp(
  225. (keyframeLocation - bestKeyframePrev) /
  226. (bestKeyframeNext - bestKeyframePrev),
  227. 0.0,
  228. 1.0
  229. );
  230. };
  231. function getKeyframeIndexPrev(targetKeyframe, keyframeNodes) {
  232. const keyframeIndex = findKeyframeIndex(targetKeyframe, keyframeNodes);
  233. return keyframeIndex < 0
  234. ? CesiumMath.clamp(~keyframeIndex - 1, 0, keyframeNodes.length - 1)
  235. : keyframeIndex;
  236. }
  237. function getWeightedKeyframeDistance(levelDistance, keyframeDistance) {
  238. // Balance quality between visual (levelDistance) and temporal (keyframeDistance)
  239. const levelWeight = Math.exp(levelDistance * 4.0);
  240. // Keyframes on the opposite of the desired direction are deprioritized.
  241. const keyframeWeight = keyframeDistance >= 0 ? 1.0 : -200.0;
  242. return levelDistance * levelWeight + keyframeDistance * keyframeWeight;
  243. }
  244. /**
  245. * @param {number} frameNumber
  246. * @returns {boolean}
  247. */
  248. SpatialNode.prototype.isVisited = function (frameNumber) {
  249. return this.visitedFrameNumber === frameNumber;
  250. };
  251. /**
  252. * @param {number} keyframe
  253. */
  254. SpatialNode.prototype.createKeyframeNode = function (keyframe) {
  255. let index = findKeyframeIndex(keyframe, this.keyframeNodes);
  256. if (index < 0) {
  257. index = ~index; // convert to insertion index
  258. const keyframeNode = new KeyframeNode(this, keyframe);
  259. this.keyframeNodes.splice(index, 0, keyframeNode);
  260. }
  261. };
  262. /**
  263. * @param {KeyframeNode} keyframeNode
  264. * @param {Megatexture[]} megatextures
  265. */
  266. SpatialNode.prototype.destroyKeyframeNode = function (
  267. keyframeNode,
  268. megatextures
  269. ) {
  270. const keyframe = keyframeNode.keyframe;
  271. const keyframeIndex = findKeyframeIndex(keyframe, this.keyframeNodes);
  272. if (keyframeIndex < 0) {
  273. throw new DeveloperError("Keyframe node does not exist.");
  274. }
  275. this.keyframeNodes.splice(keyframeIndex, 1);
  276. if (keyframeNode.megatextureIndex !== -1) {
  277. for (let i = 0; i < megatextures.length; i++) {
  278. megatextures[i].remove(keyframeNode.megatextureIndex);
  279. }
  280. const renderableKeyframeNodeIndex = findKeyframeIndex(
  281. keyframe,
  282. this.renderableKeyframeNodes
  283. );
  284. if (renderableKeyframeNodeIndex < 0) {
  285. throw new DeveloperError("Renderable keyframe node does not exist.");
  286. }
  287. this.renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 1);
  288. }
  289. keyframeNode.spatialNode = undefined;
  290. keyframeNode.state = KeyframeNode.LoadState.UNLOADED;
  291. keyframeNode.metadatas = {};
  292. keyframeNode.megatextureIndex = -1;
  293. keyframeNode.priority = -Number.MAX_VALUE;
  294. keyframeNode.highPriorityFrameNumber = -1;
  295. };
  296. /**
  297. * @param {KeyframeNode} keyframeNode
  298. * @param {Megatexture[]} megatextures
  299. */
  300. SpatialNode.prototype.addKeyframeNodeToMegatextures = function (
  301. keyframeNode,
  302. megatextures
  303. ) {
  304. if (
  305. keyframeNode.state !== KeyframeNode.LoadState.RECEIVED ||
  306. keyframeNode.megatextureIndex !== -1 ||
  307. keyframeNode.metadatas.length !== megatextures.length
  308. ) {
  309. throw new DeveloperError("Keyframe node cannot be added to megatexture");
  310. }
  311. for (let i = 0; i < megatextures.length; i++) {
  312. const megatexture = megatextures[i];
  313. keyframeNode.megatextureIndex = megatexture.add(keyframeNode.metadatas[i]);
  314. keyframeNode.metadatas[i] = undefined; // data is in megatexture so no need to hold onto it
  315. }
  316. keyframeNode.state = KeyframeNode.LoadState.LOADED;
  317. const renderableKeyframeNodes = this.renderableKeyframeNodes;
  318. let renderableKeyframeNodeIndex = findKeyframeIndex(
  319. keyframeNode.keyframe,
  320. renderableKeyframeNodes
  321. );
  322. if (renderableKeyframeNodeIndex >= 0) {
  323. throw new DeveloperError("Keyframe already renderable");
  324. }
  325. renderableKeyframeNodeIndex = ~renderableKeyframeNodeIndex;
  326. renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 0, keyframeNode);
  327. };
  328. /**
  329. * @param {number} frameNumber
  330. * @returns {boolean}
  331. */
  332. SpatialNode.prototype.isRenderable = function (frameNumber) {
  333. const previousNode = this.renderableKeyframeNodePrevious;
  334. const nextNode = this.renderableKeyframeNodeNext;
  335. const level = this.level;
  336. return (
  337. defined(previousNode) &&
  338. defined(nextNode) &&
  339. (previousNode.spatialNode.level === level ||
  340. nextNode.spatialNode.level === level) &&
  341. this.visitedFrameNumber === frameNumber
  342. );
  343. };
  344. export default SpatialNode;