diff --git a/Apps/TerrainInspector/TerrainInspector.css b/Apps/TerrainInspector/TerrainInspector.css new file mode 100644 index 000000000000..f801c5b8bf16 --- /dev/null +++ b/Apps/TerrainInspector/TerrainInspector.css @@ -0,0 +1,126 @@ +@import url(../../Source/Widgets/widgets.css); + +body { + background: #000; + color: #eee; + font-family: sans-serif; + font-size: 9pt; + padding: 0; + margin: 0; + width: 100%; + height: 100%; + overflow: hidden; +} + +.fullSize { + display: block; + position: absolute; + top: 0; + left: 0; + border: none; + width: 100%; + height: 100%; +} + +#loadingOverlay { + position: absolute; + top: 0; + left: 0; + opacity: 0.9; + width: 100%; + height: 100%; + display: none; +} + +.sandcastle-loading #loadingOverlay { + display: block; +} + +#loadingOverlay h1 { + text-align: center; + position: relative; + top: 50%; + margin-top: -0.5em; +} + +#toolbar { + margin: 5px; + padding: 2px 5px; + position: absolute; +} + +.top,.bottom { + display: block; + position: absolute; + border: none; + width: 100%; + height: 50%; + box-sizing: border-box; +} + +.top { + top: 0; + left: 0; + border-bottom: solid 2px #888; +} + +.bottom { + bottom: 0; + left: 0; +} + +#topToolbar,#bottomToolbar { + margin: 5px; + padding: 2px 5px; + position: absolute; + left: 0; +} + +#topToolbar { + top: 0; +} + +#bottomToolbar { + top: 50%; +} + +.button { + -moz-box-shadow:inset 0px 1px 0px 0px #ffffff; + -webkit-box-shadow:inset 0px 1px 0px 0px #ffffff; + box-shadow:inset 0px 1px 0px 0px #ffffff; + + background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #f9f9f9), color-stop(1, #dedede)); + background:-moz-linear-gradient(top, #f9f9f9 5%, #dedede 100%); + background:-webkit-linear-gradient(top, #f9f9f9 5%, #dedede 100%); + background:-o-linear-gradient(top, #f9f9f9 5%, #dedede 100%); + background:-ms-linear-gradient(top, #f9f9f9 5%, #dedede 100%); + background:linear-gradient(to bottom, #f9f9f9 5%, #dedede 100%); + + border-radius:3px; + border:1px solid #dcdcdc; + display:inline-block; + padding:5px 10px; + margin:3px; + text-decoration:none; + text-shadow:0px 1px 0px #ffffff; +} + +.button:hover { + background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #dedede), color-stop(1, #f9f9f9)); + background:-moz-linear-gradient(top, #dedede 5%, #f9f9f9 100%); + background:-webkit-linear-gradient(top, #dedede 5%, #f9f9f9 100%); + background:-o-linear-gradient(top, #dedede 5%, #f9f9f9 100%); + background:-ms-linear-gradient(top, #dedede 5%, #f9f9f9 100%); + background:linear-gradient(to bottom, #dedede 5%, #f9f9f9 100%); +} + +.button:active { + position:relative; + top:1px; +} + +.claro .dijitTitlePaneContentOuter { + background-color: rgba(255, 255, 255, 0.7); + color: black; + text-shadow: 0 0 5px #fff; +} \ No newline at end of file diff --git a/Apps/TerrainInspector/TerrainInspector.js b/Apps/TerrainInspector/TerrainInspector.js new file mode 100644 index 000000000000..c7dcc0a688fa --- /dev/null +++ b/Apps/TerrainInspector/TerrainInspector.js @@ -0,0 +1,188 @@ +/*global require,document*/ +require([ + 'Cesium', + 'dojo/dom-construct', + 'dijit/TitlePane', + 'dijit/form/Button', + 'dijit/form/CheckBox' +], function( + Cesium, + domConstruct, + TitlePane, + Button, + CheckBox) { + "use strict"; + + var viewer = new Cesium.Viewer('cesiumContainer'); + + var scene = viewer.scene; + var centralBody = scene.getPrimitives().getCentralBody(); + centralBody.depthTestAgainstTerrain = true; + + centralBody.terrainProvider = new Cesium.CesiumTerrainProvider({ + url : 'http://cesium.agi.com/smallterrain' + }); + + var tp = new TitlePane({ + title: 'Terrain Debugging', + id:'title-pane', + content: '
', + open: true + }); + document.getElementById("toolbar").appendChild(tp.domNode); + + domConstruct.place('Wireframe\ + Suspend LOD update\ + Show tile coordinates\ + \ + None\ +  \ +  \ + Show bounding sphere of selected tile\ + Render selected tile only', 'debuggingTable'); + + new CheckBox({ + checked: centralBody._surface.wireframe, + onChange: function(b) { + centralBody._surface._debug.wireframe = b; + + } + }).placeAt('wireFrameToggle'); + + var suspendLodCheckbox = new CheckBox({ + checked: centralBody._surface._debug.suspendLodUpdate, + onChange: function(b) { + centralBody._surface._debug.suspendLodUpdate = b; + if (!b) { + renderSelectedTileOnlyCheckbox.set("checked", false); + } + } + }); + suspendLodCheckbox.placeAt('updateLodToggle'); + + var tileBoundariesLayer; + + new CheckBox({ + checked: Cesium.defined(tileBoundariesLayer), + onChange: function(b) { + if (b && !Cesium.defined(tileBoundariesLayer)) { + tileBoundariesLayer = centralBody.getImageryLayers().addImageryProvider(new Cesium.TileCoordinatesImageryProvider({ + tilingScheme : centralBody.terrainProvider.getTilingScheme() + })); + } else if (!b && Cesium.defined(tileBoundariesLayer)) { + centralBody.getImageryLayers().remove(tileBoundariesLayer); + tileBoundariesLayer = undefined; + } + + } + }).placeAt('showTileCoordinatesToggle'); + + var selectingTile = false; + var showBoundingSphere = true; + var selectedTile; + var renderSelectedTileOnly = false; + + function selectTile(event) { + selectedTile = undefined; + + var ellipsoid = centralBody.getEllipsoid(); + var cartesian = scene.getCamera().controller.pickEllipsoid({x: event.clientX, y: event.clientY}, ellipsoid); + + if (Cesium.defined(cartesian)) { + var cartographic = ellipsoid.cartesianToCartographic(cartesian); + + // Find a tile containing this position. + + var tilesRendered = centralBody._surface._tilesToRenderByTextureCount; + for (var textureCount = 0; !selectedTile && textureCount < tilesRendered.length; ++textureCount) { + var tilesRenderedByTextureCount = tilesRendered[textureCount]; + if (!Cesium.defined(tilesRenderedByTextureCount)) { + continue; + } + + for (var tileIndex = 0; !selectedTile && tileIndex < tilesRenderedByTextureCount.length; ++tileIndex) { + var tile = tilesRenderedByTextureCount[tileIndex]; + if (tile.extent.contains(cartographic)) { + selectedTile = tile; + } + } + } + } + + var text; + var sw; + var ne; + if (selectedTile) { + text = 'L: ' + selectedTile.level + ' X: ' + selectedTile.x + ' Y: ' + selectedTile.y; + sw = 'SW corner: ' + selectedTile.extent.west + ', ' + selectedTile.extent.south; + ne = 'NE corner: ' + selectedTile.extent.east + ', ' + selectedTile.extent.north; + } else { + text = 'None'; + sw = ' '; + ne = ' '; + } + + if (showBoundingSphere) { + centralBody._surface._debug.boundingSphereTile = selectedTile; + } + + document.getElementById('selectedTileLabel').innerHTML = text; + document.getElementById('selectedTileExtentSWLabel').innerHTML = sw; + document.getElementById('selectedTileExtentNELabel').innerHTML = ne; + + viewer.cesiumWidget.canvas.removeEventListener('mousedown', selectTile, false); + selectingTile = false; + } + + new Button({ + label: "Select tile...", + showLabel: true, + onClick: function() { + selectingTile = !selectingTile; + if (selectingTile) { + document.getElementById("selectedTileLabel").innerHTML = 'Click a tile!'; + viewer.cesiumWidget.canvas.addEventListener('mousedown', selectTile, false); + } else { + viewer.cesiumWidget.canvas.removeEventListener('mousedown', selectTile, false); + } + } + }).placeAt('selectTileButton'); + + new CheckBox({ + checked: showBoundingSphere, + onChange: function(b) { + showBoundingSphere = b; + if (!showBoundingSphere) { + centralBody._surface._debug.boundingSphereTile = undefined; + } else { + centralBody._surface._debug.boundingSphereTile = selectedTile; + } + } + }).placeAt('boundingSphereToggle'); + + var renderSelectedTileOnlyCheckbox = new CheckBox({ + checked: renderSelectedTileOnly, + onChange: function(b) { + if (!b) { + suspendLodCheckbox.set("checked", false); + } else { + suspendLodCheckbox.set("checked", true); + centralBody._surface._tilesToRenderByTextureCount = []; + + if (Cesium.defined(selectedTile)) { + var readyTextureCount = 0; + var tileImageryCollection = selectedTile.imagery; + for (var i = 0, len = tileImageryCollection.length; i < len; ++i) { + var tileImagery = tileImageryCollection[i]; + if (Cesium.defined(tileImagery.readyImagery) && tileImagery.readyImagery.imageryLayer.alpha !== 0.0) { + ++readyTextureCount; + } + } + + centralBody._surface._tilesToRenderByTextureCount[readyTextureCount] = [selectedTile]; + } + } + } + }); + renderSelectedTileOnlyCheckbox.placeAt('renderSelectedTileOnlyToggle'); +}); diff --git a/Apps/TerrainInspector/index.html b/Apps/TerrainInspector/index.html new file mode 100644 index 000000000000..270ec922956e --- /dev/null +++ b/Apps/TerrainInspector/index.html @@ -0,0 +1,27 @@ + + + + + + + Terrain Inspector + + + + + + + +
+
+
+ + diff --git a/Source/Scene/CentralBodySurface.js b/Source/Scene/CentralBodySurface.js index 7ce14cdb196c..392f6a9cc276 100644 --- a/Source/Scene/CentralBodySurface.js +++ b/Source/Scene/CentralBodySurface.js @@ -22,7 +22,8 @@ define([ './SceneMode', './TerrainProvider', './TileReplacementQueue', - './TileState' + './TileState', + '../ThirdParty/when' ], function( defaultValue, defined, @@ -46,7 +47,8 @@ define([ SceneMode, TerrainProvider, TileReplacementQueue, - TileState) { + TileState, + when) { "use strict"; /** @@ -98,6 +100,7 @@ define([ this._debug = { enableDebugOutput : false, + wireframe : false, boundingSphereTile : undefined, maxDepth : 0, @@ -985,10 +988,18 @@ define([ command.shaderProgram = shaderSet.getShaderProgram(context, tileSetIndex, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha); command.renderState = renderState; - command.primitiveType = TerrainProvider.wireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES; + command.primitiveType = PrimitiveType.TRIANGLES; command.vertexArray = tile.vertexArray; command.uniformMap = uniformMap; + if (surface._debug.wireframe) { + createWireframeVertexArrayIfNecessary(context, surface, tile); + if (defined(tile.wireframeVertexArray)) { + command.vertexArray = tile.wireframeVertexArray; + command.primitiveType = PrimitiveType.LINES; + } + } + var boundingVolume = tile.boundingSphere3D; if (frameState.mode !== SceneMode.SCENE3D) { @@ -1010,5 +1021,30 @@ define([ tileCommands.length = Math.max(0, tileCommandIndex + 1); } + function createWireframeVertexArrayIfNecessary(context, surface, tile) { + if (defined(tile.wireframeVertexArray)) { + return; + } + + if (defined(tile.meshForWireframePromise)) { + return; + } + + tile.meshForWireframePromise = tile.terrainData.createMesh(surface._terrainProvider.getTilingScheme(), tile.x, tile.y, tile.level); + if (!defined(tile.meshForWireframePromise)) { + // deferrred + return; + } + + var vertexArray = tile.vertexArray; + + when(tile.meshForWireframePromise, function(mesh) { + if (tile.vertexArray === vertexArray) { + tile.wireframeVertexArray = TerrainProvider.createWireframeVertexArray(context, tile.vertexArray, mesh); + } + tile.meshForWireframePromise = undefined; + }); + } + return CentralBodySurface; }); diff --git a/Source/Scene/TerrainProvider.js b/Source/Scene/TerrainProvider.js index 977eaa12d960..591c24f4d61e 100644 --- a/Source/Scene/TerrainProvider.js +++ b/Source/Scene/TerrainProvider.js @@ -39,8 +39,6 @@ define([ textureCoordinates : 1 }; - TerrainProvider.wireframe = false; - var regularGridIndexArrays = []; TerrainProvider.getRegularGridIndices = function(width, height) { @@ -134,9 +132,6 @@ define([ var indexBuffer = indexBuffers[context.getId()]; if (!defined(indexBuffer) || indexBuffer.isDestroyed()) { var indices = buffers.indices; - if (TerrainProvider.wireframe) { - indices = trianglesToLines(buffers.indices); - } indexBuffer = context.createIndexBuffer(indices, BufferUsage.STATIC_DRAW, IndexDatatype.UNSIGNED_SHORT); indexBuffer.setVertexArrayDestroyable(false); indexBuffer.referenceCount = 1; @@ -149,6 +144,21 @@ define([ tileTerrain.vertexArray = context.createVertexArray(attributes, indexBuffer); }; + /** + * Creates a vertex array for wireframe rendering of a terrain tile. + * + * @param {Context} context The context in which to create the vertex array. + * @param {VertexArray} vertexArray The existing, non-wireframe vertex array. The new vertex array + * will share vertex buffers with this existing one. + * @param {TerrainMesh} terrainMesh The terrain mesh containing non-wireframe indices. + * @returns {VertexArray} The vertex array for wireframe rendering. + */ + TerrainProvider.createWireframeVertexArray = function(context, vertexArray, terrainMesh) { + var wireframeIndices = trianglesToLines(terrainMesh.indices); + var wireframeIndexBuffer = context.createIndexBuffer(wireframeIndices, BufferUsage.STATIC_DRAW, IndexDatatype.UNSIGNED_SHORT); + return context.createVertexArray(vertexArray._attributes, wireframeIndexBuffer); + }; + /** * Specifies the quality of terrain created from heightmaps. A value of 1.0 will * ensure that adjacent heightmap vertices are separated by no more than diff --git a/Source/Scene/Tile.js b/Source/Scene/Tile.js index 3ec28ff90de3..71d03be5693f 100644 --- a/Source/Scene/Tile.js +++ b/Source/Scene/Tile.js @@ -312,8 +312,10 @@ define([ }; Tile.prototype.freeVertexArray = function() { + var indexBuffer; + if (defined(this.vertexArray)) { - var indexBuffer = this.vertexArray.getIndexBuffer(); + indexBuffer = this.vertexArray.getIndexBuffer(); this.vertexArray.destroy(); this.vertexArray = undefined; @@ -325,6 +327,20 @@ define([ } } } + + if (typeof this.wireframeVertexArray !== 'undefined') { + indexBuffer = this.wireframeVertexArray.getIndexBuffer(); + + this.wireframeVertexArray.destroy(); + this.wireframeVertexArray = undefined; + + if (!indexBuffer.isDestroyed() && typeof indexBuffer.referenceCount !== 'undefined') { + --indexBuffer.referenceCount; + if (indexBuffer.referenceCount === 0) { + indexBuffer.destroy(); + } + } + } }; Tile.prototype.processStateMachine = function(context, terrainProvider, imageryLayerCollection) { diff --git a/index.html b/index.html index 22ed716ba0a2..b2e67588d21f 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,9 @@
  • Sandcastle
  • +
  • + Terrain Inspector +
  • Timeline Demo