import MockTerrainProvider from "../MockTerrainProvider.js"; import TerrainTileProcessor from "../TerrainTileProcessor.js"; import { Cartesian2 } from "../../Source/Cesium.js"; import { Cartesian3 } from "../../Source/Cesium.js"; import { GeographicProjection } from "../../Source/Cesium.js"; import { HeightmapTerrainData } from "../../Source/Cesium.js"; import { Intersect } from "../../Source/Cesium.js"; import { Math as CesiumMath } from "../../Source/Cesium.js"; import { Camera } from "../../Source/Cesium.js"; import { GlobeSurfaceTileProvider } from "../../Source/Cesium.js"; import { ImageryLayerCollection } from "../../Source/Cesium.js"; import { QuadtreePrimitive } from "../../Source/Cesium.js"; import { SceneMode } from "../../Source/Cesium.js"; import { TerrainFillMesh } from "../../Source/Cesium.js"; import { TileBoundingRegion } from "../../Source/Cesium.js"; import { TileSelectionResult } from "../../Source/Cesium.js"; describe("Scene/TerrainFillMesh", function () { var processor; var scene; var camera; var frameState; var imageryLayerCollection; var surfaceShaderSet; var mockTerrain; var tileProvider; var quadtree; var rootTiles; var center; var west; var south; var east; var north; var southwest; var southeast; var northwest; var northeast; beforeEach(function () { scene = { mapProjection: new GeographicProjection(), drawingBufferWidth: 1000, drawingBufferHeight: 1000, }; camera = new Camera(scene); frameState = { frameNumber: 0, passes: { render: true, }, camera: camera, fog: { enabled: false, }, context: { drawingBufferWidth: scene.drawingBufferWidth, drawingBufferHeight: scene.drawingBufferHeight, }, mode: SceneMode.SCENE3D, commandList: [], cullingVolume: jasmine.createSpyObj("CullingVolume", [ "computeVisibility", ]), afterRender: [], }; frameState.cullingVolume.computeVisibility.and.returnValue( Intersect.INTERSECTING ); imageryLayerCollection = new ImageryLayerCollection(); surfaceShaderSet = jasmine.createSpyObj("SurfaceShaderSet", [ "getShaderProgram", ]); mockTerrain = new MockTerrainProvider(); tileProvider = new GlobeSurfaceTileProvider({ terrainProvider: mockTerrain, imageryLayers: imageryLayerCollection, surfaceShaderSet: surfaceShaderSet, }); quadtree = new QuadtreePrimitive({ tileProvider: tileProvider, }); processor = new TerrainTileProcessor( frameState, mockTerrain, imageryLayerCollection ); processor.mockWebGL(); quadtree.render(frameState); rootTiles = quadtree._levelZeroTiles; center = rootTiles[0].northeastChild.southwestChild.northeastChild.southwestChild; west = center.findTileToWest(rootTiles); south = center.findTileToSouth(rootTiles); east = center.findTileToEast(rootTiles); north = center.findTileToNorth(rootTiles); southwest = west.findTileToSouth(rootTiles); southeast = east.findTileToSouth(rootTiles); northwest = west.findTileToNorth(rootTiles); northeast = east.findTileToNorth(rootTiles); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 15.0, 16.0, 17.0, 22.0, 23.0, 24.0, 29.0, 30.0, 31.0, ]), }), west ) .createMeshWillSucceed(west); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 31.0, 32.0, 33.0, 38.0, 39.0, 40.0, 45.0, 46.0, 47.0, ]), }), south ) .createMeshWillSucceed(south); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 19.0, 20.0, 21.0, 26.0, 27.0, 28.0, 33.0, 34.0, 35.0, ]), }), east ) .createMeshWillSucceed(east); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 3.0, 4.0, 5.0, 10.0, 11.0, 12.0, 17.0, 18.0, 19.0, ]), }), north ) .createMeshWillSucceed(north); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 29.0, 30.0, 31.0, 36.0, 37.0, 38.0, 43.0, 44.0, 45.0, ]), }), southwest ) .createMeshWillSucceed(southwest); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 33.0, 34.0, 35.0, 40.0, 41.0, 42.0, 47.0, 48.0, 49.0, ]), }), southeast ) .createMeshWillSucceed(southeast); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 1.0, 2.0, 3.0, 8.0, 9.0, 10.0, 15.0, 16.0, 17.0, ]), }), northwest ) .createMeshWillSucceed(northwest); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 19.0, 20.0, 21.0, ]), }), northeast ) .createMeshWillSucceed(northeast); }); describe("updateFillTiles", function () { it("does nothing if no rendered tiles are provided", function () { expect(function () { TerrainFillMesh.updateFillTiles(tileProvider, [], frameState); }).not.toThrow(); }); it("propagates edges and corners to an unloaded tile", function () { var tiles = [ center, west, south, east, north, southwest, southeast, northwest, northeast, ]; tiles.forEach(mockTerrain.createMeshWillSucceed.bind(mockTerrain)); return processor.process(tiles).then(function () { tiles.forEach(markRendered); TerrainFillMesh.updateFillTiles(tileProvider, tiles, frameState); var fill = center.data.fill; expect(fill).toBeDefined(); expect(fill.westTiles).toEqual([west]); expect(fill.westMeshes).toEqual([west.data.mesh]); expect(fill.southTiles).toEqual([south]); expect(fill.southMeshes).toEqual([south.data.mesh]); expect(fill.eastTiles).toEqual([east]); expect(fill.eastMeshes).toEqual([east.data.mesh]); expect(fill.northTiles).toEqual([north]); expect(fill.northMeshes).toEqual([north.data.mesh]); expect(fill.southwestTile).toEqual(southwest); expect(fill.southwestMesh).toEqual(southwest.data.mesh); expect(fill.southeastTile).toEqual(southeast); expect(fill.southeastMesh).toEqual(southeast.data.mesh); expect(fill.northwestTile).toEqual(northwest); expect(fill.northwestMesh).toEqual(northwest.data.mesh); expect(fill.northeastTile).toEqual(northeast); expect(fill.northeastMesh).toEqual(northeast.data.mesh); }); }); it("propagates fill tile edges to an unloaded tile", function () { var centerSW = center.southwestChild; var centerSE = center.southeastChild; var centerNW = center.northwestChild; var centerNE = center.northeastChild; var tiles = [ centerSW, centerSE, centerNW, centerNE, west, south, east, north, southwest, southeast, northwest, northeast, ]; tiles.forEach(mockTerrain.createMeshWillSucceed.bind(mockTerrain)); return processor.process(tiles).then(function () { tiles.forEach(markRendered); TerrainFillMesh.updateFillTiles(tileProvider, tiles, frameState); var sw = centerSW.data.fill; var se = centerSE.data.fill; var nw = centerNW.data.fill; var ne = centerNE.data.fill; expect(sw).toBeDefined(); expect(sw.westTiles).toEqual([west]); expect(sw.westMeshes).toEqual([west.data.mesh]); expect(sw.southTiles).toEqual([south]); expect(sw.southMeshes).toEqual([south.data.mesh]); expect(sw.southwestTile).toEqual(southwest); expect(sw.southwestMesh).toEqual(southwest.data.mesh); expect(sw.southeastTile).toBeUndefined(); expect(sw.southeastMesh).toBeUndefined(); expect(sw.northwestTile).toBeUndefined(); expect(sw.northwestMesh).toBeUndefined(); expect(se).toBeDefined(); expect(se.eastTiles).toEqual([east]); expect(se.eastMeshes).toEqual([east.data.mesh]); expect(se.southTiles).toEqual([south]); expect(se.southMeshes).toEqual([south.data.mesh]); expect(se.southeastTile).toEqual(southeast); expect(se.southeastMesh).toEqual(southeast.data.mesh); expect(se.southwestTile).toBeUndefined(); expect(se.southwestMesh).toBeUndefined(); expect(se.northeastTile).toBeUndefined(); expect(se.northeastMesh).toBeUndefined(); expect(nw).toBeDefined(); expect(nw.westTiles).toEqual([west]); expect(nw.westMeshes).toEqual([west.data.mesh]); expect(nw.northTiles).toEqual([north]); expect(nw.northMeshes).toEqual([north.data.mesh]); expect(nw.northwestTile).toEqual(northwest); expect(nw.northwestMesh).toEqual(northwest.data.mesh); expect(nw.southwestTile).toBeUndefined(); expect(nw.southwestMesh).toBeUndefined(); expect(nw.northeastTile).toBeUndefined(); expect(nw.northeastMesh).toBeUndefined(); expect(ne).toBeDefined(); expect(ne.eastTiles).toEqual([east]); expect(ne.eastMeshes).toEqual([east.data.mesh]); expect(ne.northTiles).toEqual([north]); expect(ne.northMeshes).toEqual([north.data.mesh]); expect(ne.northeastTile).toEqual(northeast); expect(ne.northeastMesh).toEqual(northeast.data.mesh); expect(ne.southeastTile).toBeUndefined(); expect(ne.southeastMesh).toBeUndefined(); expect(ne.northwestTile).toBeUndefined(); expect(ne.northwestMesh).toBeUndefined(); expect( sw.eastTiles[0] === centerSE || se.westTiles[0] === centerSW ).toBe(true); expect( nw.eastTiles[0] === centerNE || ne.westTiles[0] === centerNW ).toBe(true); expect( sw.northTiles[0] === centerNW || nw.southTiles[0] === centerSW ).toBe(true); expect( se.northTiles[0] === centerNE || ne.southTiles[0] === centerSE ).toBe(true); expect( sw.northeastTile === centerNE || ne.southwestTile === centerSW ).toBe(true); expect( nw.southeastTile === centerSE || se.northwestTile === centerNW ).toBe(true); }); }); it("does not touch disconnected tiles", function () { var disconnected = center.southwestChild.northeastChild; var tiles = [ disconnected, west, south, east, north, southwest, southeast, northwest, northeast, ]; tiles.forEach(mockTerrain.createMeshWillSucceed.bind(mockTerrain)); return processor.process(tiles).then(function () { tiles.forEach(markRendered); TerrainFillMesh.updateFillTiles(tileProvider, tiles, frameState); expect(disconnected.data.fill).toBeUndefined(); }); }); it("propagates multiple adjacent source tiles to a destination edge", function () { var tiles = [center, west, south, east, north]; [west, south, east, north].forEach(function (tile) { tile.children.forEach(function (child) { mockTerrain.willBeUnavailable(child); mockTerrain.upsampleWillSucceed(child); tiles.push(child); }); }); return processor.process(tiles).then(function () { tiles.forEach(markRendered); TerrainFillMesh.updateFillTiles(tileProvider, tiles, frameState); var fill = center.data.fill; expect(fill).toBeDefined(); expect(fill.westTiles).toEqual([ west.northeastChild, west.southeastChild, ]); expect(fill.southTiles).toEqual([ south.northwestChild, south.northeastChild, ]); expect(fill.eastTiles).toEqual([ east.southwestChild, east.northwestChild, ]); expect(fill.northTiles).toEqual([ north.southeastChild, north.southwestChild, ]); }); }); it("adjusts existing fill tiles when adjacent tiles are loaded", function () { var tiles = [center, west, south, north]; tiles.forEach(mockTerrain.createMeshWillSucceed.bind(mockTerrain)); return processor .process(tiles) .then(function () { tiles.forEach(markRendered); TerrainFillMesh.updateFillTiles(tileProvider, tiles, frameState); var fill = center.data.fill; expect(fill).toBeDefined(); expect(fill.westTiles).toEqual([west]); expect(fill.westMeshes).toEqual([west.data.mesh]); expect(fill.southTiles).toEqual([south]); expect(fill.southMeshes).toEqual([south.data.mesh]); expect(fill.eastTiles).toEqual([]); expect(fill.eastMeshes).toEqual([]); expect(fill.northTiles).toEqual([north]); expect(fill.northMeshes).toEqual([north.data.mesh]); fill.update(tileProvider, frameState); expectVertexCount(fill, 8); tiles.push(east); return processor.process(tiles); }) .then(function () { tiles.forEach(markRendered); TerrainFillMesh.updateFillTiles(tileProvider, tiles, frameState); var fill = center.data.fill; expect(fill).toBeDefined(); expect(fill.westTiles).toEqual([west]); expect(fill.westMeshes).toEqual([west.data.mesh]); expect(fill.southTiles).toEqual([south]); expect(fill.southMeshes).toEqual([south.data.mesh]); expect(fill.eastTiles).toEqual([east]); expect(fill.eastMeshes).toEqual([east.data.mesh]); expect(fill.northTiles).toEqual([north]); expect(fill.northMeshes).toEqual([north.data.mesh]); fill.update(tileProvider, frameState); expectVertexCount(fill, 9); expectVertex(fill, 1.0, 0.5, 26.0); }); }); it("adjusts existing fill tiles when an adjacent fill tile changes", function () { var dontLoad = [east, south, southeast]; dontLoad.forEach( mockTerrain.requestTileGeometryWillDefer.bind(mockTerrain) ); var tiles = [ center, west, south, east, north, southwest, southeast, northwest, northeast, ]; return processor .process(tiles) .then(function () { tiles.forEach(markRendered); TerrainFillMesh.updateFillTiles(tileProvider, tiles, frameState); center.data.fill.update(tileProvider, frameState); south.data.fill.update(tileProvider, frameState); east.data.fill.update(tileProvider, frameState); southeast.data.fill.update(tileProvider, frameState); expectVertexCount(center.data.fill, 7); expectVertex(center.data.fill, 0.0, 0.0, 31.0); expectVertex(center.data.fill, 0.0, 0.5, 24.0); expectVertex(center.data.fill, 0.0, 1.0, 17.0); expectVertex(center.data.fill, 0.5, 1.0, 18.0); expectVertex(center.data.fill, 1.0, 1.0, 19.0); expectVertexCount(south.data.fill, 6); expectVertex(south.data.fill, 0.0, 1.0, 31.0); expectVertex(south.data.fill, 0.0, 0.5, 38.0); expectVertex(south.data.fill, 0.0, 0.0, 45.0); expectVertexCount(east.data.fill, 6); expectVertex(east.data.fill, 0.0, 1.0, 19.0); expectVertex(east.data.fill, 0.5, 1.0, 20.0); expectVertex(east.data.fill, 1.0, 1.0, 21.0); expectVertexCount(southeast.data.fill, 5); expect(getHeight(center.data.fill, 1.0, 0.0)).toBe( getHeight(southeast.data.fill, 0.0, 1.0) ); expect(getHeight(center.data.fill, 1.0, 0.0)).toBe( getHeight(south.data.fill, 1.0, 1.0) ); expect(getHeight(center.data.fill, 1.0, 0.0)).toBe( getHeight(east.data.fill, 0.0, 0.0) ); expect(getHeight(center.data.fill, 1.0, 1.0)).toBe( getHeight(east.data.fill, 0.0, 1.0) ); expect(getHeight(east.data.fill, 1.0, 0.0)).toBe( getHeight(southeast.data.fill, 1.0, 1.0) ); expect(getHeight(south.data.fill, 1.0, 0.0)).toBe( getHeight(southeast.data.fill, 0.0, 0.0) ); // Now load the south tile. mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 31.0, 32.0, 33.0, 38.0, 39.0, 40.0, 45.0, 46.0, 47.0, ]), }), south ) .createMeshWillSucceed(south); return processor.process(tiles); }) .then(function () { tiles.forEach(markRendered); TerrainFillMesh.updateFillTiles(tileProvider, tiles, frameState); center.data.fill.update(tileProvider, frameState); east.data.fill.update(tileProvider, frameState); southeast.data.fill.update(tileProvider, frameState); expect(south.data.fill).toBeUndefined(); expectVertexCount(center.data.fill, 8); expectVertex(center.data.fill, 0.0, 0.0, 31.0); expectVertex(center.data.fill, 0.0, 0.5, 24.0); expectVertex(center.data.fill, 0.0, 1.0, 17.0); expectVertex(center.data.fill, 0.5, 1.0, 18.0); expectVertex(center.data.fill, 1.0, 1.0, 19.0); expectVertex(center.data.fill, 1.0, 0.0, 33.0); expectVertexCount(east.data.fill, 6); expectVertex(east.data.fill, 0.0, 1.0, 19.0); expectVertex(east.data.fill, 0.5, 1.0, 20.0); expectVertex(east.data.fill, 1.0, 1.0, 21.0); expectVertex(east.data.fill, 0.0, 0.0, 33.0); expectVertexCount(southeast.data.fill, 6); expectVertex(southeast.data.fill, 0.0, 0.0, 47.0); expectVertex(southeast.data.fill, 0.0, 0.5, 40.0); expectVertex(southeast.data.fill, 0.0, 1.0, 33.0); expect(getHeight(east.data.fill, 1.0, 0.0)).toBe( getHeight(southeast.data.fill, 1.0, 1.0) ); }); }); // Mark all the tiles rendered. function markRendered(tile) { quadtree._lastSelectionFrameNumber = frameState.frameNumber; tile._lastSelectionResultFrame = frameState.frameNumber; tile._lastSelectionResult = TileSelectionResult.RENDERED; var parent = tile.parent; while (parent) { if (parent._lastSelectionResultFrame !== frameState.frameNumber) { parent._lastSelectionResultFrame = frameState.frameNumber; parent._lastSelectionResult = TileSelectionResult.REFINED; } parent = parent.parent; } } }); describe("update", function () { it("puts a middle height at the four corners and center when there are no adjacent tiles", function () { return processor.process([center]).then(function () { center.data.tileBoundingRegion = new TileBoundingRegion({ rectangle: center.rectangle, minimumHeight: 1.0, maximumHeight: 3.0, computeBoundingVolumes: false, }); var fill = (center.data.fill = new TerrainFillMesh(center)); fill.update(tileProvider, frameState); expectVertexCount(fill, 5); expectVertex(fill, 0.0, 0.0, 2.0); expectVertex(fill, 0.0, 1.0, 2.0); expectVertex(fill, 1.0, 0.0, 2.0); expectVertex(fill, 1.0, 1.0, 2.0); expectVertex(fill, 0.5, 0.5, 2.0); }); }); it("puts zero height at the four corners and center when there are no adjacent tiles and no bounding region", function () { return processor.process([center]).then(function () { var fill = (center.data.fill = new TerrainFillMesh(center)); fill.update(tileProvider, frameState); expectVertexCount(fill, 5); expectVertex(fill, 0.0, 0.0, 0.0); expectVertex(fill, 0.0, 1.0, 0.0); expectVertex(fill, 1.0, 0.0, 0.0); expectVertex(fill, 1.0, 1.0, 0.0); expectVertex(fill, 0.5, 0.5, 0.0); }); }); it("uses adjacent edge heights", function () { return processor .process([center, west, south, east, north]) .then(function () { var fill = (center.data.fill = new TerrainFillMesh(center)); fill.westTiles.push(west); fill.westMeshes.push(west.data.mesh); fill.southTiles.push(south); fill.southMeshes.push(south.data.mesh); fill.eastTiles.push(east); fill.eastMeshes.push(east.data.mesh); fill.northTiles.push(north); fill.northMeshes.push(north.data.mesh); fill.update(tileProvider, frameState); expectVertexCount(fill, 9); expectVertex(fill, 0.0, 0.0, 31.0); expectVertex(fill, 0.5, 0.0, 32.0); expectVertex(fill, 1.0, 0.0, 33.0); expectVertex(fill, 0.0, 0.5, 24.0); expectVertex(fill, 0.5, 0.5, (33.0 + 17.0) / 2); expectVertex(fill, 1.0, 0.5, 26.0); expectVertex(fill, 0.0, 1.0, 17.0); expectVertex(fill, 0.5, 1.0, 18.0); expectVertex(fill, 1.0, 1.0, 19.0); }); }); it("uses adjacent corner heights if adjacent edges are not available", function () { return processor .process([center, southwest, southeast, northwest, northeast]) .then(function () { var fill = (center.data.fill = new TerrainFillMesh(center)); fill.southwestTile = southwest; fill.southwestMesh = southwest.data.mesh; fill.southeastTile = southeast; fill.southeastMesh = southeast.data.mesh; fill.northwestTile = northwest; fill.northwestMesh = northwest.data.mesh; fill.northeastTile = northeast; fill.northeastMesh = northeast.data.mesh; fill.update(tileProvider, frameState); expectVertexCount(fill, 5); expectVertex(fill, 0.0, 0.0, 31.0); expectVertex(fill, 1.0, 0.0, 33.0); expectVertex(fill, 0.0, 1.0, 17.0); expectVertex(fill, 1.0, 1.0, 19.0); expectVertex(fill, 0.5, 0.5, (17.0 + 33.0) / 2.0); }); }); it("finds a suitable corner vertex in a less detailed tile", function () { var sw = center.southwestChild; var se = center.southeastChild; var nw = center.northwestChild; var ne = center.northeastChild; return processor .process([sw, se, nw, ne, west, south, east, north]) .then(function () { var fillSW = (sw.data.fill = new TerrainFillMesh(sw)); var fillSE = (se.data.fill = new TerrainFillMesh(se)); var fillNW = (nw.data.fill = new TerrainFillMesh(nw)); var fillNE = (ne.data.fill = new TerrainFillMesh(ne)); fillSW.westTiles.push(west); fillSW.westMeshes.push(west.data.mesh); fillSW.southTiles.push(south); fillSW.southMeshes.push(south.data.mesh); fillSE.eastTiles.push(east); fillSE.eastMeshes.push(east.data.mesh); fillSE.southTiles.push(south); fillSE.southMeshes.push(south.data.mesh); fillNW.westTiles.push(west); fillNW.westMeshes.push(west.data.mesh); fillNW.northTiles.push(north); fillNW.northMeshes.push(north.data.mesh); fillNE.eastTiles.push(east); fillNE.eastMeshes.push(east.data.mesh); fillNE.northTiles.push(north); fillNE.northMeshes.push(north.data.mesh); fillSW.update(tileProvider, frameState); fillSE.update(tileProvider, frameState); fillNW.update(tileProvider, frameState); fillNE.update(tileProvider, frameState); expectVertexCount(fillSW, 5); expectVertex(fillSW, 0.0, 0.0, 31.0); expectVertex(fillSW, 1.0, 0.0, 32.0); expectVertex(fillSW, 0.0, 1.0, 24.0); expectVertex(fillSW, 1.0, 1.0, (24.0 + 32.0) / 2); expectVertex(fillSW, 0.5, 0.5, (24.0 + 32.0) / 2); expectVertexCount(fillSE, 5); expectVertex(fillSE, 0.0, 0.0, 32.0); expectVertex(fillSE, 1.0, 0.0, 33.0); expectVertex(fillSE, 0.0, 1.0, (32.0 + 26.0) / 2); expectVertex(fillSE, 1.0, 1.0, 26.0); expectVertex(fillSE, 0.5, 0.5, (26.0 + 33.0) / 2); expectVertexCount(fillNW, 5); expectVertex(fillNW, 0.0, 0.0, 24.0); expectVertex(fillNW, 1.0, 0.0, (18.0 + 24.0) / 2); expectVertex(fillNW, 0.0, 1.0, 17.0); expectVertex(fillNW, 1.0, 1.0, 18.0); expectVertex(fillNW, 0.5, 0.5, (17.0 + 24.0) / 2); expectVertexCount(fillNE, 5); expectVertex(fillNE, 0.0, 0.0, (18.0 + 26.0) / 2); expectVertex(fillNE, 1.0, 0.0, 26.0); expectVertex(fillNE, 0.0, 1.0, 18.0); expectVertex(fillNE, 1.0, 1.0, 19.0); expectVertex(fillNE, 0.5, 0.5, (18.0 + 26.0) / 2); }); }); it("interpolates a suitable corner vertex from a less detailed tile", function () { var sw = center.southwestChild.southwestChild; var se = center.southeastChild.southeastChild; var nw = center.northwestChild.northwestChild; var ne = center.northeastChild.northeastChild; return processor .process([sw, se, nw, ne, west, south, east, north]) .then(function () { var fillSW = (sw.data.fill = new TerrainFillMesh(sw)); var fillSE = (se.data.fill = new TerrainFillMesh(se)); var fillNW = (nw.data.fill = new TerrainFillMesh(nw)); var fillNE = (ne.data.fill = new TerrainFillMesh(ne)); fillSW.westTiles.push(west); fillSW.westMeshes.push(west.data.mesh); fillSW.southTiles.push(south); fillSW.southMeshes.push(south.data.mesh); fillSE.eastTiles.push(east); fillSE.eastMeshes.push(east.data.mesh); fillSE.southTiles.push(south); fillSE.southMeshes.push(south.data.mesh); fillNW.westTiles.push(west); fillNW.westMeshes.push(west.data.mesh); fillNW.northTiles.push(north); fillNW.northMeshes.push(north.data.mesh); fillNE.eastTiles.push(east); fillNE.eastMeshes.push(east.data.mesh); fillNE.northTiles.push(north); fillNE.northMeshes.push(north.data.mesh); fillSW.update(tileProvider, frameState); fillSE.update(tileProvider, frameState); fillNW.update(tileProvider, frameState); fillNE.update(tileProvider, frameState); expectVertexCount(fillSW, 5); expectVertex(fillSW, 0.0, 0.0, 31.0); expectVertex(fillSW, 1.0, 0.0, (31.0 + 32.0) / 2); expectVertex(fillSW, 0.0, 1.0, (31.0 + 24.0) / 2); expectVertex( fillSW, 1.0, 1.0, ((31.0 + 32.0) / 2 + (31.0 + 24.0) / 2) / 2 ); expectVertex( fillSW, 0.5, 0.5, ((31.0 + 32.0) / 2 + (31.0 + 24.0) / 2) / 2 ); expectVertexCount(fillSE, 5); expectVertex(fillSE, 0.0, 0.0, (32.0 + 33.0) / 2); expectVertex(fillSE, 1.0, 0.0, 33.0); expectVertex( fillSE, 0.0, 1.0, ((32.0 + 33.0) / 2 + (33.0 + 26.0) / 2) / 2 ); expectVertex(fillSE, 1.0, 1.0, (33.0 + 26.0) / 2); expectVertex(fillSE, 0.5, 0.5, (33.0 + (33.0 + 26.0) / 2) / 2); expectVertexCount(fillNW, 5); expectVertex(fillNW, 0.0, 0.0, (17.0 + 24.0) / 2); expectVertex( fillNW, 1.0, 0.0, ((17.0 + 18.0) / 2 + (17.0 + 24.0) / 2) / 2 ); expectVertex(fillNW, 0.0, 1.0, 17.0); expectVertex(fillNW, 1.0, 1.0, (17.0 + 18.0) / 2); expectVertex(fillNW, 0.5, 0.5, (17.0 + (17.0 + 24.0) / 2) / 2); expectVertexCount(fillNE, 5); expectVertex( fillNE, 0.0, 0.0, ((19.0 + 26.0) / 2 + (18.0 + 19.0) / 2) / 2 ); expectVertex(fillNE, 1.0, 0.0, (19.0 + 26.0) / 2); expectVertex(fillNE, 0.0, 1.0, (18.0 + 19.0) / 2); expectVertex(fillNE, 1.0, 1.0, 19.0); expectVertex( fillNE, 0.5, 0.5, ((18.0 + 19.0) / 2 + (19.0 + 26.0) / 2) / 2 ); }); }); it("uses the height of the closest vertex when an edge does not include the corner", function () { var westN = west.northeastChild.southeastChild; var westS = west.southeastChild.northeastChild; var eastN = east.northwestChild.southwestChild; var eastS = east.southwestChild.northwestChild; var northW = north.southwestChild.southeastChild; var northE = north.southeastChild.southwestChild; var southW = south.northwestChild.northeastChild; var southE = south.northeastChild.northwestChild; mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 2, height: 2, createdByUpsampling: false, buffer: new Float32Array([1.0, 1.0, 1.5, 1.5]), }), westN ) .createMeshWillSucceed(westN); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 2, height: 2, createdByUpsampling: false, buffer: new Float32Array([1.5, 1.5, 2.0, 2.0]), }), westS ) .createMeshWillSucceed(westS); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 2, height: 2, createdByUpsampling: false, buffer: new Float32Array([3.0, 3.0, 3.5, 3.5]), }), eastN ) .createMeshWillSucceed(eastN); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 2, height: 2, createdByUpsampling: false, buffer: new Float32Array([3.5, 3.5, 4.0, 4.0]), }), eastS ) .createMeshWillSucceed(eastS); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 2, height: 2, createdByUpsampling: false, buffer: new Float32Array([5.0, 5.5, 5.0, 5.5]), }), northW ) .createMeshWillSucceed(northW); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 2, height: 2, createdByUpsampling: false, buffer: new Float32Array([5.5, 6.0, 6.5, 6.0]), }), northE ) .createMeshWillSucceed(northE); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 2, height: 2, createdByUpsampling: false, buffer: new Float32Array([7.0, 7.5, 7.0, 7.5]), }), southW ) .createMeshWillSucceed(southW); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 2, height: 2, createdByUpsampling: false, buffer: new Float32Array([7.5, 8.0, 7.5, 8.0]), }), southE ) .createMeshWillSucceed(southE); return processor .process([ center, westN, westS, eastN, eastS, northE, northW, southE, southW, ]) .then(function () { var fill = (center.data.fill = new TerrainFillMesh(center)); fill.westTiles.push(westN, westS); fill.westMeshes.push(westN.data.mesh, westS.data.mesh); fill.eastTiles.push(eastS, eastN); fill.eastMeshes.push(eastS.data.mesh, eastN.data.mesh); fill.northTiles.push(northE, northW); fill.northMeshes.push(northE.data.mesh, northW.data.mesh); fill.southTiles.push(southW, southE); fill.southMeshes.push(southW.data.mesh, southE.data.mesh); fill.update(tileProvider, frameState); expectVertexCount(fill, 17); expectVertex(fill, 0.0, 0.0, (2.0 + 7.0) / 2); expectVertex(fill, 0.0, 0.25, 2.0); expectVertex(fill, 0.0, 0.5, 1.5); expectVertex(fill, 0.0, 0.75, 1.0); expectVertex(fill, 0.0, 1.0, (1.0 + 5.0) / 2); expectVertex(fill, 1.0, 0.0, (4.0 + 8.0) / 2); expectVertex(fill, 1.0, 0.25, 4.0); expectVertex(fill, 1.0, 0.5, 3.5); expectVertex(fill, 1.0, 0.75, 3.0); expectVertex(fill, 1.0, 1.0, (3.0 + 6.0) / 2); }); }); describe("correctly transforms texture coordinates across the anti-meridian", function () { var westernHemisphere; var easternHemisphere; beforeEach(function () { westernHemisphere = rootTiles[0].southwestChild.northwestChild.southwestChild .northwestChild; easternHemisphere = rootTiles[1].southeastChild.northeastChild.southeastChild .northeastChild; // Make sure we have a standard geographic tiling scheme with two root tiles, // the first covering the western hemisphere and the second the eastern. expect(rootTiles.length).toBe(2); expect(westernHemisphere.x).toBe(0); expect(easternHemisphere.x).toBe(31); }); it("western hemisphere to eastern hemisphere", function () { mockTerrain.requestTileGeometryWillDefer(easternHemisphere); mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, ]), }), westernHemisphere ) .createMeshWillSucceed(westernHemisphere); return processor .process([westernHemisphere, easternHemisphere]) .then(function () { var fill = (easternHemisphere.data.fill = new TerrainFillMesh( easternHemisphere )); fill.eastTiles.push(westernHemisphere); fill.eastMeshes.push(westernHemisphere.data.mesh); fill.update(tileProvider, frameState); expectVertexCount(fill, 6); expectVertex(fill, 1.0, 0.0, 7.0); expectVertex(fill, 1.0, 0.5, 4.0); expectVertex(fill, 1.0, 1.0, 1.0); }); }); it("eastern hemisphere to western hemisphere", function () { mockTerrain .requestTileGeometryWillSucceedWith( new HeightmapTerrainData({ width: 3, height: 3, createdByUpsampling: false, buffer: new Float32Array([ 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, ]), }), easternHemisphere ) .createMeshWillSucceed(easternHemisphere); mockTerrain.requestTileGeometryWillDefer(westernHemisphere); return processor .process([westernHemisphere, easternHemisphere]) .then(function () { var fill = (westernHemisphere.data.fill = new TerrainFillMesh( westernHemisphere )); fill.westTiles.push(easternHemisphere); fill.westMeshes.push(easternHemisphere.data.mesh); fill.update(tileProvider, frameState); expectVertexCount(fill, 6); expectVertex(fill, 0.0, 0.0, 18.0); expectVertex(fill, 0.0, 0.5, 15.0); expectVertex(fill, 0.0, 1.0, 12.0); }); }); }); }); var textureCoordinateScratch = new Cartesian2(); var positionScratch = new Cartesian3(); var expectedPositionScratch = new Cartesian3(); function getHeight(fill, u, v) { var mesh = fill.mesh; var rectangle = fill.tile.rectangle; var encoding = mesh.encoding; var vertices = mesh.vertices; var stride = encoding.getStride(); var count = mesh.vertices.length / stride; for (var i = 0; i < count; ++i) { var tc = encoding.decodeTextureCoordinates( vertices, i, textureCoordinateScratch ); var vertexHeight = encoding.decodeHeight(vertices, i); var vertexPosition = encoding.decodePosition( vertices, i, positionScratch ); if ( Math.abs(u - tc.x) < 1e-5 && Math.abs(v - tc.y) < CesiumMath.EPSILON5 ) { var longitude = CesiumMath.lerp(rectangle.west, rectangle.east, u); var latitude = CesiumMath.lerp(rectangle.south, rectangle.north, v); var expectedPosition = Cartesian3.fromRadians( longitude, latitude, vertexHeight, undefined, expectedPositionScratch ); expect(vertexPosition).toEqualEpsilon(expectedPosition, 1); return vertexHeight; } } fail("Vertex with u=" + u + ", v=" + v + " does not exist."); } function expectVertex(fill, u, v, height) { var vertexHeight = getHeight(fill, u, v); expect(vertexHeight).toEqualEpsilon(height, CesiumMath.EPSILON5); } function expectVertexCount(fill, count) { expect(fill.mesh.vertices.length).toBe( count * fill.mesh.encoding.getStride() ); } });