diff --git a/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html index a068a5f93d44..990d22d6e9a9 100644 --- a/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html +++ b/Apps/Sandcastle/gallery/Cartographic Limit Rectangle.html @@ -30,6 +30,7 @@ var coffeeBeltRectangle = Cesium.Rectangle.fromDegrees(-180.0, -23.43687, 180.0, 23.43687); viewer.scene.globe.cartographicLimitRectangle = coffeeBeltRectangle; +viewer.scene.globe.showSkirts = false; viewer.scene.skyAtmosphere.show = false; // Add rectangles to show bounds diff --git a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html index 07375e808b64..119daa0de923 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html @@ -105,6 +105,8 @@ edgeColor: Cesium.Color.WHITE, enabled : clippingPlanesEnabled }); + globe.backFaceCulling = true; + globe.showSkirts = true; viewer.trackedEntity = entity; } @@ -156,6 +158,8 @@ edgeColor: Cesium.Color.WHITE, enabled : clippingPlanesEnabled }); + globe.backFaceCulling = true; + globe.showSkirts = true; // Load tileset tileset = new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(5713) }); @@ -201,6 +205,8 @@ ], unionClippingRegions : true }); + globe.backFaceCulling = false; + globe.showSkirts = false; viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.5, boundingSphere.radius * 5.0)); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); diff --git a/CHANGES.md b/CHANGES.md index 64c6938063b8..31ecf89bea4e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,10 @@ Change Log ### 1.66.0 - 2020-02-03 +##### Additions :tada: + +* Added `Globe.showSkirts` to support the ability to hide terrain skirts when viewing terrain from below the surface. [#8489](https://github.com/AnalyticalGraphicsInc/cesium/pull/8489) + ##### Fixes :wrench: * Fixed a bug where the camera could go underground during mouse navigation. [#8504](https://github.com/AnalyticalGraphicsInc/cesium/pull/8504) diff --git a/Source/Core/GoogleEarthEnterpriseTerrainData.js b/Source/Core/GoogleEarthEnterpriseTerrainData.js index e3dfdeec5d76..d1418b26ce2f 100644 --- a/Source/Core/GoogleEarthEnterpriseTerrainData.js +++ b/Source/Core/GoogleEarthEnterpriseTerrainData.js @@ -51,7 +51,7 @@ import TerrainMesh from './TerrainMesh.js'; * }); * * @see TerrainData - * @see HeightTerrainData + * @see HeightmapTerrainData * @see QuantizedMeshTerrainData */ function GoogleEarthEnterpriseTerrainData(options) { @@ -84,8 +84,6 @@ import TerrainMesh from './TerrainMesh.js'; this._mesh = undefined; this._minimumHeight = undefined; this._maximumHeight = undefined; - this._vertexCountWithoutSkirts = undefined; - this._skirtIndex = undefined; } defineProperties(GoogleEarthEnterpriseTerrainData.prototype, { @@ -179,6 +177,8 @@ import TerrainMesh from './TerrainMesh.js'; center, new Float32Array(result.vertices), new Uint16Array(result.indices), + result.indexCountWithoutSkirts, + result.vertexCountWithoutSkirts, result.minimumHeight, result.maximumHeight, BoundingSphere.clone(result.boundingSphere3D), @@ -192,8 +192,6 @@ import TerrainMesh from './TerrainMesh.js'; result.eastIndicesNorthToSouth, result.northIndicesWestToEast); - that._vertexCountWithoutSkirts = result.vertexCountWithoutSkirts; - that._skirtIndex = result.skirtIndex; that._minimumHeight = result.minimumHeight; that._maximumHeight = result.maximumHeight; @@ -269,9 +267,9 @@ import TerrainMesh from './TerrainMesh.js'; var upsamplePromise = upsampleTaskProcessor.scheduleTask({ vertices : mesh.vertices, - vertexCountWithoutSkirts : this._vertexCountWithoutSkirts, indices : mesh.indices, - skirtIndex : this._skirtIndex, + indexCountWithoutSkirts : mesh.indexCountWithoutSkirts, + vertexCountWithoutSkirts : mesh.vertexCountWithoutSkirts, encoding : mesh.encoding, minimumHeight : this._minimumHeight, maximumHeight : this._maximumHeight, diff --git a/Source/Core/HeightmapTerrainData.js b/Source/Core/HeightmapTerrainData.js index 127872f09497..608f37872c4e 100644 --- a/Source/Core/HeightmapTerrainData.js +++ b/Source/Core/HeightmapTerrainData.js @@ -91,6 +91,7 @@ import TerrainProvider from './TerrainProvider.js'; * * @see TerrainData * @see QuantizedMeshTerrainData + * @see GoogleEarthEnterpriseTerrainData */ function HeightmapTerrainData(options) { //>>includeStart('debug', pragmas.debug); @@ -233,12 +234,23 @@ import TerrainProvider from './TerrainProvider.js'; var that = this; return when(verticesPromise, function(result) { + var indicesAndEdges; + if (that._skirtHeight > 0.0) { + indicesAndEdges = TerrainProvider.getRegularGridAndSkirtIndicesAndEdgeIndices(result.gridWidth, result.gridHeight); + } else { + indicesAndEdges = TerrainProvider.getRegularGridIndicesAndEdgeIndices(result.gridWidth, result.gridHeight); + } + + var vertexCountWithoutSkirts = result.gridWidth * result.gridHeight; + // Clone complex result objects because the transfer from the web worker // has stripped them down to JSON-style objects. that._mesh = new TerrainMesh( center, new Float32Array(result.vertices), - TerrainProvider.getRegularGridIndices(result.gridWidth, result.gridHeight), + indicesAndEdges.indices, + indicesAndEdges.indexCountWithoutSkirts, + vertexCountWithoutSkirts, result.minimumHeight, result.maximumHeight, BoundingSphere.clone(result.boundingSphere3D), @@ -247,10 +259,10 @@ import TerrainProvider from './TerrainProvider.js'; OrientedBoundingBox.clone(result.orientedBoundingBox), TerrainEncoding.clone(result.encoding), exaggeration, - result.westIndicesSouthToNorth, - result.southIndicesEastToWest, - result.eastIndicesNorthToSouth, - result.northIndicesWestToEast); + indicesAndEdges.westIndicesSouthToNorth, + indicesAndEdges.southIndicesEastToWest, + indicesAndEdges.eastIndicesNorthToSouth, + indicesAndEdges.northIndicesWestToEast); // Free memory received from server after mesh is created. that._buffer = undefined; @@ -309,20 +321,23 @@ import TerrainProvider from './TerrainProvider.js'; // Free memory received from server after mesh is created. this._buffer = undefined; - var arrayWidth = this._width; - var arrayHeight = this._height; - + var indicesAndEdges; if (this._skirtHeight > 0.0) { - arrayWidth += 2; - arrayHeight += 2; + indicesAndEdges = TerrainProvider.getRegularGridAndSkirtIndicesAndEdgeIndices(this._width, this._height); + } else { + indicesAndEdges = TerrainProvider.getRegularGridIndicesAndEdgeIndices(this._width, this._height); } + var vertexCountWithoutSkirts = result.gridWidth * result.gridHeight; + // No need to clone here (as we do in the async version) because the result // is not coming from a web worker. return new TerrainMesh( center, result.vertices, - TerrainProvider.getRegularGridIndices(arrayWidth, arrayHeight), + indicesAndEdges.indices, + indicesAndEdges.indexCountWithoutSkirts, + vertexCountWithoutSkirts, result.minimumHeight, result.maximumHeight, result.boundingSphere3D, @@ -331,10 +346,10 @@ import TerrainProvider from './TerrainProvider.js'; result.orientedBoundingBox, result.encoding, exaggeration, - result.westIndicesSouthToNorth, - result.southIndicesEastToWest, - result.eastIndicesNorthToSouth, - result.northIndicesWestToEast); + indicesAndEdges.westIndicesSouthToNorth, + indicesAndEdges.southIndicesEastToWest, + indicesAndEdges.eastIndicesNorthToSouth, + indicesAndEdges.northIndicesWestToEast); }; /** @@ -363,9 +378,8 @@ import TerrainProvider from './TerrainProvider.js'; if (defined(this._mesh)) { var buffer = this._mesh.vertices; var encoding = this._mesh.encoding; - var skirtHeight = this._skirtHeight; var exaggeration = this._mesh.exaggeration; - heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, rectangle, width, height, longitude, latitude, exaggeration); + heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, rectangle, width, height, longitude, latitude, exaggeration); } else { heightSample = interpolateHeight(this._buffer, elementsPerHeight, elementMultiplier, stride, isBigEndian, rectangle, width, height, longitude, latitude); heightSample = heightSample * heightScale + heightOffset; @@ -426,7 +440,6 @@ import TerrainProvider from './TerrainProvider.js'; var width = this._width; var height = this._height; var structure = this._structure; - var skirtHeight = this._skirtHeight; var stride = structure.stride; var heights = new this._bufferType(width * height * stride); @@ -452,7 +465,7 @@ import TerrainProvider from './TerrainProvider.js'; var latitude = CesiumMath.lerp(destinationRectangle.north, destinationRectangle.south, j / (height - 1)); for (var i = 0; i < width; ++i) { var longitude = CesiumMath.lerp(destinationRectangle.west, destinationRectangle.east, i / (width - 1)); - var heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration); + var heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, sourceRectangle, width, height, longitude, latitude, exaggeration); // Use conditionals here instead of Math.min and Math.max so that an undefined // lowestEncodedHeight or highestEncodedHeight has no effect. @@ -556,31 +569,21 @@ import TerrainProvider from './TerrainProvider.js'; return triangleInterpolateHeight(dx, dy, southwestHeight, southeastHeight, northwestHeight, northeastHeight); } - function interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration) { + function interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, sourceRectangle, width, height, longitude, latitude, exaggeration) { // returns a height encoded according to the structure's heightScale and heightOffset. var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west); var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south); - if (skirtHeight > 0) { - fromWest += 1.0; - fromSouth += 1.0; - - width += 2; - height += 2; - } - - var widthEdge = (skirtHeight > 0) ? width - 1 : width; var westInteger = fromWest | 0; var eastInteger = westInteger + 1; - if (eastInteger >= widthEdge) { + if (eastInteger >= width) { eastInteger = width - 1; westInteger = width - 2; } - var heightEdge = (skirtHeight > 0) ? height - 1 : height; var southInteger = fromSouth | 0; var northInteger = southInteger + 1; - if (northInteger >= heightEdge) { + if (northInteger >= height) { northInteger = height - 1; southInteger = height - 2; } diff --git a/Source/Core/HeightmapTessellator.js b/Source/Core/HeightmapTessellator.js index fe6e5a9ea8a8..7bcdc623a7cd 100644 --- a/Source/Core/HeightmapTessellator.js +++ b/Source/Core/HeightmapTessellator.js @@ -234,13 +234,14 @@ import WebMercatorProjection from './WebMercatorProjection.js'; var hMin = Number.POSITIVE_INFINITY; - var arrayWidth = width + (skirtHeight > 0.0 ? 2 : 0); - var arrayHeight = height + (skirtHeight > 0.0 ? 2 : 0); - var size = arrayWidth * arrayHeight; - var positions = new Array(size); - var heights = new Array(size); - var uvs = new Array(size); - var webMercatorTs = includeWebMercatorT ? new Array(size) : []; + var gridVertexCount = width * height; + var edgeVertexCount = skirtHeight > 0.0 ? (width * 2 + height * 2) : 0; + var vertexCount = gridVertexCount + edgeVertexCount; + + var positions = new Array(vertexCount); + var heights = new Array(vertexCount); + var uvs = new Array(vertexCount); + var webMercatorTs = includeWebMercatorT ? new Array(vertexCount) : []; var startRow = 0; var endRow = height; @@ -254,7 +255,7 @@ import WebMercatorProjection from './WebMercatorProjection.js'; ++endCol; } - var index = 0; + var skirtOffsetPercentage = 0.00001; for (var rowIndex = startRow; rowIndex < endRow; ++rowIndex) { var row = rowIndex; @@ -273,13 +274,23 @@ import WebMercatorProjection from './WebMercatorProjection.js'; latitude = toRadians(latitude); } + var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth); + v = CesiumMath.clamp(v, 0.0, 1.0); + + var isNorthEdge = rowIndex === startRow; + var isSouthEdge = rowIndex === endRow - 1; + if (skirtHeight > 0.0) { + if (isNorthEdge) { + latitude += skirtOffsetPercentage * rectangleHeight; + } else if (isSouthEdge) { + latitude -= skirtOffsetPercentage * rectangleHeight; + } + } + var cosLatitude = cos(latitude); var nZ = sin(latitude); var kZ = radiiSquaredZ * nZ; - var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth); - v = CesiumMath.clamp(v, 0.0, 1.0); - var webMercatorT; if (includeWebMercatorT) { webMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight; @@ -294,14 +305,6 @@ import WebMercatorProjection from './WebMercatorProjection.js'; col = width - 1; } - var longitude = nativeRectangle.west + granularityX * col; - - if (!isGeographic) { - longitude = longitude * oneOverGlobeSemimajorAxis; - } else { - longitude = toRadians(longitude); - } - var terrainOffset = row * (width * stride) + col * stride; var heightSample; @@ -324,30 +327,49 @@ import WebMercatorProjection from './WebMercatorProjection.js'; heightSample = (heightSample * heightScale + heightOffset) * exaggeration; - var u = (longitude - geographicWest) / (geographicEast - geographicWest); - u = CesiumMath.clamp(u, 0.0, 1.0); - uvs[index] = new Cartesian2(u, v); - maximumHeight = Math.max(maximumHeight, heightSample); minimumHeight = Math.min(minimumHeight, heightSample); - if (colIndex !== col || rowIndex !== row) { - var percentage = 0.00001; - if (colIndex < 0) { - longitude -= percentage * rectangleWidth; - } else { - longitude += percentage * rectangleWidth; - } - if (rowIndex < 0) { - latitude += percentage * rectangleHeight; - } else { - latitude -= percentage * rectangleHeight; - } + var longitude = nativeRectangle.west + granularityX * col; - cosLatitude = cos(latitude); - nZ = sin(latitude); - kZ = radiiSquaredZ * nZ; - heightSample -= skirtHeight; + if (!isGeographic) { + longitude = longitude * oneOverGlobeSemimajorAxis; + } else { + longitude = toRadians(longitude); + } + + var u = (longitude - geographicWest) / (geographicEast - geographicWest); + u = CesiumMath.clamp(u, 0.0, 1.0); + + var index = row * width + col; + + if (skirtHeight > 0.0) { + var isWestEdge = colIndex === startCol; + var isEastEdge = colIndex === endCol - 1; + var isEdge = isNorthEdge || isSouthEdge || isWestEdge || isEastEdge; + var isCorner = (isNorthEdge || isSouthEdge) && (isWestEdge || isEastEdge); + if (isCorner) { + // Don't generate skirts on the corners. + continue; + } else if (isEdge) { + heightSample -= skirtHeight; + + if (isWestEdge) { + // The outer loop iterates north to south but the indices are ordered south to north, hence the index flip below + index = gridVertexCount + (height - row - 1); + longitude -= skirtOffsetPercentage * rectangleWidth; + } else if (isSouthEdge) { + // Add after west indices. South indices are ordered east to west. + index = gridVertexCount + height + (width - col - 1); + } else if (isEastEdge) { + // Add after west and south indices. East indices are ordered north to south. The index is flipped like above. + index = gridVertexCount + height + width + row; + longitude += skirtOffsetPercentage * rectangleWidth; + } else if (isNorthEdge) { + // Add after west, south, and east indices. North indices are ordered west to east. + index = gridVertexCount + height + width + height + col; + } + } } var nX = cosLatitude * cos(longitude); @@ -370,13 +392,12 @@ import WebMercatorProjection from './WebMercatorProjection.js'; positions[index] = position; heights[index] = heightSample; + uvs[index] = new Cartesian2(u, v); if (includeWebMercatorT) { webMercatorTs[index] = webMercatorT; } - index++; - Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch); Cartesian3.minimumByComponent(cartesian3Scratch, minimum, minimum); @@ -399,48 +420,13 @@ import WebMercatorProjection from './WebMercatorProjection.js'; var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter); var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, false, includeWebMercatorT); - var vertices = new Float32Array(size * encoding.getStride()); + var vertices = new Float32Array(vertexCount * encoding.getStride()); var bufferIndex = 0; - for (var j = 0; j < size; ++j) { + for (var j = 0; j < vertexCount; ++j) { bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j], undefined, webMercatorTs[j]); } - var westIndicesSouthToNorth; - var southIndicesEastToWest; - var eastIndicesNorthToSouth; - var northIndicesWestToEast; - - if (skirtHeight > 0.0) { - northIndicesWestToEast = []; - southIndicesEastToWest = []; - for (var i1 = 0; i1 < width; ++i1) { - northIndicesWestToEast.push(arrayWidth + 1 + i1); - southIndicesEastToWest.push(arrayWidth * (arrayHeight - 1) - 2 - i1); - } - - westIndicesSouthToNorth = []; - eastIndicesNorthToSouth = []; - for (var i2 = 0; i2 < height; ++i2) { - eastIndicesNorthToSouth.push((i2 + 1) * arrayWidth + width); - westIndicesSouthToNorth.push((height - i2) * arrayWidth + 1); - } - } else { - northIndicesWestToEast = []; - southIndicesEastToWest = []; - for (var i3 = 0; i3 < width; ++i3) { - northIndicesWestToEast.push(i3); - southIndicesEastToWest.push(width * height - 1 - i3); - } - - westIndicesSouthToNorth = []; - eastIndicesNorthToSouth = []; - for (var i4 = 0; i4 < height; ++i4) { - eastIndicesNorthToSouth.push((i4 + 1) * width - 1); - westIndicesSouthToNorth.push((height - i4 - 1) * width ); - } - } - return { vertices : vertices, maximumHeight : maximumHeight, @@ -448,11 +434,7 @@ import WebMercatorProjection from './WebMercatorProjection.js'; encoding : encoding, boundingSphere3D : boundingSphere3D, orientedBoundingBox : orientedBoundingBox, - occludeePointInScaledSpace : occludeePointInScaledSpace, - westIndicesSouthToNorth : westIndicesSouthToNorth, - southIndicesEastToWest : southIndicesEastToWest, - eastIndicesNorthToSouth : eastIndicesNorthToSouth, - northIndicesWestToEast : northIndicesWestToEast + occludeePointInScaledSpace : occludeePointInScaledSpace }; }; export default HeightmapTessellator; diff --git a/Source/Core/QuantizedMeshTerrainData.js b/Source/Core/QuantizedMeshTerrainData.js index 38086459e4d4..5cae490b0664 100644 --- a/Source/Core/QuantizedMeshTerrainData.js +++ b/Source/Core/QuantizedMeshTerrainData.js @@ -89,6 +89,7 @@ import TerrainMesh from './TerrainMesh.js'; * * @see TerrainData * @see HeightmapTerrainData + * @see GoogleEarthEnterpriseTerrainData */ function QuantizedMeshTerrainData(options) { //>>includeStart('debug', pragmas.debug) @@ -300,8 +301,8 @@ import TerrainMesh from './TerrainMesh.js'; var that = this; return when(verticesPromise, function(result) { - var vertexCount = that._quantizedVertices.length / 3; - vertexCount += that._westIndices.length + that._southIndices.length + that._eastIndices.length + that._northIndices.length; + var vertexCountWithoutSkirts = that._quantizedVertices.length / 3; + var vertexCount = vertexCountWithoutSkirts + that._westIndices.length + that._southIndices.length + that._eastIndices.length + that._northIndices.length; var indicesTypedArray = IndexDatatype.createTypedArray(vertexCount, result.indices); var vertices = new Float32Array(result.vertices); @@ -314,15 +315,14 @@ import TerrainMesh from './TerrainMesh.js'; var stride = result.vertexStride; var terrainEncoding = TerrainEncoding.clone(result.encoding); - that._skirtIndex = result.skirtIndex; - that._vertexCountWithoutSkirts = that._quantizedVertices.length / 3; - // Clone complex result objects because the transfer from the web worker // has stripped them down to JSON-style objects. that._mesh = new TerrainMesh( rtc, vertices, indicesTypedArray, + result.indexCountWithoutSkirts, + vertexCountWithoutSkirts, minimumHeight, maximumHeight, boundingSphere, @@ -413,9 +413,9 @@ import TerrainMesh from './TerrainMesh.js'; var upsamplePromise = upsampleTaskProcessor.scheduleTask({ vertices : mesh.vertices, - vertexCountWithoutSkirts : this._vertexCountWithoutSkirts, + vertexCountWithoutSkirts : mesh.vertexCountWithoutSkirts, indices : mesh.indices, - skirtIndex : this._skirtIndex, + indexCountWithoutSkirts : mesh.indexCountWithoutSkirts, encoding : mesh.encoding, minimumHeight : this._minimumHeight, maximumHeight : this._maximumHeight, diff --git a/Source/Core/TerrainData.js b/Source/Core/TerrainData.js index 1465dd1286b8..c1a52bfab7f9 100644 --- a/Source/Core/TerrainData.js +++ b/Source/Core/TerrainData.js @@ -10,6 +10,7 @@ import DeveloperError from './DeveloperError.js'; * * @see HeightmapTerrainData * @see QuantizedMeshTerrainData + * @see GoogleEarthEnterpriseTerrainData */ function TerrainData() { DeveloperError.throwInstantiationError(); diff --git a/Source/Core/TerrainMesh.js b/Source/Core/TerrainMesh.js index ca8e43a9d4b2..8cb4165bc44a 100644 --- a/Source/Core/TerrainMesh.js +++ b/Source/Core/TerrainMesh.js @@ -13,6 +13,8 @@ import defaultValue from './defaultValue.js'; * the Cartesian position of the vertex, H is the height above the ellipsoid, and * U and V are the texture coordinates. * @param {Uint8Array|Uint16Array|Uint32Array} indices The indices describing how the vertices are connected to form triangles. + * @param {Number} indexCountWithoutSkirts The index count of the mesh not including skirts. + * @param {Number} vertexCountWithoutSkirts The vertex count of the mesh not including skirts. * @param {Number} minimumHeight The lowest height in the tile, in meters above the ellipsoid. * @param {Number} maximumHeight The highest height in the tile, in meters above the ellipsoid. * @param {BoundingSphere} boundingSphere3D A bounding sphere that completely contains the tile. @@ -31,8 +33,8 @@ import defaultValue from './defaultValue.js'; * @private */ function TerrainMesh( - center, vertices, indices, minimumHeight, maximumHeight, - boundingSphere3D, occludeePointInScaledSpace, + center, vertices, indices, indexCountWithoutSkirts, vertexCountWithoutSkirts, minimumHeight, + maximumHeight, boundingSphere3D, occludeePointInScaledSpace, vertexStride, orientedBoundingBox, encoding, exaggeration, westIndicesSouthToNorth, southIndicesEastToWest, eastIndicesNorthToSouth, northIndicesWestToEast) { @@ -66,6 +68,18 @@ import defaultValue from './defaultValue.js'; */ this.indices = indices; + /** + * The index count of the mesh not including skirts. + * @type {Number} + */ + this.indexCountWithoutSkirts = indexCountWithoutSkirts; + + /** + * The vertex count of the mesh not including skirts. + * @type {Number} + */ + this.vertexCountWithoutSkirts = vertexCountWithoutSkirts; + /** * The lowest height in the tile, in meters above the ellipsoid. * @type {Number} diff --git a/Source/Core/TerrainProvider.js b/Source/Core/TerrainProvider.js index 70a31ff13d82..c234fed8ab33 100644 --- a/Source/Core/TerrainProvider.js +++ b/Source/Core/TerrainProvider.js @@ -1,6 +1,7 @@ import defined from './defined.js'; import defineProperties from './defineProperties.js'; import DeveloperError from './DeveloperError.js'; +import IndexDatatype from './IndexDatatype.js'; import CesiumMath from './Math.js'; /** @@ -107,7 +108,7 @@ import CesiumMath from './Math.js'; } }); - var regularGridIndexArrays = []; + var regularGridIndicesCache = []; /** * Gets a list of indices for a triangle mesh representing a regular grid. Calling @@ -122,13 +123,13 @@ import CesiumMath from './Math.js'; TerrainProvider.getRegularGridIndices = function(width, height) { //>>includeStart('debug', pragmas.debug); if (width * height >= CesiumMath.FOUR_GIGABYTES) { - throw new DeveloperError('The total number of vertices (width * height) must be less than 4,294,967,295.'); + throw new DeveloperError('The total number of vertices (width * height) must be less than 4,294,967,296.'); } //>>includeEnd('debug'); - var byWidth = regularGridIndexArrays[width]; + var byWidth = regularGridIndicesCache[width]; if (!defined(byWidth)) { - regularGridIndexArrays[width] = byWidth = []; + regularGridIndicesCache[width] = byWidth = []; } var indices = byWidth[height]; @@ -138,31 +139,182 @@ import CesiumMath from './Math.js'; } else { indices = byWidth[height] = new Uint32Array((width - 1) * (height - 1) * 6); } + addRegularGridIndices(width, height, indices, 0); + } + + return indices; + }; + + var regularGridAndEdgeIndicesCache = []; + + /** + * @private + */ + TerrainProvider.getRegularGridIndicesAndEdgeIndices = function(width, height) { + //>>includeStart('debug', pragmas.debug); + if (width * height >= CesiumMath.FOUR_GIGABYTES) { + throw new DeveloperError('The total number of vertices (width * height) must be less than 4,294,967,296.'); + } + //>>includeEnd('debug'); + + var byWidth = regularGridAndEdgeIndicesCache[width]; + if (!defined(byWidth)) { + regularGridAndEdgeIndicesCache[width] = byWidth = []; + } + + var indicesAndEdges = byWidth[height]; + if (!defined(indicesAndEdges)) { + var indices = TerrainProvider.getRegularGridIndices(width, height); + + var edgeIndices = getEdgeIndices(width, height); + var westIndicesSouthToNorth = edgeIndices.westIndicesSouthToNorth; + var southIndicesEastToWest = edgeIndices.southIndicesEastToWest; + var eastIndicesNorthToSouth = edgeIndices.eastIndicesNorthToSouth; + var northIndicesWestToEast = edgeIndices.northIndicesWestToEast; + + indicesAndEdges = byWidth[height] = { + indices : indices, + westIndicesSouthToNorth : westIndicesSouthToNorth, + southIndicesEastToWest : southIndicesEastToWest, + eastIndicesNorthToSouth : eastIndicesNorthToSouth, + northIndicesWestToEast : northIndicesWestToEast + }; + } + + return indicesAndEdges; + }; + + var regularGridAndSkirtAndEdgeIndicesCache = []; + + /** + * @private + */ + TerrainProvider.getRegularGridAndSkirtIndicesAndEdgeIndices = function(width, height) { + //>>includeStart('debug', pragmas.debug); + if (width * height >= CesiumMath.FOUR_GIGABYTES) { + throw new DeveloperError('The total number of vertices (width * height) must be less than 4,294,967,296.'); + } + //>>includeEnd('debug'); + + var byWidth = regularGridAndSkirtAndEdgeIndicesCache[width]; + if (!defined(byWidth)) { + regularGridAndSkirtAndEdgeIndicesCache[width] = byWidth = []; + } + + var indicesAndEdges = byWidth[height]; + if (!defined(indicesAndEdges)) { + var gridVertexCount = width * height; + var gridIndexCount = (width - 1) * (height - 1) * 6; + var edgeVertexCount = width * 2 + height * 2; + var edgeIndexCount = Math.max(0, edgeVertexCount - 4) * 6; + var vertexCount = gridVertexCount + edgeVertexCount; + var indexCount = gridIndexCount + edgeIndexCount; + + var edgeIndices = getEdgeIndices(width, height); + var westIndicesSouthToNorth = edgeIndices.westIndicesSouthToNorth; + var southIndicesEastToWest = edgeIndices.southIndicesEastToWest; + var eastIndicesNorthToSouth = edgeIndices.eastIndicesNorthToSouth; + var northIndicesWestToEast = edgeIndices.northIndicesWestToEast; + + var indices = IndexDatatype.createTypedArray(vertexCount, indexCount); + addRegularGridIndices(width, height, indices, 0); + TerrainProvider.addSkirtIndices(westIndicesSouthToNorth, southIndicesEastToWest, eastIndicesNorthToSouth, northIndicesWestToEast, gridVertexCount, indices, gridIndexCount); + + indicesAndEdges = byWidth[height] = { + indices : indices, + westIndicesSouthToNorth : westIndicesSouthToNorth, + southIndicesEastToWest : southIndicesEastToWest, + eastIndicesNorthToSouth : eastIndicesNorthToSouth, + northIndicesWestToEast : northIndicesWestToEast, + indexCountWithoutSkirts : gridIndexCount + }; + } + + return indicesAndEdges; + }; + + /** + * @private + */ + TerrainProvider.addSkirtIndices = function(westIndicesSouthToNorth, southIndicesEastToWest, eastIndicesNorthToSouth, northIndicesWestToEast, vertexCount, indices, offset) { + var vertexIndex = vertexCount; + offset = addSkirtIndices(westIndicesSouthToNorth, vertexIndex, indices, offset); + vertexIndex += westIndicesSouthToNorth.length; + offset = addSkirtIndices(southIndicesEastToWest, vertexIndex, indices, offset); + vertexIndex += southIndicesEastToWest.length; + offset = addSkirtIndices(eastIndicesNorthToSouth, vertexIndex, indices, offset); + vertexIndex += eastIndicesNorthToSouth.length; + addSkirtIndices(northIndicesWestToEast, vertexIndex, indices, offset); + }; + + function getEdgeIndices(width, height) { + var westIndicesSouthToNorth = new Array(height); + var southIndicesEastToWest = new Array(width); + var eastIndicesNorthToSouth = new Array(height); + var northIndicesWestToEast = new Array(width); + + var i; + for (i = 0; i < width; ++i) { + northIndicesWestToEast[i] = i; + southIndicesEastToWest[i] = width * height - 1 - i; + } + + for (i = 0; i < height; ++i) { + eastIndicesNorthToSouth[i] = (i + 1) * width - 1; + westIndicesSouthToNorth[i] = (height - i - 1) * width; + } + + return { + westIndicesSouthToNorth : westIndicesSouthToNorth, + southIndicesEastToWest : southIndicesEastToWest, + eastIndicesNorthToSouth : eastIndicesNorthToSouth, + northIndicesWestToEast : northIndicesWestToEast + }; + } + + function addRegularGridIndices(width, height, indices, offset) { + var index = 0; + for (var j = 0; j < height - 1; ++j) { + for (var i = 0; i < width - 1; ++i) { + var upperLeft = index; + var lowerLeft = upperLeft + width; + var lowerRight = lowerLeft + 1; + var upperRight = upperLeft + 1; + + indices[offset++] = upperLeft; + indices[offset++] = lowerLeft; + indices[offset++] = upperRight; + indices[offset++] = upperRight; + indices[offset++] = lowerLeft; + indices[offset++] = lowerRight; - var index = 0; - var indicesIndex = 0; - for (var j = 0; j < height - 1; ++j) { - for (var i = 0; i < width - 1; ++i) { - var upperLeft = index; - var lowerLeft = upperLeft + width; - var lowerRight = lowerLeft + 1; - var upperRight = upperLeft + 1; - - indices[indicesIndex++] = upperLeft; - indices[indicesIndex++] = lowerLeft; - indices[indicesIndex++] = upperRight; - indices[indicesIndex++] = upperRight; - indices[indicesIndex++] = lowerLeft; - indices[indicesIndex++] = lowerRight; - - ++index; - } ++index; } + ++index; } + } - return indices; - }; + function addSkirtIndices(edgeIndices, vertexIndex, indices, offset) { + var previousIndex = edgeIndices[0]; + + var length = edgeIndices.length; + for (var i = 1; i < length; ++i) { + var index = edgeIndices[i]; + + indices[offset++] = previousIndex; + indices[offset++] = index; + indices[offset++] = vertexIndex; + + indices[offset++] = vertexIndex; + indices[offset++] = index; + indices[offset++] = vertexIndex + 1; + + previousIndex = index; + ++vertexIndex; + } + + return offset; + } /** * Specifies the quality of terrain created from heightmaps. A value of 1.0 will diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 30cff1b60dd3..8a4f846b7561 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -250,6 +250,15 @@ import TileSelectionResult from './TileSelectionResult.js'; */ this.atmosphereBrightnessShift = 0.0; + /** + * Whether to show terrain skirts. Terrain skirts are geometry extending downwards from a tile's edges used to hide seams between neighboring tiles. + * It may be desirable to hide terrain skirts if terrain is translucent or when viewing terrain from below the surface. + * + * @type {Boolean} + * @default true + */ + this.showSkirts = true; + /** * Whether to cull back-facing terrain. Set this to false when viewing terrain from below the surface. * @@ -754,6 +763,7 @@ import TileSelectionResult from './TileSelectionResult.js'; tileProvider.saturationShift = this.atmosphereSaturationShift; tileProvider.brightnessShift = this.atmosphereBrightnessShift; tileProvider.fillHighlightColor = this.fillHighlightColor; + tileProvider.showSkirts = this.showSkirts; tileProvider.backFaceCulling = this.backFaceCulling; surface.beginFrame(frameState); } diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index dcdef2b101e6..42204db6f248 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -101,6 +101,7 @@ import TileSelectionResult from './TileSelectionResult.js'; this.saturationShift = 0.0; this.brightnessShift = 0.0; + this.showSkirts = true; this.backFaceCulling = true; this._quadtree = undefined; @@ -1914,12 +1915,18 @@ import TileSelectionResult from './TileSelectionResult.js'; surfaceShaderSetOptions.highlightFillTile = highlightFillTile; surfaceShaderSetOptions.colorToAlpha = applyColorToAlpha; + var count = surfaceTile.renderedMesh.indices.length; + if (!tileProvider.showSkirts) { + count = surfaceTile.renderedMesh.indexCountWithoutSkirts; + } + command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(surfaceShaderSetOptions); command.castShadows = castShadows; command.receiveShadows = receiveShadows; command.renderState = renderState; command.primitiveType = PrimitiveType.TRIANGLES; command.vertexArray = surfaceTile.vertexArray || surfaceTile.fill.vertexArray; + command.count = count; command.uniformMap = uniformMap; command.pass = Pass.GLOBE; @@ -1928,6 +1935,7 @@ import TileSelectionResult from './TileSelectionResult.js'; if (defined(surfaceTile.wireframeVertexArray)) { command.vertexArray = surfaceTile.wireframeVertexArray; command.primitiveType = PrimitiveType.LINES; + command.count = count * 2; } } diff --git a/Source/Scene/TerrainFillMesh.js b/Source/Scene/TerrainFillMesh.js index 9b579c2f8983..f91699d16505 100644 --- a/Source/Scene/TerrainFillMesh.js +++ b/Source/Scene/TerrainFillMesh.js @@ -652,6 +652,8 @@ import TileSelectionResult from './TileSelectionResult.js'; encoding.center, typedArray, indices, + indexCount, + vertexCount, minimumHeight, maximumHeight, BoundingSphere.fromOrientedBoundingBox(obb), diff --git a/Source/WorkersES6/createVerticesFromGoogleEarthEnterpriseBuffer.js b/Source/WorkersES6/createVerticesFromGoogleEarthEnterpriseBuffer.js index 84a09393d862..ce9539730e77 100644 --- a/Source/WorkersES6/createVerticesFromGoogleEarthEnterpriseBuffer.js +++ b/Source/WorkersES6/createVerticesFromGoogleEarthEnterpriseBuffer.js @@ -58,7 +58,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; occludeePointInScaledSpace : statistics.occludeePointInScaledSpace, encoding : statistics.encoding, vertexCountWithoutSkirts : statistics.vertexCountWithoutSkirts, - skirtIndex : statistics.skirtIndex, + indexCountWithoutSkirts : statistics.indexCountWithoutSkirts, westIndicesSouthToNorth : statistics.westIndicesSouthToNorth, southIndicesEastToWest : statistics.southIndicesEastToWest, eastIndicesNorthToSouth : statistics.eastIndicesNorthToSouth, @@ -312,7 +312,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; } var vertexCountWithoutSkirts = pointOffset; - var skirtIndex = indicesOffset; + var indexCountWithoutSkirts = indicesOffset; // Add skirt points var skirtOptions = { @@ -402,7 +402,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; orientedBoundingBox : orientedBoundingBox, occludeePointInScaledSpace : occludeePointInScaledSpace, vertexCountWithoutSkirts : vertexCountWithoutSkirts, - skirtIndex : skirtIndex, + indexCountWithoutSkirts : indexCountWithoutSkirts, westIndicesSouthToNorth : westIndicesSouthToNorth, southIndicesEastToWest : southIndicesEastToWest, eastIndicesNorthToSouth : eastIndicesNorthToSouth, diff --git a/Source/WorkersES6/createVerticesFromHeightmap.js b/Source/WorkersES6/createVerticesFromHeightmap.js index e7cb3efd5fe4..9f3d5c8f42cb 100644 --- a/Source/WorkersES6/createVerticesFromHeightmap.js +++ b/Source/WorkersES6/createVerticesFromHeightmap.js @@ -26,14 +26,6 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; parameters.height = result.height; } - var arrayWidth = parameters.width; - var arrayHeight = parameters.height; - - if (parameters.skirtHeight > 0.0) { - arrayWidth += 2; - arrayHeight += 2; - } - parameters.ellipsoid = Ellipsoid.clone(parameters.ellipsoid); parameters.rectangle = Rectangle.clone(parameters.rectangle); @@ -46,8 +38,8 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; numberOfAttributes : statistics.encoding.getStride(), minimumHeight : statistics.minimumHeight, maximumHeight : statistics.maximumHeight, - gridWidth : arrayWidth, - gridHeight : arrayHeight, + gridWidth : parameters.width, + gridHeight : parameters.height, boundingSphere3D : statistics.boundingSphere3D, orientedBoundingBox : statistics.orientedBoundingBox, occludeePointInScaledSpace : statistics.occludeePointInScaledSpace, diff --git a/Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js b/Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js index 3333b7bbbbb0..94fd161b76b6 100644 --- a/Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js +++ b/Source/WorkersES6/createVerticesFromQuantizedTerrainMesh.js @@ -13,6 +13,7 @@ import Matrix4 from '../Core/Matrix4.js'; import OrientedBoundingBox from '../Core/OrientedBoundingBox.js'; import Rectangle from '../Core/Rectangle.js'; import TerrainEncoding from '../Core/TerrainEncoding.js'; +import TerrainProvider from '../Core/TerrainProvider.js'; import Transforms from '../Core/Transforms.js'; import WebMercatorProjection from '../Core/WebMercatorProjection.js'; import createTaskProcessorWorker from './createTaskProcessorWorker.js'; @@ -203,14 +204,15 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; // Add skirts. var vertexBufferIndex = quantizedVertexCount * vertexStride; - var indexBufferIndex = parameters.indices.length; - indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.westIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.westSkirtHeight, true, exaggeration, southMercatorY, oneOverMercatorHeight, westLongitudeOffset, westLatitudeOffset); + addSkirt(vertexBuffer, vertexBufferIndex, westIndicesSouthToNorth, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.westSkirtHeight, exaggeration, southMercatorY, oneOverMercatorHeight, westLongitudeOffset, westLatitudeOffset); vertexBufferIndex += parameters.westIndices.length * vertexStride; - indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.southIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.southSkirtHeight, false, exaggeration, southMercatorY, oneOverMercatorHeight, southLongitudeOffset, southLatitudeOffset); + addSkirt(vertexBuffer, vertexBufferIndex, southIndicesEastToWest, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.southSkirtHeight, exaggeration, southMercatorY, oneOverMercatorHeight, southLongitudeOffset, southLatitudeOffset); vertexBufferIndex += parameters.southIndices.length * vertexStride; - indexBufferIndex = addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.eastIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.eastSkirtHeight, false, exaggeration, southMercatorY, oneOverMercatorHeight, eastLongitudeOffset, eastLatitudeOffset); + addSkirt(vertexBuffer, vertexBufferIndex, eastIndicesNorthToSouth, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.eastSkirtHeight, exaggeration, southMercatorY, oneOverMercatorHeight, eastLongitudeOffset, eastLatitudeOffset); vertexBufferIndex += parameters.eastIndices.length * vertexStride; - addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, parameters.northIndices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.northSkirtHeight, true, exaggeration, southMercatorY, oneOverMercatorHeight, northLongitudeOffset, northLatitudeOffset); + addSkirt(vertexBuffer, vertexBufferIndex, northIndicesWestToEast, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, parameters.northSkirtHeight, exaggeration, southMercatorY, oneOverMercatorHeight, northLongitudeOffset, northLatitudeOffset); + + TerrainProvider.addSkirtIndices(westIndicesSouthToNorth, southIndicesEastToWest, eastIndicesNorthToSouth, northIndicesWestToEast, quantizedVertexCount, indexBuffer, parameters.indices.length); transferableObjects.push(vertexBuffer.buffer, indexBuffer.buffer); @@ -229,7 +231,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; orientedBoundingBox : orientedBoundingBox, occludeePointInScaledSpace : occludeePointInScaledSpace, encoding : encoding, - skirtIndex : parameters.indices.length + indexCountWithoutSkirts : parameters.indices.length }; } @@ -266,23 +268,8 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; return hMin; } - function addSkirt(vertexBuffer, vertexBufferIndex, indexBuffer, indexBufferIndex, edgeVertices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, skirtLength, isWestOrNorthEdge, exaggeration, southMercatorY, oneOverMercatorHeight, longitudeOffset, latitudeOffset) { - var start, end, increment; - if (isWestOrNorthEdge) { - start = edgeVertices.length - 1; - end = -1; - increment = -1; - } else { - start = 0; - end = edgeVertices.length; - increment = 1; - } - - var previousIndex = -1; - + function addSkirt(vertexBuffer, vertexBufferIndex, edgeVertices, encoding, heights, uvs, octEncodedNormals, ellipsoid, rectangle, skirtLength, exaggeration, southMercatorY, oneOverMercatorHeight, longitudeOffset, latitudeOffset) { var hasVertexNormals = defined(octEncodedNormals); - var vertexStride = encoding.getStride(); - var vertexIndex = vertexBufferIndex / vertexStride; var north = rectangle.north; var south = rectangle.south; @@ -293,7 +280,8 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; east += CesiumMath.TWO_PI; } - for (var i = start; i !== end; i += increment) { + var length = edgeVertices.length; + for (var i = 0; i < length; ++i) { var index = edgeVertices[i]; var h = heights[index]; var uv = uvs[index]; @@ -331,22 +319,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; } vertexBufferIndex = encoding.encode(vertexBuffer, vertexBufferIndex, position, uv, cartographicScratch.height, toPack, webMercatorT); - - if (previousIndex !== -1) { - indexBuffer[indexBufferIndex++] = previousIndex; - indexBuffer[indexBufferIndex++] = vertexIndex - 1; - indexBuffer[indexBufferIndex++] = index; - - indexBuffer[indexBufferIndex++] = vertexIndex - 1; - indexBuffer[indexBufferIndex++] = vertexIndex; - indexBuffer[indexBufferIndex++] = index; - } - - previousIndex = index; - ++vertexIndex; } - - return indexBufferIndex; } function copyAndSort(typedArray, comparator) { diff --git a/Source/WorkersES6/upsampleQuantizedTerrainMesh.js b/Source/WorkersES6/upsampleQuantizedTerrainMesh.js index ecfdbc163d28..b1cd8e5789ee 100644 --- a/Source/WorkersES6/upsampleQuantizedTerrainMesh.js +++ b/Source/WorkersES6/upsampleQuantizedTerrainMesh.js @@ -59,7 +59,7 @@ import createTaskProcessorWorker from './createTaskProcessorWorker.js'; var parentVertices = parameters.vertices; var parentIndices = parameters.indices; - parentIndices = parentIndices.subarray(0, parameters.skirtIndex); + parentIndices = parentIndices.subarray(0, parameters.indexCountWithoutSkirts); var encoding = TerrainEncoding.clone(parameters.encoding); var hasVertexNormals = encoding.hasVertexNormals; diff --git a/Specs/Scene/GlobeSpec.js b/Specs/Scene/GlobeSpec.js index fd7bf512354d..d1252af2ce73 100644 --- a/Specs/Scene/GlobeSpec.js +++ b/Specs/Scene/GlobeSpec.js @@ -274,4 +274,22 @@ describe('Scene/Globe', function() { expect(command.renderState.cull.enabled).toBe(false); }); }); + + it('shows terrain skirts', function() { + scene.camera.setView({ destination : new Rectangle(0.0001, 0.0001, 0.0025, 0.0025) }); + + return updateUntilDone(globe).then(function() { + globe.showSkirts = true; + scene.render(); + var command = scene.frameState.commandList[0]; + var indexCount = command.count; + expect(indexCount).toBe(command.owner.data.renderedMesh.indices.length); + + globe.showSkirts = false; + scene.render(); + command = scene.frameState.commandList[0]; + expect(command.count).toBeLessThan(indexCount); + expect(command.count).toBe(command.owner.data.renderedMesh.indexCountWithoutSkirts); + }); + }); }, 'WebGL'); diff --git a/Specs/Scene/HeightmapTessellatorSpec.js b/Specs/Scene/HeightmapTessellatorSpec.js index 30b3becc7d85..74bf0609a112 100644 --- a/Specs/Scene/HeightmapTessellatorSpec.js +++ b/Specs/Scene/HeightmapTessellatorSpec.js @@ -90,6 +90,54 @@ describe('Scene/HeightmapTessellator', function() { }).toThrowDeveloperError(); }); + function checkExpectedVertex(nativeRectangle, i, j, width, height, index, isEdge, vertices, heightmap, ellipsoid, skirtHeight) { + var latitude = CesiumMath.lerp(nativeRectangle.north, nativeRectangle.south, j / (height - 1)); + latitude = CesiumMath.toRadians(latitude); + var longitude = CesiumMath.lerp(nativeRectangle.west, nativeRectangle.east, i / (width - 1)); + longitude = CesiumMath.toRadians(longitude); + + var heightSample = heightmap[j * width + i]; + + if (isEdge) { + heightSample -= skirtHeight; + } + + var expectedVertexPosition = ellipsoid.cartographicToCartesian({ + longitude : longitude, + latitude : latitude, + height : heightSample + }); + + index = index * 6; + var vertexPosition = new Cartesian3(vertices[index], vertices[index + 1], vertices[index + 2]); + + expect(vertexPosition).toEqualEpsilon(expectedVertexPosition, 1.0); + expect(vertices[index + 3]).toEqual(heightSample); + expect(vertices[index + 4]).toEqualEpsilon(i / (width - 1), CesiumMath.EPSILON7); + expect(vertices[index + 5]).toEqualEpsilon(1.0 - j / (height - 1), CesiumMath.EPSILON7); + } + + function checkExpectedQuantizedVertex(nativeRectangle, i, j, width, height, index, isEdge, vertices, heightmap, ellipsoid, skirtHeight, encoding) { + var latitude = CesiumMath.lerp(nativeRectangle.north, nativeRectangle.south, j / (height - 1)); + latitude = CesiumMath.toRadians(latitude); + var longitude = CesiumMath.lerp(nativeRectangle.west, nativeRectangle.east, i / (width - 1)); + longitude = CesiumMath.toRadians(longitude); + + var heightSample = heightmap[j * width + i]; + + if (isEdge) { + heightSample -= skirtHeight; + } + + var expectedVertexPosition = ellipsoid.cartographicToCartesian({ + longitude : longitude, + latitude : latitude, + height : heightSample + }); + + expect(encoding.decodePosition(vertices, index)).toEqualEpsilon(expectedVertexPosition, 1.0); + } + it('creates mesh without skirt', function() { var width = 3; var height = 3; @@ -116,28 +164,11 @@ describe('Scene/HeightmapTessellator', function() { var ellipsoid = Ellipsoid.WGS84; var nativeRectangle = options.nativeRectangle; + var index = 0; + for (var j = 0; j < height; ++j) { - var latitude = CesiumMath.lerp(nativeRectangle.north, nativeRectangle.south, j / (height - 1)); - latitude = CesiumMath.toRadians(latitude); for (var i = 0; i < width; ++i) { - var longitude = CesiumMath.lerp(nativeRectangle.west, nativeRectangle.east, i / (width - 1)); - longitude = CesiumMath.toRadians(longitude); - - var heightSample = options.heightmap[j * width + i]; - - var expectedVertexPosition = ellipsoid.cartographicToCartesian({ - longitude : longitude, - latitude : latitude, - height : heightSample - }); - - var index = (j * width + i) * 6; - var vertexPosition = new Cartesian3(vertices[index], vertices[index + 1], vertices[index + 2]); - - expect(vertexPosition).toEqualEpsilon(expectedVertexPosition, 1.0); - expect(vertices[index + 3]).toEqual(heightSample); - expect(vertices[index + 4]).toEqualEpsilon(i / (width - 1), CesiumMath.EPSILON7); - expect(vertices[index + 5]).toEqualEpsilon(1.0 - j / (height - 1), CesiumMath.EPSILON7); + checkExpectedVertex(nativeRectangle, i, j, width, height, index++, false, vertices, options.heightmap, ellipsoid, options.skirtHeight); } } }); @@ -163,35 +194,35 @@ describe('Scene/HeightmapTessellator', function() { var ellipsoid = Ellipsoid.WGS84; var nativeRectangle = options.nativeRectangle; - for (var j = -1; j <= height; ++j) { - var realJ = CesiumMath.clamp(j, 0, height - 1); - var latitude = CesiumMath.lerp(nativeRectangle.north, nativeRectangle.south, realJ / (height - 1)); - latitude = CesiumMath.toRadians(latitude); - for (var i = -1; i <= width; ++i) { - var realI = CesiumMath.clamp(i, 0, width - 1); - var longitude = CesiumMath.lerp(nativeRectangle.west, nativeRectangle.east, realI / (width - 1)); - longitude = CesiumMath.toRadians(longitude); + var i, j; + var index = 0; - var heightSample = options.heightmap[realJ * width + realI]; + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + checkExpectedVertex(nativeRectangle, i, j, width, height, index++, false, vertices, options.heightmap, ellipsoid, options.skirtHeight); + } + } - if (realI !== i || realJ !== j) { - heightSample -= options.skirtHeight; - } + // Heightmap is expected to be ordered from west to east and north to south, + // so flip i and j depending on how skirts are arranged. + for (j = 0; j < height; ++j) { + // West edge goes from south to north + checkExpectedVertex(nativeRectangle, 0, height - 1 - j, width, height, index++, true, vertices, options.heightmap, ellipsoid, options.skirtHeight); + } - var expectedVertexPosition = ellipsoid.cartographicToCartesian({ - longitude : longitude, - latitude : latitude, - height : heightSample - }); + for (i = 0; i < height; ++i) { + // South edge goes from east to west + checkExpectedVertex(nativeRectangle, width - 1 - i, height - 1, width, height, index++, true, vertices, options.heightmap, ellipsoid, options.skirtHeight); + } - var index = ((j + 1) * (width + 2) + i + 1) * 6; - var vertexPosition = new Cartesian3(vertices[index], vertices[index + 1], vertices[index + 2]); + for (j = 0; j < height; ++j) { + // East edge goes from north to south + checkExpectedVertex(nativeRectangle, width - 1, j, width, height, index++, true, vertices, options.heightmap, ellipsoid, options.skirtHeight); + } - expect(vertexPosition).toEqualEpsilon(expectedVertexPosition, 1.0); - expect(vertices[index + 3]).toEqual(heightSample); - expect(vertices[index + 4]).toEqualEpsilon(realI / (width - 1), CesiumMath.EPSILON7); - expect(vertices[index + 5]).toEqualEpsilon(1.0 - realJ / (height - 1), CesiumMath.EPSILON7); - } + for (i = 0; i < height; ++i) { + // North edge goes from west to east + checkExpectedVertex(nativeRectangle, i, 0, width, height, index++, true, vertices, options.heightmap, ellipsoid, options.skirtHeight); } }); @@ -216,29 +247,35 @@ describe('Scene/HeightmapTessellator', function() { var ellipsoid = Ellipsoid.WGS84; var nativeRectangle = options.nativeRectangle; - for (var j = -1; j <= height; ++j) { - var realJ = CesiumMath.clamp(j, 0, height - 1); - var latitude = CesiumMath.lerp(nativeRectangle.north, nativeRectangle.south, realJ / (height - 1)); - latitude = CesiumMath.toRadians(latitude); - for (var i = -1; i <= width; ++i) { - var realI = CesiumMath.clamp(i, 0, width - 1); - var longitude = CesiumMath.lerp(nativeRectangle.west, nativeRectangle.east, realI / (width - 1)); - longitude = CesiumMath.toRadians(longitude); + var i, j; + var index = 0; - var heightSample = options.heightmap[realJ * width + realI]; + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + checkExpectedQuantizedVertex(nativeRectangle, i, j, width, height, index++, false, vertices, options.heightmap, ellipsoid, options.skirtHeight, results.encoding); + } + } - if (realI !== i || realJ !== j) { - heightSample -= options.skirtHeight; - } + // Heightmap is expected to be ordered from west to east and north to south, + // so flip i and j depending on how skirts are arranged. + for (j = 0; j < height; ++j) { + // West edge goes from south to north + checkExpectedQuantizedVertex(nativeRectangle, 0, height - 1 - j, width, height, index++, true, vertices, options.heightmap, ellipsoid, options.skirtHeight, results.encoding); + } - var index = ((j + 1) * (width + 2) + i + 1); - var expectedVertexPosition = ellipsoid.cartographicToCartesian({ - longitude : longitude, - latitude : latitude, - height : heightSample - }); - expect(results.encoding.decodePosition(vertices, index)).toEqualEpsilon(expectedVertexPosition, 1.0); - } + for (i = 0; i < height; ++i) { + // South edge goes from east to west + checkExpectedQuantizedVertex(nativeRectangle, width - 1 - i, height - 1, width, height, index++, true, vertices, options.heightmap, ellipsoid, options.skirtHeight, results.encoding); + } + + for (j = 0; j < height; ++j) { + // East edge goes from north to south + checkExpectedQuantizedVertex(nativeRectangle, width - 1, j, width, height, index++, true, vertices, options.heightmap, ellipsoid, options.skirtHeight, results.encoding); + } + + for (i = 0; i < height; ++i) { + // North edge goes from west to east + checkExpectedQuantizedVertex(nativeRectangle, i, 0, width, height, index++, true, vertices, options.heightmap, ellipsoid, options.skirtHeight, results.encoding); } });