| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528 |
- import BoundingSphere from "../Core/BoundingSphere.js";
- import Cartesian3 from "../Core/Cartesian3.js";
- import Color from "../Core/Color.js";
- import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribute.js";
- import CullingVolume from "../Core/CullingVolume.js";
- import defined from "../Core/defined.js";
- import deprecationWarning from "../Core/deprecationWarning.js";
- import destroyObject from "../Core/destroyObject.js";
- import Ellipsoid from "../Core/Ellipsoid.js";
- import Intersect from "../Core/Intersect.js";
- import JulianDate from "../Core/JulianDate.js";
- import CesiumMath from "../Core/Math.js";
- import Matrix3 from "../Core/Matrix3.js";
- import Matrix4 from "../Core/Matrix4.js";
- import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
- import OrthographicFrustum from "../Core/OrthographicFrustum.js";
- import Rectangle from "../Core/Rectangle.js";
- import Request from "../Core/Request.js";
- import RequestScheduler from "../Core/RequestScheduler.js";
- import RequestState from "../Core/RequestState.js";
- import RequestType from "../Core/RequestType.js";
- import Resource from "../Core/Resource.js";
- import RuntimeError from "../Core/RuntimeError.js";
- import Cesium3DContentGroup from "./Cesium3DContentGroup.js";
- import Cesium3DTileContentFactory from "./Cesium3DTileContentFactory.js";
- import Cesium3DTileContentState from "./Cesium3DTileContentState.js";
- import Cesium3DTileContentType from "./Cesium3DTileContentType.js";
- import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
- import Cesium3DTilePass from "./Cesium3DTilePass.js";
- import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
- import Empty3DTileContent from "./Empty3DTileContent.js";
- import findContentMetadata from "./findContentMetadata.js";
- import findGroupMetadata from "./findGroupMetadata.js";
- import findTileMetadata from "./findTileMetadata.js";
- import hasExtension from "./hasExtension.js";
- import Multiple3DTileContent from "./Multiple3DTileContent.js";
- import BoundingVolumeSemantics from "./BoundingVolumeSemantics.js";
- import preprocess3DTileContent from "./preprocess3DTileContent.js";
- import SceneMode from "./SceneMode.js";
- import TileBoundingRegion from "./TileBoundingRegion.js";
- import TileBoundingS2Cell from "./TileBoundingS2Cell.js";
- import TileBoundingSphere from "./TileBoundingSphere.js";
- import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js";
- import Pass from "../Renderer/Pass.js";
- import VerticalExaggeration from "../Core/VerticalExaggeration.js";
-
- /**
- * A tile in a {@link Cesium3DTileset}. When a tile is first created, its content is not loaded;
- * the content is loaded on-demand when needed based on the view.
- * <p>
- * Do not construct this directly, instead access tiles through {@link Cesium3DTileset#tileVisible}.
- * </p>
- *
- * @alias Cesium3DTile
- * @constructor
- * @param {Cesium3DTileset} tileset The tileset
- * @param {Resource} baseResource The base resource for the tileset
- * @param {object} header The JSON header for the tile
- * @param {Cesium3DTile} parent The parent tile of the new tile
- */
- function Cesium3DTile(tileset, baseResource, header, parent) {
- this._tileset = tileset;
- this._header = header;
-
- const hasContentsArray = defined(header.contents);
- const hasMultipleContents =
- (hasContentsArray && header.contents.length > 1) ||
- hasExtension(header, "3DTILES_multiple_contents");
-
- // In the 1.0 schema, content is stored in tile.content instead of tile.contents
- const contentHeader =
- hasContentsArray && !hasMultipleContents
- ? header.contents[0]
- : header.content;
-
- this._contentHeader = contentHeader;
-
- /**
- * The local transform of this tile.
- * @type {Matrix4}
- */
- this.transform = defined(header.transform)
- ? Matrix4.unpack(header.transform)
- : Matrix4.clone(Matrix4.IDENTITY);
-
- const parentTransform = defined(parent)
- ? parent.computedTransform
- : tileset.modelMatrix;
- const computedTransform = Matrix4.multiply(
- parentTransform,
- this.transform,
- new Matrix4(),
- );
-
- const parentInitialTransform = defined(parent)
- ? parent._initialTransform
- : Matrix4.IDENTITY;
- this._initialTransform = Matrix4.multiply(
- parentInitialTransform,
- this.transform,
- new Matrix4(),
- );
-
- /**
- * The final computed transform of this tile.
- * @type {Matrix4}
- * @readonly
- */
- this.computedTransform = computedTransform;
-
- /**
- * When tile metadata is present (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension is used,
- * this stores a {@link TileMetadata} object for accessing tile metadata.
- *
- * @type {TileMetadata}
- * @readonly
- * @private
- * @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.
- */
- this.metadata = findTileMetadata(tileset, header);
-
- this._verticalExaggeration = 1.0;
- this._verticalExaggerationRelativeHeight = 0.0;
-
- // Important: tile metadata must be parsed before this line so that the
- // metadata semantics TILE_BOUNDING_BOX, TILE_BOUNDING_REGION, or TILE_BOUNDING_SPHERE
- // can override header.boundingVolume (if necessary)
- this._boundingVolume = this.createBoundingVolume(
- header.boundingVolume,
- computedTransform,
- );
- this._boundingVolume2D = undefined;
-
- let contentBoundingVolume;
-
- if (defined(contentHeader) && defined(contentHeader.boundingVolume)) {
- // Non-leaf tiles may have a content bounding-volume, which is a tight-fit bounding volume
- // around only the features in the tile. This box is useful for culling for rendering,
- // but not for culling for traversing the tree since it does not guarantee spatial coherence, i.e.,
- // since it only bounds features in the tile, not the entire tile, children may be
- // outside of this box.
- contentBoundingVolume = this.createBoundingVolume(
- contentHeader.boundingVolume,
- computedTransform,
- );
- }
- this._contentBoundingVolume = contentBoundingVolume;
- this._contentBoundingVolume2D = undefined;
-
- let viewerRequestVolume;
- if (defined(header.viewerRequestVolume)) {
- viewerRequestVolume = this.createBoundingVolume(
- header.viewerRequestVolume,
- computedTransform,
- );
- }
- this._viewerRequestVolume = viewerRequestVolume;
-
- /**
- * The error, in meters, introduced if this tile is rendered and its children are not.
- * This is used to compute screen space error, i.e., the error measured in pixels.
- *
- * @type {number}
- * @readonly
- */
- this.geometricError = header.geometricError;
- this._geometricError = header.geometricError;
-
- if (!defined(this._geometricError)) {
- this._geometricError = defined(parent)
- ? parent._geometricError
- : tileset._geometricError;
- Cesium3DTile._deprecationWarning(
- "geometricErrorUndefined",
- "Required property geometricError is undefined for this tile. Using parent's geometric error instead.",
- );
- }
-
- this.updateGeometricErrorScale();
-
- let refine;
- if (defined(header.refine)) {
- if (header.refine === "replace" || header.refine === "add") {
- Cesium3DTile._deprecationWarning(
- "lowercase-refine",
- `This tile uses a lowercase refine "${
- header.refine
- }". Instead use "${header.refine.toUpperCase()}".`,
- );
- }
- refine =
- header.refine.toUpperCase() === "REPLACE"
- ? Cesium3DTileRefine.REPLACE
- : Cesium3DTileRefine.ADD;
- } else if (defined(parent)) {
- // Inherit from parent tile if omitted.
- refine = parent.refine;
- } else {
- refine = Cesium3DTileRefine.REPLACE;
- }
-
- /**
- * Specifies the type of refinement that is used when traversing this tile for rendering.
- *
- * @type {Cesium3DTileRefine}
- * @readonly
- * @private
- */
- this.refine = refine;
-
- /**
- * Gets the tile's children.
- *
- * @type {Cesium3DTile[]}
- * @readonly
- */
- this.children = [];
-
- /**
- * This tile's parent or <code>undefined</code> if this tile is the root.
- * <p>
- * When a tile's content points to an external tileset JSON file, the external tileset's
- * root tile's parent is not <code>undefined</code>; instead, the parent references
- * the tile (with its content pointing to an external tileset JSON file) as if the two tilesets were merged.
- * </p>
- *
- * @type {Cesium3DTile}
- * @readonly
- */
- this.parent = parent;
-
- let content;
- let hasEmptyContent = false;
- let contentState;
- let contentResource;
- let serverKey;
-
- baseResource = Resource.createIfNeeded(baseResource);
-
- if (hasMultipleContents) {
- contentState = Cesium3DTileContentState.UNLOADED;
- // Each content may have its own URI, but they all need to be resolved
- // relative to the tileset, so the base resource is used.
- contentResource = baseResource.clone();
- } else if (defined(contentHeader)) {
- let contentHeaderUri = contentHeader.uri;
- if (defined(contentHeader.url)) {
- Cesium3DTile._deprecationWarning(
- "contentUrl",
- 'This tileset JSON uses the "content.url" property which has been deprecated. Use "content.uri" instead.',
- );
- contentHeaderUri = contentHeader.url;
- }
- if (contentHeaderUri === "") {
- Cesium3DTile._deprecationWarning(
- "contentUriEmpty",
- "content.uri property is an empty string, which creates a circular dependency, making this tileset invalid. Omit the content property instead",
- );
- content = new Empty3DTileContent(tileset, this);
- hasEmptyContent = true;
- contentState = Cesium3DTileContentState.READY;
- } else {
- contentState = Cesium3DTileContentState.UNLOADED;
- contentResource = baseResource.getDerivedResource({
- url: contentHeaderUri,
- });
- serverKey = RequestScheduler.getServerKey(
- contentResource.getUrlComponent(),
- );
- }
- } else {
- content = new Empty3DTileContent(tileset, this);
- hasEmptyContent = true;
- contentState = Cesium3DTileContentState.READY;
- }
-
- this._content = content;
- this._contentResource = contentResource;
- this._contentState = contentState;
- this._expiredContent = undefined;
-
- this._serverKey = serverKey;
-
- /**
- * When <code>true</code>, the tile has no content.
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- this.hasEmptyContent = hasEmptyContent;
-
- /**
- * When <code>true</code>, the tile's content points to an external tileset.
- * <p>
- * This is <code>false</code> until the tile's content is loaded.
- * </p>
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- this.hasTilesetContent = false;
-
- /**
- * When <code>true</code>, the tile's content is an implicit tileset.
- * <p>
- * This is <code>false</code> until the tile's implicit content is loaded.
- * </p>
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- * @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.
- */
- this.hasImplicitContent = false;
-
- /**
- * Determines whether the tile has renderable content.
- *
- * The loading starts with the assumption that the tile does have
- * renderable content, if the content is not empty.<br>
- * <br>
- * This turns <code>false</code> only when the tile content is loaded
- * and turns out to be a single content that points to an external
- * tileset or implicit content
- * </p>
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- this.hasRenderableContent = !hasEmptyContent;
-
- /**
- * When <code>true</code>, the tile contains content metadata from implicit tiling. This flag is set
- * for tiles transcoded by <code>Implicit3DTileContent</code>.
- * <p>
- * This is <code>false</code> until the tile's content is loaded.
- * </p>
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- * @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.
- */
- this.hasImplicitContentMetadata = false;
-
- /**
- * When <code>true</code>, the tile has multiple contents, either in the tile JSON (3D Tiles 1.1)
- * or via the <code>3DTILES_multiple_contents</code> extension.
- *
- * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_multiple_contents|3DTILES_multiple_contents extension}
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- this.hasMultipleContents = hasMultipleContents;
-
- /**
- * The node in the tileset's LRU cache, used to determine when to unload a tile's content.
- *
- * See {@link Cesium3DTilesetCache}
- *
- * @type {DoublyLinkedListNode}
- * @readonly
- *
- * @private
- */
- this.cacheNode = undefined;
-
- const expire = header.expire;
- let expireDuration;
- let expireDate;
- if (defined(expire)) {
- expireDuration = expire.duration;
- if (defined(expire.date)) {
- expireDate = JulianDate.fromIso8601(expire.date);
- }
- }
-
- /**
- * The time in seconds after the tile's content is ready when the content expires and new content is requested.
- *
- * @type {number}
- */
- this.expireDuration = expireDuration;
-
- /**
- * The date when the content expires and new content is requested.
- *
- * @type {JulianDate}
- */
- this.expireDate = expireDate;
-
- /**
- * The time when a style was last applied to this tile.
- *
- * @type {number}
- *
- * @private
- */
- this.lastStyleTime = 0.0;
-
- /**
- * Marks whether the tile's children bounds are fully contained within the tile's bounds
- *
- * @type {Cesium3DTileOptimizationHint}
- *
- * @private
- */
- this._optimChildrenWithinParent = Cesium3DTileOptimizationHint.NOT_COMPUTED;
-
- /**
- * Tracks if the tile's relationship with a ClippingPlaneCollection has changed with regards
- * to the ClippingPlaneCollection's state.
- *
- * @type {boolean}
- *
- * @private
- */
- this.clippingPlanesDirty = false;
-
- /**
- * Tracks if the tile's relationship with a ClippingPolygonCollection has changed with regards
- * to the ClippingPolygonCollection's state.
- *
- * @type {boolean}
- *
- * @private
- */
- this.clippingPolygonsDirty = false;
-
- /**
- * Tracks if the tile's request should be deferred until all non-deferred
- * tiles load.
- *
- * @type {boolean}
- *
- * @private
- */
- this.priorityDeferred = false;
-
- /**
- * For implicit tiling, an ImplicitTileset object will be attached to a
- * placeholder tile with either implicit tiling in the JSON (3D Tiles 1.1)
- * or the <code>3DTILES_implicit_tiling</code> extension.
- * This way the {@link Implicit3DTileContent} can access the tile later once the content is fetched.
- *
- * @type {ImplicitTileset|undefined}
- *
- * @private
- * @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.
- */
- this.implicitTileset = undefined;
-
- /**
- * For implicit tiling, the (level, x, y, [z]) coordinates within the
- * implicit tileset are stored in the tile.
- *
- * @type {ImplicitTileCoordinates|undefined}
- *
- * @private
- * @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.
- */
- this.implicitCoordinates = undefined;
-
- /**
- * For implicit tiling, each transcoded tile will hold a weak reference to
- * the {@link ImplicitSubtree}.
- *
- * @type {ImplicitSubtree|undefined}
- *
- * @private
- * @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.
- */
- this.implicitSubtree = undefined;
-
- // Members that are updated every frame for tree traversal and rendering optimizations:
- this._distanceToCamera = 0.0;
- this._centerZDepth = 0.0;
- this._screenSpaceError = 0.0;
- this._screenSpaceErrorProgressiveResolution = 0.0; // The screen space error at a given screen height of tileset.progressiveResolutionHeightFraction * screenHeight
- this._visibilityPlaneMask = 0;
- this._visible = false;
- this._inRequestVolume = false;
-
- this._finalResolution = true;
- this._depth = 0;
- this._stackLength = 0;
- this._selectionDepth = 0;
-
- this._updatedVisibilityFrame = 0;
- this._touchedFrame = 0;
- this._visitedFrame = 0;
- this._selectedFrame = 0;
- this._wasSelectedLastFrame = false;
- this._requestedFrame = 0;
- this._ancestorWithContent = undefined;
- this._ancestorWithContentAvailable = undefined;
- this._refines = false;
- this._shouldSelect = false;
- this._isClipped = true;
- this._isClippedByPolygon = false;
- this._clippingPlanesState = 0; // encapsulates (_isClipped, clippingPlanes.enabled) and number/function
- this._clippingPolygonsState = 0; // encapsulates (_isClipped, clippingPolygons.enabled) and number/function
- this._debugBoundingVolume = undefined;
- this._debugContentBoundingVolume = undefined;
- this._debugViewerRequestVolume = undefined;
- this._debugColor = Color.fromRandom({ alpha: 1.0 });
- this._debugColorizeTiles = false;
-
- this._priority = 0.0; // The priority used for request sorting
- this._priorityHolder = this; // Reference to the ancestor up the tree that holds the _foveatedFactor and _distanceToCamera for all tiles in the refinement chain.
- this._priorityProgressiveResolution = false;
- this._priorityProgressiveResolutionScreenSpaceErrorLeaf = false;
- this._priorityReverseScreenSpaceError = 0.0;
- this._foveatedFactor = 0.0;
- 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.
-
- this._loadTimestamp = new JulianDate();
-
- this._commandsLength = 0;
-
- this._color = undefined;
- this._colorDirty = false;
-
- this._request = undefined;
- }
-
- // This can be overridden for testing purposes
- Cesium3DTile._deprecationWarning = deprecationWarning;
-
- Object.defineProperties(Cesium3DTile.prototype, {
- /**
- * The tileset containing this tile.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {Cesium3DTileset}
- * @readonly
- */
- tileset: {
- get: function () {
- return this._tileset;
- },
- },
-
- /**
- * The tile's content. This represents the actual tile's payload,
- * not the content's metadata in the tileset JSON file.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {Cesium3DTileContent}
- * @readonly
- */
- content: {
- get: function () {
- return this._content;
- },
- },
-
- /**
- * Get the tile's bounding volume.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {TileBoundingVolume}
- * @readonly
- * @private
- */
- boundingVolume: {
- get: function () {
- return this._boundingVolume;
- },
- },
-
- /**
- * Get the bounding volume of the tile's contents. This defaults to the
- * tile's bounding volume when the content's bounding volume is
- * <code>undefined</code>.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {TileBoundingVolume}
- * @readonly
- * @private
- */
- contentBoundingVolume: {
- get: function () {
- return this._contentBoundingVolume ?? this._boundingVolume;
- },
- },
-
- /**
- * Get the bounding sphere derived from the tile's bounding volume.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {BoundingSphere}
- * @readonly
- */
- boundingSphere: {
- get: function () {
- return this._boundingVolume.boundingSphere;
- },
- },
-
- /**
- * Determines if the tile is visible within the current field of view
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- isVisible: {
- get: function () {
- return this._visible && this._inRequestVolume;
- },
- },
-
- /**
- * Returns the <code>extras</code> property in the tileset JSON for this tile, which contains application specific metadata.
- * Returns <code>undefined</code> if <code>extras</code> does not exist.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {object}
- * @readonly
- * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification#specifying-extensions-and-application-specific-extras|Extras in the 3D Tiles specification.}
- */
- extras: {
- get: function () {
- return this._header.extras;
- },
- },
-
- /**
- * Gets or sets the tile's highlight color.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {Color}
- *
- * @default {@link Color.WHITE}
- *
- * @private
- */
- color: {
- get: function () {
- if (!defined(this._color)) {
- this._color = new Color();
- }
- return Color.clone(this._color);
- },
- set: function (value) {
- this._color = Color.clone(value, this._color);
- this._colorDirty = true;
- },
- },
-
- /**
- * Determines if the tile has available content to render. <code>true</code> if the tile's
- * content is ready or if it has expired content that renders while new content loads; otherwise,
- * <code>false</code>.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- contentAvailable: {
- get: function () {
- return (
- (this.contentReady && this.hasRenderableContent) ||
- (defined(this._expiredContent) && !this.contentFailed)
- );
- },
- },
-
- /**
- * Determines if the tile's content is ready. This is automatically <code>true</code> for
- * tile's with empty content.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- contentReady: {
- get: function () {
- return this._contentState === Cesium3DTileContentState.READY;
- },
- },
-
- /**
- * Determines if the tile's content has not be requested. <code>true</code> if tile's
- * content has not be requested; otherwise, <code>false</code>.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- contentUnloaded: {
- get: function () {
- return this._contentState === Cesium3DTileContentState.UNLOADED;
- },
- },
-
- /**
- * Determines if the tile has renderable content which is unloaded
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- hasUnloadedRenderableContent: {
- get: function () {
- return this.hasRenderableContent && this.contentUnloaded;
- },
- },
-
- /**
- * Determines if the tile's content is expired. <code>true</code> if tile's
- * content is expired; otherwise, <code>false</code>.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- contentExpired: {
- get: function () {
- return this._contentState === Cesium3DTileContentState.EXPIRED;
- },
- },
-
- /**
- * Determines if the tile's content failed to load. <code>true</code> if the tile's
- * content failed to load; otherwise, <code>false</code>.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {boolean}
- * @readonly
- *
- * @private
- */
- contentFailed: {
- get: function () {
- return this._contentState === Cesium3DTileContentState.FAILED;
- },
- },
-
- /**
- * Returns the number of draw commands used by this tile.
- *
- * @readonly
- *
- * @private
- */
- commandsLength: {
- get: function () {
- return this._commandsLength;
- },
- },
- });
-
- const scratchCartesian = new Cartesian3();
-
- /**
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- * @returns {boolean}
- */
- function isPriorityDeferred(tile, frameState) {
- const { tileset, boundingSphere } = tile;
- const { radius, center } = boundingSphere;
- const { camera } = frameState;
-
- // If closest point on line is inside the sphere then set foveatedFactor to 0.
- // Otherwise, the dot product is with the line from camera to the point on the sphere that is closest to the line.
- const scaledCameraDirection = Cartesian3.multiplyByScalar(
- camera.directionWC,
- tile._centerZDepth,
- scratchCartesian,
- );
- const closestPointOnLine = Cartesian3.add(
- camera.positionWC,
- scaledCameraDirection,
- scratchCartesian,
- );
- // The distance from the camera's view direction to the tile.
- const toLine = Cartesian3.subtract(
- closestPointOnLine,
- center,
- scratchCartesian,
- );
- const distanceToCenterLine = Cartesian3.magnitude(toLine);
- const notTouchingSphere = distanceToCenterLine > radius;
-
- // If camera's direction vector is inside the bounding sphere then consider
- // this tile right along the line of sight and set _foveatedFactor to 0.
- // Otherwise,_foveatedFactor is one minus the dot product of the camera's direction
- // and the vector between the camera and the point on the bounding sphere closest to the view line.
- if (notTouchingSphere) {
- const toLineNormalized = Cartesian3.normalize(toLine, scratchCartesian);
- const scaledToLine = Cartesian3.multiplyByScalar(
- toLineNormalized,
- radius,
- scratchCartesian,
- );
- const closestOnSphere = Cartesian3.add(
- center,
- scaledToLine,
- scratchCartesian,
- );
- const toClosestOnSphere = Cartesian3.subtract(
- closestOnSphere,
- camera.positionWC,
- scratchCartesian,
- );
- const toClosestOnSphereNormalize = Cartesian3.normalize(
- toClosestOnSphere,
- scratchCartesian,
- );
- tile._foveatedFactor =
- 1.0 -
- Math.abs(Cartesian3.dot(camera.directionWC, toClosestOnSphereNormalize));
- } else {
- tile._foveatedFactor = 0.0;
- }
-
- // 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)
- // Or if the tile is a preload of any kind
- const replace = tile.refine === Cesium3DTileRefine.REPLACE;
- const skipLevelOfDetail = tileset.isSkippingLevelOfDetail;
- if (
- (replace && !skipLevelOfDetail) ||
- !tileset.foveatedScreenSpaceError ||
- tileset.foveatedConeSize === 1.0 ||
- (tile._priorityProgressiveResolution && replace && skipLevelOfDetail) ||
- tileset._pass === Cesium3DTilePass.PRELOAD_FLIGHT ||
- tileset._pass === Cesium3DTilePass.PRELOAD
- ) {
- return false;
- }
-
- 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.
- const foveatedConeFactor = tileset.foveatedConeSize * maximumFovatedFactor;
-
- // If it's inside the user-defined view cone, then it should not be deferred.
- if (tile._foveatedFactor <= foveatedConeFactor) {
- return false;
- }
-
- // Relax SSE based on how big the angle is between the tile and the edge of the foveated cone.
- const range = maximumFovatedFactor - foveatedConeFactor;
- const normalizedFoveatedFactor = CesiumMath.clamp(
- (tile._foveatedFactor - foveatedConeFactor) / range,
- 0.0,
- 1.0,
- );
- const sseRelaxation = tileset.foveatedInterpolationCallback(
- tileset.foveatedMinimumScreenSpaceErrorRelaxation,
- tileset.memoryAdjustedScreenSpaceError,
- normalizedFoveatedFactor,
- );
- const sse =
- tile._screenSpaceError === 0.0 && defined(tile.parent)
- ? tile.parent._screenSpaceError * 0.5
- : tile._screenSpaceError;
-
- return tileset.memoryAdjustedScreenSpaceError - sseRelaxation <= sse;
- }
-
- const scratchJulianDate = new JulianDate();
-
- /**
- * Get the tile's screen space error.
- *
- * @private
- * @param {FrameState} frameState
- * @param {boolean} useParentGeometricError
- * @param {number} progressiveResolutionHeightFraction
- */
- Cesium3DTile.prototype.getScreenSpaceError = function (
- frameState,
- useParentGeometricError,
- progressiveResolutionHeightFraction,
- ) {
- const tileset = this._tileset;
- const heightFraction = progressiveResolutionHeightFraction ?? 1.0;
- const parentGeometricError = defined(this.parent)
- ? this.parent.geometricError
- : tileset._scaledGeometricError;
- const geometricError = useParentGeometricError
- ? parentGeometricError
- : this.geometricError;
- if (geometricError === 0.0) {
- // Leaf tiles do not have any error so save the computation
- return 0.0;
- }
- const { camera, context } = frameState;
- let frustum = camera.frustum;
- const width = context.drawingBufferWidth;
- const height = context.drawingBufferHeight * heightFraction;
- let error;
- if (
- frameState.mode === SceneMode.SCENE2D ||
- frustum instanceof OrthographicFrustum
- ) {
- const offCenterFrustum = frustum.offCenterFrustum;
- if (defined(offCenterFrustum)) {
- frustum = offCenterFrustum;
- }
- const pixelSize =
- Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) /
- Math.max(width, height);
- error = geometricError / pixelSize;
- } else {
- // Avoid divide by zero when viewer is inside the tile
- const distance = Math.max(this._distanceToCamera, CesiumMath.EPSILON7);
- const sseDenominator = frustum.sseDenominator;
- error = (geometricError * height) / (distance * sseDenominator);
- if (tileset.dynamicScreenSpaceError) {
- const density = tileset._dynamicScreenSpaceErrorComputedDensity;
- const factor = tileset.dynamicScreenSpaceErrorFactor;
- const dynamicError = CesiumMath.fog(distance, density) * factor;
- error -= dynamicError;
- }
- }
-
- error /= frameState.pixelRatio;
-
- return error;
- };
-
- /**
- * @private
- * @param {Cesium3DTileset} tileset
- * @param {Cesium3DTile} tile
- * @returns {boolean}
- */
- function isPriorityProgressiveResolution(tileset, tile) {
- if (
- tileset.progressiveResolutionHeightFraction <= 0.0 ||
- tileset.progressiveResolutionHeightFraction > 0.5
- ) {
- return false;
- }
-
- const maximumScreenSpaceError = tileset.memoryAdjustedScreenSpaceError;
- let isProgressiveResolutionTile =
- tile._screenSpaceErrorProgressiveResolution > maximumScreenSpaceError; // Mark non-SSE leaves
- tile._priorityProgressiveResolutionScreenSpaceErrorLeaf = false; // Needed for skipLOD
- const parent = tile.parent;
- const tilePasses =
- tile._screenSpaceErrorProgressiveResolution <= maximumScreenSpaceError;
- const parentFails =
- defined(parent) &&
- parent._screenSpaceErrorProgressiveResolution > maximumScreenSpaceError;
- if (tilePasses && parentFails) {
- // A progressive resolution SSE leaf, promote its priority as well
- tile._priorityProgressiveResolutionScreenSpaceErrorLeaf = true;
- isProgressiveResolutionTile = true;
- }
- return isProgressiveResolutionTile;
- }
-
- /**
- * @private
- * @param {Cesium3DTileset} tileset
- * @param {Cesium3DTile} tile
- * @returns {number}
- */
- function getPriorityReverseScreenSpaceError(tileset, tile) {
- const parent = tile.parent;
- const useParentScreenSpaceError =
- defined(parent) &&
- (!tileset.isSkippingLevelOfDetail ||
- tile._screenSpaceError === 0.0 ||
- parent.hasTilesetContent ||
- parent.hasImplicitContent);
- const screenSpaceError = useParentScreenSpaceError
- ? parent._screenSpaceError
- : tile._screenSpaceError;
- return tileset.root._screenSpaceError - screenSpaceError;
- }
-
- /**
- * Update the tile's visibility.
- *
- * @private
- * @param {FrameState} frameState
- */
- Cesium3DTile.prototype.updateVisibility = function (frameState) {
- const { parent, tileset } = this;
- if (this._updatedVisibilityFrame === tileset._updatedVisibilityFrame) {
- // The tile has already been updated for this frame
- return;
- }
-
- const parentTransform = defined(parent)
- ? parent.computedTransform
- : tileset.modelMatrix;
- const parentVisibilityPlaneMask = defined(parent)
- ? parent._visibilityPlaneMask
- : CullingVolume.MASK_INDETERMINATE;
- this.updateTransform(parentTransform, frameState);
- this._distanceToCamera = this.distanceToTile(frameState);
- this._centerZDepth = this.distanceToTileCenter(frameState);
- this._screenSpaceError = this.getScreenSpaceError(frameState, false);
- this._screenSpaceErrorProgressiveResolution = this.getScreenSpaceError(
- frameState,
- false,
- tileset.progressiveResolutionHeightFraction,
- );
- this._visibilityPlaneMask = this.visibility(
- frameState,
- parentVisibilityPlaneMask,
- ); // Use parent's plane mask to speed up visibility test
- this._visible = this._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
- this._inRequestVolume = this.insideViewerRequestVolume(frameState);
- this._priorityReverseScreenSpaceError = getPriorityReverseScreenSpaceError(
- tileset,
- this,
- );
- this._priorityProgressiveResolution = isPriorityProgressiveResolution(
- tileset,
- this,
- );
- this.priorityDeferred = isPriorityDeferred(this, frameState);
-
- this._updatedVisibilityFrame = tileset._updatedVisibilityFrame;
- };
-
- /**
- * Update whether the tile has expired.
- *
- * @private
- */
- Cesium3DTile.prototype.updateExpiration = function () {
- if (
- defined(this.expireDate) &&
- this.contentReady &&
- !this.hasEmptyContent &&
- !this.hasMultipleContents
- ) {
- const now = JulianDate.now(scratchJulianDate);
- if (JulianDate.lessThan(this.expireDate, now)) {
- this._contentState = Cesium3DTileContentState.EXPIRED;
- this._expiredContent = this._content;
- }
- }
- };
-
- /**
- * @private
- * @param {Cesium3DTile} tile
- */
- function updateExpireDate(tile) {
- if (!defined(tile.expireDuration)) {
- return;
- }
- const expireDurationDate = JulianDate.now(scratchJulianDate);
- JulianDate.addSeconds(
- expireDurationDate,
- tile.expireDuration,
- expireDurationDate,
- );
-
- if (defined(tile.expireDate)) {
- if (JulianDate.lessThan(tile.expireDate, expireDurationDate)) {
- JulianDate.clone(expireDurationDate, tile.expireDate);
- }
- } else {
- tile.expireDate = JulianDate.clone(expireDurationDate);
- }
- }
-
- /**
- * @private
- * @param {Cesium3DTile} tile
- * @returns {Function}
- */
- function createPriorityFunction(tile) {
- return function () {
- return tile._priority;
- };
- }
-
- /**
- * Requests the tile's content.
- * <p>
- * The request may not be made if the Cesium Request Scheduler can't prioritize it.
- * </p>
- *
- * @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.
- * @private
- */
- Cesium3DTile.prototype.requestContent = function () {
- // empty contents don't require any HTTP requests
- if (this.hasEmptyContent) {
- return;
- }
-
- if (this.hasMultipleContents) {
- return requestMultipleContents(this);
- }
-
- return requestSingleContent(this);
- };
-
- /**
- * Multiple {@link Cesium3DTileContent}s are allowed within a single tile either through
- * the tile JSON (3D Tiles 1.1) or the <code>3DTILES_multiple_contents</code> extension.
- * Due to differences in request scheduling, this is handled separately.
- * <p>
- * This implementation of multiple contents does not
- * support tile expiry like requestSingleContent does. If this changes,
- * note that the resource.setQueryParameters() details must go inside {@link Multiple3DTileContent} since that is per-request.
- * </p>
- *
- * @private
- * @param {Cesium3DTile} tile
- * @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
- */
- function requestMultipleContents(tile) {
- let multipleContents = tile._content;
- const tileset = tile._tileset;
-
- if (!defined(multipleContents)) {
- // Create the content object immediately, it will handle scheduling
- // requests for inner contents.
- const contentsJson = hasExtension(tile._header, "3DTILES_multiple_contents")
- ? tile._header.extensions["3DTILES_multiple_contents"]
- : tile._header;
-
- multipleContents = new Multiple3DTileContent(
- tileset,
- tile,
- tile._contentResource.clone(),
- contentsJson,
- );
- tile._content = multipleContents;
- }
-
- const promise = multipleContents.requestInnerContents();
-
- if (!defined(promise)) {
- // Request could not all be scheduled this frame
- return;
- }
-
- tile._contentState = Cesium3DTileContentState.LOADING;
- return promise
- .then((content) => {
- if (tile.isDestroyed()) {
- // Tile is unloaded before the content can process
- return;
- }
-
- // Tile was canceled, try again later
- if (!defined(content)) {
- return;
- }
-
- tile._contentState = Cesium3DTileContentState.PROCESSING;
- return multipleContents;
- })
- .catch((error) => {
- if (tile.isDestroyed()) {
- // Tile is unloaded before the content can process
- return;
- }
-
- tile._contentState = Cesium3DTileContentState.FAILED;
- throw error;
- });
- }
-
- async function processArrayBuffer(
- tile,
- tileset,
- request,
- expired,
- requestPromise,
- ) {
- const previousState = tile._contentState;
- tile._contentState = Cesium3DTileContentState.LOADING;
- ++tileset.statistics.numberOfPendingRequests;
-
- let arrayBuffer;
- try {
- arrayBuffer = await requestPromise;
- } catch (error) {
- --tileset.statistics.numberOfPendingRequests;
- if (tile.isDestroyed()) {
- // Tile is unloaded before the content can process
- return;
- }
-
- if (request.cancelled || request.state === RequestState.CANCELLED) {
- // Cancelled due to low priority - try again later.
- tile._contentState = previousState;
- ++tileset.statistics.numberOfAttemptedRequests;
- return;
- }
-
- if (isEmptyTile(tile, error)) {
- if (expired) {
- tile.expireDate = undefined;
- }
- tile._content = new Empty3DTileContent(tileset, tile);
- markTileAsEmptyContent(tile);
- tile._contentState = Cesium3DTileContentState.PROCESSING;
- return tile._content;
- }
-
- tile._contentState = Cesium3DTileContentState.FAILED;
- throw error;
- }
-
- if (tile.isDestroyed()) {
- --tileset.statistics.numberOfPendingRequests;
- // Tile is unloaded before the content can process
- return;
- }
-
- if (request.cancelled || request.state === RequestState.CANCELLED) {
- // Cancelled due to low priority - try again later.
- tile._contentState = previousState;
- --tileset.statistics.numberOfPendingRequests;
- ++tileset.statistics.numberOfAttemptedRequests;
- return;
- }
-
- try {
- const content = await makeContent(tile, arrayBuffer);
- --tileset.statistics.numberOfPendingRequests;
-
- if (tile.isDestroyed()) {
- // Tile is unloaded before the content can process
- return;
- }
-
- if (expired) {
- tile.expireDate = undefined;
- }
-
- tile._content = content;
- if (content instanceof Empty3DTileContent) {
- markTileAsEmptyContent(tile);
- }
- tile._contentState = Cesium3DTileContentState.PROCESSING;
-
- return content;
- } catch (error) {
- --tileset.statistics.numberOfPendingRequests;
- if (tile.isDestroyed()) {
- // Tile is unloaded before the content can process
- return;
- }
-
- tile._contentState = Cesium3DTileContentState.FAILED;
- throw error;
- }
- }
-
- /**
- * @private
- * @param {Cesium3DTile} tile
- * @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.
- */
- function requestSingleContent(tile) {
- // it is important to clone here. The fetchArrayBuffer() below uses
- // throttling, but other uses of the resources do not.
- const resource = tile._contentResource.clone();
- const expired = tile.contentExpired;
- if (expired) {
- // Append a query parameter of the tile expiration date to prevent caching
- resource.setQueryParameters({
- expired: tile.expireDate.toString(),
- });
- }
-
- const request = new Request({
- throttle: true,
- throttleByServer: true,
- type: RequestType.TILES3D,
- priorityFunction: createPriorityFunction(tile),
- serverKey: tile._serverKey,
- });
-
- tile._request = request;
- resource.request = request;
- const tileset = tile._tileset;
- const promise = resource.fetchArrayBuffer();
- if (!defined(promise)) {
- ++tileset.statistics.numberOfAttemptedRequests;
- return;
- }
-
- return processArrayBuffer(tile, tileset, request, expired, promise);
- }
-
- /**
- * Determines whether a tile load error should be interpreted as "no content"
- * (empty tile) rather than a true failure. A missing tile policy specifies
- * HTTP Status Codes to be interpreted as "no content", allowing tiles to be
- * statically hosted without generating and serving unnecessary content for
- * empty tiles.
- * @ignore
- */
- function isEmptyTile(tile, error) {
- const tileset = tile._tileset;
- const policy = tileset?._runtimeContentCodec?.missingTilePolicy;
- if (!defined(policy)) {
- return false;
- }
-
- if (!defined(error.statusCode)) {
- return false;
- }
- return policy.statusCodes.includes(error.statusCode);
- }
-
- function markTileAsEmptyContent(tile) {
- // These flags are mutable instance state initialized in the constructor.
- tile.hasEmptyContent = true;
- tile.hasRenderableContent = false;
- }
-
- Cesium3DTile._isEmptyTile = isEmptyTile;
-
- /**
- * Given a downloaded content payload, construct a {@link Cesium3DTileContent}.
- * <p>
- * This is only used for single contents.
- * </p>
- *
- * @param {Cesium3DTile} tile The tile
- * @param {ArrayBuffer} arrayBuffer The downloaded payload containing data for the content
- * @return {Promise<Cesium3DTileContent>} A content object
- * @private
- */
- async function makeContent(tile, arrayBuffer) {
- const tileset = tile._tileset;
- const codec = tileset?._runtimeContentCodec;
- if (defined(codec) && typeof codec.createContent === "function") {
- const content = await Promise.resolve(
- codec.createContent(tileset, tile, tile._contentResource, arrayBuffer),
- );
- if (tile.isDestroyed()) {
- return;
- }
- return content;
- }
-
- const preprocessed = preprocess3DTileContent(arrayBuffer);
-
- // Vector and Geometry tile rendering do not support the skip LOD optimization.
- tileset._disableSkipLevelOfDetail =
- tileset._disableSkipLevelOfDetail ||
- preprocessed.contentType === Cesium3DTileContentType.GEOMETRY ||
- preprocessed.contentType === Cesium3DTileContentType.VECTOR;
-
- if (
- preprocessed.contentType === Cesium3DTileContentType.IMPLICIT_SUBTREE ||
- preprocessed.contentType === Cesium3DTileContentType.IMPLICIT_SUBTREE_JSON
- ) {
- tile.hasImplicitContent = true;
- tile.hasRenderableContent = false;
- }
-
- if (preprocessed.contentType === Cesium3DTileContentType.EXTERNAL_TILESET) {
- tile.hasTilesetContent = true;
- tile.hasRenderableContent = false;
- }
-
- let content;
- const contentFactory = Cesium3DTileContentFactory[preprocessed.contentType];
- if (tile.isDestroyed()) {
- return;
- }
-
- if (defined(preprocessed.binaryPayload)) {
- content = await Promise.resolve(
- contentFactory(
- tileset,
- tile,
- tile._contentResource,
- preprocessed.binaryPayload.buffer,
- 0,
- ),
- );
- } else {
- // JSON formats
- content = await Promise.resolve(
- contentFactory(
- tileset,
- tile,
- tile._contentResource,
- preprocessed.jsonPayload,
- ),
- );
- }
-
- const contentHeader = tile._contentHeader;
-
- if (tile.hasImplicitContentMetadata) {
- const subtree = tile.implicitSubtree;
- const coordinates = tile.implicitCoordinates;
- content.metadata = subtree.getContentMetadataView(coordinates, 0);
- } else if (!tile.hasImplicitContent) {
- content.metadata = findContentMetadata(tileset, contentHeader);
- }
-
- const groupMetadata = findGroupMetadata(tileset, contentHeader);
- if (defined(groupMetadata)) {
- content.group = new Cesium3DContentGroup({
- metadata: groupMetadata,
- });
- }
-
- return content;
- }
-
- /**
- * Cancel requests for the tile's contents. This is called when the tile
- * goes out of view.
- *
- * @private
- */
- Cesium3DTile.prototype.cancelRequests = function () {
- if (this.hasMultipleContents) {
- this._content.cancelRequests();
- } else {
- this._request.cancel();
- }
- };
-
- /**
- * Unloads the tile's content.
- *
- * @private
- */
- Cesium3DTile.prototype.unloadContent = function () {
- if (!this.hasRenderableContent) {
- return;
- }
-
- this._content = this._content && this._content.destroy();
- this._contentState = Cesium3DTileContentState.UNLOADED;
-
- this.lastStyleTime = 0.0;
- this.clippingPlanesDirty = this._clippingPlanesState === 0;
- this._clippingPlanesState = 0;
- this.clippingPolygonsDirty = this._clippingPolygonsState === 0;
- this._clippingPolygonsState = 0;
-
- this._debugColorizeTiles = false;
-
- this._debugBoundingVolume =
- this._debugBoundingVolume && this._debugBoundingVolume.destroy();
- this._debugContentBoundingVolume =
- this._debugContentBoundingVolume &&
- this._debugContentBoundingVolume.destroy();
- this._debugViewerRequestVolume =
- this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy();
- };
-
- const scratchProjectedBoundingSphere = new BoundingSphere();
-
- /**
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- * @returns {TileBoundingVolume}
- */
- function getBoundingVolume(tile, frameState) {
- if (
- frameState.mode !== SceneMode.SCENE3D &&
- !defined(tile._boundingVolume2D)
- ) {
- const boundingSphere = tile._boundingVolume.boundingSphere;
- const sphere = BoundingSphere.projectTo2D(
- boundingSphere,
- frameState.mapProjection,
- scratchProjectedBoundingSphere,
- );
- tile._boundingVolume2D = new TileBoundingSphere(
- sphere.center,
- sphere.radius,
- );
- }
-
- return frameState.mode !== SceneMode.SCENE3D
- ? tile._boundingVolume2D
- : tile._boundingVolume;
- }
-
- /**
- * @private
- * @param {Cesium3DTile} tile
- * @param {FrameState} frameState
- * @returns {TileBoundingVolume}
- */
- function getContentBoundingVolume(tile, frameState) {
- if (
- frameState.mode !== SceneMode.SCENE3D &&
- !defined(tile._contentBoundingVolume2D)
- ) {
- const boundingSphere = tile._contentBoundingVolume.boundingSphere;
- const sphere = BoundingSphere.projectTo2D(
- boundingSphere,
- frameState.mapProjection,
- scratchProjectedBoundingSphere,
- );
- tile._contentBoundingVolume2D = new TileBoundingSphere(
- sphere.center,
- sphere.radius,
- );
- }
- return frameState.mode !== SceneMode.SCENE3D
- ? tile._contentBoundingVolume2D
- : tile._contentBoundingVolume;
- }
-
- /**
- * Determines whether the tile's bounding volume intersects the culling volume.
- *
- * @param {FrameState} frameState The frame state.
- * @param {number} parentVisibilityPlaneMask The parent's plane mask to speed up the visibility check.
- * @returns {number} A plane mask as described above in {@link CullingVolume#computeVisibilityWithPlaneMask}.
- *
- * @private
- */
- Cesium3DTile.prototype.visibility = function (
- frameState,
- parentVisibilityPlaneMask,
- ) {
- const cullingVolume = frameState.cullingVolume;
- const boundingVolume = getBoundingVolume(this, frameState);
-
- const tileset = this._tileset;
- const clippingPlanes = tileset.clippingPlanes;
- if (defined(clippingPlanes) && clippingPlanes.enabled) {
- const intersection = clippingPlanes.computeIntersectionWithBoundingVolume(
- boundingVolume,
- tileset.clippingPlanesOriginMatrix,
- );
- this._isClipped = intersection !== Intersect.INSIDE;
- if (intersection === Intersect.OUTSIDE) {
- return CullingVolume.MASK_OUTSIDE;
- }
- }
-
- const clippingPolygons = tileset.clippingPolygons;
- if (defined(clippingPolygons) && clippingPolygons.enabled) {
- const intersection =
- clippingPolygons.computeIntersectionWithBoundingVolume(boundingVolume);
-
- this._isClippedByPolygon = intersection !== Intersect.OUTSIDE;
- // Polygon clipping intersections are determined by outer rectangles, therefore we cannot
- // preemptively determine if a tile is completely clipped or not here.
- }
-
- return cullingVolume.computeVisibilityWithPlaneMask(
- boundingVolume,
- parentVisibilityPlaneMask,
- );
- };
-
- /**
- * Assuming the tile's bounding volume intersects the culling volume, determines
- * whether the tile's content's bounding volume intersects the culling volume.
- *
- * @param {FrameState} frameState The frame state.
- * @returns {Intersect} The result of the intersection: the tile's content is completely outside, completely inside, or intersecting the culling volume.
- *
- * @private
- */
- Cesium3DTile.prototype.contentVisibility = function (frameState) {
- // Assumes the tile's bounding volume intersects the culling volume already, so
- // just return Intersect.INSIDE if there is no content bounding volume.
- if (!defined(this._contentBoundingVolume)) {
- return Intersect.INSIDE;
- }
-
- if (this._visibilityPlaneMask === CullingVolume.MASK_INSIDE) {
- // The tile's bounding volume is completely inside the culling volume so
- // the content bounding volume must also be inside.
- return Intersect.INSIDE;
- }
-
- // PERFORMANCE_IDEA: is it possible to burn less CPU on this test since we know the
- // tile's (not the content's) bounding volume intersects the culling volume?
- const cullingVolume = frameState.cullingVolume;
- const boundingVolume = getContentBoundingVolume(this, frameState);
-
- const tileset = this._tileset;
- const clippingPlanes = tileset.clippingPlanes;
- if (defined(clippingPlanes) && clippingPlanes.enabled) {
- const intersection = clippingPlanes.computeIntersectionWithBoundingVolume(
- boundingVolume,
- tileset.clippingPlanesOriginMatrix,
- );
- this._isClipped = intersection !== Intersect.INSIDE;
- if (intersection === Intersect.OUTSIDE) {
- return Intersect.OUTSIDE;
- }
- }
-
- const clippingPolygons = tileset.clippingPolygons;
- if (defined(clippingPolygons) && clippingPolygons.enabled) {
- const intersection =
- clippingPolygons.computeIntersectionWithBoundingVolume(boundingVolume);
- this._isClippedByPolygon = intersection !== Intersect.OUTSIDE;
- if (intersection === Intersect.INSIDE) {
- return Intersect.OUTSIDE;
- }
- }
-
- return cullingVolume.computeVisibility(boundingVolume);
- };
-
- /**
- * Computes the (potentially approximate) distance from the closest point of the tile's bounding volume to the camera.
- *
- * @param {FrameState} frameState The frame state.
- * @returns {number} The distance, in meters, or zero if the camera is inside the bounding volume.
- *
- * @private
- */
- Cesium3DTile.prototype.distanceToTile = function (frameState) {
- const boundingVolume = getBoundingVolume(this, frameState);
- return boundingVolume.distanceToCamera(frameState);
- };
-
- const scratchToTileCenter = new Cartesian3();
-
- /**
- * Computes the distance from the center of the tile's bounding volume to the camera's plane defined by its position and view direction.
- *
- * @param {FrameState} frameState The frame state.
- * @returns {number} The distance, in meters.
- *
- * @private
- */
- Cesium3DTile.prototype.distanceToTileCenter = function (frameState) {
- const tileBoundingVolume = getBoundingVolume(this, frameState);
- const boundingVolume = tileBoundingVolume.boundingVolume; // Gets the underlying OrientedBoundingBox or BoundingSphere
- const toCenter = Cartesian3.subtract(
- boundingVolume.center,
- frameState.camera.positionWC,
- scratchToTileCenter,
- );
- return Cartesian3.dot(frameState.camera.directionWC, toCenter);
- };
-
- /**
- * Checks if the camera is inside the viewer request volume.
- *
- * @param {FrameState} frameState The frame state.
- * @returns {boolean} Whether the camera is inside the volume.
- *
- * @private
- */
- Cesium3DTile.prototype.insideViewerRequestVolume = function (frameState) {
- const viewerRequestVolume = this._viewerRequestVolume;
- return (
- !defined(viewerRequestVolume) ||
- viewerRequestVolume.distanceToCamera(frameState) === 0.0
- );
- };
-
- const scratchMatrix = new Matrix3();
- const scratchScale = new Cartesian3();
- const scratchHalfAxes = new Matrix3();
- const scratchCenter = new Cartesian3();
- const scratchRectangle = new Rectangle();
- const scratchOrientedBoundingBox = new OrientedBoundingBox();
- const scratchTransform = new Matrix4();
-
- /**
- * @private
- * @param {Array} box An array of 12 numbers that define an oriented bounding box
- * @param {Matrix4} transform
- * @param {TileBoundingVolume} [result]
- * @returns {TileOrientedBoundingBox}
- */
- function createBox(box, transform, result) {
- let center = Cartesian3.fromElements(box[0], box[1], box[2], scratchCenter);
- let halfAxes = Matrix3.fromArray(box, 3, scratchHalfAxes);
-
- // Find the transformed center and halfAxes
- center = Matrix4.multiplyByPoint(transform, center, center);
- const rotationScale = Matrix4.getMatrix3(transform, scratchMatrix);
- halfAxes = Matrix3.multiply(rotationScale, halfAxes, halfAxes);
-
- if (defined(result)) {
- result.update(center, halfAxes);
- return result;
- }
- return new TileOrientedBoundingBox(center, halfAxes);
- }
-
- /**
- * @private
- * @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]
- * @param {Matrix4} transform
- * @param {Matrix4} initialTransform
- * @param {TileOrientedBoundingBox} [result]
- * @returns {TileOrientedBoundingBox}
- */
- function createBoxFromTransformedRegion(
- region,
- transform,
- initialTransform,
- result,
- ) {
- const rectangle = Rectangle.unpack(region, 0, scratchRectangle);
- const minimumHeight = region[4];
- const maximumHeight = region[5];
-
- const orientedBoundingBox = OrientedBoundingBox.fromRectangle(
- rectangle,
- minimumHeight,
- maximumHeight,
- Ellipsoid.WGS84,
- scratchOrientedBoundingBox,
- );
- let center = orientedBoundingBox.center;
- let halfAxes = orientedBoundingBox.halfAxes;
-
- // A region bounding volume is not transformed by the transform in the tileset JSON,
- // but may be transformed by additional transforms applied in Cesium.
- // This is why the transform is calculated as the difference between the initial transform and the current transform.
- transform = Matrix4.multiplyTransformation(
- transform,
- Matrix4.inverseTransformation(initialTransform, scratchTransform),
- scratchTransform,
- );
- center = Matrix4.multiplyByPoint(transform, center, center);
- const rotationScale = Matrix4.getMatrix3(transform, scratchMatrix);
- halfAxes = Matrix3.multiply(rotationScale, halfAxes, halfAxes);
-
- if (defined(result) && result instanceof TileOrientedBoundingBox) {
- result.update(center, halfAxes);
- return result;
- }
-
- return new TileOrientedBoundingBox(center, halfAxes);
- }
-
- /**
- * Creates a TileBoundingVolume from the given region and transform
- * information.
- *
- * This may either be a TileBoundingRegion or a TileOrientedBoundingBox.
- *
- * If the given transform is the initial transform, then this will return
- * a TileBoundingRegion. This will either be the given result parameter,
- * or a new TileBoundingRegion, if the given result parameter was not
- * a TileBoundingRegion.
- *
- * If the given transform deviates from the initial transform, then this
- * will return a TileOrientedBoundingBox that was computed by applying
- * the given transform to the given region. This will either be the
- * given result parameter, or a new TileOrientedBoundingBox, if the given
- * result parameter was not a TileOrientedBoundingBox
- *
- * @private
- * @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]
- * @param {Matrix4} transform The current computedTransform of the tile,
- * which includes the parent transform (which, in turn, includes the
- * modelMatrix of the containing tileset)
- * @param {Matrix4} initialTransform The initial transform of the tile,
- * before any changes to the modelMatrix of the containing tileset
- * @param {TileBoundingVolume} [result] An optional result.
- * @returns {TileBoundingVolume} The resulting bounding volume
- */
- function createRegion(region, transform, initialTransform, result) {
- if (
- !Matrix4.equalsEpsilon(transform, initialTransform, CesiumMath.EPSILON8)
- ) {
- if (result instanceof TileOrientedBoundingBox) {
- return createBoxFromTransformedRegion(
- region,
- transform,
- initialTransform,
- result,
- );
- }
- return createBoxFromTransformedRegion(
- region,
- transform,
- initialTransform,
- undefined,
- );
- }
-
- const rectangleRegion = Rectangle.unpack(region, 0, scratchRectangle);
-
- if (result instanceof TileBoundingRegion) {
- result.rectangle = Rectangle.clone(rectangleRegion, result.rectangle);
- result.minimumHeight = region[4];
- result.maximumHeight = region[5];
- // The TileBoundingRegion was already constructed with the default
- // WGS84 ellipsoid, so keep it consistent when updating.
- result.computeBoundingVolumes(Ellipsoid.WGS84);
- return result;
- }
-
- return new TileBoundingRegion({
- rectangle: rectangleRegion,
- minimumHeight: region[4],
- maximumHeight: region[5],
- });
- }
-
- /**
- * @private
- * @param {Array} sphere An array of four numbers that define a bounding sphere
- * @param {Matrix4} transform
- * @param {TileBoundingVolume} [result]
- * @returns {TileBoundingSphere}
- */
- function createSphere(sphere, transform, result) {
- let center = Cartesian3.fromElements(
- sphere[0],
- sphere[1],
- sphere[2],
- scratchCenter,
- );
- let radius = sphere[3];
-
- // Find the transformed center and radius
- center = Matrix4.multiplyByPoint(transform, center, center);
- const scale = Matrix4.getScale(transform, scratchScale);
- const uniformScale = Cartesian3.maximumComponent(scale);
- radius *= uniformScale;
-
- if (defined(result)) {
- result.update(center, radius);
- return result;
- }
- return new TileBoundingSphere(center, radius);
- }
-
- /**
- * Create a bounding volume from the tile's bounding volume header.
- *
- * @param {object} boundingVolumeHeader The tile's bounding volume header.
- * @param {Matrix4} transform The transform to apply to the bounding volume.
- * @param {TileBoundingVolume} [result] The object onto which to store the result.
- *
- * @returns {TileBoundingVolume} The modified result parameter or a new TileBoundingVolume instance if none was provided.
- *
- * @private
- */
- Cesium3DTile.prototype.createBoundingVolume = function (
- boundingVolumeHeader,
- transform,
- result,
- ) {
- // if explicit tile metadata includes TILE_BOUNDING_BOX, TILE_BOUNDING_REGION,
- // or TILE_BOUNDING_SPHERE, override tile.boundingVolume.
- const tileMetadata = this.metadata;
- let metadataBoundingVolumeHeader;
- if (defined(tileMetadata)) {
- metadataBoundingVolumeHeader =
- BoundingVolumeSemantics.parseBoundingVolumeSemantic("TILE", tileMetadata);
- }
- if (defined(metadataBoundingVolumeHeader)) {
- boundingVolumeHeader = metadataBoundingVolumeHeader;
- }
-
- if (!defined(boundingVolumeHeader)) {
- throw new RuntimeError("boundingVolume must be defined");
- }
-
- if (hasExtension(boundingVolumeHeader, "3DTILES_bounding_volume_S2")) {
- return new TileBoundingS2Cell(
- boundingVolumeHeader.extensions["3DTILES_bounding_volume_S2"],
- );
- }
-
- const { box, region, sphere } = boundingVolumeHeader;
- if (defined(box)) {
- const tileOrientedBoundingBox = createBox(box, transform, result);
- if (this._verticalExaggeration !== 1.0) {
- exaggerateBoundingBox(
- tileOrientedBoundingBox,
- this._verticalExaggeration,
- this._verticalExaggerationRelativeHeight,
- );
- }
- return tileOrientedBoundingBox;
- }
- if (defined(region)) {
- const tileBoundingVolume = createRegion(
- region,
- transform,
- this._initialTransform,
- result,
- );
- if (this._verticalExaggeration === 1.0) {
- return tileBoundingVolume;
- }
- if (tileBoundingVolume instanceof TileOrientedBoundingBox) {
- exaggerateBoundingBox(
- tileBoundingVolume,
- this._verticalExaggeration,
- this._verticalExaggerationRelativeHeight,
- );
- } else {
- tileBoundingVolume.minimumHeight = VerticalExaggeration.getHeight(
- tileBoundingVolume.minimumHeight,
- this._verticalExaggeration,
- this._verticalExaggerationRelativeHeight,
- );
- tileBoundingVolume.maximumHeight = VerticalExaggeration.getHeight(
- tileBoundingVolume.maximumHeight,
- this._verticalExaggeration,
- this._verticalExaggerationRelativeHeight,
- );
- tileBoundingVolume.computeBoundingVolumes(Ellipsoid.WGS84);
- }
- return tileBoundingVolume;
- }
- if (defined(sphere)) {
- const tileBoundingSphere = createSphere(sphere, transform, result);
- if (this._verticalExaggeration !== 1.0) {
- const exaggeratedCenter = VerticalExaggeration.getPosition(
- tileBoundingSphere.center,
- Ellipsoid.WGS84,
- this._verticalExaggeration,
- this._verticalExaggerationRelativeHeight,
- scratchCenter,
- );
- const exaggeratedRadius =
- tileBoundingSphere.radius * this._verticalExaggeration;
- tileBoundingSphere.update(exaggeratedCenter, exaggeratedRadius);
- }
- return tileBoundingSphere;
- }
- throw new RuntimeError(
- "boundingVolume must contain a sphere, region, or box",
- );
- };
-
- const scratchExaggeratedCorners = Cartesian3.unpackArray(
- new Array(8 * 3).fill(0),
- );
-
- /**
- * Exaggerates the bounding box of a tile based on the provided exaggeration factors.
- *
- * @private
- * @param {TileOrientedBoundingBox} tileOrientedBoundingBox - The oriented bounding box of the tile.
- * @param {number} exaggeration - The exaggeration factor to apply to the tile's bounding box.
- * @param {number} exaggerationRelativeHeight - The height relative to which exaggeration will be applied.
- */
- function exaggerateBoundingBox(
- tileOrientedBoundingBox,
- exaggeration,
- exaggerationRelativeHeight,
- ) {
- const exaggeratedCorners = tileOrientedBoundingBox.boundingVolume
- .computeCorners(scratchExaggeratedCorners)
- .map((corner) =>
- VerticalExaggeration.getPosition(
- corner,
- Ellipsoid.WGS84,
- exaggeration,
- exaggerationRelativeHeight,
- corner,
- ),
- );
- const exaggeratedBox = OrientedBoundingBox.fromPoints(
- exaggeratedCorners,
- scratchOrientedBoundingBox,
- );
- tileOrientedBoundingBox.update(
- exaggeratedBox.center,
- exaggeratedBox.halfAxes,
- );
- }
-
- /**
- * Update the tile's transform. The transform is applied to the tile's bounding volumes.
- *
- * @private
- * @param {Matrix4} parentTransform
- * @param {FrameState} [frameState]
- */
- Cesium3DTile.prototype.updateTransform = function (
- parentTransform,
- frameState,
- ) {
- parentTransform = parentTransform ?? Matrix4.IDENTITY;
- const computedTransform = Matrix4.multiplyTransformation(
- parentTransform,
- this.transform,
- scratchTransform,
- );
- const transformChanged = !Matrix4.equals(
- computedTransform,
- this.computedTransform,
- );
- const exaggerationChanged =
- defined(frameState) &&
- (this._verticalExaggeration !== frameState.verticalExaggeration ||
- this._verticalExaggerationRelativeHeight !==
- frameState.verticalExaggerationRelativeHeight);
-
- if (!transformChanged && !exaggerationChanged) {
- return;
- }
- if (transformChanged) {
- Matrix4.clone(computedTransform, this.computedTransform);
- }
- if (exaggerationChanged) {
- this._verticalExaggeration = frameState.verticalExaggeration;
- this._verticalExaggerationRelativeHeight =
- frameState.verticalExaggerationRelativeHeight;
- }
-
- // Update the bounding volumes
- const header = this._header;
- const contentHeader = this._contentHeader;
- this._boundingVolume = this.createBoundingVolume(
- header.boundingVolume,
- this.computedTransform,
- this._boundingVolume,
- );
- if (defined(this._contentBoundingVolume)) {
- this._contentBoundingVolume = this.createBoundingVolume(
- contentHeader.boundingVolume,
- this.computedTransform,
- this._contentBoundingVolume,
- );
- }
- if (defined(this._viewerRequestVolume)) {
- this._viewerRequestVolume = this.createBoundingVolume(
- header.viewerRequestVolume,
- this.computedTransform,
- this._viewerRequestVolume,
- );
- }
-
- this.updateGeometricErrorScale();
-
- // Destroy the debug bounding volumes. They will be generated fresh.
- this._debugBoundingVolume =
- this._debugBoundingVolume && this._debugBoundingVolume.destroy();
- this._debugContentBoundingVolume =
- this._debugContentBoundingVolume &&
- this._debugContentBoundingVolume.destroy();
- this._debugViewerRequestVolume =
- this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy();
- };
-
- Cesium3DTile.prototype.updateGeometricErrorScale = function () {
- const scale = Matrix4.getScale(this.computedTransform, scratchScale);
- const uniformScale = Cartesian3.maximumComponent(scale);
- this.geometricError = this._geometricError * uniformScale;
-
- if (!defined(this.parent)) {
- // Update the tileset's geometric error
- const tileset = this._tileset;
- tileset._scaledGeometricError = tileset._geometricError * uniformScale;
- }
- };
-
- /**
- * @private
- * @param {Cesium3DTile} tile
- * @param {Cesium3DTileset} tileset
- * @param {FrameState} frameState
- * @param {object} passOptions
- */
- function applyDebugSettings(tile, tileset, frameState, passOptions) {
- if (!passOptions.isRender) {
- return;
- }
-
- const hasContentBoundingVolume =
- defined(tile._contentHeader) && defined(tile._contentHeader.boundingVolume);
-
- const showVolume =
- tileset.debugShowBoundingVolume ||
- (tileset.debugShowContentBoundingVolume && !hasContentBoundingVolume);
- if (showVolume) {
- let color;
- if (!tile._finalResolution) {
- color = Color.YELLOW;
- } else if (!tile.hasRenderableContent) {
- color = Color.DARKGRAY;
- } else {
- color = Color.WHITE;
- }
- if (!defined(tile._debugBoundingVolume)) {
- tile._debugBoundingVolume = tile._boundingVolume.createDebugVolume(color);
- }
- tile._debugBoundingVolume.update(frameState);
- const attributes =
- tile._debugBoundingVolume.getGeometryInstanceAttributes("outline");
- attributes.color = ColorGeometryInstanceAttribute.toValue(
- color,
- attributes.color,
- );
- } else if (!showVolume && defined(tile._debugBoundingVolume)) {
- tile._debugBoundingVolume = tile._debugBoundingVolume.destroy();
- }
-
- if (tileset.debugShowContentBoundingVolume && hasContentBoundingVolume) {
- if (!defined(tile._debugContentBoundingVolume)) {
- tile._debugContentBoundingVolume =
- tile._contentBoundingVolume.createDebugVolume(Color.BLUE);
- }
- tile._debugContentBoundingVolume.update(frameState);
- } else if (
- !tileset.debugShowContentBoundingVolume &&
- defined(tile._debugContentBoundingVolume)
- ) {
- tile._debugContentBoundingVolume =
- tile._debugContentBoundingVolume.destroy();
- }
-
- if (
- tileset.debugShowViewerRequestVolume &&
- defined(tile._viewerRequestVolume)
- ) {
- if (!defined(tile._debugViewerRequestVolume)) {
- tile._debugViewerRequestVolume =
- tile._viewerRequestVolume.createDebugVolume(Color.YELLOW);
- }
- tile._debugViewerRequestVolume.update(frameState);
- } else if (
- !tileset.debugShowViewerRequestVolume &&
- defined(tile._debugViewerRequestVolume)
- ) {
- tile._debugViewerRequestVolume = tile._debugViewerRequestVolume.destroy();
- }
-
- const debugColorizeTilesOn =
- (tileset.debugColorizeTiles && !tile._debugColorizeTiles) ||
- defined(tileset._heatmap.tilePropertyName);
- const debugColorizeTilesOff =
- !tileset.debugColorizeTiles && tile._debugColorizeTiles;
-
- if (debugColorizeTilesOn) {
- tileset._heatmap.colorize(tile, frameState); // Skipped if tileset._heatmap.tilePropertyName is undefined
- tile._debugColorizeTiles = true;
- tile.color = tile._debugColor;
- } else if (debugColorizeTilesOff) {
- tile._debugColorizeTiles = false;
- tile.color = Color.WHITE;
- }
-
- if (tile._colorDirty) {
- tile._colorDirty = false;
- tile._content.applyDebugSettings(true, tile._color);
- }
-
- if (debugColorizeTilesOff) {
- tileset.makeStyleDirty(); // Re-apply style now that colorize is switched off
- }
- }
-
- /**
- * @private
- * @param {Cesium3DTile} tile
- * @param {Cesium3DTileset} tileset
- * @param {FrameState} frameState
- */
- function updateContent(tile, tileset, frameState) {
- const expiredContent = tile._expiredContent;
-
- // expired content is not supported for multiple contents
- if (!tile.hasMultipleContents && defined(expiredContent)) {
- if (!tile.contentReady) {
- // Render the expired content while the content loads
- try {
- expiredContent.update(tileset, frameState);
- } catch (error) {
- // Eat error for expired content
- }
- return;
- }
-
- // New content is ready, destroy expired content
- tile._expiredContent.destroy();
- tile._expiredContent = undefined;
- }
-
- if (!defined(tile.content)) {
- // Implicit placeholder tile
- return;
- }
-
- try {
- tile.content.update(tileset, frameState);
- } catch (error) {
- tile._contentState = Cesium3DTileContentState.FAILED;
- throw error;
- }
- }
-
- /**
- * Compute and compare ClippingPlanes state:
- * - enabled-ness - are clipping planes enabled? is this tile clipped?
- * - clipping plane count
- * - clipping function (union v. intersection)
-
- * @private
- * @param {Cesium3DTile} tile
- * @param {Cesium3DTileset} tileset
- */
- function updateClippingPlanes(tile, tileset) {
- const clippingPlanes = tileset.clippingPlanes;
- let currentClippingPlanesState = 0;
- if (defined(clippingPlanes) && tile._isClipped && clippingPlanes.enabled) {
- currentClippingPlanesState = clippingPlanes.clippingPlanesState;
- }
- // If clippingPlaneState for tile changed, mark clippingPlanesDirty so content can update
- if (currentClippingPlanesState !== tile._clippingPlanesState) {
- tile._clippingPlanesState = currentClippingPlanesState;
- tile.clippingPlanesDirty = true;
- }
- }
-
- /**
- * Compute and compare ClippingPolygons state:
- * - enabled-ness - are clipping polygons enabled? is this tile clipped?
- * - clipping polygon count & position count
- * - clipping function (inverse)
-
- * @private
- * @param {Cesium3DTile} tile
- * @param {Cesium3DTileset} tileset
- */
- function updateClippingPolygons(tile, tileset) {
- const clippingPolygons = tileset.clippingPolygons;
- let currentClippingPolygonsState = 0;
- if (
- defined(clippingPolygons) &&
- tile._isClippedByPolygon &&
- clippingPolygons.enabled
- ) {
- currentClippingPolygonsState = clippingPolygons.clippingPolygonsState;
- }
- // If clippingPolygonState for tile changed, mark clippingPolygonsDirty so content can update
- if (currentClippingPolygonsState !== tile._clippingPolygonsState) {
- tile._clippingPolygonsState = currentClippingPolygonsState;
- tile.clippingPolygonsDirty = true;
- }
- }
-
- /**
- * Get the draw commands needed to render this tile.
- *
- * @private
- * @param {Cesium3DTileset} tileset
- * @param {FrameState} frameState
- * @param {object} passOptions
- */
- Cesium3DTile.prototype.update = function (tileset, frameState, passOptions) {
- const { commandList } = frameState;
- const commandStart = commandList.length;
-
- updateClippingPlanes(this, tileset);
- updateClippingPolygons(this, tileset);
- applyDebugSettings(this, tileset, frameState, passOptions);
- updateContent(this, tileset, frameState);
-
- const commandEnd = commandList.length;
- this._commandsLength = commandEnd - commandStart;
-
- for (let i = commandStart; i < commandEnd; ++i) {
- const command = commandList[i];
- const translucent = command.pass === Pass.TRANSLUCENT;
- command.depthForTranslucentClassification = translucent;
- }
-
- this.clippingPlanesDirty = false; // reset after content update
- this.clippingPolygonsDirty = false;
- };
-
- const scratchCommandList = [];
-
- /**
- * Processes the tile's content, e.g., create WebGL resources, to move from the PROCESSING to READY state.
- *
- * @param {Cesium3DTileset} tileset The tileset containing this tile.
- * @param {FrameState} frameState The frame state.
- *
- * @private
- */
- Cesium3DTile.prototype.process = function (tileset, frameState) {
- if (!this.contentExpired && !this.contentReady && this._content.ready) {
- updateExpireDate(this);
-
- // Refresh style for expired content
- this._selectedFrame = 0;
- this.lastStyleTime = 0.0;
-
- JulianDate.now(this._loadTimestamp);
- this._contentState = Cesium3DTileContentState.READY;
-
- if (!this.hasTilesetContent && !this.hasImplicitContent) {
- // RESEARCH_IDEA: ability to unload tiles (without content) for an
- // external tileset when all the tiles are unloaded.
- tileset._statistics.incrementLoadCounts(this.content);
- ++tileset._statistics.numberOfTilesWithContentReady;
- ++tileset._statistics.numberOfLoadedTilesTotal;
-
- // Add to the tile cache. Previously expired tiles are already in the cache and won't get re-added.
- tileset._cache.add(this);
- }
- }
-
- const savedCommandList = frameState.commandList;
- frameState.commandList = scratchCommandList;
-
- try {
- this._content.update(tileset, frameState);
- } catch (error) {
- this._contentState = Cesium3DTileContentState.FAILED;
- throw error;
- }
-
- scratchCommandList.length = 0;
- frameState.commandList = savedCommandList;
- };
-
- /**
- * @private
- * @param {number} normalizedValue
- * @param {number} numberOfDigits
- * @param {number} leftShift
- * @returns {number}
- */
- function isolateDigits(normalizedValue, numberOfDigits, leftShift) {
- const scaled = normalizedValue * Math.pow(10, numberOfDigits);
- const integer = parseInt(scaled);
- return integer * Math.pow(10, leftShift);
- }
-
- /**
- * @private
- * @param {number} value
- * @param {number} minimum
- * @param {number} maximum
- * @returns {number}
- */
- function priorityNormalizeAndClamp(value, minimum, maximum) {
- // Subtract epsilon since we only want decimal digits present in the output.
- return Math.max(
- CesiumMath.normalize(value, minimum, maximum) - CesiumMath.EPSILON7,
- 0.0,
- );
- }
-
- /**
- * Sets the priority of the tile based on distance and depth
- * @private
- */
- Cesium3DTile.prototype.updatePriority = function () {
- const tileset = this.tileset;
- const preferLeaves = tileset.preferLeaves;
- const minimumPriority = tileset._minimumPriority;
- const maximumPriority = tileset._maximumPriority;
-
- // Combine priority systems together by mapping them into a base 10 number where each priority controls a specific set of digits in the number.
- // 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.
- // For blending number priorities, normalize them to 0-1 and interpolate to get a combined 0-1 number, then proceed as normal.
- // Booleans can just be 0 or 10^leftshift.
- // 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.
- // Priority number format: preloadFlightDigits(1) | foveatedDeferDigits(1) | foveatedDigits(4) | preloadProgressiveResolutionDigits(1) | preferredSortingDigits(4) . depthDigits(the decimal digits)
- // Certain flags like preferLeaves will flip / turn off certain digits to get desired load order.
-
- // Setup leftShifts, digit counts, and scales (for booleans)
- const digitsForANumber = 4;
- const digitsForABoolean = 1;
-
- const preferredSortingLeftShift = 0;
- const preferredSortingDigitsCount = digitsForANumber;
-
- const foveatedLeftShift =
- preferredSortingLeftShift + preferredSortingDigitsCount;
- const foveatedDigitsCount = digitsForANumber;
-
- const preloadProgressiveResolutionLeftShift =
- foveatedLeftShift + foveatedDigitsCount;
- const preloadProgressiveResolutionDigitsCount = digitsForABoolean;
- const preloadProgressiveResolutionScale = Math.pow(
- 10,
- preloadProgressiveResolutionLeftShift,
- );
-
- const foveatedDeferLeftShift =
- preloadProgressiveResolutionLeftShift +
- preloadProgressiveResolutionDigitsCount;
- const foveatedDeferDigitsCount = digitsForABoolean;
- const foveatedDeferScale = Math.pow(10, foveatedDeferLeftShift);
-
- const preloadFlightLeftShift =
- foveatedDeferLeftShift + foveatedDeferDigitsCount;
- const preloadFlightScale = Math.pow(10, preloadFlightLeftShift);
-
- // Compute the digits for each priority
- let depthDigits = priorityNormalizeAndClamp(
- this._depth,
- minimumPriority.depth,
- maximumPriority.depth,
- );
- depthDigits = preferLeaves ? 1.0 - depthDigits : depthDigits;
-
- // Map 0-1 then convert to digit. Include a distance sort when doing non-skipLOD and replacement refinement, helps things like non-skipLOD photogrammetry
- const useDistance =
- !tileset.isSkippingLevelOfDetail &&
- this.refine === Cesium3DTileRefine.REPLACE;
- const normalizedPreferredSorting = useDistance
- ? priorityNormalizeAndClamp(
- this._priorityHolder._distanceToCamera,
- minimumPriority.distance,
- maximumPriority.distance,
- )
- : priorityNormalizeAndClamp(
- this._priorityReverseScreenSpaceError,
- minimumPriority.reverseScreenSpaceError,
- maximumPriority.reverseScreenSpaceError,
- );
- const preferredSortingDigits = isolateDigits(
- normalizedPreferredSorting,
- preferredSortingDigitsCount,
- preferredSortingLeftShift,
- );
-
- const preloadProgressiveResolutionDigits = this._priorityProgressiveResolution
- ? 0
- : preloadProgressiveResolutionScale;
-
- const normalizedFoveatedFactor = priorityNormalizeAndClamp(
- this._priorityHolder._foveatedFactor,
- minimumPriority.foveatedFactor,
- maximumPriority.foveatedFactor,
- );
- const foveatedDigits = isolateDigits(
- normalizedFoveatedFactor,
- foveatedDigitsCount,
- foveatedLeftShift,
- );
-
- const foveatedDeferDigits = this.priorityDeferred ? foveatedDeferScale : 0;
-
- const preloadFlightDigits =
- tileset._pass === Cesium3DTilePass.PRELOAD_FLIGHT ? 0 : preloadFlightScale;
-
- // Get the final base 10 number
- this._priority =
- depthDigits +
- preferredSortingDigits +
- preloadProgressiveResolutionDigits +
- foveatedDigits +
- foveatedDeferDigits +
- preloadFlightDigits;
- };
-
- /**
- * @private
- */
- Cesium3DTile.prototype.isDestroyed = function () {
- return false;
- };
-
- /**
- * @private
- */
- Cesium3DTile.prototype.destroy = function () {
- // For the interval between new content being requested and downloaded, expiredContent === content, so don't destroy twice
- this._content = this._content && this._content.destroy();
- this._expiredContent =
- this._expiredContent &&
- !this._expiredContent.isDestroyed() &&
- this._expiredContent.destroy();
- this._debugBoundingVolume =
- this._debugBoundingVolume && this._debugBoundingVolume.destroy();
- this._debugContentBoundingVolume =
- this._debugContentBoundingVolume &&
- this._debugContentBoundingVolume.destroy();
- this._debugViewerRequestVolume =
- this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy();
- return destroyObject(this);
- };
-
- export default Cesium3DTile;
|