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