diff --git a/CHANGELOG.md b/CHANGELOG.md index 43173a788d..195c28799a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### 🐞 Bug fixes +- Fix holes at the poles when terrain is used with globe ([#5232](https://github.com/maplibre/maplibre-gl-js/pull/5232)) +- Fix geometry artifacts when globe terrain is zoomed out too much ([#5232](https://github.com/maplibre/maplibre-gl-js/pull/5232)) - _...Add new stuff here..._ ## 5.0.0-pre.10 diff --git a/src/render/draw_terrain.ts b/src/render/draw_terrain.ts index c66db651af..3bad5c25d3 100644 --- a/src/render/draw_terrain.ts +++ b/src/render/draw_terrain.ts @@ -19,13 +19,13 @@ function drawDepth(painter: Painter, terrain: Terrain) { const tr = painter.transform; const colorMode = ColorMode.unblended; const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, [0, 1]); - const mesh = terrain.getTerrainMesh(); const tiles = terrain.sourceCache.getRenderableTiles(); const program = painter.useProgram('terrainDepth'); context.bindFramebuffer.set(terrain.getFramebuffer('depth').framebuffer); context.viewport.set([0, 0, painter.width / devicePixelRatio, painter.height / devicePixelRatio]); context.clear({color: Color.transparent, depth: 1}); for (const tile of tiles) { + const mesh = terrain.getTerrainMesh(tile.tileID); const terrainData = terrain.getTerrainData(tile.tileID); const projectionData = tr.getProjectionData({overscaledTileID: tile.tileID, applyTerrainMatrix: false, applyGlobeMatrix: true}); const uniformValues = terrainDepthUniformValues(terrain.getMeshFrameDelta(tr.zoom)); @@ -46,7 +46,6 @@ function drawCoords(painter: Painter, terrain: Terrain) { const tr = painter.transform; const colorMode = ColorMode.unblended; const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, [0, 1]); - const mesh = terrain.getTerrainMesh(); const coords = terrain.getCoordsTexture(); const tiles = terrain.sourceCache.getRenderableTiles(); @@ -57,6 +56,7 @@ function drawCoords(painter: Painter, terrain: Terrain) { context.clear({color: Color.transparent, depth: 1}); terrain.coordsIndex = []; for (const tile of tiles) { + const mesh = terrain.getTerrainMesh(tile.tileID); const terrainData = terrain.getTerrainData(tile.tileID); context.activeTexture.set(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, coords.texture); @@ -77,12 +77,12 @@ function drawTerrain(painter: Painter, terrain: Terrain, tiles: Array, ren const colorMode = painter.colorModeForRenderPass(); const depthMode = painter.getDepthModeFor3D(); const program = painter.useProgram('terrain'); - const mesh = terrain.getTerrainMesh(); context.bindFramebuffer.set(null); context.viewport.set([0, 0, painter.width, painter.height]); for (const tile of tiles) { + const mesh = terrain.getTerrainMesh(tile.tileID); const texture = painter.renderToTexture.getTexture(tile); const terrainData = terrain.getTerrainData(tile.tileID); context.activeTexture.set(gl.TEXTURE0); diff --git a/src/render/render_to_texture.test.ts b/src/render/render_to_texture.test.ts index 4c17ed7c09..9abd9d78af 100644 --- a/src/render/render_to_texture.test.ts +++ b/src/render/render_to_texture.test.ts @@ -92,6 +92,9 @@ describe('render to texture', () => { 'maine-hillshade': hillshadeLayer, 'maine-line': lineLayer, 'maine-symbol': symbolLayer + }, + projection: { + transitionState: 0, } } as any as Style; painter.style = style; diff --git a/src/render/terrain.test.ts b/src/render/terrain.test.ts index 1238831feb..af4b4a3f8f 100644 --- a/src/render/terrain.test.ts +++ b/src/render/terrain.test.ts @@ -238,6 +238,11 @@ describe('Terrain', () => { }, width: 1, height: 1, + style: { + projection: { + transitionState: 0, + } + } } as any as Painter; const sourceCache = { _source: {maxzoom: 12, tileSize: 512}, @@ -249,10 +254,10 @@ describe('Terrain', () => { {exaggeration: 1} as any as TerrainSpecification, ); terrain.meshSize = 4; - terrain.getTerrainMesh(); + terrain.getTerrainMesh(new OverscaledTileID(2, 0, 2, 1, 1)); expect(terrain.getMeshFrameDelta(16)).toBe(122.16256373312942); - expect(actualIndexArray).toStrictEqual([0, 5, 6, 0, 6, 1, 1, 6, 7, 1, 7, 2, 2, 7, 8, 2, 8, 3, 3, 8, 9, 3, 9, 4, 5, 10, 11, 5, 11, 6, 6, 11, 12, 6, 12, 7, 7, 12, 13, 7, 13, 8, 8, 13, 14, 8, 14, 9, 10, 15, 16, 10, 16, 11, 11, 16, 17, 11, 17, 12, 12, 17, 18, 12, 18, 13, 13, 18, 19, 13, 19, 14, 15, 20, 21, 15, 21, 16, 16, 21, 22, 16, 22, 17, 17, 22, 23, 17, 23, 18, 18, 23, 24, 18, 24, 19, 35, 36, 38, 35, 38, 37, 25, 28, 26, 25, 27, 28, 37, 38, 40, 37, 40, 39, 27, 30, 28, 27, 29, 30, 39, 40, 42, 39, 42, 41, 29, 32, 30, 29, 31, 32, 41, 42, 44, 41, 44, 43, 31, 34, 32, 31, 33, 34, 45, 46, 48, 45, 48, 47, 55, 58, 56, 55, 57, 58, 47, 48, 50, 47, 50, 49, 57, 60, 58, 57, 59, 60, 49, 50, 52, 49, 52, 51, 59, 62, 60, 59, 61, 62, 51, 52, 54, 51, 54, 53, 61, 64, 62, 61, 63, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - expect(actualVertexArray).toStrictEqual([0, 0, 0, 2048, 0, 0, 4096, 0, 0, 6144, 0, 0, 8192, 0, 0, 0, 2048, 0, 2048, 2048, 0, 4096, 2048, 0, 6144, 2048, 0, 8192, 2048, 0, 0, 4096, 0, 2048, 4096, 0, 4096, 4096, 0, 6144, 4096, 0, 8192, 4096, 0, 0, 6144, 0, 2048, 6144, 0, 4096, 6144, 0, 6144, 6144, 0, 8192, 6144, 0, 0, 8192, 0, 2048, 8192, 0, 4096, 8192, 0, 6144, 8192, 0, 8192, 8192, 0, 0, 0, 0, 0, 0, 1, 2048, 0, 0, 2048, 0, 1, 4096, 0, 0, 4096, 0, 1, 6144, 0, 0, 6144, 0, 1, 8192, 0, 0, 8192, 0, 1, 0, 8192, 0, 0, 8192, 1, 2048, 8192, 0, 2048, 8192, 1, 4096, 8192, 0, 4096, 8192, 1, 6144, 8192, 0, 6144, 8192, 1, 8192, 8192, 0, 8192, 8192, 1, 0, 0, 0, 0, 0, 1, 0, 2048, 0, 0, 2048, 1, 0, 4096, 0, 0, 4096, 1, 0, 6144, 0, 0, 6144, 1, 0, 8192, 0, 0, 8192, 1, 8192, 0, 0, 8192, 0, 1, 8192, 2048, 0, 8192, 2048, 1, 8192, 4096, 0, 8192, 4096, 1, 8192, 6144, 0, 8192, 6144, 1, 8192, 8192, 0, 8192, 8192, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + expect(actualIndexArray).toStrictEqual([0, 5, 6, 0, 6, 1, 1, 6, 7, 1, 7, 2, 2, 7, 8, 2, 8, 3, 3, 8, 9, 3, 9, 4, 5, 10, 11, 5, 11, 6, 6, 11, 12, 6, 12, 7, 7, 12, 13, 7, 13, 8, 8, 13, 14, 8, 14, 9, 10, 15, 16, 10, 16, 11, 11, 16, 17, 11, 17, 12, 12, 17, 18, 12, 18, 13, 13, 18, 19, 13, 19, 14, 15, 20, 21, 15, 21, 16, 16, 21, 22, 16, 22, 17, 17, 22, 23, 17, 23, 18, 18, 23, 24, 18, 24, 19, 20, 30, 31, 20, 31, 21, 0, 26, 25, 0, 1, 26, 21, 31, 32, 21, 32, 22, 1, 27, 26, 1, 2, 27, 22, 32, 33, 22, 33, 23, 2, 28, 27, 2, 3, 28, 23, 33, 34, 23, 34, 24, 3, 29, 28, 3, 4, 29, 35, 36, 38, 35, 38, 37, 45, 48, 46, 45, 47, 48, 37, 38, 40, 37, 40, 39, 47, 50, 48, 47, 49, 50, 39, 40, 42, 39, 42, 41, 49, 52, 50, 49, 51, 52, 41, 42, 44, 41, 44, 43, 51, 54, 52, 51, 53, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + expect(actualVertexArray).toStrictEqual([0, 0, 0, 2048, 0, 0, 4096, 0, 0, 6144, 0, 0, 8192, 0, 0, 0, 2048, 0, 2048, 2048, 0, 4096, 2048, 0, 6144, 2048, 0, 8192, 2048, 0, 0, 4096, 0, 2048, 4096, 0, 4096, 4096, 0, 6144, 4096, 0, 8192, 4096, 0, 0, 6144, 0, 2048, 6144, 0, 4096, 6144, 0, 6144, 6144, 0, 8192, 6144, 0, 0, 8192, 0, 2048, 8192, 0, 4096, 8192, 0, 6144, 8192, 0, 8192, 8192, 0, 0, 0, 1, 2048, 0, 1, 4096, 0, 1, 6144, 0, 1, 8192, 0, 1, 0, 8192, 1, 2048, 8192, 1, 4096, 8192, 1, 6144, 8192, 1, 8192, 8192, 1, 0, 0, 0, 0, 0, 1, 0, 2048, 0, 0, 2048, 1, 0, 4096, 0, 0, 4096, 1, 0, 6144, 0, 0, 6144, 1, 0, 8192, 0, 0, 8192, 1, 8192, 0, 0, 8192, 0, 1, 8192, 2048, 0, 8192, 2048, 1, 8192, 4096, 0, 8192, 4096, 1, 8192, 6144, 0, 8192, 6144, 1, 8192, 8192, 0, 8192, 8192, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); }); test('interpolation works', () => { diff --git a/src/render/terrain.ts b/src/render/terrain.ts index 4439c37bcd..bcfad0fedd 100644 --- a/src/render/terrain.ts +++ b/src/render/terrain.ts @@ -19,6 +19,7 @@ import type {TerrainSpecification} from '@maplibre/maplibre-gl-style-spec'; import {type LngLat, earthRadius} from '../geo/lng_lat'; import {Mesh} from './mesh'; import {isInBoundsForZoomLngLat} from '../util/world_bounds'; +import {NORTH_POLE_Y, SOUTH_POLE_Y} from './subdivision'; /** * @internal @@ -108,7 +109,7 @@ export class Terrain { * GL Objects for the terrain-mesh * The mesh is a regular mesh, which has the advantage that it can be reused for all tiles. */ - _mesh: Mesh; + _meshCache: { [key: string]: Mesh } = {}; /** * coords index contains a list of tileID.keys. This index is used to identify * the tile via the alpha-cannel in the coords-texture. @@ -376,46 +377,70 @@ export class Terrain { * create a regular mesh which will be used by all terrain-tiles * @returns the created regular mesh */ - getTerrainMesh(): Mesh { - if (this._mesh) return this._mesh; + getTerrainMesh(tileId: OverscaledTileID): Mesh { + const globeEnabled = this.painter.style.projection.transitionState > 0; + const northPole = globeEnabled && tileId.canonical.y === 0; + const southPole = globeEnabled && tileId.canonical.y === (1 << tileId.canonical.z) - 1; + const key = `m_${northPole ? 'n' : ''}_${southPole ? 's' : ''}`; + if (this._meshCache[key]) { + return this._meshCache[key]; + } const context = this.painter.context; + const vertexArray = new Pos3dArray(); const indexArray = new TriangleIndexArray(); const meshSize = this.meshSize; const delta = EXTENT / meshSize; const meshSize2 = meshSize * meshSize; - for (let y = 0; y <= meshSize; y++) for (let x = 0; x <= meshSize; x++) + for (let y = 0; y <= meshSize; y++) for (let x = 0; x <= meshSize; x++) { vertexArray.emplaceBack(x * delta, y * delta, 0); + } for (let y = 0; y < meshSize2; y += meshSize + 1) for (let x = 0; x < meshSize; x++) { indexArray.emplaceBack(x + y, meshSize + x + y + 1, meshSize + x + y + 2); indexArray.emplaceBack(x + y, meshSize + x + y + 2, x + y + 1); } // add an extra frame around the mesh to avoid stitching on tile boundaries with different zoomlevels - // first code-block is for top-bottom frame and second for left-right frame - const offsetTop = vertexArray.length, offsetBottom = offsetTop + (meshSize + 1) * 2; - for (const y of [0, 1]) for (let x = 0; x <= meshSize; x++) for (const z of [0, 1]) - vertexArray.emplaceBack(x * delta, y * EXTENT, z); - for (let x = 0; x < meshSize * 2; x += 2) { - indexArray.emplaceBack(offsetBottom + x, offsetBottom + x + 1, offsetBottom + x + 3); - indexArray.emplaceBack(offsetBottom + x, offsetBottom + x + 3, offsetBottom + x + 2); - indexArray.emplaceBack(offsetTop + x, offsetTop + x + 3, offsetTop + x + 1); - indexArray.emplaceBack(offsetTop + x, offsetTop + x + 2, offsetTop + x + 3); + // top-bottom frame + pole vertices, if needed + const offsetTop = vertexArray.length; + const offsetTopEdge = 0; + const offsetBottom = offsetTop + (meshSize + 1); + const offsetBottomEdge = (meshSize + 1) * meshSize; + const northY = northPole ? NORTH_POLE_Y : 0; + const northZ = northPole ? 0 : 1; + const southY = southPole ? SOUTH_POLE_Y : EXTENT; + const southZ = southPole ? 0 : 1; + for (let x = 0; x <= meshSize; x++) { + vertexArray.emplaceBack(x * delta, northY, northZ); + } + for (let x = 0; x <= meshSize; x++) { + vertexArray.emplaceBack(x * delta, southY, southZ); } - const offsetLeft = vertexArray.length, offsetRight = offsetLeft + (meshSize + 1) * 2; - for (const x of [0, 1]) for (let y = 0; y <= meshSize; y++) for (const z of [0, 1]) + for (let x = 0; x < meshSize; x++) { + indexArray.emplaceBack(offsetBottomEdge + x, offsetBottom + x, offsetBottom + x + 1); + indexArray.emplaceBack(offsetBottomEdge + x, offsetBottom + x + 1, offsetBottomEdge + x + 1); + indexArray.emplaceBack(offsetTopEdge + x, offsetTop + x + 1, offsetTop + x); + indexArray.emplaceBack(offsetTopEdge + x, offsetTopEdge + x + 1, offsetTop + x + 1); + } + // left-right frame + const offsetLeft = vertexArray.length; + const offsetRight = offsetLeft + (meshSize + 1) * 2; + for (const x of [0, 1]) for (let y = 0; y <= meshSize; y++) for (const z of [0, 1]) { vertexArray.emplaceBack(x * EXTENT, y * delta, z); + } for (let y = 0; y < meshSize * 2; y += 2) { indexArray.emplaceBack(offsetLeft + y, offsetLeft + y + 1, offsetLeft + y + 3); indexArray.emplaceBack(offsetLeft + y, offsetLeft + y + 3, offsetLeft + y + 2); indexArray.emplaceBack(offsetRight + y, offsetRight + y + 3, offsetRight + y + 1); indexArray.emplaceBack(offsetRight + y, offsetRight + y + 2, offsetRight + y + 3); } - this._mesh = new Mesh( + + const mesh = new Mesh( context.createVertexBuffer(vertexArray, pos3dAttributes.members), context.createIndexBuffer(indexArray), SegmentVector.simpleSegment(0, 0, vertexArray.length, indexArray.length) ); - return this._mesh; + this._meshCache[key] = mesh; + return mesh; } /** @@ -426,7 +451,7 @@ export class Terrain { */ getMeshFrameDelta(zoom: number): number { // divide by 5 is evaluated by trial & error to get a frame in the right height - return 2 * Math.PI * earthRadius / Math.pow(2, zoom) / 5; + return 2 * Math.PI * earthRadius / Math.pow(2, Math.max(zoom, 0)) / 5; } getMinTileElevationForLngLatZoom(lnglat: LngLat, zoom: number) { diff --git a/src/shaders/_prelude.vertex.glsl b/src/shaders/_prelude.vertex.glsl index e3eb359188..db871edbf3 100644 --- a/src/shaders/_prelude.vertex.glsl +++ b/src/shaders/_prelude.vertex.glsl @@ -145,6 +145,11 @@ float ele(vec2 pos) { // calculate the elevation with linear interpolation for a coordinate float get_elevation(vec2 pos) { #ifdef TERRAIN3D + #ifdef GLOBE + if ((pos.y < -32767.5) || (pos.y > 32766.5)) { + return 0.0; + } + #endif vec2 coord = (u_terrain_matrix * vec4(pos, 0.0, 1.0)).xy * u_terrain_dim + 1.0; vec2 f = fract(coord); vec2 c = (floor(coord) + 0.5) / (u_terrain_dim + 2.0); // get the pixel center diff --git a/src/shaders/_projection_globe.vertex.glsl b/src/shaders/_projection_globe.vertex.glsl index 48ed141d34..7f74bf9740 100644 --- a/src/shaders/_projection_globe.vertex.glsl +++ b/src/shaders/_projection_globe.vertex.glsl @@ -43,10 +43,13 @@ float projectLineThickness(float tileY) { } } -// get position inside the tile in range 0..8192 and project it onto the surface of a unit sphere -vec3 projectToSphere(vec2 posInTile, vec2 rawPos) { +// Get position inside the tile in range 0..8192 and project it onto the surface of a unit sphere. +// Additionally project special Y values to the poles. +// - translatedPos: tile-space vertex position, optionally with user-specified translation already applied +// - rawPos: the original tile-space vertex position *without translation* - needed because we would not be able to detect pole vertices from coordinates modified by translation. +vec3 projectToSphere(vec2 translatedPos, vec2 rawPos) { // Compute position in range 0..1 of the base tile of web mercator - vec2 mercator_pos = u_projection_tile_mercator_coords.xy + u_projection_tile_mercator_coords.zw * posInTile; + vec2 mercator_pos = u_projection_tile_mercator_coords.xy + u_projection_tile_mercator_coords.zw * translatedPos; // Now compute angular coordinates on the surface of a perfect sphere vec2 spherical; @@ -129,7 +132,7 @@ vec4 projectTile(vec2 posInTile) { return interpolateProjection(posInTile, projectToSphere(posInTile), 0.0); } -// A variant that supports special pole and planet center vertices. +// A variant that supports special pole vertices. vec4 projectTile(vec2 posInTile, vec2 rawPos) { return interpolateProjection(posInTile, projectToSphere(posInTile, rawPos), 0.0); } @@ -142,7 +145,8 @@ vec4 projectTileWithElevation(vec2 posInTile, float elevation) { } // Projects the tile coordinates+elevation while **preserving Z** value from multiplication with the projection matrix. +// Applies pole vertices. vec4 projectTileFor3D(vec2 posInTile, float elevation) { - vec3 spherePos = projectToSphere(posInTile); + vec3 spherePos = projectToSphere(posInTile, posInTile); return interpolateProjectionFor3D(posInTile, spherePos, elevation); } diff --git a/test/build/min.test.ts b/test/build/min.test.ts index 55b6568d45..735c4298f7 100644 --- a/test/build/min.test.ts +++ b/test/build/min.test.ts @@ -38,7 +38,7 @@ describe('test min build', () => { const decreaseQuota = 4096; // feel free to update this value after you've checked that it has changed on purpose :-) - const expectedBytes = 907610; + const expectedBytes = 907350; expect(actualBytes).toBeLessThan(expectedBytes + increaseQuota); expect(actualBytes).toBeGreaterThan(expectedBytes - decreaseQuota); diff --git a/test/integration/render/tests/projection/globe/terrain/fill-planet-pole/expected.png b/test/integration/render/tests/projection/globe/terrain/fill-planet-pole/expected.png new file mode 100644 index 0000000000..0137ea4f97 Binary files /dev/null and b/test/integration/render/tests/projection/globe/terrain/fill-planet-pole/expected.png differ diff --git a/test/integration/render/tests/projection/globe/terrain/fill-planet-pole/style.json b/test/integration/render/tests/projection/globe/terrain/fill-planet-pole/style.json new file mode 100644 index 0000000000..8d8c5b544b --- /dev/null +++ b/test/integration/render/tests/projection/globe/terrain/fill-planet-pole/style.json @@ -0,0 +1,54 @@ +{ + "version": 8, + "metadata": { + "test": {} + }, + "sky": { + "atmosphere-blend": 0.0 + }, + "center": [ + 0.0, + 80.0 + ], + "zoom": -0.5, + "projection": { + "type": "globe" + }, + "terrain": { + "source": "terrain", + "exaggeration": 2 + }, + "sources": { + "terrain": { + "type": "raster-dem", + "tiles": ["local://tiles/{z}-{x}-{y}.terrain.png"], + "maxzoom": 15, + "tileSize": 256 + }, + "vector_tiles": { + "type": "vector", + "maxzoom": 0, + "tiles": [ + "local://tiles/{z}-{x}-{y}.mvt" + ] + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "land", + "type": "fill", + "source": "vector_tiles", + "source-layer": "water", + "paint": { + "fill-color": "blue" + } + } + ] +} diff --git a/test/integration/render/tests/terrain/pitched-world/expected.png b/test/integration/render/tests/terrain/pitched-world/expected.png new file mode 100644 index 0000000000..406358c129 Binary files /dev/null and b/test/integration/render/tests/terrain/pitched-world/expected.png differ diff --git a/test/integration/render/tests/terrain/pitched-world/style.json b/test/integration/render/tests/terrain/pitched-world/style.json new file mode 100644 index 0000000000..7eddbaec3e --- /dev/null +++ b/test/integration/render/tests/terrain/pitched-world/style.json @@ -0,0 +1,50 @@ +{ + "version": 8, + "metadata": { + "test": {}, + "description": "Tests that globe's terrain meshes with poles are not used under mercator projection." + }, + "sky": { + "atmosphere-blend": 0.0 + }, + "zoom": -2.5, + "pitch": 60, + "bearing": 45, + "terrain": { + "source": "terrain", + "exaggeration": 2 + }, + "sources": { + "terrain": { + "type": "raster-dem", + "tiles": ["local://tiles/{z}-{x}-{y}.terrain.png"], + "maxzoom": 15, + "tileSize": 256 + }, + "vector_tiles": { + "type": "vector", + "maxzoom": 0, + "tiles": [ + "local://tiles/{z}-{x}-{y}.mvt" + ] + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "land", + "type": "fill", + "source": "vector_tiles", + "source-layer": "water", + "paint": { + "fill-color": "blue" + } + } + ] +}