Cesium3DTile.js 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259
  1. import BoundingSphere from "../Core/BoundingSphere.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import Color from "../Core/Color.js";
  4. import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribute.js";
  5. import CullingVolume from "../Core/CullingVolume.js";
  6. import defaultValue from "../Core/defaultValue.js";
  7. import defined from "../Core/defined.js";
  8. import deprecationWarning from "../Core/deprecationWarning.js";
  9. import destroyObject from "../Core/destroyObject.js";
  10. import Ellipsoid from "../Core/Ellipsoid.js";
  11. import Intersect from "../Core/Intersect.js";
  12. import JulianDate from "../Core/JulianDate.js";
  13. import CesiumMath from "../Core/Math.js";
  14. import Matrix3 from "../Core/Matrix3.js";
  15. import Matrix4 from "../Core/Matrix4.js";
  16. import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
  17. import OrthographicFrustum from "../Core/OrthographicFrustum.js";
  18. import Rectangle from "../Core/Rectangle.js";
  19. import Request from "../Core/Request.js";
  20. import RequestScheduler from "../Core/RequestScheduler.js";
  21. import RequestState from "../Core/RequestState.js";
  22. import RequestType from "../Core/RequestType.js";
  23. import Resource from "../Core/Resource.js";
  24. import RuntimeError from "../Core/RuntimeError.js";
  25. import Cesium3DContentGroup from "./Cesium3DContentGroup.js";
  26. import Cesium3DTileContentFactory from "./Cesium3DTileContentFactory.js";
  27. import Cesium3DTileContentState from "./Cesium3DTileContentState.js";
  28. import Cesium3DTileContentType from "./Cesium3DTileContentType.js";
  29. import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
  30. import Cesium3DTilePass from "./Cesium3DTilePass.js";
  31. import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
  32. import Empty3DTileContent from "./Empty3DTileContent.js";
  33. import findContentMetadata from "./findContentMetadata.js";
  34. import findGroupMetadata from "./findGroupMetadata.js";
  35. import findTileMetadata from "./findTileMetadata.js";
  36. import hasExtension from "./hasExtension.js";
  37. import Multiple3DTileContent from "./Multiple3DTileContent.js";
  38. import preprocess3DTileContent from "./preprocess3DTileContent.js";
  39. import SceneMode from "./SceneMode.js";
  40. import TileBoundingRegion from "./TileBoundingRegion.js";
  41. import TileBoundingS2Cell from "./TileBoundingS2Cell.js";
  42. import TileBoundingSphere from "./TileBoundingSphere.js";
  43. import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js";
  44. import Pass from "../Renderer/Pass.js";
  45. /**
  46. * A tile in a {@link Cesium3DTileset}. When a tile is first created, its content is not loaded;
  47. * the content is loaded on-demand when needed based on the view.
  48. * <p>
  49. * Do not construct this directly, instead access tiles through {@link Cesium3DTileset#tileVisible}.
  50. * </p>
  51. *
  52. * @alias Cesium3DTile
  53. * @constructor
  54. * @param {Cesium3DTileset} tileset The tileset
  55. * @param {Resource} baseResource The base resource for the tileset
  56. * @param {object} header The JSON header for the tile
  57. * @param {Cesium3DTile} parent The parent tile of the new tile
  58. */
  59. function Cesium3DTile(tileset, baseResource, header, parent) {
  60. this._tileset = tileset;
  61. this._header = header;
  62. const hasContentsArray = defined(header.contents);
  63. const hasMultipleContents =
  64. (hasContentsArray && header.contents.length > 1) ||
  65. hasExtension(header, "3DTILES_multiple_contents");
  66. // In the 1.0 schema, content is stored in tile.content instead of tile.contents
  67. const contentHeader =
  68. hasContentsArray && !hasMultipleContents
  69. ? header.contents[0]
  70. : header.content;
  71. this._contentHeader = contentHeader;
  72. /**
  73. * The local transform of this tile.
  74. * @type {Matrix4}
  75. */
  76. this.transform = defined(header.transform)
  77. ? Matrix4.unpack(header.transform)
  78. : Matrix4.clone(Matrix4.IDENTITY);
  79. const parentTransform = defined(parent)
  80. ? parent.computedTransform
  81. : tileset.modelMatrix;
  82. const computedTransform = Matrix4.multiply(
  83. parentTransform,
  84. this.transform,
  85. new Matrix4()
  86. );
  87. const parentInitialTransform = defined(parent)
  88. ? parent._initialTransform
  89. : Matrix4.IDENTITY;
  90. this._initialTransform = Matrix4.multiply(
  91. parentInitialTransform,
  92. this.transform,
  93. new Matrix4()
  94. );
  95. /**
  96. * The final computed transform of this tile.
  97. * @type {Matrix4}
  98. * @readonly
  99. */
  100. this.computedTransform = computedTransform;
  101. this._boundingVolume = this.createBoundingVolume(
  102. header.boundingVolume,
  103. computedTransform
  104. );
  105. this._boundingVolume2D = undefined;
  106. let contentBoundingVolume;
  107. if (defined(contentHeader) && defined(contentHeader.boundingVolume)) {
  108. // Non-leaf tiles may have a content bounding-volume, which is a tight-fit bounding volume
  109. // around only the features in the tile. This box is useful for culling for rendering,
  110. // but not for culling for traversing the tree since it does not guarantee spatial coherence, i.e.,
  111. // since it only bounds features in the tile, not the entire tile, children may be
  112. // outside of this box.
  113. contentBoundingVolume = this.createBoundingVolume(
  114. contentHeader.boundingVolume,
  115. computedTransform
  116. );
  117. }
  118. this._contentBoundingVolume = contentBoundingVolume;
  119. this._contentBoundingVolume2D = undefined;
  120. let viewerRequestVolume;
  121. if (defined(header.viewerRequestVolume)) {
  122. viewerRequestVolume = this.createBoundingVolume(
  123. header.viewerRequestVolume,
  124. computedTransform
  125. );
  126. }
  127. this._viewerRequestVolume = viewerRequestVolume;
  128. /**
  129. * The error, in meters, introduced if this tile is rendered and its children are not.
  130. * This is used to compute screen space error, i.e., the error measured in pixels.
  131. *
  132. * @type {number}
  133. * @readonly
  134. */
  135. this.geometricError = header.geometricError;
  136. this._geometricError = header.geometricError;
  137. if (!defined(this._geometricError)) {
  138. this._geometricError = defined(parent)
  139. ? parent._geometricError
  140. : tileset._geometricError;
  141. Cesium3DTile._deprecationWarning(
  142. "geometricErrorUndefined",
  143. "Required property geometricError is undefined for this tile. Using parent's geometric error instead."
  144. );
  145. }
  146. this.updateGeometricErrorScale();
  147. let refine;
  148. if (defined(header.refine)) {
  149. if (header.refine === "replace" || header.refine === "add") {
  150. Cesium3DTile._deprecationWarning(
  151. "lowercase-refine",
  152. `This tile uses a lowercase refine "${
  153. header.refine
  154. }". Instead use "${header.refine.toUpperCase()}".`
  155. );
  156. }
  157. refine =
  158. header.refine.toUpperCase() === "REPLACE"
  159. ? Cesium3DTileRefine.REPLACE
  160. : Cesium3DTileRefine.ADD;
  161. } else if (defined(parent)) {
  162. // Inherit from parent tile if omitted.
  163. refine = parent.refine;
  164. } else {
  165. refine = Cesium3DTileRefine.REPLACE;
  166. }
  167. /**
  168. * Specifies the type of refinement that is used when traversing this tile for rendering.
  169. *
  170. * @type {Cesium3DTileRefine}
  171. * @readonly
  172. * @private
  173. */
  174. this.refine = refine;
  175. /**
  176. * Gets the tile's children.
  177. *
  178. * @type {Cesium3DTile[]}
  179. * @readonly
  180. */
  181. this.children = [];
  182. /**
  183. * This tile's parent or <code>undefined</code> if this tile is the root.
  184. * <p>
  185. * When a tile's content points to an external tileset JSON file, the external tileset's
  186. * root tile's parent is not <code>undefined</code>; instead, the parent references
  187. * the tile (with its content pointing to an external tileset JSON file) as if the two tilesets were merged.
  188. * </p>
  189. *
  190. * @type {Cesium3DTile}
  191. * @readonly
  192. */
  193. this.parent = parent;
  194. let content;
  195. let hasEmptyContent = false;
  196. let contentState;
  197. let contentResource;
  198. let serverKey;
  199. baseResource = Resource.createIfNeeded(baseResource);
  200. if (hasMultipleContents) {
  201. contentState = Cesium3DTileContentState.UNLOADED;
  202. // Each content may have its own URI, but they all need to be resolved
  203. // relative to the tileset, so the base resource is used.
  204. contentResource = baseResource.clone();
  205. } else if (defined(contentHeader)) {
  206. let contentHeaderUri = contentHeader.uri;
  207. if (defined(contentHeader.url)) {
  208. Cesium3DTile._deprecationWarning(
  209. "contentUrl",
  210. 'This tileset JSON uses the "content.url" property which has been deprecated. Use "content.uri" instead.'
  211. );
  212. contentHeaderUri = contentHeader.url;
  213. }
  214. if (contentHeaderUri === "") {
  215. Cesium3DTile._deprecationWarning(
  216. "contentUriEmpty",
  217. "content.uri property is an empty string, which creates a circular dependency, making this tileset invalid. Omit the content property instead"
  218. );
  219. content = new Empty3DTileContent(tileset, this);
  220. hasEmptyContent = true;
  221. contentState = Cesium3DTileContentState.READY;
  222. } else {
  223. contentState = Cesium3DTileContentState.UNLOADED;
  224. contentResource = baseResource.getDerivedResource({
  225. url: contentHeaderUri,
  226. });
  227. serverKey = RequestScheduler.getServerKey(
  228. contentResource.getUrlComponent()
  229. );
  230. }
  231. } else {
  232. content = new Empty3DTileContent(tileset, this);
  233. hasEmptyContent = true;
  234. contentState = Cesium3DTileContentState.READY;
  235. }
  236. this._content = content;
  237. this._contentResource = contentResource;
  238. this._contentState = contentState;
  239. this._expiredContent = undefined;
  240. this._serverKey = serverKey;
  241. /**
  242. * When <code>true</code>, the tile has no content.
  243. *
  244. * @type {boolean}
  245. * @readonly
  246. *
  247. * @private
  248. */
  249. this.hasEmptyContent = hasEmptyContent;
  250. /**
  251. * When <code>true</code>, the tile's content points to an external tileset.
  252. * <p>
  253. * This is <code>false</code> until the tile's content is loaded.
  254. * </p>
  255. *
  256. * @type {boolean}
  257. * @readonly
  258. *
  259. * @private
  260. */
  261. this.hasTilesetContent = false;
  262. /**
  263. * When <code>true</code>, the tile's content is an implicit tileset.
  264. * <p>
  265. * This is <code>false</code> until the tile's implicit content is loaded.
  266. * </p>
  267. *
  268. * @type {boolean}
  269. * @readonly
  270. *
  271. * @private
  272. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  273. */
  274. this.hasImplicitContent = false;
  275. /**
  276. * When <code>true</code>, the tile contains content metadata from implicit tiling. This flag is set
  277. * for tiles transcoded by <code>Implicit3DTileContent</code>.
  278. * <p>
  279. * This is <code>false</code> until the tile's content is loaded.
  280. * </p>
  281. *
  282. * @type {boolean}
  283. * @readonly
  284. *
  285. * @private
  286. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  287. */
  288. this.hasImplicitContentMetadata = false;
  289. /**
  290. * When <code>true</code>, the tile has multiple contents, either in the tile JSON (3D Tiles 1.1)
  291. * or via the <code>3DTILES_multiple_contents</code> extension.
  292. *
  293. * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_multiple_contents|3DTILES_multiple_contents extension}
  294. *
  295. * @type {boolean}
  296. * @readonly
  297. *
  298. * @private
  299. */
  300. this.hasMultipleContents = hasMultipleContents;
  301. /**
  302. * When tile metadata is present (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension is used,
  303. * this stores a {@link TileMetadata} object for accessing tile metadata.
  304. *
  305. * @type {TileMetadata}
  306. * @readonly
  307. * @private
  308. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  309. */
  310. this.metadata = findTileMetadata(tileset, header);
  311. /**
  312. * The node in the tileset's LRU cache, used to determine when to unload a tile's content.
  313. *
  314. * See {@link Cesium3DTilesetCache}
  315. *
  316. * @type {DoublyLinkedListNode}
  317. * @readonly
  318. *
  319. * @private
  320. */
  321. this.cacheNode = undefined;
  322. const expire = header.expire;
  323. let expireDuration;
  324. let expireDate;
  325. if (defined(expire)) {
  326. expireDuration = expire.duration;
  327. if (defined(expire.date)) {
  328. expireDate = JulianDate.fromIso8601(expire.date);
  329. }
  330. }
  331. /**
  332. * The time in seconds after the tile's content is ready when the content expires and new content is requested.
  333. *
  334. * @type {number}
  335. */
  336. this.expireDuration = expireDuration;
  337. /**
  338. * The date when the content expires and new content is requested.
  339. *
  340. * @type {JulianDate}
  341. */
  342. this.expireDate = expireDate;
  343. /**
  344. * The time when a style was last applied to this tile.
  345. *
  346. * @type {number}
  347. *
  348. * @private
  349. */
  350. this.lastStyleTime = 0.0;
  351. /**
  352. * Marks whether the tile's children bounds are fully contained within the tile's bounds
  353. *
  354. * @type {Cesium3DTileOptimizationHint}
  355. *
  356. * @private
  357. */
  358. this._optimChildrenWithinParent = Cesium3DTileOptimizationHint.NOT_COMPUTED;
  359. /**
  360. * Tracks if the tile's relationship with a ClippingPlaneCollection has changed with regards
  361. * to the ClippingPlaneCollection's state.
  362. *
  363. * @type {boolean}
  364. *
  365. * @private
  366. */
  367. this.clippingPlanesDirty = false;
  368. /**
  369. * Tracks if the tile's request should be deferred until all non-deferred
  370. * tiles load.
  371. *
  372. * @type {boolean}
  373. *
  374. * @private
  375. */
  376. this.priorityDeferred = false;
  377. /**
  378. * For implicit tiling, an ImplicitTileset object will be attached to a
  379. * placeholder tile with either implicit tiling in the JSON (3D Tiles 1.1)
  380. * or the <code>3DTILES_implicit_tiling</code> extension.
  381. * This way the {@link Implicit3DTileContent} can access the tile later once the content is fetched.
  382. *
  383. * @type {ImplicitTileset|undefined}
  384. *
  385. * @private
  386. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  387. */
  388. this.implicitTileset = undefined;
  389. /**
  390. * For implicit tiling, the (level, x, y, [z]) coordinates within the
  391. * implicit tileset are stored in the tile.
  392. *
  393. * @type {ImplicitTileCoordinates|undefined}
  394. *
  395. * @private
  396. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  397. */
  398. this.implicitCoordinates = undefined;
  399. /**
  400. * For implicit tiling, each transcoded tile will hold a weak reference to
  401. * the {@link ImplicitSubtree}.
  402. *
  403. * @type {ImplicitSubtree|undefined}
  404. *
  405. * @private
  406. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  407. */
  408. this.implicitSubtree = undefined;
  409. // Members that are updated every frame for tree traversal and rendering optimizations:
  410. this._distanceToCamera = 0.0;
  411. this._centerZDepth = 0.0;
  412. this._screenSpaceError = 0.0;
  413. this._screenSpaceErrorProgressiveResolution = 0.0; // The screen space error at a given screen height of tileset.progressiveResolutionHeightFraction * screenHeight
  414. this._visibilityPlaneMask = 0;
  415. this._visible = false;
  416. this._inRequestVolume = false;
  417. this._finalResolution = true;
  418. this._depth = 0;
  419. this._stackLength = 0;
  420. this._selectionDepth = 0;
  421. this._updatedVisibilityFrame = 0;
  422. this._touchedFrame = 0;
  423. this._visitedFrame = 0;
  424. this._selectedFrame = 0;
  425. this._requestedFrame = 0;
  426. this._ancestorWithContent = undefined;
  427. this._ancestorWithContentAvailable = undefined;
  428. this._refines = false;
  429. this._shouldSelect = false;
  430. this._isClipped = true;
  431. this._clippingPlanesState = 0; // encapsulates (_isClipped, clippingPlanes.enabled) and number/function
  432. this._debugBoundingVolume = undefined;
  433. this._debugContentBoundingVolume = undefined;
  434. this._debugViewerRequestVolume = undefined;
  435. this._debugColor = Color.fromRandom({ alpha: 1.0 });
  436. this._debugColorizeTiles = false;
  437. this._priority = 0.0; // The priority used for request sorting
  438. this._priorityHolder = this; // Reference to the ancestor up the tree that holds the _foveatedFactor and _distanceToCamera for all tiles in the refinement chain.
  439. this._priorityProgressiveResolution = false;
  440. this._priorityProgressiveResolutionScreenSpaceErrorLeaf = false;
  441. this._priorityReverseScreenSpaceError = 0.0;
  442. this._foveatedFactor = 0.0;
  443. this._wasMinPriorityChild = false; // Needed for knowing when to continue a refinement chain. Gets reset in updateTile in traversal and gets set in updateAndPushChildren in traversal.
  444. this._loadTimestamp = new JulianDate();
  445. this._commandsLength = 0;
  446. this._color = undefined;
  447. this._colorDirty = false;
  448. this._request = undefined;
  449. }
  450. // This can be overridden for testing purposes
  451. Cesium3DTile._deprecationWarning = deprecationWarning;
  452. Object.defineProperties(Cesium3DTile.prototype, {
  453. /**
  454. * The tileset containing this tile.
  455. *
  456. * @memberof Cesium3DTile.prototype
  457. *
  458. * @type {Cesium3DTileset}
  459. * @readonly
  460. */
  461. tileset: {
  462. get: function () {
  463. return this._tileset;
  464. },
  465. },
  466. /**
  467. * The tile's content. This represents the actual tile's payload,
  468. * not the content's metadata in the tileset JSON file.
  469. *
  470. * @memberof Cesium3DTile.prototype
  471. *
  472. * @type {Cesium3DTileContent}
  473. * @readonly
  474. */
  475. content: {
  476. get: function () {
  477. return this._content;
  478. },
  479. },
  480. /**
  481. * Get the tile's bounding volume.
  482. *
  483. * @memberof Cesium3DTile.prototype
  484. *
  485. * @type {TileBoundingVolume}
  486. * @readonly
  487. * @private
  488. */
  489. boundingVolume: {
  490. get: function () {
  491. return this._boundingVolume;
  492. },
  493. },
  494. /**
  495. * Get the bounding volume of the tile's contents. This defaults to the
  496. * tile's bounding volume when the content's bounding volume is
  497. * <code>undefined</code>.
  498. *
  499. * @memberof Cesium3DTile.prototype
  500. *
  501. * @type {TileBoundingVolume}
  502. * @readonly
  503. * @private
  504. */
  505. contentBoundingVolume: {
  506. get: function () {
  507. return defaultValue(this._contentBoundingVolume, this._boundingVolume);
  508. },
  509. },
  510. /**
  511. * Get the bounding sphere derived from the tile's bounding volume.
  512. *
  513. * @memberof Cesium3DTile.prototype
  514. *
  515. * @type {BoundingSphere}
  516. * @readonly
  517. */
  518. boundingSphere: {
  519. get: function () {
  520. return this._boundingVolume.boundingSphere;
  521. },
  522. },
  523. /**
  524. * Determines if the tile is visible within the current field of view
  525. *
  526. * @memberof Cesium3DTile.prototype
  527. *
  528. * @type {boolean}
  529. * @readonly
  530. *
  531. * @private
  532. */
  533. isVisible: {
  534. get: function () {
  535. return this._visible && this._inRequestVolume;
  536. },
  537. },
  538. /**
  539. * Returns the <code>extras</code> property in the tileset JSON for this tile, which contains application specific metadata.
  540. * Returns <code>undefined</code> if <code>extras</code> does not exist.
  541. *
  542. * @memberof Cesium3DTile.prototype
  543. *
  544. * @type {object}
  545. * @readonly
  546. * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification#specifying-extensions-and-application-specific-extras|Extras in the 3D Tiles specification.}
  547. */
  548. extras: {
  549. get: function () {
  550. return this._header.extras;
  551. },
  552. },
  553. /**
  554. * Gets or sets the tile's highlight color.
  555. *
  556. * @memberof Cesium3DTile.prototype
  557. *
  558. * @type {Color}
  559. *
  560. * @default {@link Color.WHITE}
  561. *
  562. * @private
  563. */
  564. color: {
  565. get: function () {
  566. if (!defined(this._color)) {
  567. this._color = new Color();
  568. }
  569. return Color.clone(this._color);
  570. },
  571. set: function (value) {
  572. this._color = Color.clone(value, this._color);
  573. this._colorDirty = true;
  574. },
  575. },
  576. /**
  577. * Determines if the tile's content is renderable. <code>false</code> if the
  578. * tile has empty content or if it points to an external tileset or implicit content
  579. *
  580. * @memberof Cesium3DTile.prototype
  581. *
  582. * @type {boolean}
  583. * @readonly
  584. *
  585. * @private
  586. */
  587. hasRenderableContent: {
  588. get: function () {
  589. return (
  590. !this.hasEmptyContent &&
  591. !this.hasTilesetContent &&
  592. !this.hasImplicitContent
  593. );
  594. },
  595. },
  596. /**
  597. * Determines if the tile has available content to render. <code>true</code> if the tile's
  598. * content is ready or if it has expired content that renders while new content loads; otherwise,
  599. * <code>false</code>.
  600. *
  601. * @memberof Cesium3DTile.prototype
  602. *
  603. * @type {boolean}
  604. * @readonly
  605. *
  606. * @private
  607. */
  608. contentAvailable: {
  609. get: function () {
  610. return (
  611. (this.contentReady && this.hasRenderableContent) ||
  612. (defined(this._expiredContent) && !this.contentFailed)
  613. );
  614. },
  615. },
  616. /**
  617. * Determines if the tile's content is ready. This is automatically <code>true</code> for
  618. * tile's with empty content.
  619. *
  620. * @memberof Cesium3DTile.prototype
  621. *
  622. * @type {boolean}
  623. * @readonly
  624. *
  625. * @private
  626. */
  627. contentReady: {
  628. get: function () {
  629. return this._contentState === Cesium3DTileContentState.READY;
  630. },
  631. },
  632. /**
  633. * Determines if the tile's content has not be requested. <code>true</code> if tile's
  634. * content has not be requested; otherwise, <code>false</code>.
  635. *
  636. * @memberof Cesium3DTile.prototype
  637. *
  638. * @type {boolean}
  639. * @readonly
  640. *
  641. * @private
  642. */
  643. contentUnloaded: {
  644. get: function () {
  645. return this._contentState === Cesium3DTileContentState.UNLOADED;
  646. },
  647. },
  648. /**
  649. * Determines if the tile has renderable content which is unloaded
  650. *
  651. * @memberof Cesium3DTile.prototype
  652. *
  653. * @type {boolean}
  654. * @readonly
  655. *
  656. * @private
  657. */
  658. hasUnloadedRenderableContent: {
  659. get: function () {
  660. return this.hasRenderableContent && this.contentUnloaded;
  661. },
  662. },
  663. /**
  664. * Determines if the tile's content is expired. <code>true</code> if tile's
  665. * content is expired; otherwise, <code>false</code>.
  666. *
  667. * @memberof Cesium3DTile.prototype
  668. *
  669. * @type {boolean}
  670. * @readonly
  671. *
  672. * @private
  673. */
  674. contentExpired: {
  675. get: function () {
  676. return this._contentState === Cesium3DTileContentState.EXPIRED;
  677. },
  678. },
  679. /**
  680. * Determines if the tile's content failed to load. <code>true</code> if the tile's
  681. * content failed to load; otherwise, <code>false</code>.
  682. *
  683. * @memberof Cesium3DTile.prototype
  684. *
  685. * @type {boolean}
  686. * @readonly
  687. *
  688. * @private
  689. */
  690. contentFailed: {
  691. get: function () {
  692. return this._contentState === Cesium3DTileContentState.FAILED;
  693. },
  694. },
  695. /**
  696. * Returns the number of draw commands used by this tile.
  697. *
  698. * @readonly
  699. *
  700. * @private
  701. */
  702. commandsLength: {
  703. get: function () {
  704. return this._commandsLength;
  705. },
  706. },
  707. });
  708. const scratchCartesian = new Cartesian3();
  709. /**
  710. * @private
  711. * @param {Cesium3DTile} tile
  712. * @param {FrameState} frameState
  713. * @returns {Boolean}
  714. */
  715. function isPriorityDeferred(tile, frameState) {
  716. const { tileset, boundingSphere } = tile;
  717. const { radius, center } = boundingSphere;
  718. const { camera } = frameState;
  719. // If closest point on line is inside the sphere then set foveatedFactor to 0.
  720. // Otherwise, the dot product is with the line from camera to the point on the sphere that is closest to the line.
  721. const scaledCameraDirection = Cartesian3.multiplyByScalar(
  722. camera.directionWC,
  723. tile._centerZDepth,
  724. scratchCartesian
  725. );
  726. const closestPointOnLine = Cartesian3.add(
  727. camera.positionWC,
  728. scaledCameraDirection,
  729. scratchCartesian
  730. );
  731. // The distance from the camera's view direction to the tile.
  732. const toLine = Cartesian3.subtract(
  733. closestPointOnLine,
  734. center,
  735. scratchCartesian
  736. );
  737. const distanceToCenterLine = Cartesian3.magnitude(toLine);
  738. const notTouchingSphere = distanceToCenterLine > radius;
  739. // If camera's direction vector is inside the bounding sphere then consider
  740. // this tile right along the line of sight and set _foveatedFactor to 0.
  741. // Otherwise,_foveatedFactor is one minus the dot product of the camera's direction
  742. // and the vector between the camera and the point on the bounding sphere closest to the view line.
  743. if (notTouchingSphere) {
  744. const toLineNormalized = Cartesian3.normalize(toLine, scratchCartesian);
  745. const scaledToLine = Cartesian3.multiplyByScalar(
  746. toLineNormalized,
  747. radius,
  748. scratchCartesian
  749. );
  750. const closestOnSphere = Cartesian3.add(
  751. center,
  752. scaledToLine,
  753. scratchCartesian
  754. );
  755. const toClosestOnSphere = Cartesian3.subtract(
  756. closestOnSphere,
  757. camera.positionWC,
  758. scratchCartesian
  759. );
  760. const toClosestOnSphereNormalize = Cartesian3.normalize(
  761. toClosestOnSphere,
  762. scratchCartesian
  763. );
  764. tile._foveatedFactor =
  765. 1.0 -
  766. Math.abs(Cartesian3.dot(camera.directionWC, toClosestOnSphereNormalize));
  767. } else {
  768. tile._foveatedFactor = 0.0;
  769. }
  770. // Skip this feature if: non-skipLevelOfDetail and replace refine, if the foveated settings are turned off, if tile is progressive resolution and replace refine and skipLevelOfDetail (will help get rid of ancestor artifacts faster)
  771. // Or if the tile is a preload of any kind
  772. const replace = tile.refine === Cesium3DTileRefine.REPLACE;
  773. const skipLevelOfDetail = tileset.isSkippingLevelOfDetail;
  774. if (
  775. (replace && !skipLevelOfDetail) ||
  776. !tileset.foveatedScreenSpaceError ||
  777. tileset.foveatedConeSize === 1.0 ||
  778. (tile._priorityProgressiveResolution && replace && skipLevelOfDetail) ||
  779. tileset._pass === Cesium3DTilePass.PRELOAD_FLIGHT ||
  780. tileset._pass === Cesium3DTilePass.PRELOAD
  781. ) {
  782. return false;
  783. }
  784. const maximumFovatedFactor = 1.0 - Math.cos(camera.frustum.fov * 0.5); // 0.14 for fov = 60. NOTE very hard to defer vertically foveated tiles since max is based on fovy (which is fov). Lowering the 0.5 to a smaller fraction of the screen height will start to defer vertically foveated tiles.
  785. const foveatedConeFactor = tileset.foveatedConeSize * maximumFovatedFactor;
  786. // If it's inside the user-defined view cone, then it should not be deferred.
  787. if (tile._foveatedFactor <= foveatedConeFactor) {
  788. return false;
  789. }
  790. // Relax SSE based on how big the angle is between the tile and the edge of the foveated cone.
  791. const range = maximumFovatedFactor - foveatedConeFactor;
  792. const normalizedFoveatedFactor = CesiumMath.clamp(
  793. (tile._foveatedFactor - foveatedConeFactor) / range,
  794. 0.0,
  795. 1.0
  796. );
  797. const sseRelaxation = tileset.foveatedInterpolationCallback(
  798. tileset.foveatedMinimumScreenSpaceErrorRelaxation,
  799. tileset.maximumScreenSpaceError,
  800. normalizedFoveatedFactor
  801. );
  802. const sse =
  803. tile._screenSpaceError === 0.0 && defined(tile.parent)
  804. ? tile.parent._screenSpaceError * 0.5
  805. : tile._screenSpaceError;
  806. return tileset.maximumScreenSpaceError - sseRelaxation <= sse;
  807. }
  808. const scratchJulianDate = new JulianDate();
  809. /**
  810. * Get the tile's screen space error.
  811. *
  812. * @private
  813. * @param {FrameState} frameState
  814. * @param {Boolean} useParentGeometricError
  815. * @param {number} progressiveResolutionHeightFraction
  816. */
  817. Cesium3DTile.prototype.getScreenSpaceError = function (
  818. frameState,
  819. useParentGeometricError,
  820. progressiveResolutionHeightFraction
  821. ) {
  822. const tileset = this._tileset;
  823. const heightFraction = defaultValue(progressiveResolutionHeightFraction, 1.0);
  824. const parentGeometricError = defined(this.parent)
  825. ? this.parent.geometricError
  826. : tileset._scaledGeometricError;
  827. const geometricError = useParentGeometricError
  828. ? parentGeometricError
  829. : this.geometricError;
  830. if (geometricError === 0.0) {
  831. // Leaf tiles do not have any error so save the computation
  832. return 0.0;
  833. }
  834. const { camera, context } = frameState;
  835. let frustum = camera.frustum;
  836. const width = context.drawingBufferWidth;
  837. const height = context.drawingBufferHeight * heightFraction;
  838. let error;
  839. if (
  840. frameState.mode === SceneMode.SCENE2D ||
  841. frustum instanceof OrthographicFrustum
  842. ) {
  843. const offCenterFrustum = frustum.offCenterFrustum;
  844. if (defined(offCenterFrustum)) {
  845. frustum = offCenterFrustum;
  846. }
  847. const pixelSize =
  848. Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) /
  849. Math.max(width, height);
  850. error = geometricError / pixelSize;
  851. } else {
  852. // Avoid divide by zero when viewer is inside the tile
  853. const distance = Math.max(this._distanceToCamera, CesiumMath.EPSILON7);
  854. const sseDenominator = frustum.sseDenominator;
  855. error = (geometricError * height) / (distance * sseDenominator);
  856. if (tileset.dynamicScreenSpaceError) {
  857. const density = tileset._dynamicScreenSpaceErrorComputedDensity;
  858. const factor = tileset.dynamicScreenSpaceErrorFactor;
  859. const dynamicError = CesiumMath.fog(distance, density) * factor;
  860. error -= dynamicError;
  861. }
  862. }
  863. error /= frameState.pixelRatio;
  864. return error;
  865. };
  866. /**
  867. * @private
  868. * @param {Cesium3DTileset} tileset
  869. * @param {Cesium3DTile} tile
  870. * @returns {Boolean}
  871. */
  872. function isPriorityProgressiveResolution(tileset, tile) {
  873. if (
  874. tileset.progressiveResolutionHeightFraction <= 0.0 ||
  875. tileset.progressiveResolutionHeightFraction > 0.5
  876. ) {
  877. return false;
  878. }
  879. let isProgressiveResolutionTile =
  880. tile._screenSpaceErrorProgressiveResolution >
  881. tileset._maximumScreenSpaceError; // Mark non-SSE leaves
  882. tile._priorityProgressiveResolutionScreenSpaceErrorLeaf = false; // Needed for skipLOD
  883. const parent = tile.parent;
  884. const maximumScreenSpaceError = tileset._maximumScreenSpaceError;
  885. const tilePasses =
  886. tile._screenSpaceErrorProgressiveResolution <= maximumScreenSpaceError;
  887. const parentFails =
  888. defined(parent) &&
  889. parent._screenSpaceErrorProgressiveResolution > maximumScreenSpaceError;
  890. if (tilePasses && parentFails) {
  891. // A progressive resolution SSE leaf, promote its priority as well
  892. tile._priorityProgressiveResolutionScreenSpaceErrorLeaf = true;
  893. isProgressiveResolutionTile = true;
  894. }
  895. return isProgressiveResolutionTile;
  896. }
  897. /**
  898. * @private
  899. * @param {Cesium3DTileset} tileset
  900. * @param {Cesium3DTile} tile
  901. * @returns {number}
  902. */
  903. function getPriorityReverseScreenSpaceError(tileset, tile) {
  904. const parent = tile.parent;
  905. const useParentScreenSpaceError =
  906. defined(parent) &&
  907. (!tileset.isSkippingLevelOfDetail ||
  908. tile._screenSpaceError === 0.0 ||
  909. parent.hasTilesetContent ||
  910. parent.hasImplicitContent);
  911. const screenSpaceError = useParentScreenSpaceError
  912. ? parent._screenSpaceError
  913. : tile._screenSpaceError;
  914. return tileset.root._screenSpaceError - screenSpaceError;
  915. }
  916. /**
  917. * Update the tile's visibility.
  918. *
  919. * @private
  920. * @param {FrameState} frameState
  921. */
  922. Cesium3DTile.prototype.updateVisibility = function (frameState) {
  923. const { parent, tileset } = this;
  924. if (this._updatedVisibilityFrame === tileset._updatedVisibilityFrame) {
  925. // The tile has already been updated for this frame
  926. return;
  927. }
  928. const parentTransform = defined(parent)
  929. ? parent.computedTransform
  930. : tileset.modelMatrix;
  931. const parentVisibilityPlaneMask = defined(parent)
  932. ? parent._visibilityPlaneMask
  933. : CullingVolume.MASK_INDETERMINATE;
  934. this.updateTransform(parentTransform);
  935. this._distanceToCamera = this.distanceToTile(frameState);
  936. this._centerZDepth = this.distanceToTileCenter(frameState);
  937. this._screenSpaceError = this.getScreenSpaceError(frameState, false);
  938. this._screenSpaceErrorProgressiveResolution = this.getScreenSpaceError(
  939. frameState,
  940. false,
  941. tileset.progressiveResolutionHeightFraction
  942. );
  943. this._visibilityPlaneMask = this.visibility(
  944. frameState,
  945. parentVisibilityPlaneMask
  946. ); // Use parent's plane mask to speed up visibility test
  947. this._visible = this._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
  948. this._inRequestVolume = this.insideViewerRequestVolume(frameState);
  949. this._priorityReverseScreenSpaceError = getPriorityReverseScreenSpaceError(
  950. tileset,
  951. this
  952. );
  953. this._priorityProgressiveResolution = isPriorityProgressiveResolution(
  954. tileset,
  955. this
  956. );
  957. this.priorityDeferred = isPriorityDeferred(this, frameState);
  958. this._updatedVisibilityFrame = tileset._updatedVisibilityFrame;
  959. };
  960. /**
  961. * Update whether the tile has expired.
  962. *
  963. * @private
  964. */
  965. Cesium3DTile.prototype.updateExpiration = function () {
  966. if (
  967. defined(this.expireDate) &&
  968. this.contentReady &&
  969. !this.hasEmptyContent &&
  970. !this.hasMultipleContents
  971. ) {
  972. const now = JulianDate.now(scratchJulianDate);
  973. if (JulianDate.lessThan(this.expireDate, now)) {
  974. this._contentState = Cesium3DTileContentState.EXPIRED;
  975. this._expiredContent = this._content;
  976. }
  977. }
  978. };
  979. /**
  980. * @private
  981. * @param {Cesium3DTile} tile
  982. */
  983. function updateExpireDate(tile) {
  984. if (!defined(tile.expireDuration)) {
  985. return;
  986. }
  987. const expireDurationDate = JulianDate.now(scratchJulianDate);
  988. JulianDate.addSeconds(
  989. expireDurationDate,
  990. tile.expireDuration,
  991. expireDurationDate
  992. );
  993. if (defined(tile.expireDate)) {
  994. if (JulianDate.lessThan(tile.expireDate, expireDurationDate)) {
  995. JulianDate.clone(expireDurationDate, tile.expireDate);
  996. }
  997. } else {
  998. tile.expireDate = JulianDate.clone(expireDurationDate);
  999. }
  1000. }
  1001. /**
  1002. * @private
  1003. * @param {Cesium3DTile} tile
  1004. * @returns {Function}
  1005. */
  1006. function createPriorityFunction(tile) {
  1007. return function () {
  1008. return tile._priority;
  1009. };
  1010. }
  1011. /**
  1012. * Requests the tile's content.
  1013. * <p>
  1014. * The request may not be made if the Cesium Request Scheduler can't prioritize it.
  1015. * </p>
  1016. *
  1017. * @return {Promise<Cesium3DTileContent>|undefined} A promise that resolves when the request completes, or undefined if there is no request needed, or the request cannot be scheduled.
  1018. * @private
  1019. */
  1020. Cesium3DTile.prototype.requestContent = function () {
  1021. // empty contents don't require any HTTP requests
  1022. if (this.hasEmptyContent) {
  1023. return;
  1024. }
  1025. if (this.hasMultipleContents) {
  1026. return requestMultipleContents(this);
  1027. }
  1028. return requestSingleContent(this);
  1029. };
  1030. /**
  1031. * Multiple {@link Cesium3DTileContent}s are allowed within a single tile either through
  1032. * the tile JSON (3D Tiles 1.1) or the <code>3DTILES_multiple_contents</code> extension.
  1033. * Due to differences in request scheduling, this is handled separately.
  1034. * <p>
  1035. * This implementation of multiple contents does not
  1036. * support tile expiry like requestSingleContent does. If this changes,
  1037. * note that the resource.setQueryParameters() details must go inside {@link Multiple3DTileContent} since that is per-request.
  1038. * </p>
  1039. *
  1040. * @private
  1041. * @param {Cesium3DTile} tile
  1042. * @returns {Promise<Cesium3DTileContent>|Promise<undefined>|undefined} A promise that resolves to the tile content once loaded, or a promise that resolves to undefined if the request was cancelled mid-flight, or undefined if the request cannot be scheduled this frame
  1043. */
  1044. function requestMultipleContents(tile) {
  1045. let multipleContents = tile._content;
  1046. const tileset = tile._tileset;
  1047. if (!defined(multipleContents)) {
  1048. // Create the content object immediately, it will handle scheduling
  1049. // requests for inner contents.
  1050. const contentsJson = hasExtension(tile._header, "3DTILES_multiple_contents")
  1051. ? tile._header.extensions["3DTILES_multiple_contents"]
  1052. : tile._header;
  1053. multipleContents = new Multiple3DTileContent(
  1054. tileset,
  1055. tile,
  1056. tile._contentResource.clone(),
  1057. contentsJson
  1058. );
  1059. tile._content = multipleContents;
  1060. }
  1061. const promise = multipleContents.requestInnerContents();
  1062. if (!defined(promise)) {
  1063. // Request could not all be scheduled this frame
  1064. return;
  1065. }
  1066. tile._contentState = Cesium3DTileContentState.LOADING;
  1067. return promise
  1068. .then((content) => {
  1069. if (tile.isDestroyed()) {
  1070. // Tile is unloaded before the content can process
  1071. return;
  1072. }
  1073. // Tile was canceled, try again later
  1074. if (!defined(content)) {
  1075. return;
  1076. }
  1077. tile._contentState = Cesium3DTileContentState.PROCESSING;
  1078. return multipleContents;
  1079. })
  1080. .catch((error) => {
  1081. if (tile.isDestroyed()) {
  1082. // Tile is unloaded before the content can process
  1083. return;
  1084. }
  1085. tile._contentState = Cesium3DTileContentState.FAILED;
  1086. throw error;
  1087. });
  1088. }
  1089. async function processArrayBuffer(
  1090. tile,
  1091. tileset,
  1092. request,
  1093. expired,
  1094. requestPromise
  1095. ) {
  1096. const previousState = tile._contentState;
  1097. tile._contentState = Cesium3DTileContentState.LOADING;
  1098. ++tileset.statistics.numberOfPendingRequests;
  1099. let arrayBuffer;
  1100. try {
  1101. arrayBuffer = await requestPromise;
  1102. } catch (error) {
  1103. --tileset.statistics.numberOfPendingRequests;
  1104. if (tile.isDestroyed()) {
  1105. // Tile is unloaded before the content can process
  1106. return;
  1107. }
  1108. if (request.cancelled || request.state === RequestState.CANCELLED) {
  1109. // Cancelled due to low priority - try again later.
  1110. tile._contentState = previousState;
  1111. ++tileset.statistics.numberOfAttemptedRequests;
  1112. return;
  1113. }
  1114. tile._contentState = Cesium3DTileContentState.FAILED;
  1115. throw error;
  1116. }
  1117. if (tile.isDestroyed()) {
  1118. --tileset.statistics.numberOfPendingRequests;
  1119. // Tile is unloaded before the content can process
  1120. return;
  1121. }
  1122. if (request.cancelled || request.state === RequestState.CANCELLED) {
  1123. // Cancelled due to low priority - try again later.
  1124. tile._contentState = previousState;
  1125. --tileset.statistics.numberOfPendingRequests;
  1126. ++tileset.statistics.numberOfAttemptedRequests;
  1127. return;
  1128. }
  1129. try {
  1130. const content = await makeContent(tile, arrayBuffer);
  1131. --tileset.statistics.numberOfPendingRequests;
  1132. if (tile.isDestroyed()) {
  1133. // Tile is unloaded before the content can process
  1134. return;
  1135. }
  1136. if (expired) {
  1137. tile.expireDate = undefined;
  1138. }
  1139. tile._content = content;
  1140. tile._contentState = Cesium3DTileContentState.PROCESSING;
  1141. return content;
  1142. } catch (error) {
  1143. --tileset.statistics.numberOfPendingRequests;
  1144. if (tile.isDestroyed()) {
  1145. // Tile is unloaded before the content can process
  1146. return;
  1147. }
  1148. tile._contentState = Cesium3DTileContentState.FAILED;
  1149. throw error;
  1150. }
  1151. }
  1152. /**
  1153. * @private
  1154. * @param {Cesium3DTile} tile
  1155. * @returns {Promise<Cesium3DTileContent>|Promise<undefined>|undefined} A promise that resolves to the tile content once loaded; a promise that resolves to undefined if the tile was destroyed before processing can happen or the request was cancelled mid-flight; or undefined if the request cannot be scheduled this frame.
  1156. */
  1157. function requestSingleContent(tile) {
  1158. // it is important to clone here. The fetchArrayBuffer() below uses
  1159. // throttling, but other uses of the resources do not.
  1160. const resource = tile._contentResource.clone();
  1161. const expired = tile.contentExpired;
  1162. if (expired) {
  1163. // Append a query parameter of the tile expiration date to prevent caching
  1164. resource.setQueryParameters({
  1165. expired: tile.expireDate.toString(),
  1166. });
  1167. }
  1168. const request = new Request({
  1169. throttle: true,
  1170. throttleByServer: true,
  1171. type: RequestType.TILES3D,
  1172. priorityFunction: createPriorityFunction(tile),
  1173. serverKey: tile._serverKey,
  1174. });
  1175. tile._request = request;
  1176. resource.request = request;
  1177. const tileset = tile._tileset;
  1178. const promise = resource.fetchArrayBuffer();
  1179. if (!defined(promise)) {
  1180. ++tileset.statistics.numberOfAttemptedRequests;
  1181. return;
  1182. }
  1183. return processArrayBuffer(tile, tileset, request, expired, promise);
  1184. }
  1185. /**
  1186. * Given a downloaded content payload, construct a {@link Cesium3DTileContent}.
  1187. * <p>
  1188. * This is only used for single contents.
  1189. * </p>
  1190. *
  1191. * @param {Cesium3DTile} tile The tile
  1192. * @param {ArrayBuffer} arrayBuffer The downloaded payload containing data for the content
  1193. * @return {Promise<Cesium3DTileContent>} A content object
  1194. * @private
  1195. */
  1196. async function makeContent(tile, arrayBuffer) {
  1197. const preprocessed = preprocess3DTileContent(arrayBuffer);
  1198. // Vector and Geometry tile rendering do not support the skip LOD optimization.
  1199. const tileset = tile._tileset;
  1200. tileset._disableSkipLevelOfDetail =
  1201. tileset._disableSkipLevelOfDetail ||
  1202. preprocessed.contentType === Cesium3DTileContentType.GEOMETRY ||
  1203. preprocessed.contentType === Cesium3DTileContentType.VECTOR;
  1204. if (
  1205. preprocessed.contentType === Cesium3DTileContentType.IMPLICIT_SUBTREE ||
  1206. preprocessed.contentType === Cesium3DTileContentType.IMPLICIT_SUBTREE_JSON
  1207. ) {
  1208. tile.hasImplicitContent = true;
  1209. }
  1210. if (preprocessed.contentType === Cesium3DTileContentType.EXTERNAL_TILESET) {
  1211. tile.hasTilesetContent = true;
  1212. }
  1213. let content;
  1214. const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType];
  1215. if (tile.isDestroyed()) {
  1216. return;
  1217. }
  1218. if (defined(preprocessed.binaryPayload)) {
  1219. content = await Promise.resolve(
  1220. contentFactory(
  1221. tileset,
  1222. tile,
  1223. tile._contentResource,
  1224. preprocessed.binaryPayload.buffer,
  1225. 0
  1226. )
  1227. );
  1228. } else {
  1229. // JSON formats
  1230. content = await Promise.resolve(
  1231. contentFactory(
  1232. tileset,
  1233. tile,
  1234. tile._contentResource,
  1235. preprocessed.jsonPayload
  1236. )
  1237. );
  1238. }
  1239. const contentHeader = tile._contentHeader;
  1240. if (tile.hasImplicitContentMetadata) {
  1241. const subtree = tile.implicitSubtree;
  1242. const coordinates = tile.implicitCoordinates;
  1243. content.metadata = subtree.getContentMetadataView(coordinates, 0);
  1244. } else if (!tile.hasImplicitContent) {
  1245. content.metadata = findContentMetadata(tileset, contentHeader);
  1246. }
  1247. const groupMetadata = findGroupMetadata(tileset, contentHeader);
  1248. if (defined(groupMetadata)) {
  1249. content.group = new Cesium3DContentGroup({
  1250. metadata: groupMetadata,
  1251. });
  1252. }
  1253. return content;
  1254. }
  1255. /**
  1256. * Cancel requests for the tile's contents. This is called when the tile
  1257. * goes out of view.
  1258. *
  1259. * @private
  1260. */
  1261. Cesium3DTile.prototype.cancelRequests = function () {
  1262. if (this.hasMultipleContents) {
  1263. this._content.cancelRequests();
  1264. } else {
  1265. this._request.cancel();
  1266. }
  1267. };
  1268. /**
  1269. * Unloads the tile's content.
  1270. *
  1271. * @private
  1272. */
  1273. Cesium3DTile.prototype.unloadContent = function () {
  1274. if (!this.hasRenderableContent) {
  1275. return;
  1276. }
  1277. this._content = this._content && this._content.destroy();
  1278. this._contentState = Cesium3DTileContentState.UNLOADED;
  1279. this.lastStyleTime = 0.0;
  1280. this.clippingPlanesDirty = this._clippingPlanesState === 0;
  1281. this._clippingPlanesState = 0;
  1282. this._debugColorizeTiles = false;
  1283. this._debugBoundingVolume =
  1284. this._debugBoundingVolume && this._debugBoundingVolume.destroy();
  1285. this._debugContentBoundingVolume =
  1286. this._debugContentBoundingVolume &&
  1287. this._debugContentBoundingVolume.destroy();
  1288. this._debugViewerRequestVolume =
  1289. this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy();
  1290. };
  1291. const scratchProjectedBoundingSphere = new BoundingSphere();
  1292. /**
  1293. * @private
  1294. * @param {Cesium3DTile} tile
  1295. * @param {FrameState} frameState
  1296. * @returns {TileBoundingVolume}
  1297. */
  1298. function getBoundingVolume(tile, frameState) {
  1299. if (
  1300. frameState.mode !== SceneMode.SCENE3D &&
  1301. !defined(tile._boundingVolume2D)
  1302. ) {
  1303. const boundingSphere = tile._boundingVolume.boundingSphere;
  1304. const sphere = BoundingSphere.projectTo2D(
  1305. boundingSphere,
  1306. frameState.mapProjection,
  1307. scratchProjectedBoundingSphere
  1308. );
  1309. tile._boundingVolume2D = new TileBoundingSphere(
  1310. sphere.center,
  1311. sphere.radius
  1312. );
  1313. }
  1314. return frameState.mode !== SceneMode.SCENE3D
  1315. ? tile._boundingVolume2D
  1316. : tile._boundingVolume;
  1317. }
  1318. /**
  1319. * @private
  1320. * @param {Cesium3DTile} tile
  1321. * @param {FrameState} frameState
  1322. * @returns {TileBoundingVolume}
  1323. */
  1324. function getContentBoundingVolume(tile, frameState) {
  1325. if (
  1326. frameState.mode !== SceneMode.SCENE3D &&
  1327. !defined(tile._contentBoundingVolume2D)
  1328. ) {
  1329. const boundingSphere = tile._contentBoundingVolume.boundingSphere;
  1330. const sphere = BoundingSphere.projectTo2D(
  1331. boundingSphere,
  1332. frameState.mapProjection,
  1333. scratchProjectedBoundingSphere
  1334. );
  1335. tile._contentBoundingVolume2D = new TileBoundingSphere(
  1336. sphere.center,
  1337. sphere.radius
  1338. );
  1339. }
  1340. return frameState.mode !== SceneMode.SCENE3D
  1341. ? tile._contentBoundingVolume2D
  1342. : tile._contentBoundingVolume;
  1343. }
  1344. /**
  1345. * Determines whether the tile's bounding volume intersects the culling volume.
  1346. *
  1347. * @param {FrameState} frameState The frame state.
  1348. * @param {number} parentVisibilityPlaneMask The parent's plane mask to speed up the visibility check.
  1349. * @returns {number} A plane mask as described above in {@link CullingVolume#computeVisibilityWithPlaneMask}.
  1350. *
  1351. * @private
  1352. */
  1353. Cesium3DTile.prototype.visibility = function (
  1354. frameState,
  1355. parentVisibilityPlaneMask
  1356. ) {
  1357. const cullingVolume = frameState.cullingVolume;
  1358. const boundingVolume = getBoundingVolume(this, frameState);
  1359. const tileset = this._tileset;
  1360. const clippingPlanes = tileset.clippingPlanes;
  1361. if (defined(clippingPlanes) && clippingPlanes.enabled) {
  1362. const intersection = clippingPlanes.computeIntersectionWithBoundingVolume(
  1363. boundingVolume,
  1364. tileset.clippingPlanesOriginMatrix
  1365. );
  1366. this._isClipped = intersection !== Intersect.INSIDE;
  1367. if (intersection === Intersect.OUTSIDE) {
  1368. return CullingVolume.MASK_OUTSIDE;
  1369. }
  1370. }
  1371. return cullingVolume.computeVisibilityWithPlaneMask(
  1372. boundingVolume,
  1373. parentVisibilityPlaneMask
  1374. );
  1375. };
  1376. /**
  1377. * Assuming the tile's bounding volume intersects the culling volume, determines
  1378. * whether the tile's content's bounding volume intersects the culling volume.
  1379. *
  1380. * @param {FrameState} frameState The frame state.
  1381. * @returns {Intersect} The result of the intersection: the tile's content is completely outside, completely inside, or intersecting the culling volume.
  1382. *
  1383. * @private
  1384. */
  1385. Cesium3DTile.prototype.contentVisibility = function (frameState) {
  1386. // Assumes the tile's bounding volume intersects the culling volume already, so
  1387. // just return Intersect.INSIDE if there is no content bounding volume.
  1388. if (!defined(this._contentBoundingVolume)) {
  1389. return Intersect.INSIDE;
  1390. }
  1391. if (this._visibilityPlaneMask === CullingVolume.MASK_INSIDE) {
  1392. // The tile's bounding volume is completely inside the culling volume so
  1393. // the content bounding volume must also be inside.
  1394. return Intersect.INSIDE;
  1395. }
  1396. // PERFORMANCE_IDEA: is it possible to burn less CPU on this test since we know the
  1397. // tile's (not the content's) bounding volume intersects the culling volume?
  1398. const cullingVolume = frameState.cullingVolume;
  1399. const boundingVolume = getContentBoundingVolume(this, frameState);
  1400. const tileset = this._tileset;
  1401. const clippingPlanes = tileset.clippingPlanes;
  1402. if (defined(clippingPlanes) && clippingPlanes.enabled) {
  1403. const intersection = clippingPlanes.computeIntersectionWithBoundingVolume(
  1404. boundingVolume,
  1405. tileset.clippingPlanesOriginMatrix
  1406. );
  1407. this._isClipped = intersection !== Intersect.INSIDE;
  1408. if (intersection === Intersect.OUTSIDE) {
  1409. return Intersect.OUTSIDE;
  1410. }
  1411. }
  1412. return cullingVolume.computeVisibility(boundingVolume);
  1413. };
  1414. /**
  1415. * Computes the (potentially approximate) distance from the closest point of the tile's bounding volume to the camera.
  1416. *
  1417. * @param {FrameState} frameState The frame state.
  1418. * @returns {number} The distance, in meters, or zero if the camera is inside the bounding volume.
  1419. *
  1420. * @private
  1421. */
  1422. Cesium3DTile.prototype.distanceToTile = function (frameState) {
  1423. const boundingVolume = getBoundingVolume(this, frameState);
  1424. return boundingVolume.distanceToCamera(frameState);
  1425. };
  1426. const scratchToTileCenter = new Cartesian3();
  1427. /**
  1428. * Computes the distance from the center of the tile's bounding volume to the camera's plane defined by its position and view direction.
  1429. *
  1430. * @param {FrameState} frameState The frame state.
  1431. * @returns {number} The distance, in meters.
  1432. *
  1433. * @private
  1434. */
  1435. Cesium3DTile.prototype.distanceToTileCenter = function (frameState) {
  1436. const tileBoundingVolume = getBoundingVolume(this, frameState);
  1437. const boundingVolume = tileBoundingVolume.boundingVolume; // Gets the underlying OrientedBoundingBox or BoundingSphere
  1438. const toCenter = Cartesian3.subtract(
  1439. boundingVolume.center,
  1440. frameState.camera.positionWC,
  1441. scratchToTileCenter
  1442. );
  1443. return Cartesian3.dot(frameState.camera.directionWC, toCenter);
  1444. };
  1445. /**
  1446. * Checks if the camera is inside the viewer request volume.
  1447. *
  1448. * @param {FrameState} frameState The frame state.
  1449. * @returns {boolean} Whether the camera is inside the volume.
  1450. *
  1451. * @private
  1452. */
  1453. Cesium3DTile.prototype.insideViewerRequestVolume = function (frameState) {
  1454. const viewerRequestVolume = this._viewerRequestVolume;
  1455. return (
  1456. !defined(viewerRequestVolume) ||
  1457. viewerRequestVolume.distanceToCamera(frameState) === 0.0
  1458. );
  1459. };
  1460. const scratchMatrix = new Matrix3();
  1461. const scratchScale = new Cartesian3();
  1462. const scratchHalfAxes = new Matrix3();
  1463. const scratchCenter = new Cartesian3();
  1464. const scratchRectangle = new Rectangle();
  1465. const scratchOrientedBoundingBox = new OrientedBoundingBox();
  1466. const scratchTransform = new Matrix4();
  1467. /**
  1468. * @private
  1469. * @param {Array} box An array of 12 numbers that define an oriented bounding box
  1470. * @param {Matrix4} transform
  1471. * @param {TileBoundingVolume} [result]
  1472. * @returns {TileOrientedBoundingBox}
  1473. */
  1474. function createBox(box, transform, result) {
  1475. let center = Cartesian3.fromElements(box[0], box[1], box[2], scratchCenter);
  1476. let halfAxes = Matrix3.fromArray(box, 3, scratchHalfAxes);
  1477. // Find the transformed center and halfAxes
  1478. center = Matrix4.multiplyByPoint(transform, center, center);
  1479. const rotationScale = Matrix4.getMatrix3(transform, scratchMatrix);
  1480. halfAxes = Matrix3.multiply(rotationScale, halfAxes, halfAxes);
  1481. if (defined(result)) {
  1482. result.update(center, halfAxes);
  1483. return result;
  1484. }
  1485. return new TileOrientedBoundingBox(center, halfAxes);
  1486. }
  1487. /**
  1488. * @private
  1489. * @param {Array} region An array of six numbers that define a bounding geographic region in EPSG:4979 coordinates with the order [west, south, east, north, minimum height, maximum height]
  1490. * @param {Matrix4} transform
  1491. * @param {Matrix4} initialTransform
  1492. * @param {TileOrientedBoundingBox} [result]
  1493. * @returns {TileOrientedBoundingBox}
  1494. */
  1495. function createBoxFromTransformedRegion(
  1496. region,
  1497. transform,
  1498. initialTransform,
  1499. result
  1500. ) {
  1501. const rectangle = Rectangle.unpack(region, 0, scratchRectangle);
  1502. const minimumHeight = region[4];
  1503. const maximumHeight = region[5];
  1504. const orientedBoundingBox = OrientedBoundingBox.fromRectangle(
  1505. rectangle,
  1506. minimumHeight,
  1507. maximumHeight,
  1508. Ellipsoid.WGS84,
  1509. scratchOrientedBoundingBox
  1510. );
  1511. let center = orientedBoundingBox.center;
  1512. let halfAxes = orientedBoundingBox.halfAxes;
  1513. // A region bounding volume is not transformed by the transform in the tileset JSON,
  1514. // but may be transformed by additional transforms applied in Cesium.
  1515. // This is why the transform is calculated as the difference between the initial transform and the current transform.
  1516. transform = Matrix4.multiplyTransformation(
  1517. transform,
  1518. Matrix4.inverseTransformation(initialTransform, scratchTransform),
  1519. scratchTransform
  1520. );
  1521. center = Matrix4.multiplyByPoint(transform, center, center);
  1522. const rotationScale = Matrix4.getMatrix3(transform, scratchMatrix);
  1523. halfAxes = Matrix3.multiply(rotationScale, halfAxes, halfAxes);
  1524. if (defined(result) && result instanceof TileOrientedBoundingBox) {
  1525. result.update(center, halfAxes);
  1526. return result;
  1527. }
  1528. return new TileOrientedBoundingBox(center, halfAxes);
  1529. }
  1530. /**
  1531. * @private
  1532. * @param {Array} region An array of six numbers that define a bounding geographic region in EPSG:4979 coordinates with the order [west, south, east, north, minimum height, maximum height]
  1533. * @param {Matrix4} transform
  1534. * @param {Matrix4} initialTransform
  1535. * @param {TileBoundingVolume} [result]
  1536. * @returns {TileBoundingVolume}
  1537. */
  1538. function createRegion(region, transform, initialTransform, result) {
  1539. if (
  1540. !Matrix4.equalsEpsilon(transform, initialTransform, CesiumMath.EPSILON8)
  1541. ) {
  1542. return createBoxFromTransformedRegion(
  1543. region,
  1544. transform,
  1545. initialTransform,
  1546. result
  1547. );
  1548. }
  1549. if (defined(result)) {
  1550. return result;
  1551. }
  1552. const rectangleRegion = Rectangle.unpack(region, 0, scratchRectangle);
  1553. return new TileBoundingRegion({
  1554. rectangle: rectangleRegion,
  1555. minimumHeight: region[4],
  1556. maximumHeight: region[5],
  1557. });
  1558. }
  1559. /**
  1560. * @private
  1561. * @param {Array} sphere An array of four numbers that define a bounding sphere
  1562. * @param {Matrix4} transform
  1563. * @param {TileBoundingVolume} [result]
  1564. * @returns {TileBoundingSphere}
  1565. */
  1566. function createSphere(sphere, transform, result) {
  1567. let center = Cartesian3.fromElements(
  1568. sphere[0],
  1569. sphere[1],
  1570. sphere[2],
  1571. scratchCenter
  1572. );
  1573. let radius = sphere[3];
  1574. // Find the transformed center and radius
  1575. center = Matrix4.multiplyByPoint(transform, center, center);
  1576. const scale = Matrix4.getScale(transform, scratchScale);
  1577. const uniformScale = Cartesian3.maximumComponent(scale);
  1578. radius *= uniformScale;
  1579. if (defined(result)) {
  1580. result.update(center, radius);
  1581. return result;
  1582. }
  1583. return new TileBoundingSphere(center, radius);
  1584. }
  1585. /**
  1586. * Create a bounding volume from the tile's bounding volume header.
  1587. *
  1588. * @param {object} boundingVolumeHeader The tile's bounding volume header.
  1589. * @param {Matrix4} transform The transform to apply to the bounding volume.
  1590. * @param {TileBoundingVolume} [result] The object onto which to store the result.
  1591. *
  1592. * @returns {TileBoundingVolume} The modified result parameter or a new TileBoundingVolume instance if none was provided.
  1593. *
  1594. * @private
  1595. */
  1596. Cesium3DTile.prototype.createBoundingVolume = function (
  1597. boundingVolumeHeader,
  1598. transform,
  1599. result
  1600. ) {
  1601. if (!defined(boundingVolumeHeader)) {
  1602. throw new RuntimeError("boundingVolume must be defined");
  1603. }
  1604. if (hasExtension(boundingVolumeHeader, "3DTILES_bounding_volume_S2")) {
  1605. return new TileBoundingS2Cell(
  1606. boundingVolumeHeader.extensions["3DTILES_bounding_volume_S2"]
  1607. );
  1608. }
  1609. const { box, region, sphere } = boundingVolumeHeader;
  1610. if (defined(box)) {
  1611. return createBox(box, transform, result);
  1612. }
  1613. if (defined(region)) {
  1614. return createRegion(region, transform, this._initialTransform, result);
  1615. }
  1616. if (defined(sphere)) {
  1617. return createSphere(sphere, transform, result);
  1618. }
  1619. throw new RuntimeError(
  1620. "boundingVolume must contain a sphere, region, or box"
  1621. );
  1622. };
  1623. /**
  1624. * Update the tile's transform. The transform is applied to the tile's bounding volumes.
  1625. *
  1626. * @private
  1627. * @param {Matrix4} parentTransform
  1628. */
  1629. Cesium3DTile.prototype.updateTransform = function (parentTransform) {
  1630. parentTransform = defaultValue(parentTransform, Matrix4.IDENTITY);
  1631. const computedTransform = Matrix4.multiplyTransformation(
  1632. parentTransform,
  1633. this.transform,
  1634. scratchTransform
  1635. );
  1636. const transformChanged = !Matrix4.equals(
  1637. computedTransform,
  1638. this.computedTransform
  1639. );
  1640. if (!transformChanged) {
  1641. return;
  1642. }
  1643. Matrix4.clone(computedTransform, this.computedTransform);
  1644. // Update the bounding volumes
  1645. const header = this._header;
  1646. const contentHeader = this._contentHeader;
  1647. this._boundingVolume = this.createBoundingVolume(
  1648. header.boundingVolume,
  1649. this.computedTransform,
  1650. this._boundingVolume
  1651. );
  1652. if (defined(this._contentBoundingVolume)) {
  1653. this._contentBoundingVolume = this.createBoundingVolume(
  1654. contentHeader.boundingVolume,
  1655. this.computedTransform,
  1656. this._contentBoundingVolume
  1657. );
  1658. }
  1659. if (defined(this._viewerRequestVolume)) {
  1660. this._viewerRequestVolume = this.createBoundingVolume(
  1661. header.viewerRequestVolume,
  1662. this.computedTransform,
  1663. this._viewerRequestVolume
  1664. );
  1665. }
  1666. this.updateGeometricErrorScale();
  1667. // Destroy the debug bounding volumes. They will be generated fresh.
  1668. this._debugBoundingVolume =
  1669. this._debugBoundingVolume && this._debugBoundingVolume.destroy();
  1670. this._debugContentBoundingVolume =
  1671. this._debugContentBoundingVolume &&
  1672. this._debugContentBoundingVolume.destroy();
  1673. this._debugViewerRequestVolume =
  1674. this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy();
  1675. };
  1676. Cesium3DTile.prototype.updateGeometricErrorScale = function () {
  1677. const scale = Matrix4.getScale(this.computedTransform, scratchScale);
  1678. const uniformScale = Cartesian3.maximumComponent(scale);
  1679. this.geometricError = this._geometricError * uniformScale;
  1680. if (!defined(this.parent)) {
  1681. // Update the tileset's geometric error
  1682. const tileset = this._tileset;
  1683. tileset._scaledGeometricError = tileset._geometricError * uniformScale;
  1684. }
  1685. };
  1686. /**
  1687. * @private
  1688. * @param {Cesium3DTile} tile
  1689. * @param {Cesium3DTileset} tileset
  1690. * @param {FrameState} frameState
  1691. * @param {object} passOptions
  1692. */
  1693. function applyDebugSettings(tile, tileset, frameState, passOptions) {
  1694. if (!passOptions.isRender) {
  1695. return;
  1696. }
  1697. const hasContentBoundingVolume =
  1698. defined(tile._contentHeader) && defined(tile._contentHeader.boundingVolume);
  1699. const showVolume =
  1700. tileset.debugShowBoundingVolume ||
  1701. (tileset.debugShowContentBoundingVolume && !hasContentBoundingVolume);
  1702. if (showVolume) {
  1703. let color;
  1704. if (!tile._finalResolution) {
  1705. color = Color.YELLOW;
  1706. } else if (!tile.hasRenderableContent) {
  1707. color = Color.DARKGRAY;
  1708. } else {
  1709. color = Color.WHITE;
  1710. }
  1711. if (!defined(tile._debugBoundingVolume)) {
  1712. tile._debugBoundingVolume = tile._boundingVolume.createDebugVolume(color);
  1713. }
  1714. tile._debugBoundingVolume.update(frameState);
  1715. const attributes = tile._debugBoundingVolume.getGeometryInstanceAttributes(
  1716. "outline"
  1717. );
  1718. attributes.color = ColorGeometryInstanceAttribute.toValue(
  1719. color,
  1720. attributes.color
  1721. );
  1722. } else if (!showVolume && defined(tile._debugBoundingVolume)) {
  1723. tile._debugBoundingVolume = tile._debugBoundingVolume.destroy();
  1724. }
  1725. if (tileset.debugShowContentBoundingVolume && hasContentBoundingVolume) {
  1726. if (!defined(tile._debugContentBoundingVolume)) {
  1727. tile._debugContentBoundingVolume = tile._contentBoundingVolume.createDebugVolume(
  1728. Color.BLUE
  1729. );
  1730. }
  1731. tile._debugContentBoundingVolume.update(frameState);
  1732. } else if (
  1733. !tileset.debugShowContentBoundingVolume &&
  1734. defined(tile._debugContentBoundingVolume)
  1735. ) {
  1736. tile._debugContentBoundingVolume = tile._debugContentBoundingVolume.destroy();
  1737. }
  1738. if (
  1739. tileset.debugShowViewerRequestVolume &&
  1740. defined(tile._viewerRequestVolume)
  1741. ) {
  1742. if (!defined(tile._debugViewerRequestVolume)) {
  1743. tile._debugViewerRequestVolume = tile._viewerRequestVolume.createDebugVolume(
  1744. Color.YELLOW
  1745. );
  1746. }
  1747. tile._debugViewerRequestVolume.update(frameState);
  1748. } else if (
  1749. !tileset.debugShowViewerRequestVolume &&
  1750. defined(tile._debugViewerRequestVolume)
  1751. ) {
  1752. tile._debugViewerRequestVolume = tile._debugViewerRequestVolume.destroy();
  1753. }
  1754. const debugColorizeTilesOn =
  1755. (tileset.debugColorizeTiles && !tile._debugColorizeTiles) ||
  1756. defined(tileset._heatmap.tilePropertyName);
  1757. const debugColorizeTilesOff =
  1758. !tileset.debugColorizeTiles && tile._debugColorizeTiles;
  1759. if (debugColorizeTilesOn) {
  1760. tileset._heatmap.colorize(tile, frameState); // Skipped if tileset._heatmap.tilePropertyName is undefined
  1761. tile._debugColorizeTiles = true;
  1762. tile.color = tile._debugColor;
  1763. } else if (debugColorizeTilesOff) {
  1764. tile._debugColorizeTiles = false;
  1765. tile.color = Color.WHITE;
  1766. }
  1767. if (tile._colorDirty) {
  1768. tile._colorDirty = false;
  1769. tile._content.applyDebugSettings(true, tile._color);
  1770. }
  1771. if (debugColorizeTilesOff) {
  1772. tileset.makeStyleDirty(); // Re-apply style now that colorize is switched off
  1773. }
  1774. }
  1775. /**
  1776. * @private
  1777. * @param {Cesium3DTile} tile
  1778. * @param {Cesium3DTileset} tileset
  1779. * @param {FrameState} frameState
  1780. */
  1781. function updateContent(tile, tileset, frameState) {
  1782. const expiredContent = tile._expiredContent;
  1783. // expired content is not supported for multiple contents
  1784. if (!tile.hasMultipleContents && defined(expiredContent)) {
  1785. if (!tile.contentReady) {
  1786. // Render the expired content while the content loads
  1787. try {
  1788. expiredContent.update(tileset, frameState);
  1789. } catch (error) {
  1790. // Eat error for expired content
  1791. }
  1792. return;
  1793. }
  1794. // New content is ready, destroy expired content
  1795. tile._expiredContent.destroy();
  1796. tile._expiredContent = undefined;
  1797. }
  1798. if (!defined(tile.content)) {
  1799. // Implicit placeholder tile
  1800. return;
  1801. }
  1802. try {
  1803. tile.content.update(tileset, frameState);
  1804. } catch (error) {
  1805. tile._contentState = Cesium3DTileContentState.FAILED;
  1806. throw error;
  1807. }
  1808. }
  1809. /**
  1810. * Compute and compare ClippingPlanes state:
  1811. * - enabled-ness - are clipping planes enabled? is this tile clipped?
  1812. * - clipping plane count
  1813. * - clipping function (union v. intersection)
  1814. * @private
  1815. * @param {Cesium3DTile} tile
  1816. * @param {Cesium3DTileset} tileset
  1817. */
  1818. function updateClippingPlanes(tile, tileset) {
  1819. const clippingPlanes = tileset.clippingPlanes;
  1820. let currentClippingPlanesState = 0;
  1821. if (defined(clippingPlanes) && tile._isClipped && clippingPlanes.enabled) {
  1822. currentClippingPlanesState = clippingPlanes.clippingPlanesState;
  1823. }
  1824. // If clippingPlaneState for tile changed, mark clippingPlanesDirty so content can update
  1825. if (currentClippingPlanesState !== tile._clippingPlanesState) {
  1826. tile._clippingPlanesState = currentClippingPlanesState;
  1827. tile.clippingPlanesDirty = true;
  1828. }
  1829. }
  1830. /**
  1831. * Get the draw commands needed to render this tile.
  1832. *
  1833. * @private
  1834. * @param {Cesium3DTileset} tileset
  1835. * @param {FrameState} frameState
  1836. * @param {object} passOptions
  1837. */
  1838. Cesium3DTile.prototype.update = function (tileset, frameState, passOptions) {
  1839. const { commandList } = frameState;
  1840. const commandStart = commandList.length;
  1841. updateClippingPlanes(this, tileset);
  1842. applyDebugSettings(this, tileset, frameState, passOptions);
  1843. updateContent(this, tileset, frameState);
  1844. const commandEnd = commandList.length;
  1845. this._commandsLength = commandEnd - commandStart;
  1846. for (let i = commandStart; i < commandEnd; ++i) {
  1847. const command = commandList[i];
  1848. const translucent = command.pass === Pass.TRANSLUCENT;
  1849. command.depthForTranslucentClassification = translucent;
  1850. }
  1851. this.clippingPlanesDirty = false; // reset after content update
  1852. };
  1853. const scratchCommandList = [];
  1854. /**
  1855. * Processes the tile's content, e.g., create WebGL resources, to move from the PROCESSING to READY state.
  1856. *
  1857. * @param {Cesium3DTileset} tileset The tileset containing this tile.
  1858. * @param {FrameState} frameState The frame state.
  1859. *
  1860. * @private
  1861. */
  1862. Cesium3DTile.prototype.process = function (tileset, frameState) {
  1863. if (!this.contentExpired && !this.contentReady && this._content.ready) {
  1864. updateExpireDate(this);
  1865. // Refresh style for expired content
  1866. this._selectedFrame = 0;
  1867. this.lastStyleTime = 0.0;
  1868. JulianDate.now(this._loadTimestamp);
  1869. this._contentState = Cesium3DTileContentState.READY;
  1870. if (!this.hasTilesetContent && !this.hasImplicitContent) {
  1871. // RESEARCH_IDEA: ability to unload tiles (without content) for an
  1872. // external tileset when all the tiles are unloaded.
  1873. tileset._statistics.incrementLoadCounts(this.content);
  1874. ++tileset._statistics.numberOfTilesWithContentReady;
  1875. ++tileset._statistics.numberOfLoadedTilesTotal;
  1876. // Add to the tile cache. Previously expired tiles are already in the cache and won't get re-added.
  1877. tileset._cache.add(this);
  1878. }
  1879. }
  1880. const savedCommandList = frameState.commandList;
  1881. frameState.commandList = scratchCommandList;
  1882. try {
  1883. this._content.update(tileset, frameState);
  1884. } catch (error) {
  1885. this._contentState = Cesium3DTileContentState.FAILED;
  1886. throw error;
  1887. }
  1888. scratchCommandList.length = 0;
  1889. frameState.commandList = savedCommandList;
  1890. };
  1891. /**
  1892. * @private
  1893. * @param {number} normalizedValue
  1894. * @param {number} numberOfDigits
  1895. * @param {number} leftShift
  1896. * @returns {number}
  1897. */
  1898. function isolateDigits(normalizedValue, numberOfDigits, leftShift) {
  1899. const scaled = normalizedValue * Math.pow(10, numberOfDigits);
  1900. const integer = parseInt(scaled);
  1901. return integer * Math.pow(10, leftShift);
  1902. }
  1903. /**
  1904. * @private
  1905. * @param {number} value
  1906. * @param {number} minimum
  1907. * @param {number} maximum
  1908. * @returns {number}
  1909. */
  1910. function priorityNormalizeAndClamp(value, minimum, maximum) {
  1911. // Subtract epsilon since we only want decimal digits present in the output.
  1912. return Math.max(
  1913. CesiumMath.normalize(value, minimum, maximum) - CesiumMath.EPSILON7,
  1914. 0.0
  1915. );
  1916. }
  1917. /**
  1918. * Sets the priority of the tile based on distance and depth
  1919. * @private
  1920. */
  1921. Cesium3DTile.prototype.updatePriority = function () {
  1922. const tileset = this.tileset;
  1923. const preferLeaves = tileset.preferLeaves;
  1924. const minimumPriority = tileset._minimumPriority;
  1925. const maximumPriority = tileset._maximumPriority;
  1926. // Combine priority systems together by mapping them into a base 10 number where each priority controls a specific set of digits in the number.
  1927. // For number priorities, map them to a 0.xxxxx number then left shift it up into a set number of digits before the decimal point. Chop of the fractional part then left shift again into the position it needs to go.
  1928. // For blending number priorities, normalize them to 0-1 and interpolate to get a combined 0-1 number, then proceed as normal.
  1929. // Booleans can just be 0 or 10^leftshift.
  1930. // Think of digits as penalties since smaller numbers are higher priority. If a tile has some large quantity or has a flag raised it's (usually) penalized for it, expressed as a higher number for the digit.
  1931. // Priority number format: preloadFlightDigits(1) | foveatedDeferDigits(1) | foveatedDigits(4) | preloadProgressiveResolutionDigits(1) | preferredSortingDigits(4) . depthDigits(the decimal digits)
  1932. // Certain flags like preferLeaves will flip / turn off certain digits to get desired load order.
  1933. // Setup leftShifts, digit counts, and scales (for booleans)
  1934. const digitsForANumber = 4;
  1935. const digitsForABoolean = 1;
  1936. const preferredSortingLeftShift = 0;
  1937. const preferredSortingDigitsCount = digitsForANumber;
  1938. const foveatedLeftShift =
  1939. preferredSortingLeftShift + preferredSortingDigitsCount;
  1940. const foveatedDigitsCount = digitsForANumber;
  1941. const preloadProgressiveResolutionLeftShift =
  1942. foveatedLeftShift + foveatedDigitsCount;
  1943. const preloadProgressiveResolutionDigitsCount = digitsForABoolean;
  1944. const preloadProgressiveResolutionScale = Math.pow(
  1945. 10,
  1946. preloadProgressiveResolutionLeftShift
  1947. );
  1948. const foveatedDeferLeftShift =
  1949. preloadProgressiveResolutionLeftShift +
  1950. preloadProgressiveResolutionDigitsCount;
  1951. const foveatedDeferDigitsCount = digitsForABoolean;
  1952. const foveatedDeferScale = Math.pow(10, foveatedDeferLeftShift);
  1953. const preloadFlightLeftShift =
  1954. foveatedDeferLeftShift + foveatedDeferDigitsCount;
  1955. const preloadFlightScale = Math.pow(10, preloadFlightLeftShift);
  1956. // Compute the digits for each priority
  1957. let depthDigits = priorityNormalizeAndClamp(
  1958. this._depth,
  1959. minimumPriority.depth,
  1960. maximumPriority.depth
  1961. );
  1962. depthDigits = preferLeaves ? 1.0 - depthDigits : depthDigits;
  1963. // Map 0-1 then convert to digit. Include a distance sort when doing non-skipLOD and replacement refinement, helps things like non-skipLOD photogrammetry
  1964. const useDistance =
  1965. !tileset.isSkippingLevelOfDetail &&
  1966. this.refine === Cesium3DTileRefine.REPLACE;
  1967. const normalizedPreferredSorting = useDistance
  1968. ? priorityNormalizeAndClamp(
  1969. this._priorityHolder._distanceToCamera,
  1970. minimumPriority.distance,
  1971. maximumPriority.distance
  1972. )
  1973. : priorityNormalizeAndClamp(
  1974. this._priorityReverseScreenSpaceError,
  1975. minimumPriority.reverseScreenSpaceError,
  1976. maximumPriority.reverseScreenSpaceError
  1977. );
  1978. const preferredSortingDigits = isolateDigits(
  1979. normalizedPreferredSorting,
  1980. preferredSortingDigitsCount,
  1981. preferredSortingLeftShift
  1982. );
  1983. const preloadProgressiveResolutionDigits = this._priorityProgressiveResolution
  1984. ? 0
  1985. : preloadProgressiveResolutionScale;
  1986. const normalizedFoveatedFactor = priorityNormalizeAndClamp(
  1987. this._priorityHolder._foveatedFactor,
  1988. minimumPriority.foveatedFactor,
  1989. maximumPriority.foveatedFactor
  1990. );
  1991. const foveatedDigits = isolateDigits(
  1992. normalizedFoveatedFactor,
  1993. foveatedDigitsCount,
  1994. foveatedLeftShift
  1995. );
  1996. const foveatedDeferDigits = this.priorityDeferred ? foveatedDeferScale : 0;
  1997. const preloadFlightDigits =
  1998. tileset._pass === Cesium3DTilePass.PRELOAD_FLIGHT ? 0 : preloadFlightScale;
  1999. // Get the final base 10 number
  2000. this._priority =
  2001. depthDigits +
  2002. preferredSortingDigits +
  2003. preloadProgressiveResolutionDigits +
  2004. foveatedDigits +
  2005. foveatedDeferDigits +
  2006. preloadFlightDigits;
  2007. };
  2008. /**
  2009. * @private
  2010. */
  2011. Cesium3DTile.prototype.isDestroyed = function () {
  2012. return false;
  2013. };
  2014. /**
  2015. * @private
  2016. */
  2017. Cesium3DTile.prototype.destroy = function () {
  2018. // For the interval between new content being requested and downloaded, expiredContent === content, so don't destroy twice
  2019. this._content = this._content && this._content.destroy();
  2020. this._expiredContent =
  2021. this._expiredContent &&
  2022. !this._expiredContent.isDestroyed() &&
  2023. this._expiredContent.destroy();
  2024. this._debugBoundingVolume =
  2025. this._debugBoundingVolume && this._debugBoundingVolume.destroy();
  2026. this._debugContentBoundingVolume =
  2027. this._debugContentBoundingVolume &&
  2028. this._debugContentBoundingVolume.destroy();
  2029. this._debugViewerRequestVolume =
  2030. this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy();
  2031. return destroyObject(this);
  2032. };
  2033. export default Cesium3DTile;