import { Cartesian3 } from "../../Source/Cesium.js"; import { Cartographic } from "../../Source/Cesium.js"; import { Ellipsoid } from "../../Source/Cesium.js"; import { GeographicTilingScheme } from "../../Source/Cesium.js"; import { GoogleEarthEnterpriseTerrainData } from "../../Source/Cesium.js"; import { Math as CesiumMath } from "../../Source/Cesium.js"; import { Rectangle } from "../../Source/Cesium.js"; import { TerrainData } from "../../Source/Cesium.js"; import { TerrainMesh } from "../../Source/Cesium.js"; import { when } from "../../Source/Cesium.js"; describe("Core/GoogleEarthEnterpriseTerrainData", function () { var sizeOfUint8 = Uint8Array.BYTES_PER_ELEMENT; var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; var toEarthRadii = 1.0 / 6371010.0; function getBuffer(tilingScheme, x, y, level) { var rectangle = tilingScheme.tileXYToRectangle(x, y, level); var center = Rectangle.center(rectangle); var southwest = Rectangle.southwest(rectangle); var stepX = CesiumMath.toDegrees(rectangle.width / 2) / 180.0; var stepY = CesiumMath.toDegrees(rectangle.height / 2) / 180.0; // 2 Uint8s: x and y values in units of step var pointSize = 2 * sizeOfUint8 + sizeOfFloat; // 3 shorts var faceSize = 3 * sizeOfUint16; // Doubles: OriginX, OriginY, SizeX, SizeY // Int32s: numPoints, numFaces, level // 4 corner points // 2 face (3 shorts) var quadSize = 4 * sizeOfDouble + 3 * sizeOfInt32 + 4 * pointSize + 2 * faceSize; // QuadSize + size of each quad var totalSize = 4 * (quadSize + sizeOfUint32); var buf = new ArrayBuffer(totalSize); var dv = new DataView(buf); var altitudeStart = 0; var offset = 0; for (var i = 0; i < 4; ++i) { altitudeStart = 0; dv.setUint32(offset, quadSize, true); offset += sizeOfUint32; // Origin var xOrigin = southwest.longitude; var yOrigin = southwest.latitude; if ((i & 2) !== 0) { // Top row if ((i & 1) === 0) { // NE xOrigin = center.longitude; altitudeStart = 10; } yOrigin = center.latitude; } else if ((i & 1) !== 0) { // SE xOrigin = center.longitude; altitudeStart = 10; } dv.setFloat64(offset, CesiumMath.toDegrees(xOrigin) / 180.0, true); offset += sizeOfDouble; dv.setFloat64(offset, CesiumMath.toDegrees(yOrigin) / 180.0, true); offset += sizeOfDouble; // Step - Each step is a degree dv.setFloat64(offset, stepX, true); offset += sizeOfDouble; dv.setFloat64(offset, stepY, true); offset += sizeOfDouble; // NumPoints dv.setInt32(offset, 4, true); offset += sizeOfInt32; // NumFaces dv.setInt32(offset, 2, true); offset += sizeOfInt32; // Level dv.setInt32(offset, 0, true); offset += sizeOfInt32; // Points var j; for (j = 0; j < 4; ++j) { var xPos = 0; var yPos = 0; var altitude = altitudeStart; if (j & 1) { ++xPos; altitude += 10; } if (j & 2) { ++yPos; } dv.setUint8(offset++, xPos); dv.setUint8(offset++, yPos); dv.setFloat32(offset, altitude * toEarthRadii, true); offset += sizeOfFloat; } // Faces var indices = [0, 1, 2, 1, 3, 2]; for (j = 0; j < indices.length; ++j) { dv.setUint16(offset, indices[j], true); offset += sizeOfUint16; } } return buf; } it("conforms to TerrainData interface", function () { expect(GoogleEarthEnterpriseTerrainData).toConformToInterface(TerrainData); }); describe("upsample", function () { it("works for all four children of a simple quad", function () { var maxShort = 32767; var tilingScheme = new GeographicTilingScheme(); var buffer = getBuffer(tilingScheme, 0, 0, 0); var data = new GoogleEarthEnterpriseTerrainData({ buffer: buffer, childTileMask: 15, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); tilingScheme = new GeographicTilingScheme(); var childRectangles = [ tilingScheme.tileXYToRectangle(0, 0, 1), tilingScheme.tileXYToRectangle(1, 0, 1), tilingScheme.tileXYToRectangle(0, 1, 1), tilingScheme.tileXYToRectangle(1, 1, 1), ]; return when( data.createMesh({ tilingScheme: tilingScheme, x: 0, y: 0, level: 0, }) ) .then(function () { var swPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 0, 1); var sePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 0, 1); var nwPromise = data.upsample(tilingScheme, 0, 0, 0, 0, 1, 1); var nePromise = data.upsample(tilingScheme, 0, 0, 0, 1, 1, 1); return when.join(swPromise, sePromise, nwPromise, nePromise); }) .then(function (upsampleResults) { expect(upsampleResults.length).toBe(4); for (var i = 0; i < upsampleResults.length; ++i) { var upsampled = upsampleResults[i]; expect(upsampled).toBeDefined(); var uBuffer = upsampled._uValues; var vBuffer = upsampled._vValues; var ib = upsampled._indices; var heights = upsampled._heightValues; expect(uBuffer.length).toBe(4); expect(vBuffer.length).toBe(4); expect(heights.length).toBe(4); expect(ib.length).toBe(6); var rectangle = childRectangles[i]; var north = 0; var south = 0; var east = 0; var west = 0; var index, u, v; for (var j = 0; j < ib.length; ++j) { index = ib[j]; u = (uBuffer[index] / maxShort) * rectangle.width + rectangle.west; v = (vBuffer[index] / maxShort) * rectangle.height + rectangle.south; if ( CesiumMath.equalsEpsilon(u, rectangle.west, CesiumMath.EPSILON7) ) { ++west; } else if ( CesiumMath.equalsEpsilon(u, rectangle.east, CesiumMath.EPSILON7) ) { ++east; } if ( CesiumMath.equalsEpsilon( v, rectangle.south, CesiumMath.EPSILON7 ) ) { ++south; } else if ( CesiumMath.equalsEpsilon( v, rectangle.north, CesiumMath.EPSILON7 ) ) { ++north; } } // Each one is made up of 2 triangles expect(north).toEqual(3); expect(south).toEqual(3); expect(east).toEqual(3); expect(west).toEqual(3); // Each side of quad has 2 edge points expect(upsampled._westIndices.length).toEqual(2); expect(upsampled._southIndices.length).toEqual(2); expect(upsampled._eastIndices.length).toEqual(2); expect(upsampled._northIndices.length).toEqual(2); } }); }); }); describe("createMesh", function () { var data; var tilingScheme; var buffer; beforeEach(function () { tilingScheme = new GeographicTilingScheme(); buffer = getBuffer(tilingScheme, 0, 0, 0); data = new GoogleEarthEnterpriseTerrainData({ buffer: buffer, childTileMask: 15, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); }); it("requires tilingScheme", function () { expect(function () { data.createMesh({ tilingScheme: undefined, x: 0, y: 0, level: 0, }); }).toThrowDeveloperError(); }); it("requires x", function () { expect(function () { data.createMesh({ tilingScheme: tilingScheme, x: undefined, y: 0, level: 0, }); }).toThrowDeveloperError(); }); it("requires y", function () { expect(function () { data.createMesh({ tilingScheme: tilingScheme, x: 0, y: undefined, level: 0, }); }).toThrowDeveloperError(); }); it("requires level", function () { expect(function () { data.createMesh({ tilingScheme: tilingScheme, x: 0, y: 0, level: undefined, }); }).toThrowDeveloperError(); }); it("creates specified vertices plus skirt vertices", function () { var rectangle = tilingScheme.tileXYToRectangle(0, 0, 0); var wgs84 = Ellipsoid.WGS84; return data .createMesh({ tilingScheme: tilingScheme, x: 0, y: 0, level: 0, }) .then(function (mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); expect(mesh.vertices.length).toBe(17 * mesh.encoding.getStride()); // 9 regular + 8 skirt vertices expect(mesh.indices.length).toBe(4 * 6 * 3); // 2 regular + 4 skirt triangles per quad expect(mesh.minimumHeight).toBe(0); expect(mesh.maximumHeight).toBeCloseTo(20, 5); var encoding = mesh.encoding; var cartesian = new Cartesian3(); var cartographic = new Cartographic(); var count = mesh.vertices.length / mesh.encoding.getStride(); for (var i = 0; i < count; ++i) { var height = encoding.decodeHeight(mesh.vertices, i); if (i < 9) { // Original vertices expect(height).toBeBetween(0, 20); // Only test on original positions as the skirts angle outward encoding.decodePosition(mesh.vertices, i, cartesian); wgs84.cartesianToCartographic(cartesian, cartographic); cartographic.longitude = CesiumMath.convertLongitudeRange( cartographic.longitude ); expect(Rectangle.contains(rectangle, cartographic)).toBe(true); } else { // Skirts expect(height).toBeBetween(-1000, -980); } } }); }); it("exaggerates mesh", function () { return data .createMesh({ tilingScheme: tilingScheme, x: 0, y: 0, level: 0, exaggeration: 2, }) .then(function (mesh) { expect(mesh).toBeInstanceOf(TerrainMesh); expect(mesh.vertices.length).toBe(17 * mesh.encoding.getStride()); // 9 regular + 8 skirt vertices expect(mesh.indices.length).toBe(4 * 6 * 3); // 2 regular + 4 skirt triangles per quad expect(mesh.minimumHeight).toBe(0); expect(mesh.maximumHeight).toBeCloseTo(40, 5); var encoding = mesh.encoding; var count = mesh.vertices.length / mesh.encoding.getStride(); for (var i = 0; i < count; ++i) { var height = encoding.decodeHeight(mesh.vertices, i); if (i < 9) { // Original vertices expect(height).toBeBetween(0, 40); } else { // Skirts expect(height).toBeBetween(-1000, -960); } } }); }); }); describe("interpolateHeight", function () { var tilingScheme; var rectangle; var mesh; beforeEach(function () { tilingScheme = new GeographicTilingScheme(); rectangle = tilingScheme.tileXYToRectangle(7, 6, 5); var buffer = getBuffer(tilingScheme, 7, 6, 5); mesh = new GoogleEarthEnterpriseTerrainData({ buffer: buffer, childTileMask: 15, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); }); it("clamps coordinates if given a position outside the mesh", function () { expect(mesh.interpolateHeight(rectangle, 0.0, 0.0)).toBe( mesh.interpolateHeight(rectangle, rectangle.east, rectangle.south) ); }); it("returns a height interpolated from the correct triangle", function () { // position in the northwest quadrant of the tile. var longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.25; var latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.75; var result = mesh.interpolateHeight(rectangle, longitude, latitude); expect(result).toBeBetween(0.0, 10.0); // position in the southeast quadrant of the tile. longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.75; latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.25; result = mesh.interpolateHeight(rectangle, longitude, latitude); expect(result).toBeBetween(10.0, 20.0); // position on the line between the southwest and northeast corners. longitude = rectangle.west + (rectangle.east - rectangle.west) * 0.5; latitude = rectangle.south + (rectangle.north - rectangle.south) * 0.5; result = mesh.interpolateHeight(rectangle, longitude, latitude); expect(result).toEqualEpsilon(10.0, 1e-6); }); }); describe("isChildAvailable", function () { var data; beforeEach(function () { data = new GoogleEarthEnterpriseTerrainData({ buffer: new ArrayBuffer(1), childTileMask: 15, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); }); it("requires thisX", function () { expect(function () { data.isChildAvailable(undefined, 0, 0, 0); }).toThrowDeveloperError(); }); it("requires thisY", function () { expect(function () { data.isChildAvailable(0, undefined, 0, 0); }).toThrowDeveloperError(); }); it("requires childX", function () { expect(function () { data.isChildAvailable(0, 0, undefined, 0); }).toThrowDeveloperError(); }); it("requires childY", function () { expect(function () { data.isChildAvailable(0, 0, 0, undefined); }).toThrowDeveloperError(); }); it("returns true for all children when child mask is not explicitly specified", function () { data = new GoogleEarthEnterpriseTerrainData({ buffer: new ArrayBuffer(1), negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); expect(data.isChildAvailable(10, 20, 20, 40)).toBe(true); expect(data.isChildAvailable(10, 20, 21, 40)).toBe(true); expect(data.isChildAvailable(10, 20, 20, 41)).toBe(true); expect(data.isChildAvailable(10, 20, 21, 41)).toBe(true); }); it("works when only southwest child is available", function () { data = new GoogleEarthEnterpriseTerrainData({ buffer: new ArrayBuffer(1), childTileMask: 1, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); expect(data.isChildAvailable(10, 20, 20, 40)).toBe(false); expect(data.isChildAvailable(10, 20, 21, 40)).toBe(false); expect(data.isChildAvailable(10, 20, 20, 41)).toBe(true); expect(data.isChildAvailable(10, 20, 21, 41)).toBe(false); }); it("works when only southeast child is available", function () { data = new GoogleEarthEnterpriseTerrainData({ buffer: new ArrayBuffer(1), childTileMask: 2, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); expect(data.isChildAvailable(10, 20, 20, 40)).toBe(false); expect(data.isChildAvailable(10, 20, 21, 40)).toBe(false); expect(data.isChildAvailable(10, 20, 20, 41)).toBe(false); expect(data.isChildAvailable(10, 20, 21, 41)).toBe(true); }); it("works when only northeast child is available", function () { data = new GoogleEarthEnterpriseTerrainData({ buffer: new ArrayBuffer(1), childTileMask: 4, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); expect(data.isChildAvailable(10, 20, 20, 40)).toBe(false); expect(data.isChildAvailable(10, 20, 21, 40)).toBe(true); expect(data.isChildAvailable(10, 20, 20, 41)).toBe(false); expect(data.isChildAvailable(10, 20, 21, 41)).toBe(false); }); it("works when only northwest child is available", function () { data = new GoogleEarthEnterpriseTerrainData({ buffer: new ArrayBuffer(1), childTileMask: 8, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); expect(data.isChildAvailable(10, 20, 20, 40)).toBe(true); expect(data.isChildAvailable(10, 20, 21, 40)).toBe(false); expect(data.isChildAvailable(10, 20, 20, 41)).toBe(false); expect(data.isChildAvailable(10, 20, 21, 41)).toBe(false); }); }); it("requires buffer", function () { expect(function () { /*eslint-disable no-unused-vars*/ var data = new GoogleEarthEnterpriseTerrainData({ childTileMask: 8, negativeAltitudeExponentBias: 32, negativeElevationThreshold: CesiumMath.EPSILON12, }); /*eslint-enable no-unused-vars*/ }).toThrowDeveloperError(); }); it("requires negativeAltitudeExponentBias", function () { expect(function () { /*eslint-disable no-unused-vars*/ var data = new GoogleEarthEnterpriseTerrainData({ buffer: new ArrayBuffer(1), childTileMask: 8, negativeElevationThreshold: CesiumMath.EPSILON12, }); /*eslint-enable no-unused-vars*/ }).toThrowDeveloperError(); }); it("requires negativeElevationThreshold", function () { expect(function () { /*eslint-disable no-unused-vars*/ var data = new GoogleEarthEnterpriseTerrainData({ buffer: new ArrayBuffer(1), childTileMask: 8, negativeAltitudeExponentBias: 32, }); /*eslint-enable no-unused-vars*/ }).toThrowDeveloperError(); }); });