From 662700db0996f3cb7e36e7ea30ee614c35c21171 Mon Sep 17 00:00:00 2001 From: Harel M Date: Wed, 27 Nov 2024 10:59:43 +0200 Subject: [PATCH] Fix render world copies regression (#5101) * Fix renderWorldCopies regression (#5027) * set renderWorldCopies in GlobeTransform, and return it when not isGlobeRendering * fix #5024 --------- Co-authored-by: Harel M * Move tests to a designated file * Update changelog * Add test to make sure the behavior is kept * Fix lint * Update CHANGELOG.md --------- Co-authored-by: Nathan Olson --- CHANGELOG.md | 2 + src/geo/projection/covering_tiles.test.ts | 455 ++++++++++++++++++ src/geo/projection/covering_tiles.ts | 2 +- .../covering_tiles_details_provider.ts | 5 + .../globe_covering_tiles_details_provider.ts | 4 + src/geo/projection/globe_transform.test.ts | 180 +------ src/geo/projection/globe_transform.ts | 5 +- ...ercator_covering_tiles_details_provider.ts | 4 + src/geo/projection/mercator_transform.test.ts | 279 +---------- 9 files changed, 494 insertions(+), 442 deletions(-) create mode 100644 src/geo/projection/covering_tiles.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bac3e09147..bdb5e9b3fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - _...Add new stuff here..._ ### 🐞 Bug fixes + +- Fix regression in render world copies ([#5101](https://github.com/maplibre/maplibre-gl-js/pull/5101)) - _...Add new stuff here..._ ## 5.0.0-pre.8 diff --git a/src/geo/projection/covering_tiles.test.ts b/src/geo/projection/covering_tiles.test.ts new file mode 100644 index 0000000000..b13d64df91 --- /dev/null +++ b/src/geo/projection/covering_tiles.test.ts @@ -0,0 +1,455 @@ +import {beforeEach, describe, expect, test} from 'vitest'; +import {GlobeTransform} from './globe_transform'; +import {globeConstants, type GlobeProjection} from './globe'; +import {getGlobeProjectionMock} from '../../util/test/util'; +import {LngLat} from '../lng_lat'; +import {coveringTiles, coveringZoomLevel, type CoveringZoomOptions} from './covering_tiles'; +import {OverscaledTileID} from '../../source/tile_id'; +import {MercatorTransform} from './mercator_transform'; + +describe('coveringTiles', () => { + describe('globe', () => { + let globeProjectionMock: GlobeProjection; + + beforeEach(() => { + globeProjectionMock = getGlobeProjectionMock(); + // Force faster animations so we can use shorter sleeps when testing them + globeConstants.globeTransitionTimeSeconds = 0.1; + globeConstants.errorTransitionTimeSeconds = 0.1; + }); + + test('zoomed out', () => { + const transform = new GlobeTransform(globeProjectionMock); + transform.resize(128, 128); + transform.setCenter(new LngLat(0.0, 0.0)); + transform.setZoom(-1); + + const tiles = coveringTiles(transform, { + tileSize: 512, + }); + + expect(tiles).toEqual([ + new OverscaledTileID(0, 0, 0, 0, 0) + ]); + }); + + test('zoomed in', () => { + const transform = new GlobeTransform(globeProjectionMock); + transform.resize(128, 128); + transform.setCenter(new LngLat(-0.02, 0.01)); + transform.setZoom(3); + + const tiles = coveringTiles(transform, { + tileSize: 512, + }); + + expect(tiles).toEqual([ + new OverscaledTileID(3, 0, 3, 3, 3), + new OverscaledTileID(3, 0, 3, 3, 4), + new OverscaledTileID(3, 0, 3, 4, 3), + new OverscaledTileID(3, 0, 3, 4, 4), + ]); + }); + + test('zoomed in 512x512', () => { + const transform = new GlobeTransform(globeProjectionMock); + transform.resize(512, 512); + transform.setCenter(new LngLat(-0.02, 0.01)); + transform.setZoom(3); + + const tiles = coveringTiles(transform, { + tileSize: 512, + }); + + expect(tiles).toEqual([ + new OverscaledTileID(3, 0, 3, 3, 3), + new OverscaledTileID(3, 0, 3, 3, 4), + new OverscaledTileID(3, 0, 3, 4, 3), + new OverscaledTileID(3, 0, 3, 4, 4), + new OverscaledTileID(3, 0, 3, 2, 3), + new OverscaledTileID(3, 0, 3, 2, 4), + new OverscaledTileID(3, 0, 3, 5, 3), + new OverscaledTileID(3, 0, 3, 5, 4), + new OverscaledTileID(3, 0, 3, 2, 2), + new OverscaledTileID(3, 0, 3, 2, 5), + new OverscaledTileID(3, 0, 3, 5, 2), + new OverscaledTileID(3, 0, 3, 5, 5), + ]); + }); + + test('pitched', () => { + const transform = new GlobeTransform(globeProjectionMock); + transform.resize(128, 128); + transform.setCenter(new LngLat(-0.002, 0.001)); + transform.setZoom(8); + transform.setMaxPitch(80); + transform.setPitch(80); + + const tiles = coveringTiles(transform, { + tileSize: 512, + }); + + expect(tiles).toEqual([ + new OverscaledTileID(6, 0, 6, 32, 31), + new OverscaledTileID(6, 0, 6, 31, 31), + new OverscaledTileID(10, 0, 10, 511, 512), + new OverscaledTileID(10, 0, 10, 512, 512) + ]); + }); + + test('pitched+rotated', () => { + const transform = new GlobeTransform(globeProjectionMock); + transform.resize(128, 128); + transform.setCenter(new LngLat(-0.002, 0.001)); + transform.setZoom(8); + transform.setMaxPitch(80); + transform.setPitch(80); + transform.setBearing(45); + + const tiles = coveringTiles(transform, { + tileSize: 512, + }); + + expect(tiles).toEqual([ + new OverscaledTileID(7, 0, 7, 64, 64), + new OverscaledTileID(7, 0, 7, 65, 63), + new OverscaledTileID(7, 0, 7, 64, 63), + new OverscaledTileID(7, 0, 7, 63, 63), + new OverscaledTileID(7, 0, 7, 64, 62), + new OverscaledTileID(10, 0, 10, 510, 512), + new OverscaledTileID(10, 0, 10, 511, 512), + new OverscaledTileID(10, 0, 10, 511, 513) + ]); + }); + + test('antimeridian1', () => { + const transform = new GlobeTransform(globeProjectionMock); + transform.resize(128, 128); + transform.setCenter(new LngLat(179.99, -0.001)); + transform.setZoom(5); + + const tiles = coveringTiles(transform, { + tileSize: 512, + }); + + expect(tiles).toEqual([ + new OverscaledTileID(5, 0, 5, 31, 16), + new OverscaledTileID(5, 0, 5, 31, 15), + new OverscaledTileID(5, 1, 5, 0, 16), + new OverscaledTileID(5, 1, 5, 0, 15), + ]); + }); + + test('antimeridian2', () => { + const transform = new GlobeTransform(globeProjectionMock); + transform.resize(128, 128); + transform.setCenter(new LngLat(-179.99, 0.001)); + transform.setZoom(5); + + const tiles = coveringTiles(transform, { + tileSize: 512, + }); + + expect(tiles).toEqual([ + new OverscaledTileID(5, 0, 5, 0, 15), + new OverscaledTileID(5, 0, 5, 0, 16), + new OverscaledTileID(5, -1, 5, 31, 15), + new OverscaledTileID(5, -1, 5, 31, 16), + ]); + }); + + test('zoom < 0', () => { + const transform = new GlobeTransform(globeProjectionMock); + transform.resize(128, 128); + transform.setCenter(new LngLat(0.0, 80.0)); + transform.setZoom(-0.5); + + const tiles = coveringTiles(transform, { + tileSize: 512, + minzoom: 0, + maxzoom: 0, + reparseOverscaled: true + }); + + expect(tiles).toEqual([ + new OverscaledTileID(0, 0, 0, 0, 0) + ]); + }); + }); + + describe('mercator', () => { + const options = { + minzoom: 1, + maxzoom: 10, + tileSize: 512 + }; + + const transform = new MercatorTransform(0, 22, 0, 85, true); + transform.resize(200, 200); + + test('general', () => { + + // make slightly off center so that sort order is not subject to precision issues + transform.setCenter(new LngLat(-0.01, 0.01)); + + transform.setZoom(0); + expect(coveringTiles(transform, options)).toEqual([]); + + transform.setZoom(1); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(1, 0, 1, 0, 0), + new OverscaledTileID(1, 0, 1, 1, 0), + new OverscaledTileID(1, 0, 1, 0, 1), + new OverscaledTileID(1, 0, 1, 1, 1)]); + + transform.setZoom(2.4); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(2, 0, 2, 1, 1), + new OverscaledTileID(2, 0, 2, 2, 1), + new OverscaledTileID(2, 0, 2, 1, 2), + new OverscaledTileID(2, 0, 2, 2, 2)]); + + transform.setZoom(10); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(10, 0, 10, 511, 511), + new OverscaledTileID(10, 0, 10, 512, 511), + new OverscaledTileID(10, 0, 10, 511, 512), + new OverscaledTileID(10, 0, 10, 512, 512)]); + + transform.setZoom(11); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(10, 0, 10, 511, 511), + new OverscaledTileID(10, 0, 10, 512, 511), + new OverscaledTileID(10, 0, 10, 511, 512), + new OverscaledTileID(10, 0, 10, 512, 512)]); + + transform.setZoom(5.1); + transform.setPitch(60.0); + transform.setBearing(32.0); + transform.setCenter(new LngLat(56.90, 48.20)); + transform.resize(1024, 768); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(5, 0, 5, 21, 11), + new OverscaledTileID(5, 0, 5, 20, 11), + new OverscaledTileID(5, 0, 5, 21, 10), + new OverscaledTileID(5, 0, 5, 20, 10), + new OverscaledTileID(5, 0, 5, 21, 12), + new OverscaledTileID(5, 0, 5, 22, 11), + new OverscaledTileID(5, 0, 5, 20, 12), + new OverscaledTileID(5, 0, 5, 22, 10), + new OverscaledTileID(5, 0, 5, 21, 9), + new OverscaledTileID(5, 0, 5, 20, 9), + new OverscaledTileID(5, 0, 5, 22, 9), + new OverscaledTileID(5, 0, 5, 23, 10), + new OverscaledTileID(5, 0, 5, 21, 8), + new OverscaledTileID(5, 0, 5, 20, 8), + new OverscaledTileID(5, 0, 5, 23, 9), + new OverscaledTileID(5, 0, 5, 22, 8), + new OverscaledTileID(5, 0, 5, 23, 8), + new OverscaledTileID(5, 0, 5, 21, 7), + new OverscaledTileID(5, 0, 5, 20, 7), + new OverscaledTileID(5, 0, 5, 24, 9), + new OverscaledTileID(5, 0, 5, 22, 7) + ]); + + transform.setZoom(8); + transform.setPitch(85.0); + transform.setBearing(0.0); + transform.setCenter(new LngLat(20.918, 39.232)); + transform.resize(50, 1000); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(8, 0, 8, 142, 98), + new OverscaledTileID(7, 0, 7, 71, 48), + new OverscaledTileID(5, 0, 5, 17, 11), + new OverscaledTileID(5, 0, 5, 17, 10), + new OverscaledTileID(9, 0, 9, 285, 198), + new OverscaledTileID(9, 0, 9, 285, 199) + ]); + + transform.setZoom(8); + transform.setPitch(60); + transform.setBearing(45.0); + transform.setCenter(new LngLat(25.02, 60.15)); + transform.resize(300, 50); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(8, 0, 8, 145, 74), + new OverscaledTileID(8, 0, 8, 145, 73), + new OverscaledTileID(8, 0, 8, 146, 74) + ]); + + transform.resize(50, 300); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(8, 0, 8, 145, 74), + new OverscaledTileID(8, 0, 8, 145, 73), + new OverscaledTileID(8, 0, 8, 146, 74), + new OverscaledTileID(8, 0, 8, 146, 73) + ]); + + const optionsWithCustomTileLoading = { + minzoom: 1, + maxzoom: 10, + tileSize: 512, + calculateTileZoom: (_requestedCenterZoom: number, + _distanceToTile2D: number, + _distanceToTileZ: number, + _distanceToCenter3D: number, + _cameraVerticalFOV: number) => { return 7; } + }; + transform.resize(50, 300); + transform.setPitch(70); + expect(coveringTiles(transform, optionsWithCustomTileLoading)).toEqual([ + new OverscaledTileID(7, 0, 7, 74, 36), + new OverscaledTileID(7, 0, 7, 73, 37), + new OverscaledTileID(7, 0, 7, 74, 35), + new OverscaledTileID(7, 0, 7, 73, 36), + new OverscaledTileID(7, 0, 7, 72, 37), + new OverscaledTileID(7, 0, 7, 73, 35), + new OverscaledTileID(7, 0, 7, 72, 36) + ]); + + transform.setZoom(2); + transform.setPitch(0); + transform.setBearing(0); + transform.resize(300, 300); + }); + + test('calculates tile coverage at w > 0', () => { + transform.setCenter(new LngLat(630.01, 0.01)); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(2, 2, 2, 1, 1), + new OverscaledTileID(2, 2, 2, 1, 2), + new OverscaledTileID(2, 2, 2, 0, 1), + new OverscaledTileID(2, 2, 2, 0, 2) + ]); + }); + + test('calculates tile coverage at w = -1', () => { + transform.setCenter(new LngLat(-360.01, 0.01)); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(2, -1, 2, 1, 1), + new OverscaledTileID(2, -1, 2, 1, 2), + new OverscaledTileID(2, -1, 2, 2, 1), + new OverscaledTileID(2, -1, 2, 2, 2) + ]); + }); + + test('calculates tile coverage across meridian', () => { + transform.setZoom(1); + transform.setCenter(new LngLat(-180.01, 0.01)); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(1, 0, 1, 0, 0), + new OverscaledTileID(1, 0, 1, 0, 1), + new OverscaledTileID(1, -1, 1, 1, 0), + new OverscaledTileID(1, -1, 1, 1, 1) + ]); + }); + + test('only includes tiles for a single world, if renderWorldCopies is set to false', () => { + transform.setZoom(1); + transform.setCenter(new LngLat(-180.01, 0.01)); + transform.setRenderWorldCopies(false); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(1, 0, 1, 0, 0), + new OverscaledTileID(1, 0, 1, 0, 1) + ]); + }); + + test('overscaledZ', () => { + const options = { + minzoom: 1, + maxzoom: 10, + tileSize: 256, + reparseOverscaled: true + }; + + const transform = new MercatorTransform(0, 10, 0, 85, true); + transform.resize(10, 400); + // make slightly off center so that sort order is not subject to precision issues + transform.setCenter(new LngLat(-0.01, 0.01)); + transform.setPitch(85); + transform.setFov(10); + + transform.setZoom(10); + const tiles = coveringTiles(transform, options); + for (const tile of tiles) { + expect(tile.overscaledZ).toBeGreaterThanOrEqual(tile.canonical.z); + } + }); + + test('maxzoom-0', () => { + const options = { + minzoom: 0, + maxzoom: 0, + tileSize: 512 + }; + + const transform = new MercatorTransform(0, 0, 0, 60, true); + transform.resize(200, 200); + transform.setCenter(new LngLat(0.01, 0.01)); + transform.setZoom(8); + expect(coveringTiles(transform, options)).toEqual([ + new OverscaledTileID(0, 0, 0, 0, 0) + ]); + }); + + }); +}); + +describe('coveringZoomLevel', () => { + let transform: MercatorTransform; + let options: CoveringZoomOptions; + + beforeEach(() => { + transform = new MercatorTransform(0, 22, 0, 60, true); + options = { + tileSize: 512, + roundZoom: false, + }; + }); + + test('zoom 0', () => { + transform.setZoom(0); + expect(coveringZoomLevel(transform, options)).toBe(0); + }); + + test('small zoom should be floored to 0', () => { + transform.setZoom(0.1); + expect(coveringZoomLevel(transform, options)).toBe(0); + }); + + test('zoom 2.7 should be floored to 2', () => { + transform.setZoom(2.7); + expect(coveringZoomLevel(transform, options)).toBe(2); + }); + + test('zoom 0 for small tile size', () => { + options.tileSize = 256; + transform.setZoom(0); + expect(coveringZoomLevel(transform, options)).toBe(1); + }); + + test('zoom 0.1 for small tile size', () => { + options.tileSize = 256; + transform.setZoom(0.1); + expect(coveringZoomLevel(transform, options)).toBe(1); + }); + + test('zoom 1 for small tile size', () => { + options.tileSize = 256; + transform.setZoom(1); + expect(coveringZoomLevel(transform, options)).toBe(2); + }); + + test('zoom 2.4 for small tile size', () => { + options.tileSize = 256; + transform.setZoom(2.4); + expect(coveringZoomLevel(transform, options)).toBe(3); + }); + + test('zoom 11.5 with rounded setting and small tile size', () => { + options.tileSize = 256; + options.roundZoom = true; + transform.setZoom(11.5); + expect(coveringZoomLevel(transform, options)).toBe(13); + }); +}); \ No newline at end of file diff --git a/src/geo/projection/covering_tiles.ts b/src/geo/projection/covering_tiles.ts index a70540d698..7ce9de8797 100644 --- a/src/geo/projection/covering_tiles.ts +++ b/src/geo/projection/covering_tiles.ts @@ -187,7 +187,7 @@ export function coveringTiles(transform: IReadonlyTransform, options: CoveringTi const stack: Array = []; const result: Array = []; - if (transform.renderWorldCopies) { + if (transform.renderWorldCopies && detailsProvider.allowWorldCopies()) { // Render copy of the globe thrice on both sides for (let i = 1; i <= 3; i++) { stack.push(newRootTile(-i)); diff --git a/src/geo/projection/covering_tiles_details_provider.ts b/src/geo/projection/covering_tiles_details_provider.ts index 75c63338cd..344f129dc1 100644 --- a/src/geo/projection/covering_tiles_details_provider.ts +++ b/src/geo/projection/covering_tiles_details_provider.ts @@ -31,4 +31,9 @@ export interface CoveringTilesDetailsProvider { * Whether to allow variable zoom, which is used at high pitch angle to avoid loading an excessive amount of tiles. */ allowVariableZoom: (transform: IReadonlyTransform, options: CoveringTilesOptions) => boolean; + + /** + * Whether to allow world copies to be rendered. + */ + allowWorldCopies: () => boolean; } diff --git a/src/geo/projection/globe_covering_tiles_details_provider.ts b/src/geo/projection/globe_covering_tiles_details_provider.ts index 41e7a3aa95..6f7d27e524 100644 --- a/src/geo/projection/globe_covering_tiles_details_provider.ts +++ b/src/geo/projection/globe_covering_tiles_details_provider.ts @@ -98,6 +98,10 @@ export class GlobeCoveringTilesDetailsProvider implements CoveringTilesDetailsPr return coveringZoomLevel(transform, options) > 4; } + allowWorldCopies(): boolean { + return false; + } + getTileAABB(tileID: { x: number; y: number; z: number }, wrap: number, elevation: number, options: CoveringTilesOptions) { return this._aabbCache.getTileAABB(tileID, wrap, elevation, options); } diff --git a/src/geo/projection/globe_transform.test.ts b/src/geo/projection/globe_transform.test.ts index 0062396ca1..0c619e1ce6 100644 --- a/src/geo/projection/globe_transform.test.ts +++ b/src/geo/projection/globe_transform.test.ts @@ -9,7 +9,7 @@ import {angularCoordinatesRadiansToVector, mercatorCoordinatesToAngularCoordinat import {expectToBeCloseToArray, getGlobeProjectionMock, sleep} from '../../util/test/util'; import {MercatorCoordinate} from '../mercator_coordinate'; import {tileCoordinatesToLocation} from './mercator_utils'; -import {coveringTiles} from './covering_tiles'; +import {MercatorTransform} from './mercator_transform'; function testPlaneAgainstLngLat(lngDegrees: number, latDegrees: number, plane: Array) { const lat = latDegrees / 180.0 * Math.PI; @@ -612,166 +612,6 @@ describe('GlobeTransform', () => { }); }); - describe('coveringTiles', () => { - test('zoomed out', () => { - const transform = new GlobeTransform(globeProjectionMock); - transform.resize(128, 128); - transform.setCenter(new LngLat(0.0, 0.0)); - transform.setZoom(-1); - - const tiles = coveringTiles(transform, { - tileSize: 512, - }); - - expect(tiles).toEqual([ - new OverscaledTileID(0, 0, 0, 0, 0) - ]); - }); - - test('zoomed in', () => { - const transform = new GlobeTransform(globeProjectionMock); - transform.resize(128, 128); - transform.setCenter(new LngLat(-0.02, 0.01)); - transform.setZoom(3); - - const tiles = coveringTiles(transform, { - tileSize: 512, - }); - - expect(tiles).toEqual([ - new OverscaledTileID(3, 0, 3, 3, 3), - new OverscaledTileID(3, 0, 3, 3, 4), - new OverscaledTileID(3, 0, 3, 4, 3), - new OverscaledTileID(3, 0, 3, 4, 4), - ]); - }); - - test('zoomed in 512x512', () => { - const transform = new GlobeTransform(globeProjectionMock); - transform.resize(512, 512); - transform.setCenter(new LngLat(-0.02, 0.01)); - transform.setZoom(3); - - const tiles = coveringTiles(transform, { - tileSize: 512, - }); - - expect(tiles).toEqual([ - new OverscaledTileID(3, 0, 3, 3, 3), - new OverscaledTileID(3, 0, 3, 3, 4), - new OverscaledTileID(3, 0, 3, 4, 3), - new OverscaledTileID(3, 0, 3, 4, 4), - new OverscaledTileID(3, 0, 3, 2, 3), - new OverscaledTileID(3, 0, 3, 2, 4), - new OverscaledTileID(3, 0, 3, 5, 3), - new OverscaledTileID(3, 0, 3, 5, 4), - new OverscaledTileID(3, 0, 3, 2, 2), - new OverscaledTileID(3, 0, 3, 2, 5), - new OverscaledTileID(3, 0, 3, 5, 2), - new OverscaledTileID(3, 0, 3, 5, 5), - ]); - }); - - test('pitched', () => { - const transform = new GlobeTransform(globeProjectionMock); - transform.resize(128, 128); - transform.setCenter(new LngLat(-0.002, 0.001)); - transform.setZoom(8); - transform.setMaxPitch(80); - transform.setPitch(80); - - const tiles = coveringTiles(transform, { - tileSize: 512, - }); - - expect(tiles).toEqual([ - new OverscaledTileID(6, 0, 6, 32, 31), - new OverscaledTileID(6, 0, 6, 31, 31), - new OverscaledTileID(10, 0, 10, 511, 512), - new OverscaledTileID(10, 0, 10, 512, 512) - ]); - }); - - test('pitched+rotated', () => { - const transform = new GlobeTransform(globeProjectionMock); - transform.resize(128, 128); - transform.setCenter(new LngLat(-0.002, 0.001)); - transform.setZoom(8); - transform.setMaxPitch(80); - transform.setPitch(80); - transform.setBearing(45); - - const tiles = coveringTiles(transform, { - tileSize: 512, - }); - - expect(tiles).toEqual([ - new OverscaledTileID(7, 0, 7, 64, 64), - new OverscaledTileID(7, 0, 7, 65, 63), - new OverscaledTileID(7, 0, 7, 64, 63), - new OverscaledTileID(7, 0, 7, 63, 63), - new OverscaledTileID(7, 0, 7, 64, 62), - new OverscaledTileID(10, 0, 10, 510, 512), - new OverscaledTileID(10, 0, 10, 511, 512), - new OverscaledTileID(10, 0, 10, 511, 513) - ]); - }); - - test('antimeridian1', () => { - const transform = new GlobeTransform(globeProjectionMock); - transform.resize(128, 128); - transform.setCenter(new LngLat(179.99, -0.001)); - transform.setZoom(5); - - const tiles = coveringTiles(transform, { - tileSize: 512, - }); - - expect(tiles).toEqual([ - new OverscaledTileID(5, 0, 5, 31, 16), - new OverscaledTileID(5, 0, 5, 31, 15), - new OverscaledTileID(5, 1, 5, 0, 16), - new OverscaledTileID(5, 1, 5, 0, 15), - ]); - }); - - test('antimeridian2', () => { - const transform = new GlobeTransform(globeProjectionMock); - transform.resize(128, 128); - transform.setCenter(new LngLat(-179.99, 0.001)); - transform.setZoom(5); - - const tiles = coveringTiles(transform, { - tileSize: 512, - }); - - expect(tiles).toEqual([ - new OverscaledTileID(5, 0, 5, 0, 15), - new OverscaledTileID(5, 0, 5, 0, 16), - new OverscaledTileID(5, -1, 5, 31, 15), - new OverscaledTileID(5, -1, 5, 31, 16), - ]); - }); - - test('zoom < 0', () => { - const transform = new GlobeTransform(globeProjectionMock); - transform.resize(128, 128); - transform.setCenter(new LngLat(0.0, 80.0)); - transform.setZoom(-0.5); - - const tiles = coveringTiles(transform, { - tileSize: 512, - minzoom: 0, - maxzoom: 0, - reparseOverscaled: true - }); - - expect(tiles).toEqual([ - new OverscaledTileID(0, 0, 0, 0, 0) - ]); - }); - }); - test('transform and projection instance are synchronized properly', async () => { const projectionMock = getGlobeProjectionMock(); const globeTransform = createGlobeTransform(projectionMock); @@ -793,4 +633,22 @@ describe('GlobeTransform', () => { expect(projectionMock.useGlobeRendering).toBe(true); expect(globeTransform.isGlobeRendering).toBe(projectionMock.useGlobeRendering); }); + + describe('render world copies', () => { + test('change projection and make sure render world copies is kept', () => { + const globeTransform = createGlobeTransform(globeProjectionMock); + globeTransform.setRenderWorldCopies(true); + + expect(globeTransform.renderWorldCopies).toBeTruthy(); + }); + + test('change transform and make sure render world copies is kept', () => { + const globeTransform = createGlobeTransform(globeProjectionMock); + globeTransform.setRenderWorldCopies(true); + const mercator = new MercatorTransform(0, 1, 2, 3, false); + mercator.apply(globeTransform); + + expect(mercator.renderWorldCopies).toBeTruthy(); + }); + }); }); diff --git a/src/geo/projection/globe_transform.ts b/src/geo/projection/globe_transform.ts index 409f253b3e..527a297051 100644 --- a/src/geo/projection/globe_transform.ts +++ b/src/geo/projection/globe_transform.ts @@ -85,7 +85,8 @@ export class GlobeTransform implements ITransform { setMaxPitch(pitch: number): void { this._helper.setMaxPitch(pitch); } - setRenderWorldCopies(_renderWorldCopies: boolean): void { + setRenderWorldCopies(renderWorldCopies: boolean): void { + this._helper.setRenderWorldCopies(renderWorldCopies); } setBearing(bearing: number): void { this._helper.setBearing(bearing); @@ -212,7 +213,7 @@ export class GlobeTransform implements ITransform { return this._helper.unmodified; } get renderWorldCopies(): boolean { - return false; + return this._helper.renderWorldCopies; } // diff --git a/src/geo/projection/mercator_covering_tiles_details_provider.ts b/src/geo/projection/mercator_covering_tiles_details_provider.ts index cc034fae20..feed063e4d 100644 --- a/src/geo/projection/mercator_covering_tiles_details_provider.ts +++ b/src/geo/projection/mercator_covering_tiles_details_provider.ts @@ -44,4 +44,8 @@ export class MercatorCoveringTilesDetailsProvider implements CoveringTilesDetail const maxConstantZoomPitch = clamp(78.5 - zfov / 2, 0.0, 60.0); return (!!options.terrain || transform.pitch > maxConstantZoomPitch || transform.padding.top >= 0.1) } + + allowWorldCopies(): boolean { + return true; + } } \ No newline at end of file diff --git a/src/geo/projection/mercator_transform.test.ts b/src/geo/projection/mercator_transform.test.ts index 9e37478b61..e1f7618dd2 100644 --- a/src/geo/projection/mercator_transform.test.ts +++ b/src/geo/projection/mercator_transform.test.ts @@ -1,7 +1,7 @@ import {describe, test, expect} from 'vitest'; import Point from '@mapbox/point-geometry'; import {LngLat} from '../lng_lat'; -import {OverscaledTileID, CanonicalTileID, UnwrappedTileID} from '../../source/tile_id'; +import {CanonicalTileID, UnwrappedTileID} from '../../source/tile_id'; import {fixedLngLat, fixedCoord} from '../../../test/unit/lib/fixed'; import type {Terrain} from '../../render/terrain'; import {MercatorTransform} from './mercator_transform'; @@ -9,7 +9,6 @@ import {LngLatBounds} from '../lng_lat_bounds'; import {getMercatorHorizon} from './mercator_utils'; import {mat4} from 'gl-matrix'; import {expectToBeCloseToArray} from '../../util/test/util'; -import {coveringTiles, coveringZoomLevel} from './covering_tiles'; describe('transform', () => { test('creates a transform', () => { @@ -196,282 +195,6 @@ describe('transform', () => { } }); - describe('coveringTiles', () => { - const options = { - minzoom: 1, - maxzoom: 10, - tileSize: 512 - }; - - const transform = new MercatorTransform(0, 22, 0, 85, true); - transform.resize(200, 200); - - test('general', () => { - - // make slightly off center so that sort order is not subject to precision issues - transform.setCenter(new LngLat(-0.01, 0.01)); - - transform.setZoom(0); - expect(coveringTiles(transform, options)).toEqual([]); - - transform.setZoom(1); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(1, 0, 1, 0, 0), - new OverscaledTileID(1, 0, 1, 1, 0), - new OverscaledTileID(1, 0, 1, 0, 1), - new OverscaledTileID(1, 0, 1, 1, 1)]); - - transform.setZoom(2.4); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(2, 0, 2, 1, 1), - new OverscaledTileID(2, 0, 2, 2, 1), - new OverscaledTileID(2, 0, 2, 1, 2), - new OverscaledTileID(2, 0, 2, 2, 2)]); - - transform.setZoom(10); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(10, 0, 10, 511, 511), - new OverscaledTileID(10, 0, 10, 512, 511), - new OverscaledTileID(10, 0, 10, 511, 512), - new OverscaledTileID(10, 0, 10, 512, 512)]); - - transform.setZoom(11); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(10, 0, 10, 511, 511), - new OverscaledTileID(10, 0, 10, 512, 511), - new OverscaledTileID(10, 0, 10, 511, 512), - new OverscaledTileID(10, 0, 10, 512, 512)]); - - transform.setZoom(5.1); - transform.setPitch(60.0); - transform.setBearing(32.0); - transform.setCenter(new LngLat(56.90, 48.20)); - transform.resize(1024, 768); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(5, 0, 5, 21, 11), - new OverscaledTileID(5, 0, 5, 20, 11), - new OverscaledTileID(5, 0, 5, 21, 10), - new OverscaledTileID(5, 0, 5, 20, 10), - new OverscaledTileID(5, 0, 5, 21, 12), - new OverscaledTileID(5, 0, 5, 22, 11), - new OverscaledTileID(5, 0, 5, 20, 12), - new OverscaledTileID(5, 0, 5, 22, 10), - new OverscaledTileID(5, 0, 5, 21, 9), - new OverscaledTileID(5, 0, 5, 20, 9), - new OverscaledTileID(5, 0, 5, 22, 9), - new OverscaledTileID(5, 0, 5, 23, 10), - new OverscaledTileID(5, 0, 5, 21, 8), - new OverscaledTileID(5, 0, 5, 20, 8), - new OverscaledTileID(5, 0, 5, 23, 9), - new OverscaledTileID(5, 0, 5, 22, 8), - new OverscaledTileID(5, 0, 5, 23, 8), - new OverscaledTileID(5, 0, 5, 21, 7), - new OverscaledTileID(5, 0, 5, 20, 7), - new OverscaledTileID(5, 0, 5, 24, 9), - new OverscaledTileID(5, 0, 5, 22, 7) - ]); - - transform.setZoom(8); - transform.setPitch(85.0); - transform.setBearing(0.0); - transform.setCenter(new LngLat(20.918, 39.232)); - transform.resize(50, 1000); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(8, 0, 8, 142, 98), - new OverscaledTileID(7, 0, 7, 71, 48), - new OverscaledTileID(5, 0, 5, 17, 11), - new OverscaledTileID(5, 0, 5, 17, 10), - new OverscaledTileID(9, 0, 9, 285, 198), - new OverscaledTileID(9, 0, 9, 285, 199) - ]); - - transform.setZoom(8); - transform.setPitch(60); - transform.setBearing(45.0); - transform.setCenter(new LngLat(25.02, 60.15)); - transform.resize(300, 50); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(8, 0, 8, 145, 74), - new OverscaledTileID(8, 0, 8, 145, 73), - new OverscaledTileID(8, 0, 8, 146, 74) - ]); - - transform.resize(50, 300); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(8, 0, 8, 145, 74), - new OverscaledTileID(8, 0, 8, 145, 73), - new OverscaledTileID(8, 0, 8, 146, 74), - new OverscaledTileID(8, 0, 8, 146, 73) - ]); - - const optionsWithCustomTileLoading = { - minzoom: 1, - maxzoom: 10, - tileSize: 512, - calculateTileZoom: (_requestedCenterZoom: number, - _distanceToTile2D: number, - _distanceToTileZ: number, - _distanceToCenter3D: number, - _cameraVerticalFOV: number) => { return 7; } - }; - transform.resize(50, 300); - transform.setPitch(70); - expect(coveringTiles(transform, optionsWithCustomTileLoading)).toEqual([ - new OverscaledTileID(7, 0, 7, 74, 36), - new OverscaledTileID(7, 0, 7, 73, 37), - new OverscaledTileID(7, 0, 7, 74, 35), - new OverscaledTileID(7, 0, 7, 73, 36), - new OverscaledTileID(7, 0, 7, 72, 37), - new OverscaledTileID(7, 0, 7, 73, 35), - new OverscaledTileID(7, 0, 7, 72, 36) - ]); - - transform.setZoom(2); - transform.setPitch(0); - transform.setBearing(0); - transform.resize(300, 300); - }); - - test('calculates tile coverage at w > 0', () => { - transform.setCenter(new LngLat(630.01, 0.01)); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(2, 2, 2, 1, 1), - new OverscaledTileID(2, 2, 2, 1, 2), - new OverscaledTileID(2, 2, 2, 0, 1), - new OverscaledTileID(2, 2, 2, 0, 2) - ]); - }); - - test('calculates tile coverage at w = -1', () => { - transform.setCenter(new LngLat(-360.01, 0.01)); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(2, -1, 2, 1, 1), - new OverscaledTileID(2, -1, 2, 1, 2), - new OverscaledTileID(2, -1, 2, 2, 1), - new OverscaledTileID(2, -1, 2, 2, 2) - ]); - }); - - test('calculates tile coverage across meridian', () => { - transform.setZoom(1); - transform.setCenter(new LngLat(-180.01, 0.01)); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(1, 0, 1, 0, 0), - new OverscaledTileID(1, 0, 1, 0, 1), - new OverscaledTileID(1, -1, 1, 1, 0), - new OverscaledTileID(1, -1, 1, 1, 1) - ]); - }); - - test('only includes tiles for a single world, if renderWorldCopies is set to false', () => { - transform.setZoom(1); - transform.setCenter(new LngLat(-180.01, 0.01)); - transform.setRenderWorldCopies(false); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(1, 0, 1, 0, 0), - new OverscaledTileID(1, 0, 1, 0, 1) - ]); - }); - - }); - - test('coveringTiles: overscaledZ', () => { - const options = { - minzoom: 1, - maxzoom: 10, - tileSize: 256, - reparseOverscaled: true - }; - - const transform = new MercatorTransform(0, 10, 0, 85, true); - transform.resize(10, 400); - // make slightly off center so that sort order is not subject to precision issues - transform.setCenter(new LngLat(-0.01, 0.01)); - transform.setPitch(85); - transform.setFov(10); - - transform.setZoom(10); - const tiles = coveringTiles(transform, options); - for (const tile of tiles) { - expect(tile.overscaledZ).toBeGreaterThanOrEqual(tile.canonical.z); - } - }); - - test('maxzoom-0', () => { - const options = { - minzoom: 0, - maxzoom: 0, - tileSize: 512 - }; - - const transform = new MercatorTransform(0, 0, 0, 60, true); - transform.resize(200, 200); - transform.setCenter(new LngLat(0.01, 0.01)); - transform.setZoom(8); - expect(coveringTiles(transform, options)).toEqual([ - new OverscaledTileID(0, 0, 0, 0, 0) - ]); - }); - - test('coveringZoomLevel', () => { - const options = { - minzoom: 1, - maxzoom: 10, - tileSize: 512, - roundZoom: false, - }; - - const transform = new MercatorTransform(0, 22, 0, 60, true); - - transform.setZoom(0); - expect(coveringZoomLevel(transform, options)).toBe(0); - - transform.setZoom(0.1); - expect(coveringZoomLevel(transform, options)).toBe(0); - - transform.setZoom(1); - expect(coveringZoomLevel(transform, options)).toBe(1); - - transform.setZoom(2.4); - expect(coveringZoomLevel(transform, options)).toBe(2); - - transform.setZoom(10); - expect(coveringZoomLevel(transform, options)).toBe(10); - - transform.setZoom(11); - expect(coveringZoomLevel(transform, options)).toBe(11); - - transform.setZoom(11.5); - expect(coveringZoomLevel(transform, options)).toBe(11); - - options.tileSize = 256; - - transform.setZoom(0); - expect(coveringZoomLevel(transform, options)).toBe(1); - - transform.setZoom(0.1); - expect(coveringZoomLevel(transform, options)).toBe(1); - - transform.setZoom(1); - expect(coveringZoomLevel(transform, options)).toBe(2); - - transform.setZoom(2.4); - expect(coveringZoomLevel(transform, options)).toBe(3); - - transform.setZoom(10); - expect(coveringZoomLevel(transform, options)).toBe(11); - - transform.setZoom(11); - expect(coveringZoomLevel(transform, options)).toBe(12); - - transform.setZoom(11.5); - expect(coveringZoomLevel(transform, options)).toBe(12); - - options.roundZoom = true; - - expect(coveringZoomLevel(transform, options)).toBe(13); - }); - test('clamps pitch', () => { const transform = new MercatorTransform(0, 22, 0, 60, true);