import { Cartesian3, defined, destroyObject, DebugModelMatrixPrimitive, DeveloperError, PerformanceDisplay, Ray, Rectangle, ScreenSpaceEventHandler, ScreenSpaceEventType, TileCoordinatesImageryProvider, } from "@cesium/engine"; import knockout from "../ThirdParty/knockout.js"; import createCommand from "../createCommand.js"; function frustumStatisticsToString(statistics) { let str; if (defined(statistics)) { str = "Command Statistics"; const com = statistics.commandsInFrustums; for (const n in com) { if (com.hasOwnProperty(n)) { let num = parseInt(n, 10); let s; if (num === 7) { s = "1, 2 and 3"; } else { const f = []; for (let i = 2; i >= 0; i--) { const p = Math.pow(2, i); if (num >= p) { f.push(i + 1); num -= p; } } s = f.reverse().join(" and "); } str += `
    ${com[n]} in frustum ${s}`; } } str += `
Total: ${statistics.totalCommands}`; } return str; } function boundDepthFrustum(lower, upper, proposed) { let bounded = Math.min(proposed, upper); bounded = Math.max(bounded, lower); return bounded; } const scratchPickRay = new Ray(); const scratchPickCartesian = new Cartesian3(); /** * The view model for {@link CesiumInspector}. * @alias CesiumInspectorViewModel * @constructor * * @param {Scene} scene The scene instance to use. * @param {Element} performanceContainer The instance to use for performance container. */ function CesiumInspectorViewModel(scene, performanceContainer) { //>>includeStart('debug', pragmas.debug); if (!defined(scene)) { throw new DeveloperError("scene is required"); } if (!defined(performanceContainer)) { throw new DeveloperError("performanceContainer is required"); } //>>includeEnd('debug'); const that = this; const canvas = scene.canvas; const eventHandler = new ScreenSpaceEventHandler(canvas); this._eventHandler = eventHandler; this._scene = scene; this._canvas = canvas; this._primitive = undefined; this._tile = undefined; this._modelMatrixPrimitive = undefined; this._performanceDisplay = undefined; this._performanceContainer = performanceContainer; const globe = this._scene.globe; globe.depthTestAgainstTerrain = true; /** * Gets or sets the show frustums state. This property is observable. * @type {boolean} * @default false */ this.frustums = false; /** * Gets or sets the show frustum planes state. This property is observable. * @type {boolean} * @default false */ this.frustumPlanes = false; /** * Gets or sets the show performance display state. This property is observable. * @type {boolean} * @default false */ this.performance = false; /** * Gets or sets the shader cache text. This property is observable. * @type {string} * @default '' */ this.shaderCacheText = ""; /** * Gets or sets the show primitive bounding sphere state. This property is observable. * @type {boolean} * @default false */ this.primitiveBoundingSphere = false; /** * Gets or sets the show primitive reference frame state. This property is observable. * @type {boolean} * @default false */ this.primitiveReferenceFrame = false; /** * Gets or sets the filter primitive state. This property is observable. * @type {boolean} * @default false */ this.filterPrimitive = false; /** * Gets or sets the show tile bounding sphere state. This property is observable. * @type {boolean} * @default false */ this.tileBoundingSphere = false; /** * Gets or sets the filter tile state. This property is observable. * @type {boolean} * @default false */ this.filterTile = false; /** * Gets or sets the show wireframe state. This property is observable. * @type {boolean} * @default false */ this.wireframe = false; /** * Gets or sets the index of the depth frustum to display. This property is observable. * @type {number} * @default 1 */ this.depthFrustum = 1; this._numberOfFrustums = 1; /** * Gets or sets the suspend updates state. This property is observable. * @type {boolean} * @default false */ this.suspendUpdates = false; /** * Gets or sets the show tile coordinates state. This property is observable. * @type {boolean} * @default false */ this.tileCoordinates = false; /** * Gets or sets the frustum statistic text. This property is observable. * @type {string} * @default '' */ this.frustumStatisticText = false; /** * Gets or sets the selected tile information text. This property is observable. * @type {string} * @default '' */ this.tileText = ""; /** * Gets if a primitive has been selected. This property is observable. * @type {boolean} * @default false */ this.hasPickedPrimitive = false; /** * Gets if a tile has been selected. This property is observable * @type {boolean} * @default false */ this.hasPickedTile = false; /** * Gets if the picking primitive command is active. This property is observable. * @type {boolean} * @default false */ this.pickPrimitiveActive = false; /** * Gets if the picking tile command is active. This property is observable. * @type {boolean} * @default false */ this.pickTileActive = false; /** * Gets or sets if the cesium inspector drop down is visible. This property is observable. * @type {boolean} * @default true */ this.dropDownVisible = true; /** * Gets or sets if the general section is visible. This property is observable. * @type {boolean} * @default true */ this.generalVisible = true; /** * Gets or sets if the primitive section is visible. This property is observable. * @type {boolean} * @default false */ this.primitivesVisible = false; /** * Gets or sets if the terrain section is visible. This property is observable. * @type {boolean} * @default false */ this.terrainVisible = false; /** * Gets or sets the index of the depth frustum text. This property is observable. * @type {string} * @default '' */ this.depthFrustumText = ""; knockout.track(this, [ "frustums", "frustumPlanes", "performance", "shaderCacheText", "primitiveBoundingSphere", "primitiveReferenceFrame", "filterPrimitive", "tileBoundingSphere", "filterTile", "wireframe", "depthFrustum", "suspendUpdates", "tileCoordinates", "frustumStatisticText", "tileText", "hasPickedPrimitive", "hasPickedTile", "pickPrimitiveActive", "pickTileActive", "dropDownVisible", "generalVisible", "primitivesVisible", "terrainVisible", "depthFrustumText", ]); this._toggleDropDown = createCommand(function () { that.dropDownVisible = !that.dropDownVisible; }); this._toggleGeneral = createCommand(function () { that.generalVisible = !that.generalVisible; }); this._togglePrimitives = createCommand(function () { that.primitivesVisible = !that.primitivesVisible; }); this._toggleTerrain = createCommand(function () { that.terrainVisible = !that.terrainVisible; }); this._frustumsSubscription = knockout .getObservable(this, "frustums") .subscribe(function (val) { that._scene.debugShowFrustums = val; that._scene.requestRender(); }); this._frustumPlanesSubscription = knockout .getObservable(this, "frustumPlanes") .subscribe(function (val) { that._scene.debugShowFrustumPlanes = val; that._scene.requestRender(); }); this._performanceSubscription = knockout .getObservable(this, "performance") .subscribe(function (val) { if (val) { that._performanceDisplay = new PerformanceDisplay({ container: that._performanceContainer, }); } else { that._performanceContainer.innerHTML = ""; } }); this._showPrimitiveBoundingSphere = createCommand(function () { that._primitive.debugShowBoundingVolume = that.primitiveBoundingSphere; that._scene.requestRender(); return true; }); this._primitiveBoundingSphereSubscription = knockout .getObservable(this, "primitiveBoundingSphere") .subscribe(function () { that._showPrimitiveBoundingSphere(); }); this._showPrimitiveReferenceFrame = createCommand(function () { if (that.primitiveReferenceFrame) { const modelMatrix = that._primitive.modelMatrix; that._modelMatrixPrimitive = new DebugModelMatrixPrimitive({ modelMatrix: modelMatrix, }); that._scene.primitives.add(that._modelMatrixPrimitive); } else if (defined(that._modelMatrixPrimitive)) { that._scene.primitives.remove(that._modelMatrixPrimitive); that._modelMatrixPrimitive = undefined; } that._scene.requestRender(); return true; }); this._primitiveReferenceFrameSubscription = knockout .getObservable(this, "primitiveReferenceFrame") .subscribe(function () { that._showPrimitiveReferenceFrame(); }); this._doFilterPrimitive = createCommand(function () { if (that.filterPrimitive) { that._scene.debugCommandFilter = function (command) { if ( defined(that._modelMatrixPrimitive) && command.owner === that._modelMatrixPrimitive._primitive ) { return true; } else if (defined(that._primitive)) { return ( command.owner === that._primitive || command.owner === that._primitive._billboardCollection || command.owner.primitive === that._primitive ); } return false; }; } else { that._scene.debugCommandFilter = undefined; } return true; }); this._filterPrimitiveSubscription = knockout .getObservable(this, "filterPrimitive") .subscribe(function () { that._doFilterPrimitive(); that._scene.requestRender(); }); this._wireframeSubscription = knockout .getObservable(this, "wireframe") .subscribe(function (val) { globe._surface.tileProvider._debug.wireframe = val; that._scene.requestRender(); }); this._depthFrustumSubscription = knockout .getObservable(this, "depthFrustum") .subscribe(function (val) { that._scene.debugShowDepthFrustum = val; that._scene.requestRender(); }); this._incrementDepthFrustum = createCommand(function () { const next = that.depthFrustum + 1; that.depthFrustum = boundDepthFrustum(1, that._numberOfFrustums, next); that._scene.requestRender(); return true; }); this._decrementDepthFrustum = createCommand(function () { const next = that.depthFrustum - 1; that.depthFrustum = boundDepthFrustum(1, that._numberOfFrustums, next); that._scene.requestRender(); return true; }); this._suspendUpdatesSubscription = knockout .getObservable(this, "suspendUpdates") .subscribe(function (val) { globe._surface._debug.suspendLodUpdate = val; if (!val) { that.filterTile = false; } }); let tileBoundariesLayer; this._showTileCoordinates = createCommand(function () { if (that.tileCoordinates && !defined(tileBoundariesLayer)) { tileBoundariesLayer = scene.imageryLayers.addImageryProvider( new TileCoordinatesImageryProvider({ tilingScheme: scene.terrainProvider.tilingScheme, }) ); } else if (!that.tileCoordinates && defined(tileBoundariesLayer)) { scene.imageryLayers.remove(tileBoundariesLayer); tileBoundariesLayer = undefined; } return true; }); this._tileCoordinatesSubscription = knockout .getObservable(this, "tileCoordinates") .subscribe(function () { that._showTileCoordinates(); that._scene.requestRender(); }); this._tileBoundingSphereSubscription = knockout .getObservable(this, "tileBoundingSphere") .subscribe(function () { that._showTileBoundingSphere(); that._scene.requestRender(); }); this._showTileBoundingSphere = createCommand(function () { if (that.tileBoundingSphere) { globe._surface.tileProvider._debug.boundingSphereTile = that._tile; } else { globe._surface.tileProvider._debug.boundingSphereTile = undefined; } that._scene.requestRender(); return true; }); this._doFilterTile = createCommand(function () { if (!that.filterTile) { that.suspendUpdates = false; } else { that.suspendUpdates = true; globe._surface._tilesToRender = []; if (defined(that._tile) && that._tile.renderable) { globe._surface._tilesToRender.push(that._tile); } } return true; }); this._filterTileSubscription = knockout .getObservable(this, "filterTile") .subscribe(function () { that.doFilterTile(); that._scene.requestRender(); }); function pickPrimitive(e) { const newPick = that._scene.pick({ x: e.position.x, y: e.position.y, }); if (defined(newPick)) { that.primitive = defined(newPick.collection) ? newPick.collection : newPick.primitive; } that._scene.requestRender(); that.pickPrimitiveActive = false; } this._pickPrimitive = createCommand(function () { that.pickPrimitiveActive = !that.pickPrimitiveActive; }); this._pickPrimitiveActiveSubscription = knockout .getObservable(this, "pickPrimitiveActive") .subscribe(function (val) { if (val) { eventHandler.setInputAction( pickPrimitive, ScreenSpaceEventType.LEFT_CLICK ); } else { eventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK); } }); function selectTile(e) { let selectedTile; const ellipsoid = globe.ellipsoid; const ray = that._scene.camera.getPickRay(e.position, scratchPickRay); const cartesian = globe.pick(ray, that._scene, scratchPickCartesian); if (defined(cartesian)) { const cartographic = ellipsoid.cartesianToCartographic(cartesian); const tilesRendered = globe._surface.tileProvider._tilesToRenderByTextureCount; for ( let textureCount = 0; !selectedTile && textureCount < tilesRendered.length; ++textureCount ) { const tilesRenderedByTextureCount = tilesRendered[textureCount]; if (!defined(tilesRenderedByTextureCount)) { continue; } for ( let tileIndex = 0; !selectedTile && tileIndex < tilesRenderedByTextureCount.length; ++tileIndex ) { const tile = tilesRenderedByTextureCount[tileIndex]; if (Rectangle.contains(tile.rectangle, cartographic)) { selectedTile = tile; } } } } that.tile = selectedTile; that.pickTileActive = false; } this._pickTile = createCommand(function () { that.pickTileActive = !that.pickTileActive; }); this._pickTileActiveSubscription = knockout .getObservable(this, "pickTileActive") .subscribe(function (val) { if (val) { eventHandler.setInputAction( selectTile, ScreenSpaceEventType.LEFT_CLICK ); } else { eventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK); } }); this._removePostRenderEvent = scene.postRender.addEventListener(function () { that._update(); }); } Object.defineProperties(CesiumInspectorViewModel.prototype, { /** * Gets the scene to control. * @memberof CesiumInspectorViewModel.prototype * * @type {Scene} */ scene: { get: function () { return this._scene; }, }, /** * Gets the container of the PerformanceDisplay * @memberof CesiumInspectorViewModel.prototype * * @type {Element} */ performanceContainer: { get: function () { return this._performanceContainer; }, }, /** * Gets the command to toggle the visibility of the drop down. * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ toggleDropDown: { get: function () { return this._toggleDropDown; }, }, /** * Gets the command to toggle the visibility of a BoundingSphere for a primitive * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ showPrimitiveBoundingSphere: { get: function () { return this._showPrimitiveBoundingSphere; }, }, /** * Gets the command to toggle the visibility of a {@link DebugModelMatrixPrimitive} for the model matrix of a primitive * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ showPrimitiveReferenceFrame: { get: function () { return this._showPrimitiveReferenceFrame; }, }, /** * Gets the command to toggle a filter that renders only a selected primitive * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ doFilterPrimitive: { get: function () { return this._doFilterPrimitive; }, }, /** * Gets the command to increment the depth frustum index to be shown * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ incrementDepthFrustum: { get: function () { return this._incrementDepthFrustum; }, }, /** * Gets the command to decrement the depth frustum index to be shown * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ decrementDepthFrustum: { get: function () { return this._decrementDepthFrustum; }, }, /** * Gets the command to toggle the visibility of tile coordinates * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ showTileCoordinates: { get: function () { return this._showTileCoordinates; }, }, /** * Gets the command to toggle the visibility of a BoundingSphere for a selected tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ showTileBoundingSphere: { get: function () { return this._showTileBoundingSphere; }, }, /** * Gets the command to toggle a filter that renders only a selected tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ doFilterTile: { get: function () { return this._doFilterTile; }, }, /** * Gets the command to expand and collapse the general section * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ toggleGeneral: { get: function () { return this._toggleGeneral; }, }, /** * Gets the command to expand and collapse the primitives section * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ togglePrimitives: { get: function () { return this._togglePrimitives; }, }, /** * Gets the command to expand and collapse the terrain section * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ toggleTerrain: { get: function () { return this._toggleTerrain; }, }, /** * Gets the command to pick a primitive * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ pickPrimitive: { get: function () { return this._pickPrimitive; }, }, /** * Gets the command to pick a tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ pickTile: { get: function () { return this._pickTile; }, }, /** * Gets the command to pick a tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ selectParent: { get: function () { const that = this; return createCommand(function () { that.tile = that.tile.parent; }); }, }, /** * Gets the command to pick a tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ selectNW: { get: function () { const that = this; return createCommand(function () { that.tile = that.tile.northwestChild; }); }, }, /** * Gets the command to pick a tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ selectNE: { get: function () { const that = this; return createCommand(function () { that.tile = that.tile.northeastChild; }); }, }, /** * Gets the command to pick a tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ selectSW: { get: function () { const that = this; return createCommand(function () { that.tile = that.tile.southwestChild; }); }, }, /** * Gets the command to pick a tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ selectSE: { get: function () { const that = this; return createCommand(function () { that.tile = that.tile.southeastChild; }); }, }, /** * Gets or sets the current selected primitive * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ primitive: { get: function () { return this._primitive; }, set: function (newPrimitive) { const oldPrimitive = this._primitive; if (newPrimitive !== oldPrimitive) { this.hasPickedPrimitive = true; if (defined(oldPrimitive)) { oldPrimitive.debugShowBoundingVolume = false; } this._scene.debugCommandFilter = undefined; if (defined(this._modelMatrixPrimitive)) { this._scene.primitives.remove(this._modelMatrixPrimitive); this._modelMatrixPrimitive = undefined; } this._primitive = newPrimitive; newPrimitive.show = false; setTimeout(function () { newPrimitive.show = true; }, 50); this.showPrimitiveBoundingSphere(); this.showPrimitiveReferenceFrame(); this.doFilterPrimitive(); } }, }, /** * Gets or sets the current selected tile * @memberof CesiumInspectorViewModel.prototype * * @type {Command} */ tile: { get: function () { return this._tile; }, set: function (newTile) { if (defined(newTile)) { this.hasPickedTile = true; const oldTile = this._tile; if (newTile !== oldTile) { this.tileText = `L: ${newTile.level} X: ${newTile.x} Y: ${newTile.y}`; this.tileText += `
SW corner: ${newTile.rectangle.west}, ${newTile.rectangle.south}`; this.tileText += `
NE corner: ${newTile.rectangle.east}, ${newTile.rectangle.north}`; const data = newTile.data; if (defined(data) && defined(data.tileBoundingRegion)) { this.tileText += `
Min: ${data.tileBoundingRegion.minimumHeight} Max: ${data.tileBoundingRegion.maximumHeight}`; } else { this.tileText += "
(Tile is not loaded)"; } } this._tile = newTile; this.showTileBoundingSphere(); this.doFilterTile(); } else { this.hasPickedTile = false; this._tile = undefined; } }, }, }); /** * Updates the view model * @private */ CesiumInspectorViewModel.prototype._update = function () { if (this.frustums) { this.frustumStatisticText = frustumStatisticsToString( this._scene.debugFrustumStatistics ); } // Determine the number of frustums being used. const numberOfFrustums = this._scene.numberOfFrustums; this._numberOfFrustums = numberOfFrustums; // Bound the frustum to be displayed. this.depthFrustum = boundDepthFrustum(1, numberOfFrustums, this.depthFrustum); // Update the displayed text. this.depthFrustumText = `${this.depthFrustum} of ${numberOfFrustums}`; if (this.performance) { this._performanceDisplay.update(); } if (this.primitiveReferenceFrame) { this._modelMatrixPrimitive.modelMatrix = this._primitive.modelMatrix; } this.shaderCacheText = `Cached shaders: ${this._scene.context.shaderCache.numberOfShaders}`; }; /** * @returns {boolean} true if the object has been destroyed, false otherwise. */ CesiumInspectorViewModel.prototype.isDestroyed = function () { return false; }; /** * Destroys the widget. Should be called if permanently * removing the widget from layout. */ CesiumInspectorViewModel.prototype.destroy = function () { this._eventHandler.destroy(); this._removePostRenderEvent(); this._frustumsSubscription.dispose(); this._frustumPlanesSubscription.dispose(); this._performanceSubscription.dispose(); this._primitiveBoundingSphereSubscription.dispose(); this._primitiveReferenceFrameSubscription.dispose(); this._filterPrimitiveSubscription.dispose(); this._wireframeSubscription.dispose(); this._depthFrustumSubscription.dispose(); this._suspendUpdatesSubscription.dispose(); this._tileCoordinatesSubscription.dispose(); this._tileBoundingSphereSubscription.dispose(); this._filterTileSubscription.dispose(); this._pickPrimitiveActiveSubscription.dispose(); this._pickTileActiveSubscription.dispose(); return destroyObject(this); }; export default CesiumInspectorViewModel;