From 3b6026c6428e9d5e66ad651d9028977136f29109 Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 08:55:27 +0200 Subject: [PATCH 1/9] Rename projection files --- src/geo/projection/covering_tiles.test.ts | 2 +- src/geo/projection/globe_camera_helper.ts | 2 +- src/geo/projection/{globe.ts => globe_projection.ts} | 2 +- src/geo/projection/globe_transform.test.ts | 2 +- src/geo/projection/globe_transform.ts | 2 +- src/geo/projection/{mercator.ts => mercator_projection.ts} | 0 src/geo/projection/projection_factory.ts | 4 ++-- src/render/draw_custom.test.ts | 2 +- src/render/draw_symbol.test.ts | 2 +- src/render/painter.ts | 2 +- src/ui/camera.test.ts | 2 +- src/ui/map_tests/map_events.test.ts | 2 +- src/ui/map_tests/map_resize.test.ts | 2 +- src/util/test/util.ts | 2 +- test/bench/benchmarks/covering_tiles_globe.ts | 2 +- test/bench/benchmarks/symbol_collision_box.ts | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) rename src/geo/projection/{globe.ts => globe_projection.ts} (99%) rename src/geo/projection/{mercator.ts => mercator_projection.ts} (100%) diff --git a/src/geo/projection/covering_tiles.test.ts b/src/geo/projection/covering_tiles.test.ts index b13d64df91..c864960c3c 100644 --- a/src/geo/projection/covering_tiles.test.ts +++ b/src/geo/projection/covering_tiles.test.ts @@ -1,6 +1,6 @@ import {beforeEach, describe, expect, test} from 'vitest'; import {GlobeTransform} from './globe_transform'; -import {globeConstants, type GlobeProjection} from './globe'; +import {globeConstants, type GlobeProjection} from './globe_projection'; import {getGlobeProjectionMock} from '../../util/test/util'; import {LngLat} from '../lng_lat'; import {coveringTiles, coveringZoomLevel, type CoveringZoomOptions} from './covering_tiles'; diff --git a/src/geo/projection/globe_camera_helper.ts b/src/geo/projection/globe_camera_helper.ts index a6bc4de361..408791e7f7 100644 --- a/src/geo/projection/globe_camera_helper.ts +++ b/src/geo/projection/globe_camera_helper.ts @@ -9,7 +9,7 @@ import {normalizeCenter} from '../transform_helper'; import {interpolates} from '@maplibre/maplibre-gl-style-spec'; import type {IReadonlyTransform, ITransform} from '../transform_interface'; -import type {GlobeProjection} from './globe'; +import type {GlobeProjection} from './globe_projection'; import type {CameraForBoundsOptions} from '../../ui/camera'; import type {LngLatBounds} from '../lng_lat_bounds'; import type {PaddingOptions} from '../edge_insets'; diff --git a/src/geo/projection/globe.ts b/src/geo/projection/globe_projection.ts similarity index 99% rename from src/geo/projection/globe.ts rename to src/geo/projection/globe_projection.ts index 7d728dad32..ab9b4f19ab 100644 --- a/src/geo/projection/globe.ts +++ b/src/geo/projection/globe_projection.ts @@ -7,7 +7,7 @@ import {mercatorYfromLat} from '../mercator_coordinate'; import {SubdivisionGranularityExpression, SubdivisionGranularitySetting} from '../../render/subdivision_granularity_settings'; import type {Projection, ProjectionGPUContext, TileMeshUsage} from './projection'; import {type PreparedShader, shaders} from '../../shaders/shaders'; -import {MercatorProjection} from './mercator'; +import {MercatorProjection} from './mercator_projection'; import {ProjectionErrorMeasurement} from './globe_projection_error_measurement'; import {createTileMeshWithBuffers, type CreateTileMeshOptions} from '../../util/create_tile_mesh'; diff --git a/src/geo/projection/globe_transform.test.ts b/src/geo/projection/globe_transform.test.ts index fc7abf8d1d..daf6775130 100644 --- a/src/geo/projection/globe_transform.test.ts +++ b/src/geo/projection/globe_transform.test.ts @@ -1,5 +1,5 @@ import {describe, expect, test} from 'vitest'; -import {globeConstants, type GlobeProjection} from './globe'; +import {globeConstants, type GlobeProjection} from './globe_projection'; import {EXTENT} from '../../data/extent'; import Point from '@mapbox/point-geometry'; import {LngLat} from '../lng_lat'; diff --git a/src/geo/projection/globe_transform.ts b/src/geo/projection/globe_transform.ts index 4057a4df8f..6fda28c3a5 100644 --- a/src/geo/projection/globe_transform.ts +++ b/src/geo/projection/globe_transform.ts @@ -6,7 +6,7 @@ import {LngLat, type LngLatLike,} from '../lng_lat'; import {createMat4f32, createMat4f64, differenceOfAnglesDegrees, easeCubicInOut, lerp, warnOnce} from '../../util/util'; import {OverscaledTileID, type UnwrappedTileID, type CanonicalTileID} from '../../source/tile_id'; import {browser} from '../../util/browser'; -import {globeConstants, type GlobeProjection} from './globe'; +import {globeConstants, type GlobeProjection} from './globe_projection'; import {EXTENT} from '../../data/extent'; import type Point from '@mapbox/point-geometry'; diff --git a/src/geo/projection/mercator.ts b/src/geo/projection/mercator_projection.ts similarity index 100% rename from src/geo/projection/mercator.ts rename to src/geo/projection/mercator_projection.ts diff --git a/src/geo/projection/projection_factory.ts b/src/geo/projection/projection_factory.ts index c7618b1c67..7e98a6fc01 100644 --- a/src/geo/projection/projection_factory.ts +++ b/src/geo/projection/projection_factory.ts @@ -1,9 +1,9 @@ import {type ProjectionSpecification} from '@maplibre/maplibre-gl-style-spec'; import {warnOnce} from '../../util/util'; -import {MercatorProjection} from './mercator'; +import {MercatorProjection} from './mercator_projection'; import {MercatorTransform} from './mercator_transform'; import {MercatorCameraHelper} from './mercator_camera_helper'; -import {GlobeProjection} from './globe'; +import {GlobeProjection} from './globe_projection'; import {GlobeTransform} from './globe_transform'; import {GlobeCameraHelper} from './globe_camera_helper'; import {VerticalPerspectiveTransform} from './vertical_perspective_transform'; diff --git a/src/render/draw_custom.test.ts b/src/render/draw_custom.test.ts index 79fffa0574..1e16e2ddad 100644 --- a/src/render/draw_custom.test.ts +++ b/src/render/draw_custom.test.ts @@ -7,7 +7,7 @@ import type {Map} from '../ui/map'; import {drawCustom} from './draw_custom'; import {CustomStyleLayer} from '../style/style_layer/custom_style_layer'; import {MercatorTransform} from '../geo/projection/mercator_transform'; -import {MercatorProjection} from '../geo/projection/mercator'; +import {MercatorProjection} from '../geo/projection/mercator_projection'; vi.mock('./painter'); vi.mock('./program'); diff --git a/src/render/draw_symbol.test.ts b/src/render/draw_symbol.test.ts index 0d66fd44c4..c847fc5f98 100644 --- a/src/render/draw_symbol.test.ts +++ b/src/render/draw_symbol.test.ts @@ -15,7 +15,7 @@ import {type IReadonlyTransform} from '../geo/transform_interface'; import type {EvaluationParameters} from '../style/evaluation_parameters'; import type {SymbolLayerSpecification} from '@maplibre/maplibre-gl-style-spec'; import {type Style} from '../style/style'; -import {MercatorProjection} from '../geo/projection/mercator'; +import {MercatorProjection} from '../geo/projection/mercator_projection'; import type {ProjectionData} from '../geo/projection/projection_data'; vi.mock('./painter'); diff --git a/src/render/painter.ts b/src/render/painter.ts index 300501ab82..fab875fee5 100644 --- a/src/render/painter.ts +++ b/src/render/painter.ts @@ -33,7 +33,7 @@ import {drawDepth, drawCoords} from './draw_terrain'; import {type OverscaledTileID} from '../source/tile_id'; import {drawSky, drawAtmosphere} from './draw_sky'; import {Mesh} from './mesh'; -import {MercatorShaderDefine, MercatorShaderVariantKey} from '../geo/projection/mercator'; +import {MercatorShaderDefine, MercatorShaderVariantKey} from '../geo/projection/mercator_projection'; import type {IReadonlyTransform} from '../geo/transform_interface'; import type {Style} from '../style/style'; diff --git a/src/ui/camera.test.ts b/src/ui/camera.test.ts index 83c14d8c7a..35060a0a80 100644 --- a/src/ui/camera.test.ts +++ b/src/ui/camera.test.ts @@ -13,7 +13,7 @@ import {getZoomAdjustment} from '../geo/projection/globe_utils'; import {GlobeCameraHelper} from '../geo/projection/globe_camera_helper'; import {MercatorCameraHelper} from '../geo/projection/mercator_camera_helper'; -import type {GlobeProjection} from '../geo/projection/globe'; +import type {GlobeProjection} from '../geo/projection/globe_projection'; import type {Terrain} from '../render/terrain'; beforeEach(() => { diff --git a/src/ui/map_tests/map_events.test.ts b/src/ui/map_tests/map_events.test.ts index e59a35d3bb..91a21f4948 100644 --- a/src/ui/map_tests/map_events.test.ts +++ b/src/ui/map_tests/map_events.test.ts @@ -6,7 +6,7 @@ import {type MapGeoJSONFeature} from '../../util/vectortile_to_geojson'; import {type MapLayerEventType, type MapLibreEvent} from '../events'; import {Map, type MapOptions} from '../map'; import {Event as EventedEvent, ErrorEvent} from '../../util/evented'; -import {GlobeProjection} from '../../geo/projection/globe'; +import {GlobeProjection} from '../../geo/projection/globe_projection'; type IsAny = 0 extends T & 1 ? T : never; type NotAny = T extends IsAny ? never : T; diff --git a/src/ui/map_tests/map_resize.test.ts b/src/ui/map_tests/map_resize.test.ts index 29f89bf09b..e5ea84b8d1 100644 --- a/src/ui/map_tests/map_resize.test.ts +++ b/src/ui/map_tests/map_resize.test.ts @@ -1,5 +1,5 @@ import {describe, beforeEach, test, expect, vi} from 'vitest'; -import {MercatorProjection} from '../../geo/projection/mercator'; +import {MercatorProjection} from '../../geo/projection/mercator_projection'; import {createMap, beforeMapTest, sleep} from '../../util/test/util'; beforeEach(() => { diff --git a/src/util/test/util.ts b/src/util/test/util.ts index cdd092bcb8..77be325ec4 100644 --- a/src/util/test/util.ts +++ b/src/util/test/util.ts @@ -9,7 +9,7 @@ import {MercatorTransform} from '../../geo/projection/mercator_transform'; import {RequestManager} from '../request_manager'; import {type IReadonlyTransform, type ITransform} from '../../geo/transform_interface'; import {type Style} from '../../style/style'; -import type {GlobeProjection} from '../../geo/projection/globe'; +import type {GlobeProjection} from '../../geo/projection/globe_projection'; export class StubMap extends Evented { style: Style; diff --git a/test/bench/benchmarks/covering_tiles_globe.ts b/test/bench/benchmarks/covering_tiles_globe.ts index f562855f21..dd907d75e9 100644 --- a/test/bench/benchmarks/covering_tiles_globe.ts +++ b/test/bench/benchmarks/covering_tiles_globe.ts @@ -1,6 +1,6 @@ import Benchmark from '../lib/benchmark'; import { GlobeTransform } from '../../../src/geo/projection/globe_transform'; -import { GlobeProjection } from '../../../src/geo/projection/globe'; +import { GlobeProjection } from '../../../src/geo/projection/globe_projection'; import { LngLat } from '../styles'; import { coveringTiles } from '../../../src/geo/projection/covering_tiles'; diff --git a/test/bench/benchmarks/symbol_collision_box.ts b/test/bench/benchmarks/symbol_collision_box.ts index 6fc22a408e..665aa5769b 100644 --- a/test/bench/benchmarks/symbol_collision_box.ts +++ b/test/bench/benchmarks/symbol_collision_box.ts @@ -8,7 +8,7 @@ import {SingleCollisionBox} from '../../../src/data/bucket/symbol_bucket'; import {EXTENT} from '../../../src/data/extent'; import {MercatorTransform} from '../../../src/geo/projection/mercator_transform'; import {mat4} from 'gl-matrix'; -import {GlobeProjection} from '../../../src/geo/projection/globe'; +import {GlobeProjection} from '../../../src/geo/projection/globe_projection'; import {GlobeTransform} from '../../../src/geo/projection/globe_transform'; type TestSymbol = { From 5e020b26c5af3e8976a31cea5e5b4e722b7365c5 Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 09:13:38 +0200 Subject: [PATCH 2/9] Rename globe to vertical-perspective --- src/geo/projection/covering_tiles.test.ts | 4 ++-- src/geo/projection/globe_camera_helper.ts | 6 +++--- src/geo/projection/globe_transform.test.ts | 4 ++-- src/geo/projection/globe_transform.ts | 6 +++--- src/geo/projection/projection_factory.ts | 6 +++--- ...lobe_projection.ts => vertial_perspective_projection.ts} | 2 +- src/ui/camera.test.ts | 4 ++-- src/ui/map_tests/map_events.test.ts | 4 ++-- src/util/test/util.ts | 6 +++--- test/bench/benchmarks/covering_tiles_globe.ts | 4 ++-- test/bench/benchmarks/symbol_collision_box.ts | 4 ++-- 11 files changed, 25 insertions(+), 25 deletions(-) rename src/geo/projection/{globe_projection.ts => vertial_perspective_projection.ts} (99%) diff --git a/src/geo/projection/covering_tiles.test.ts b/src/geo/projection/covering_tiles.test.ts index c864960c3c..78dd407b71 100644 --- a/src/geo/projection/covering_tiles.test.ts +++ b/src/geo/projection/covering_tiles.test.ts @@ -1,6 +1,6 @@ import {beforeEach, describe, expect, test} from 'vitest'; import {GlobeTransform} from './globe_transform'; -import {globeConstants, type GlobeProjection} from './globe_projection'; +import {globeConstants, type VerticalPerspectiveProjection} from './vertial_perspective_projection'; import {getGlobeProjectionMock} from '../../util/test/util'; import {LngLat} from '../lng_lat'; import {coveringTiles, coveringZoomLevel, type CoveringZoomOptions} from './covering_tiles'; @@ -9,7 +9,7 @@ import {MercatorTransform} from './mercator_transform'; describe('coveringTiles', () => { describe('globe', () => { - let globeProjectionMock: GlobeProjection; + let globeProjectionMock: VerticalPerspectiveProjection; beforeEach(() => { globeProjectionMock = getGlobeProjectionMock(); diff --git a/src/geo/projection/globe_camera_helper.ts b/src/geo/projection/globe_camera_helper.ts index 408791e7f7..c7c926b493 100644 --- a/src/geo/projection/globe_camera_helper.ts +++ b/src/geo/projection/globe_camera_helper.ts @@ -9,7 +9,7 @@ import {normalizeCenter} from '../transform_helper'; import {interpolates} from '@maplibre/maplibre-gl-style-spec'; import type {IReadonlyTransform, ITransform} from '../transform_interface'; -import type {GlobeProjection} from './globe_projection'; +import type {VerticalPerspectiveProjection} from './vertial_perspective_projection'; import type {CameraForBoundsOptions} from '../../ui/camera'; import type {LngLatBounds} from '../lng_lat_bounds'; import type {PaddingOptions} from '../edge_insets'; @@ -18,10 +18,10 @@ import type {PaddingOptions} from '../edge_insets'; * @internal */ export class GlobeCameraHelper implements ICameraHelper { - private _globe: GlobeProjection; + private _globe: VerticalPerspectiveProjection; private _mercatorCameraHelper: MercatorCameraHelper; - constructor(globe: GlobeProjection) { + constructor(globe: VerticalPerspectiveProjection) { this._globe = globe; this._mercatorCameraHelper = new MercatorCameraHelper(); } diff --git a/src/geo/projection/globe_transform.test.ts b/src/geo/projection/globe_transform.test.ts index daf6775130..d2da8343f8 100644 --- a/src/geo/projection/globe_transform.test.ts +++ b/src/geo/projection/globe_transform.test.ts @@ -1,5 +1,5 @@ import {describe, expect, test} from 'vitest'; -import {globeConstants, type GlobeProjection} from './globe_projection'; +import {globeConstants, type VerticalPerspectiveProjection} from './vertial_perspective_projection'; import {EXTENT} from '../../data/extent'; import Point from '@mapbox/point-geometry'; import {LngLat} from '../lng_lat'; @@ -27,7 +27,7 @@ function planeDistance(point: Array, plane: Array) { return point[0] * plane[0] + point[1] * plane[1] + point[2] * plane[2] + plane[3]; } -function createGlobeTransform(globeProjection: GlobeProjection) { +function createGlobeTransform(globeProjection: VerticalPerspectiveProjection) { const globeTransform = new GlobeTransform(globeProjection); globeTransform.resize(640, 480); globeTransform.setFov(45); diff --git a/src/geo/projection/globe_transform.ts b/src/geo/projection/globe_transform.ts index 6fda28c3a5..706f299880 100644 --- a/src/geo/projection/globe_transform.ts +++ b/src/geo/projection/globe_transform.ts @@ -6,7 +6,7 @@ import {LngLat, type LngLatLike,} from '../lng_lat'; import {createMat4f32, createMat4f64, differenceOfAnglesDegrees, easeCubicInOut, lerp, warnOnce} from '../../util/util'; import {OverscaledTileID, type UnwrappedTileID, type CanonicalTileID} from '../../source/tile_id'; import {browser} from '../../util/browser'; -import {globeConstants, type GlobeProjection} from './globe_projection'; +import {globeConstants, type VerticalPerspectiveProjection} from './vertial_perspective_projection'; import {EXTENT} from '../../data/extent'; import type Point from '@mapbox/point-geometry'; @@ -225,7 +225,7 @@ export class GlobeTransform implements ITransform { * Note: projection instance should only be accessed in the {@link newFrameUpdate} function. * to ensure the transform's state isn't unintentionally changed. */ - private _projectionInstance: GlobeProjection; + private _projectionInstance: VerticalPerspectiveProjection; private _globeLatitudeErrorCorrectionRadians: number = 0; /** @@ -249,7 +249,7 @@ export class GlobeTransform implements ITransform { private _mercatorTransform: MercatorTransform; private _verticalPerspectiveTransform: VerticalPerspectiveTransform; - public constructor(globeProjection: GlobeProjection) { + public constructor(globeProjection: VerticalPerspectiveProjection) { this._helper = new TransformHelper({ calcMatrices: () => { this._calcMatrices(); }, diff --git a/src/geo/projection/projection_factory.ts b/src/geo/projection/projection_factory.ts index 7e98a6fc01..6b98bd9c9d 100644 --- a/src/geo/projection/projection_factory.ts +++ b/src/geo/projection/projection_factory.ts @@ -3,7 +3,7 @@ import {warnOnce} from '../../util/util'; import {MercatorProjection} from './mercator_projection'; import {MercatorTransform} from './mercator_transform'; import {MercatorCameraHelper} from './mercator_camera_helper'; -import {GlobeProjection} from './globe_projection'; +import {VerticalPerspectiveProjection} from './vertial_perspective_projection'; import {GlobeTransform} from './globe_transform'; import {GlobeCameraHelper} from './globe_camera_helper'; import {VerticalPerspectiveTransform} from './vertical_perspective_transform'; @@ -27,7 +27,7 @@ export function createProjectionFromName(name: ProjectionSpecification['type']): } case 'globe': { - const proj = new GlobeProjection(); + const proj = new VerticalPerspectiveProjection(); return { projection: proj, transform: new GlobeTransform(proj), @@ -36,7 +36,7 @@ export function createProjectionFromName(name: ProjectionSpecification['type']): } case 'vertical-perspective': { - const proj = new GlobeProjection(); + const proj = new VerticalPerspectiveProjection(); return { projection: proj, transform: new VerticalPerspectiveTransform(), diff --git a/src/geo/projection/globe_projection.ts b/src/geo/projection/vertial_perspective_projection.ts similarity index 99% rename from src/geo/projection/globe_projection.ts rename to src/geo/projection/vertial_perspective_projection.ts index ab9b4f19ab..5040d7b44a 100644 --- a/src/geo/projection/globe_projection.ts +++ b/src/geo/projection/vertial_perspective_projection.ts @@ -32,7 +32,7 @@ const granularitySettingsGlobe: SubdivisionGranularitySetting = new SubdivisionG circle: 3 }); -export class GlobeProjection implements Projection { +export class VerticalPerspectiveProjection implements Projection { private _mercator: MercatorProjection; private _tileMeshCache: {[_: string]: Mesh} = {}; diff --git a/src/ui/camera.test.ts b/src/ui/camera.test.ts index 35060a0a80..6d6e09aa00 100644 --- a/src/ui/camera.test.ts +++ b/src/ui/camera.test.ts @@ -13,7 +13,7 @@ import {getZoomAdjustment} from '../geo/projection/globe_utils'; import {GlobeCameraHelper} from '../geo/projection/globe_camera_helper'; import {MercatorCameraHelper} from '../geo/projection/mercator_camera_helper'; -import type {GlobeProjection} from '../geo/projection/globe_projection'; +import type {VerticalPerspectiveProjection} from '../geo/projection/vertial_perspective_projection'; import type {Terrain} from '../render/terrain'; beforeEach(() => { @@ -53,7 +53,7 @@ function createCamera(options?): Camera & { simulateFrame: () => void } { const camera = attachSimulateFrame(new CameraMock(transform, new MercatorCameraHelper(), {} as any)); if (options.globe) { - camera.cameraHelper = new GlobeCameraHelper({useGlobeRendering: true} as GlobeProjection); + camera.cameraHelper = new GlobeCameraHelper({useGlobeRendering: true} as VerticalPerspectiveProjection); } camera.jumpTo(options); diff --git a/src/ui/map_tests/map_events.test.ts b/src/ui/map_tests/map_events.test.ts index 91a21f4948..c0cfcf0b31 100644 --- a/src/ui/map_tests/map_events.test.ts +++ b/src/ui/map_tests/map_events.test.ts @@ -6,7 +6,7 @@ import {type MapGeoJSONFeature} from '../../util/vectortile_to_geojson'; import {type MapLayerEventType, type MapLibreEvent} from '../events'; import {Map, type MapOptions} from '../map'; import {Event as EventedEvent, ErrorEvent} from '../../util/evented'; -import {GlobeProjection} from '../../geo/projection/globe_projection'; +import {VerticalPerspectiveProjection} from '../../geo/projection/vertial_perspective_projection'; type IsAny = 0 extends T & 1 ? T : never; type NotAny = T extends IsAny ? never : T; @@ -1071,7 +1071,7 @@ describe('map events', () => { }); test('projectiontransition is fired when globe transitions to mercator', async () => { const map = createMap(); - vi.spyOn(GlobeProjection.prototype, 'updateGPUdependent').mockImplementation(() => {}); + vi.spyOn(VerticalPerspectiveProjection.prototype, 'updateGPUdependent').mockImplementation(() => {}); await map.once('load'); const spy = vi.fn(); diff --git a/src/util/test/util.ts b/src/util/test/util.ts index 77be325ec4..17383d6607 100644 --- a/src/util/test/util.ts +++ b/src/util/test/util.ts @@ -9,7 +9,7 @@ import {MercatorTransform} from '../../geo/projection/mercator_transform'; import {RequestManager} from '../request_manager'; import {type IReadonlyTransform, type ITransform} from '../../geo/transform_interface'; import {type Style} from '../../style/style'; -import type {GlobeProjection} from '../../geo/projection/globe_projection'; +import type {VerticalPerspectiveProjection} from '../../geo/projection/vertial_perspective_projection'; export class StubMap extends Evented { style: Style; @@ -225,7 +225,7 @@ export function expectToBeCloseToArray(actual: Array, expected: Array { return undefined; }, From 89de97949633aedf894e44f3c30b62a9f2d94794 Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 09:40:04 +0200 Subject: [PATCH 3/9] Another rename + added projection for both mercator and vertical perspective --- src/geo/projection/covering_tiles.test.ts | 2 +- src/geo/projection/globe_camera_helper.ts | 2 +- src/geo/projection/globe_projection.ts | 118 ++++++++++++++++++ src/geo/projection/globe_transform.test.ts | 2 +- src/geo/projection/globe_transform.ts | 2 +- src/geo/projection/mercator_projection.ts | 6 + src/geo/projection/projection.ts | 10 +- src/geo/projection/projection_factory.ts | 2 +- ....ts => vertical_perspective_projection.ts} | 26 ++-- src/style/style.ts | 1 + src/ui/camera.test.ts | 2 +- src/ui/map_tests/map_events.test.ts | 2 +- src/util/test/util.ts | 2 +- test/bench/benchmarks/covering_tiles_globe.ts | 2 +- test/bench/benchmarks/symbol_collision_box.ts | 2 +- 15 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 src/geo/projection/globe_projection.ts rename src/geo/projection/{vertial_perspective_projection.ts => vertical_perspective_projection.ts} (93%) diff --git a/src/geo/projection/covering_tiles.test.ts b/src/geo/projection/covering_tiles.test.ts index 78dd407b71..74b70efca8 100644 --- a/src/geo/projection/covering_tiles.test.ts +++ b/src/geo/projection/covering_tiles.test.ts @@ -1,6 +1,6 @@ import {beforeEach, describe, expect, test} from 'vitest'; import {GlobeTransform} from './globe_transform'; -import {globeConstants, type VerticalPerspectiveProjection} from './vertial_perspective_projection'; +import {globeConstants, type VerticalPerspectiveProjection} from './vertical_perspective_projection'; import {getGlobeProjectionMock} from '../../util/test/util'; import {LngLat} from '../lng_lat'; import {coveringTiles, coveringZoomLevel, type CoveringZoomOptions} from './covering_tiles'; diff --git a/src/geo/projection/globe_camera_helper.ts b/src/geo/projection/globe_camera_helper.ts index c7c926b493..1491769936 100644 --- a/src/geo/projection/globe_camera_helper.ts +++ b/src/geo/projection/globe_camera_helper.ts @@ -9,7 +9,7 @@ import {normalizeCenter} from '../transform_helper'; import {interpolates} from '@maplibre/maplibre-gl-style-spec'; import type {IReadonlyTransform, ITransform} from '../transform_interface'; -import type {VerticalPerspectiveProjection} from './vertial_perspective_projection'; +import type {VerticalPerspectiveProjection} from './vertical_perspective_projection'; import type {CameraForBoundsOptions} from '../../ui/camera'; import type {LngLatBounds} from '../lng_lat_bounds'; import type {PaddingOptions} from '../edge_insets'; diff --git a/src/geo/projection/globe_projection.ts b/src/geo/projection/globe_projection.ts new file mode 100644 index 0000000000..ff237e3036 --- /dev/null +++ b/src/geo/projection/globe_projection.ts @@ -0,0 +1,118 @@ +import { ProjectionDefinition, ProjectionDefinitionSpecification, ProjectionSpecification, StylePropertySpecification, latest as styleSpec } from '@maplibre/maplibre-gl-style-spec'; +import { DataConstantProperty, PossiblyEvaluated, Properties, Transitionable, Transitioning, TransitionParameters } from '../../style/properties'; +import { Evented } from '../../util/evented'; +import { EvaluationParameters } from '../../style/evaluation_parameters'; +import { MercatorProjection } from './mercator_projection'; +import { VerticalPerspectiveProjection } from './vertical_perspective_projection'; +import { Projection, ProjectionGPUContext, TileMeshUsage } from './projection'; +import { PreparedShader } from '../../shaders/shaders'; +import { SubdivisionGranularitySetting } from '../../render/subdivision_granularity_settings'; +import { Context } from '../../gl/context'; +import { CanonicalTileID } from '../../source/tile_id'; +import { Mesh } from '../../render/mesh'; + +type ProjectionProps = { + type: DataConstantProperty; +} + +type ProjectionPossiblyEvaluated = { + type: ProjectionDefinitionSpecification; +} + +const properties: Properties = new Properties({ + 'type': new DataConstantProperty(styleSpec.projection.type as StylePropertySpecification) +}); + +export class GlobeProjection extends Evented implements Projection { + properties: PossiblyEvaluated; + + _transitionable: Transitionable; + _transitioning: Transitioning; + _mercatorProjection: MercatorProjection; + _verticalPerspectiveProjection: VerticalPerspectiveProjection; + + constructor(projection?: ProjectionSpecification) { + super(); + this._transitionable = new Transitionable(properties); + this.setProjection(projection); + this._transitioning = this._transitionable.untransitioned(); + this.recalculate(new EvaluationParameters(0)); + this._mercatorProjection = new MercatorProjection(); + this._verticalPerspectiveProjection = new VerticalPerspectiveProjection(); + } + + private get currentProjection(): Projection { + return this.useGlobeControls ? this._verticalPerspectiveProjection : this._mercatorProjection; + } + + setProjection(projection?: ProjectionSpecification) { + this._transitionable.setValue('type', projection.type); + } + + updateTransitions(parameters: TransitionParameters) { + this._transitioning = this._transitionable.transitioned(parameters, this._transitioning); + } + + hasTransition() { + return this._transitioning.hasTransition(); + } + + recalculate(parameters: EvaluationParameters) { + this.properties = this._transitioning.possiblyEvaluate(parameters); + } + + get name(): ProjectionSpecification['type'] { + return this.currentProjection.name; + } + + get useSubdivision(): boolean { + return this.currentProjection.useSubdivision; + } + + get shaderVariantName(): string { + return this.currentProjection.shaderVariantName; + } + + get shaderDefine(): string { + return this.currentProjection.shaderDefine; + } + + get shaderPreludeCode(): PreparedShader { + return this.currentProjection.shaderPreludeCode; + } + + get vertexShaderPreludeCode(): string { + return this.currentProjection.vertexShaderPreludeCode; + } + + get subdivisionGranularity(): SubdivisionGranularitySetting { + return this.currentProjection.subdivisionGranularity; + } + + get useGlobeControls(): boolean { + let currentProjectionSpecValue = this.properties.get('type'); + if (typeof currentProjectionSpecValue === 'string' && currentProjectionSpecValue === 'mercator') { + return false; + } + return true; + } + + public destroy(): void { + this._mercatorProjection.destroy(); + this._verticalPerspectiveProjection.destroy(); + } + + public isRenderingDirty(): boolean { + return this.currentProjection.isRenderingDirty(); + } + + public updateGPUdependent(context: ProjectionGPUContext): void { + this._mercatorProjection.updateGPUdependent(context); + this._verticalPerspectiveProjection.updateGPUdependent(context); + + } + + public getMeshFromTileID(context: Context, _tileID: CanonicalTileID, _hasBorder: boolean, _allowPoles: boolean, _usage: TileMeshUsage): Mesh { + return this.currentProjection.getMeshFromTileID(context, _tileID, _hasBorder, _allowPoles, _usage); + } +} \ No newline at end of file diff --git a/src/geo/projection/globe_transform.test.ts b/src/geo/projection/globe_transform.test.ts index d2da8343f8..d9161d6869 100644 --- a/src/geo/projection/globe_transform.test.ts +++ b/src/geo/projection/globe_transform.test.ts @@ -1,5 +1,5 @@ import {describe, expect, test} from 'vitest'; -import {globeConstants, type VerticalPerspectiveProjection} from './vertial_perspective_projection'; +import {globeConstants, type VerticalPerspectiveProjection} from './vertical_perspective_projection'; import {EXTENT} from '../../data/extent'; import Point from '@mapbox/point-geometry'; import {LngLat} from '../lng_lat'; diff --git a/src/geo/projection/globe_transform.ts b/src/geo/projection/globe_transform.ts index 706f299880..7fd0bd8d9f 100644 --- a/src/geo/projection/globe_transform.ts +++ b/src/geo/projection/globe_transform.ts @@ -6,7 +6,7 @@ import {LngLat, type LngLatLike,} from '../lng_lat'; import {createMat4f32, createMat4f64, differenceOfAnglesDegrees, easeCubicInOut, lerp, warnOnce} from '../../util/util'; import {OverscaledTileID, type UnwrappedTileID, type CanonicalTileID} from '../../source/tile_id'; import {browser} from '../../util/browser'; -import {globeConstants, type VerticalPerspectiveProjection} from './vertial_perspective_projection'; +import {globeConstants, type VerticalPerspectiveProjection} from './vertical_perspective_projection'; import {EXTENT} from '../../data/extent'; import type Point from '@mapbox/point-geometry'; diff --git a/src/geo/projection/mercator_projection.ts b/src/geo/projection/mercator_projection.ts index 9ae1a390a7..296e2ca86e 100644 --- a/src/geo/projection/mercator_projection.ts +++ b/src/geo/projection/mercator_projection.ts @@ -8,6 +8,7 @@ import {PosArray, TriangleIndexArray} from '../../data/array_types.g'; import {SegmentVector} from '../../data/segment'; import posAttributes from '../../data/pos_attributes'; import {SubdivisionGranularitySetting} from '../../render/subdivision_granularity_settings'; +import {EvaluationParameters} from '../../style/evaluation_parameters'; export const MercatorShaderDefine = '#define PROJECTION_MERCATOR'; export const MercatorShaderVariantKey = 'mercator'; @@ -84,4 +85,9 @@ export class MercatorProjection implements Projection { this._cachedMesh = new Mesh(tileExtentBuffer, quadTriangleIndexBuffer, tileExtentSegments); return this._cachedMesh; } + + recalculate(_params: EvaluationParameters): void { + // Do nothing. + } + } diff --git a/src/geo/projection/projection.ts b/src/geo/projection/projection.ts index adf37ff2e0..94aa7798fd 100644 --- a/src/geo/projection/projection.ts +++ b/src/geo/projection/projection.ts @@ -1,10 +1,11 @@ +import type {ProjectionSpecification} from '@maplibre/maplibre-gl-style-spec'; import type {CanonicalTileID} from '../../source/tile_id'; import type {PreparedShader} from '../../shaders/shaders'; import type {Context} from '../../gl/context'; import type {Mesh} from '../../render/mesh'; import type {Program} from '../../render/program'; import type {SubdivisionGranularitySetting} from '../../render/subdivision_granularity_settings'; -import type {ProjectionSpecification} from '@maplibre/maplibre-gl-style-spec'; +import type {EvaluationParameters} from '../../style/evaluation_parameters'; /** * Custom projections are handled both by a class which implements this `Projection` interface, @@ -118,4 +119,11 @@ export interface Projection { * @param usage - Specify the usage of the tile mesh, as different usages might use different levels of subdivision. */ getMeshFromTileID(context: Context, tileID: CanonicalTileID, hasBorder: boolean, allowPoles: boolean, usage: TileMeshUsage): Mesh; + + /** + * @internal + * Recalculates the projection state based on the current evaluation parameters. + * @param params - Evaluation parameters. + */ + recalculate(params: EvaluationParameters): void; } diff --git a/src/geo/projection/projection_factory.ts b/src/geo/projection/projection_factory.ts index 6b98bd9c9d..984227b863 100644 --- a/src/geo/projection/projection_factory.ts +++ b/src/geo/projection/projection_factory.ts @@ -3,7 +3,7 @@ import {warnOnce} from '../../util/util'; import {MercatorProjection} from './mercator_projection'; import {MercatorTransform} from './mercator_transform'; import {MercatorCameraHelper} from './mercator_camera_helper'; -import {VerticalPerspectiveProjection} from './vertial_perspective_projection'; +import {VerticalPerspectiveProjection} from './vertical_perspective_projection'; import {GlobeTransform} from './globe_transform'; import {GlobeCameraHelper} from './globe_camera_helper'; import {VerticalPerspectiveTransform} from './vertical_perspective_transform'; diff --git a/src/geo/projection/vertial_perspective_projection.ts b/src/geo/projection/vertical_perspective_projection.ts similarity index 93% rename from src/geo/projection/vertial_perspective_projection.ts rename to src/geo/projection/vertical_perspective_projection.ts index 5040d7b44a..fcaedd51ce 100644 --- a/src/geo/projection/vertial_perspective_projection.ts +++ b/src/geo/projection/vertical_perspective_projection.ts @@ -10,6 +10,10 @@ import {type PreparedShader, shaders} from '../../shaders/shaders'; import {MercatorProjection} from './mercator_projection'; import {ProjectionErrorMeasurement} from './globe_projection_error_measurement'; import {createTileMeshWithBuffers, type CreateTileMeshOptions} from '../../util/create_tile_mesh'; +import {EvaluationParameters} from '../../style/evaluation_parameters'; + +export const VerticalPerspectiveShaderDefine = '#define GLOBE'; +export const VerticalPerspectiveShaderVariantKey = 'globe'; export const globeConstants = { globeTransitionTimeSeconds: 0.5, @@ -51,8 +55,8 @@ export class VerticalPerspectiveProjection implements Projection { private _errorCorrectionPreviousValue: number = 0.0; private _errorMeasurementLastChangeTime: number = -1000.0; - get name(): 'globe' { - return 'globe'; + get name(): 'vertical-perspective' { + return 'vertical-perspective'; } /** @@ -72,19 +76,19 @@ export class VerticalPerspectiveProjection implements Projection { } get useSubdivision(): boolean { - return this.useGlobeRendering; + return true; } get shaderVariantName(): string { - return this.useGlobeRendering ? 'globe' : this._mercator.shaderVariantName; + return VerticalPerspectiveShaderVariantKey; } get shaderDefine(): string { - return this.useGlobeRendering ? '#define GLOBE' : this._mercator.shaderDefine; + return VerticalPerspectiveShaderDefine; } get shaderPreludeCode(): PreparedShader { - return this.useGlobeRendering ? shaders.projectionGlobe : this._mercator.shaderPreludeCode; + return shaders.projectionGlobe; } get vertexShaderPreludeCode(): string { @@ -96,7 +100,7 @@ export class VerticalPerspectiveProjection implements Projection { } get useGlobeControls(): boolean { - return this._useGlobeRendering; + return true; } get errorQueryLatitudeDegrees(): number { return this._errorQueryLatitudeDegrees; } @@ -118,10 +122,6 @@ export class VerticalPerspectiveProjection implements Projection { */ get latitudeErrorCorrectionRadians(): number { return this._errorCorrectionUsable; } - constructor() { - this._mercator = new MercatorProjection(); - } - public destroy() { if (this._errorMeasurement) { this._errorMeasurement.destroy(); @@ -190,4 +190,8 @@ export class VerticalPerspectiveProjection implements Projection { this._tileMeshCache[key] = mesh; return mesh; } + + recalculate(_params: EvaluationParameters): void { + // Do nothing. + } } diff --git a/src/style/style.ts b/src/style/style.ts index 3bf8fa1218..1087412a43 100644 --- a/src/style/style.ts +++ b/src/style/style.ts @@ -657,6 +657,7 @@ export class Style extends Evented { this.light.recalculate(parameters); this.sky.recalculate(parameters); + this.projection.recalculate(parameters); this.z = parameters.zoom; if (changed) { diff --git a/src/ui/camera.test.ts b/src/ui/camera.test.ts index 6d6e09aa00..e343a88c92 100644 --- a/src/ui/camera.test.ts +++ b/src/ui/camera.test.ts @@ -13,7 +13,7 @@ import {getZoomAdjustment} from '../geo/projection/globe_utils'; import {GlobeCameraHelper} from '../geo/projection/globe_camera_helper'; import {MercatorCameraHelper} from '../geo/projection/mercator_camera_helper'; -import type {VerticalPerspectiveProjection} from '../geo/projection/vertial_perspective_projection'; +import type {VerticalPerspectiveProjection} from '../geo/projection/vertical_perspective_projection'; import type {Terrain} from '../render/terrain'; beforeEach(() => { diff --git a/src/ui/map_tests/map_events.test.ts b/src/ui/map_tests/map_events.test.ts index c0cfcf0b31..6c482e0115 100644 --- a/src/ui/map_tests/map_events.test.ts +++ b/src/ui/map_tests/map_events.test.ts @@ -6,7 +6,7 @@ import {type MapGeoJSONFeature} from '../../util/vectortile_to_geojson'; import {type MapLayerEventType, type MapLibreEvent} from '../events'; import {Map, type MapOptions} from '../map'; import {Event as EventedEvent, ErrorEvent} from '../../util/evented'; -import {VerticalPerspectiveProjection} from '../../geo/projection/vertial_perspective_projection'; +import {VerticalPerspectiveProjection} from '../../geo/projection/vertical_perspective_projection'; type IsAny = 0 extends T & 1 ? T : never; type NotAny = T extends IsAny ? never : T; diff --git a/src/util/test/util.ts b/src/util/test/util.ts index 17383d6607..0870b03d27 100644 --- a/src/util/test/util.ts +++ b/src/util/test/util.ts @@ -9,7 +9,7 @@ import {MercatorTransform} from '../../geo/projection/mercator_transform'; import {RequestManager} from '../request_manager'; import {type IReadonlyTransform, type ITransform} from '../../geo/transform_interface'; import {type Style} from '../../style/style'; -import type {VerticalPerspectiveProjection} from '../../geo/projection/vertial_perspective_projection'; +import type {VerticalPerspectiveProjection} from '../../geo/projection/vertical_perspective_projection'; export class StubMap extends Evented { style: Style; diff --git a/test/bench/benchmarks/covering_tiles_globe.ts b/test/bench/benchmarks/covering_tiles_globe.ts index b6cabb7f36..836376252f 100644 --- a/test/bench/benchmarks/covering_tiles_globe.ts +++ b/test/bench/benchmarks/covering_tiles_globe.ts @@ -1,6 +1,6 @@ import Benchmark from '../lib/benchmark'; import { GlobeTransform } from '../../../src/geo/projection/globe_transform'; -import { VerticalPerspectiveProjection } from '../../../src/geo/projection/vertial_perspective_projection'; +import { VerticalPerspectiveProjection } from '../../../src/geo/projection/vertical_perspective_projection'; import { LngLat } from '../styles'; import { coveringTiles } from '../../../src/geo/projection/covering_tiles'; diff --git a/test/bench/benchmarks/symbol_collision_box.ts b/test/bench/benchmarks/symbol_collision_box.ts index 0a98ba2ec5..eee48e2209 100644 --- a/test/bench/benchmarks/symbol_collision_box.ts +++ b/test/bench/benchmarks/symbol_collision_box.ts @@ -8,7 +8,7 @@ import {SingleCollisionBox} from '../../../src/data/bucket/symbol_bucket'; import {EXTENT} from '../../../src/data/extent'; import {MercatorTransform} from '../../../src/geo/projection/mercator_transform'; import {mat4} from 'gl-matrix'; -import {VerticalPerspectiveProjection} from '../../../src/geo/projection/vertial_perspective_projection'; +import {VerticalPerspectiveProjection} from '../../../src/geo/projection/vertical_perspective_projection'; import {GlobeTransform} from '../../../src/geo/projection/globe_transform'; type TestSymbol = { From 8d63ebff0df35fd0e89fd09d5a685442fe189b29 Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 09:47:44 +0200 Subject: [PATCH 4/9] Improve types to see where we have issues --- src/geo/projection/globe_projection.ts | 2 +- src/geo/projection/projection.ts | 2 +- src/render/painter.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/geo/projection/globe_projection.ts b/src/geo/projection/globe_projection.ts index ff237e3036..a86002efeb 100644 --- a/src/geo/projection/globe_projection.ts +++ b/src/geo/projection/globe_projection.ts @@ -61,7 +61,7 @@ export class GlobeProjection extends Evented implements Projection { this.properties = this._transitioning.possiblyEvaluate(parameters); } - get name(): ProjectionSpecification['type'] { + get name(): 'mercator' | 'vertical-perspective' { return this.currentProjection.name; } diff --git a/src/geo/projection/projection.ts b/src/geo/projection/projection.ts index 94aa7798fd..eda2ad542b 100644 --- a/src/geo/projection/projection.ts +++ b/src/geo/projection/projection.ts @@ -47,7 +47,7 @@ export interface Projection { * @internal * A short, descriptive name of this projection, such as 'mercator' or 'globe'. */ - get name(): ProjectionSpecification['type']; + get name(): 'mercator' | 'vertical-perspective'; /** * @internal diff --git a/src/render/painter.ts b/src/render/painter.ts index fab875fee5..09fd845bf0 100644 --- a/src/render/painter.ts +++ b/src/render/painter.ts @@ -492,7 +492,7 @@ export class Painter { const coordsAscending: {[_: string]: Array} = {}; const coordsDescending: {[_: string]: Array} = {}; const coordsDescendingSymbol: {[_: string]: Array} = {}; - const renderOptions: RenderOptions = {isRenderingToTexture: false, isRenderingGlobe: style.projection.name === 'globe'}; + const renderOptions: RenderOptions = {isRenderingToTexture: false, isRenderingGlobe: style.projection.name === 'vertical-perspective'}; for (const id in sourceCaches) { const sourceCache = sourceCaches[id]; From 41f12ba3bb4b387e466abc7aadff97296a29c359 Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 09:48:54 +0200 Subject: [PATCH 5/9] Fix lint --- src/geo/projection/globe_projection.ts | 26 +++++++++---------- src/geo/projection/mercator_projection.ts | 2 +- src/geo/projection/projection.ts | 1 - .../vertical_perspective_projection.ts | 4 +-- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/geo/projection/globe_projection.ts b/src/geo/projection/globe_projection.ts index a86002efeb..a7089111e8 100644 --- a/src/geo/projection/globe_projection.ts +++ b/src/geo/projection/globe_projection.ts @@ -1,15 +1,15 @@ -import { ProjectionDefinition, ProjectionDefinitionSpecification, ProjectionSpecification, StylePropertySpecification, latest as styleSpec } from '@maplibre/maplibre-gl-style-spec'; -import { DataConstantProperty, PossiblyEvaluated, Properties, Transitionable, Transitioning, TransitionParameters } from '../../style/properties'; -import { Evented } from '../../util/evented'; -import { EvaluationParameters } from '../../style/evaluation_parameters'; -import { MercatorProjection } from './mercator_projection'; -import { VerticalPerspectiveProjection } from './vertical_perspective_projection'; -import { Projection, ProjectionGPUContext, TileMeshUsage } from './projection'; -import { PreparedShader } from '../../shaders/shaders'; -import { SubdivisionGranularitySetting } from '../../render/subdivision_granularity_settings'; -import { Context } from '../../gl/context'; -import { CanonicalTileID } from '../../source/tile_id'; -import { Mesh } from '../../render/mesh'; +import {type ProjectionDefinition, type ProjectionDefinitionSpecification, type ProjectionSpecification, type StylePropertySpecification, latest as styleSpec} from '@maplibre/maplibre-gl-style-spec'; +import {DataConstantProperty, type PossiblyEvaluated, Properties, Transitionable, type Transitioning, type TransitionParameters} from '../../style/properties'; +import {Evented} from '../../util/evented'; +import {EvaluationParameters} from '../../style/evaluation_parameters'; +import {MercatorProjection} from './mercator_projection'; +import {VerticalPerspectiveProjection} from './vertical_perspective_projection'; +import {type Projection, type ProjectionGPUContext, type TileMeshUsage} from './projection'; +import {type PreparedShader} from '../../shaders/shaders'; +import {type SubdivisionGranularitySetting} from '../../render/subdivision_granularity_settings'; +import {type Context} from '../../gl/context'; +import {type CanonicalTileID} from '../../source/tile_id'; +import {type Mesh} from '../../render/mesh'; type ProjectionProps = { type: DataConstantProperty; @@ -90,7 +90,7 @@ export class GlobeProjection extends Evented implements Projection { } get useGlobeControls(): boolean { - let currentProjectionSpecValue = this.properties.get('type'); + const currentProjectionSpecValue = this.properties.get('type'); if (typeof currentProjectionSpecValue === 'string' && currentProjectionSpecValue === 'mercator') { return false; } diff --git a/src/geo/projection/mercator_projection.ts b/src/geo/projection/mercator_projection.ts index 296e2ca86e..76d5cdafca 100644 --- a/src/geo/projection/mercator_projection.ts +++ b/src/geo/projection/mercator_projection.ts @@ -8,7 +8,7 @@ import {PosArray, TriangleIndexArray} from '../../data/array_types.g'; import {SegmentVector} from '../../data/segment'; import posAttributes from '../../data/pos_attributes'; import {SubdivisionGranularitySetting} from '../../render/subdivision_granularity_settings'; -import {EvaluationParameters} from '../../style/evaluation_parameters'; +import {type EvaluationParameters} from '../../style/evaluation_parameters'; export const MercatorShaderDefine = '#define PROJECTION_MERCATOR'; export const MercatorShaderVariantKey = 'mercator'; diff --git a/src/geo/projection/projection.ts b/src/geo/projection/projection.ts index eda2ad542b..a9a9abacee 100644 --- a/src/geo/projection/projection.ts +++ b/src/geo/projection/projection.ts @@ -1,4 +1,3 @@ -import type {ProjectionSpecification} from '@maplibre/maplibre-gl-style-spec'; import type {CanonicalTileID} from '../../source/tile_id'; import type {PreparedShader} from '../../shaders/shaders'; import type {Context} from '../../gl/context'; diff --git a/src/geo/projection/vertical_perspective_projection.ts b/src/geo/projection/vertical_perspective_projection.ts index fcaedd51ce..d459d418e8 100644 --- a/src/geo/projection/vertical_perspective_projection.ts +++ b/src/geo/projection/vertical_perspective_projection.ts @@ -7,10 +7,10 @@ import {mercatorYfromLat} from '../mercator_coordinate'; import {SubdivisionGranularityExpression, SubdivisionGranularitySetting} from '../../render/subdivision_granularity_settings'; import type {Projection, ProjectionGPUContext, TileMeshUsage} from './projection'; import {type PreparedShader, shaders} from '../../shaders/shaders'; -import {MercatorProjection} from './mercator_projection'; +import {type MercatorProjection} from './mercator_projection'; import {ProjectionErrorMeasurement} from './globe_projection_error_measurement'; import {createTileMeshWithBuffers, type CreateTileMeshOptions} from '../../util/create_tile_mesh'; -import {EvaluationParameters} from '../../style/evaluation_parameters'; +import {type EvaluationParameters} from '../../style/evaluation_parameters'; export const VerticalPerspectiveShaderDefine = '#define GLOBE'; export const VerticalPerspectiveShaderVariantKey = 'globe'; From d71a9c035692da34309280b9a1840d306c4204e0 Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 09:56:23 +0200 Subject: [PATCH 6/9] Rename file --- src/geo/projection/projection_factory.ts | 2 +- ...e_camera_helper.ts => vertical_perspective_camera_helper.ts} | 0 src/ui/camera.test.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/geo/projection/{globe_camera_helper.ts => vertical_perspective_camera_helper.ts} (100%) diff --git a/src/geo/projection/projection_factory.ts b/src/geo/projection/projection_factory.ts index 984227b863..4cc4542546 100644 --- a/src/geo/projection/projection_factory.ts +++ b/src/geo/projection/projection_factory.ts @@ -5,7 +5,7 @@ import {MercatorTransform} from './mercator_transform'; import {MercatorCameraHelper} from './mercator_camera_helper'; import {VerticalPerspectiveProjection} from './vertical_perspective_projection'; import {GlobeTransform} from './globe_transform'; -import {GlobeCameraHelper} from './globe_camera_helper'; +import {GlobeCameraHelper} from './vertical_perspective_camera_helper'; import {VerticalPerspectiveTransform} from './vertical_perspective_transform'; import type {Projection} from './projection'; import type {ITransform} from '../transform_interface'; diff --git a/src/geo/projection/globe_camera_helper.ts b/src/geo/projection/vertical_perspective_camera_helper.ts similarity index 100% rename from src/geo/projection/globe_camera_helper.ts rename to src/geo/projection/vertical_perspective_camera_helper.ts diff --git a/src/ui/camera.test.ts b/src/ui/camera.test.ts index e343a88c92..d41b6e8307 100644 --- a/src/ui/camera.test.ts +++ b/src/ui/camera.test.ts @@ -10,7 +10,7 @@ import {LngLatBounds} from '../geo/lng_lat_bounds'; import {MercatorTransform} from '../geo/projection/mercator_transform'; import {GlobeTransform} from '../geo/projection/globe_transform'; import {getZoomAdjustment} from '../geo/projection/globe_utils'; -import {GlobeCameraHelper} from '../geo/projection/globe_camera_helper'; +import {GlobeCameraHelper} from '../geo/projection/vertical_perspective_camera_helper'; import {MercatorCameraHelper} from '../geo/projection/mercator_camera_helper'; import type {VerticalPerspectiveProjection} from '../geo/projection/vertical_perspective_projection'; From 83e891d94ad62487a5f8205444a40476f1b99adb Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 09:58:06 +0200 Subject: [PATCH 7/9] Rename class --- src/geo/projection/projection_factory.ts | 6 +++--- .../projection/vertical_perspective_camera_helper.ts | 10 +++++----- src/ui/camera.test.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/geo/projection/projection_factory.ts b/src/geo/projection/projection_factory.ts index 4cc4542546..addc09d6f0 100644 --- a/src/geo/projection/projection_factory.ts +++ b/src/geo/projection/projection_factory.ts @@ -5,7 +5,7 @@ import {MercatorTransform} from './mercator_transform'; import {MercatorCameraHelper} from './mercator_camera_helper'; import {VerticalPerspectiveProjection} from './vertical_perspective_projection'; import {GlobeTransform} from './globe_transform'; -import {GlobeCameraHelper} from './vertical_perspective_camera_helper'; +import {VerticalPerspectiveCameraHelper} from './vertical_perspective_camera_helper'; import {VerticalPerspectiveTransform} from './vertical_perspective_transform'; import type {Projection} from './projection'; import type {ITransform} from '../transform_interface'; @@ -31,7 +31,7 @@ export function createProjectionFromName(name: ProjectionSpecification['type']): return { projection: proj, transform: new GlobeTransform(proj), - cameraHelper: new GlobeCameraHelper(proj), + cameraHelper: new VerticalPerspectiveCameraHelper(proj), }; } case 'vertical-perspective': @@ -40,7 +40,7 @@ export function createProjectionFromName(name: ProjectionSpecification['type']): return { projection: proj, transform: new VerticalPerspectiveTransform(), - cameraHelper: new GlobeCameraHelper(proj), + cameraHelper: new VerticalPerspectiveCameraHelper(proj), }; } default: diff --git a/src/geo/projection/vertical_perspective_camera_helper.ts b/src/geo/projection/vertical_perspective_camera_helper.ts index 1491769936..1ab7f69deb 100644 --- a/src/geo/projection/vertical_perspective_camera_helper.ts +++ b/src/geo/projection/vertical_perspective_camera_helper.ts @@ -17,7 +17,7 @@ import type {PaddingOptions} from '../edge_insets'; /** * @internal */ -export class GlobeCameraHelper implements ICameraHelper { +export class VerticalPerspectiveCameraHelper implements ICameraHelper { private _globe: VerticalPerspectiveProjection; private _mercatorCameraHelper: MercatorCameraHelper; @@ -215,13 +215,13 @@ export class GlobeCameraHelper implements ICameraHelper { let smallestNeededScale = Number.POSITIVE_INFINITY; for (const vec of testVectors) { if (xLeft < 0) - smallestNeededScale = GlobeCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, GlobeCameraHelper.solveVectorScale(vec, vecToCenter, matrix, 'x', xLeft)); + smallestNeededScale = VerticalPerspectiveCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, VerticalPerspectiveCameraHelper.solveVectorScale(vec, vecToCenter, matrix, 'x', xLeft)); if (xRight > 0) - smallestNeededScale = GlobeCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, GlobeCameraHelper.solveVectorScale(vec, vecToCenter, matrix, 'x', xRight)); + smallestNeededScale = VerticalPerspectiveCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, VerticalPerspectiveCameraHelper.solveVectorScale(vec, vecToCenter, matrix, 'x', xRight)); if (yTop > 0) - smallestNeededScale = GlobeCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, GlobeCameraHelper.solveVectorScale(vec, vecToCenter, matrix, 'y', yTop)); + smallestNeededScale = VerticalPerspectiveCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, VerticalPerspectiveCameraHelper.solveVectorScale(vec, vecToCenter, matrix, 'y', yTop)); if (yBottom < 0) - smallestNeededScale = GlobeCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, GlobeCameraHelper.solveVectorScale(vec, vecToCenter, matrix, 'y', yBottom)); + smallestNeededScale = VerticalPerspectiveCameraHelper.getLesserNonNegativeNonNull(smallestNeededScale, VerticalPerspectiveCameraHelper.solveVectorScale(vec, vecToCenter, matrix, 'y', yBottom)); } if (!Number.isFinite(smallestNeededScale) || smallestNeededScale === 0) { diff --git a/src/ui/camera.test.ts b/src/ui/camera.test.ts index d41b6e8307..481b134e72 100644 --- a/src/ui/camera.test.ts +++ b/src/ui/camera.test.ts @@ -10,7 +10,7 @@ import {LngLatBounds} from '../geo/lng_lat_bounds'; import {MercatorTransform} from '../geo/projection/mercator_transform'; import {GlobeTransform} from '../geo/projection/globe_transform'; import {getZoomAdjustment} from '../geo/projection/globe_utils'; -import {GlobeCameraHelper} from '../geo/projection/vertical_perspective_camera_helper'; +import {VerticalPerspectiveCameraHelper} from '../geo/projection/vertical_perspective_camera_helper'; import {MercatorCameraHelper} from '../geo/projection/mercator_camera_helper'; import type {VerticalPerspectiveProjection} from '../geo/projection/vertical_perspective_projection'; @@ -53,7 +53,7 @@ function createCamera(options?): Camera & { simulateFrame: () => void } { const camera = attachSimulateFrame(new CameraMock(transform, new MercatorCameraHelper(), {} as any)); if (options.globe) { - camera.cameraHelper = new GlobeCameraHelper({useGlobeRendering: true} as VerticalPerspectiveProjection); + camera.cameraHelper = new VerticalPerspectiveCameraHelper({useGlobeRendering: true} as VerticalPerspectiveProjection); } camera.jumpTo(options); From fcdf8a62b2f5245185d3dc157d2754c91282047c Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 10:01:07 +0200 Subject: [PATCH 8/9] Fix unit tests --- src/ui/control/globe_control.test.ts | 2 +- src/ui/map_tests/map_events.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/control/globe_control.test.ts b/src/ui/control/globe_control.test.ts index 9d974a70c7..018b154243 100644 --- a/src/ui/control/globe_control.test.ts +++ b/src/ui/control/globe_control.test.ts @@ -57,7 +57,7 @@ describe('GlobeControl', () => { button.click(); await new Promise(resolve => setTimeout(resolve, 0)); - expect(map.style.projection.name).toBe('globe'); + expect(map.style.projection.name).toBe('vertical-perspective'); button.click(); await new Promise(resolve => setTimeout(resolve, 0)); diff --git a/src/ui/map_tests/map_events.test.ts b/src/ui/map_tests/map_events.test.ts index 6c482e0115..7affb8ed83 100644 --- a/src/ui/map_tests/map_events.test.ts +++ b/src/ui/map_tests/map_events.test.ts @@ -1066,7 +1066,7 @@ describe('map events', () => { type: 'mercator', }); expect(spy).toHaveBeenCalledTimes(2); - expect(spy).toHaveBeenNthCalledWith(1, 'globe'); + expect(spy).toHaveBeenNthCalledWith(1, 'vertical-perspective'); expect(spy).toHaveBeenNthCalledWith(2, 'mercator'); }); test('projectiontransition is fired when globe transitions to mercator', async () => { @@ -1093,7 +1093,7 @@ describe('map events', () => { }); expect(spy).toHaveBeenCalledTimes(4); - expect(spy).toHaveBeenNthCalledWith(1, 'globe'); + expect(spy).toHaveBeenNthCalledWith(1, 'vertical-perspective'); expect(spy).toHaveBeenNthCalledWith(2, 'globe-mercator'); expect(spy).toHaveBeenNthCalledWith(3, 'globe'); expect(spy).toHaveBeenNthCalledWith(4, 'mercator'); From be99d1c417f9f3f7b2438e778e700aab7637269f Mon Sep 17 00:00:00 2001 From: HarelM Date: Fri, 6 Dec 2024 13:35:40 +0200 Subject: [PATCH 9/9] Final changes --- src/geo/projection/camera_helper.ts | 72 +++++++++- src/geo/projection/covering_tiles.test.ts | 22 ++- src/geo/projection/globe_camera_helper.ts | 66 +++++++++ src/geo/projection/globe_projection.ts | 25 +++- src/geo/projection/globe_transform.test.ts | 62 +++----- src/geo/projection/globe_transform.ts | 136 +++++------------- src/geo/projection/mercator_camera_helper.ts | 71 +-------- src/geo/projection/mercator_projection.ts | 4 + src/geo/projection/mercator_transform.ts | 9 +- src/geo/projection/projection.ts | 2 + src/geo/projection/projection_factory.ts | 26 ++-- .../vertical_perspective_camera_helper.ts | 48 +------ .../vertical_perspective_projection.ts | 6 +- .../vertical_perspective_transform.ts | 9 +- src/geo/transform_interface.ts | 23 ++- src/ui/camera.test.ts | 5 +- src/ui/control/navigation_control.ts | 1 - src/ui/map.ts | 14 +- test/bench/benchmarks/covering_tiles_globe.ts | 10 +- test/bench/benchmarks/symbol_collision_box.ts | 3 +- 20 files changed, 289 insertions(+), 325 deletions(-) create mode 100644 src/geo/projection/globe_camera_helper.ts diff --git a/src/geo/projection/camera_helper.ts b/src/geo/projection/camera_helper.ts index 6887af51eb..a0acb06dbb 100644 --- a/src/geo/projection/camera_helper.ts +++ b/src/geo/projection/camera_helper.ts @@ -1,12 +1,13 @@ -import type Point from '@mapbox/point-geometry'; +import Point from '@mapbox/point-geometry'; import {type IReadonlyTransform, type ITransform} from '../transform_interface'; import {type LngLat, type LngLatLike} from '../lng_lat'; import {type CameraForBoundsOptions, type PointLike} from '../../ui/camera'; import {type PaddingOptions} from '../edge_insets'; import {type LngLatBounds} from '../lng_lat_bounds'; -import {getRollPitchBearing, type RollPitchBearing, rollPitchBearingToQuat, warnOnce} from '../../util/util'; +import {degreesToRadians, getRollPitchBearing, type RollPitchBearing, rollPitchBearingToQuat, scaleZoom, warnOnce, zoomScale} from '../../util/util'; import {quat} from 'gl-matrix'; import {interpolates} from '@maplibre/maplibre-gl-style-spec'; +import {projectToWorldCoordinates, unprojectFromWorldCoordinates} from './mercator_utils'; export type MapControlsDeltas = { panDelta: Point; @@ -151,3 +152,70 @@ export function updateRotation(args: UpdateRotationArgs) { args.tr.setBearing(interpolates.number(args.startEulerAngles.bearing, args.endEulerAngles.bearing, args.k)); } } + +export function cameraForBoxAndBearing(options: CameraForBoundsOptions, padding: PaddingOptions, bounds: LngLatBounds, bearing: number, tr: IReadonlyTransform): CameraForBoxAndBearingHandlerResult { + const edgePadding = tr.padding; + + // Consider all corners of the rotated bounding box derived from the given points + // when find the camera position that fits the given points. + + const nwWorld = projectToWorldCoordinates(tr.worldSize, bounds.getNorthWest()); + const neWorld = projectToWorldCoordinates(tr.worldSize, bounds.getNorthEast()); + const seWorld = projectToWorldCoordinates(tr.worldSize, bounds.getSouthEast()); + const swWorld = projectToWorldCoordinates(tr.worldSize, bounds.getSouthWest()); + + const bearingRadians = degreesToRadians(-bearing); + + const nwRotatedWorld = nwWorld.rotate(bearingRadians); + const neRotatedWorld = neWorld.rotate(bearingRadians); + const seRotatedWorld = seWorld.rotate(bearingRadians); + const swRotatedWorld = swWorld.rotate(bearingRadians); + + const upperRight = new Point( + Math.max(nwRotatedWorld.x, neRotatedWorld.x, swRotatedWorld.x, seRotatedWorld.x), + Math.max(nwRotatedWorld.y, neRotatedWorld.y, swRotatedWorld.y, seRotatedWorld.y) + ); + + const lowerLeft = new Point( + Math.min(nwRotatedWorld.x, neRotatedWorld.x, swRotatedWorld.x, seRotatedWorld.x), + Math.min(nwRotatedWorld.y, neRotatedWorld.y, swRotatedWorld.y, seRotatedWorld.y) + ); + + // Calculate zoom: consider the original bbox and padding. + const size = upperRight.sub(lowerLeft); + + const availableWidth = (tr.width - (edgePadding.left + edgePadding.right + padding.left + padding.right)); + const availableHeight = (tr.height - (edgePadding.top + edgePadding.bottom + padding.top + padding.bottom)); + const scaleX = availableWidth / size.x; + const scaleY = availableHeight / size.y; + + if (scaleY < 0 || scaleX < 0) { + cameraBoundsWarning(); + return undefined; + } + + const zoom = Math.min(scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom); + + // Calculate center: apply the zoom, the configured offset, as well as offset that exists as a result of padding. + const offset = Point.convert(options.offset); + const paddingOffsetX = (padding.left - padding.right) / 2; + const paddingOffsetY = (padding.top - padding.bottom) / 2; + const paddingOffset = new Point(paddingOffsetX, paddingOffsetY); + const rotatedPaddingOffset = paddingOffset.rotate(degreesToRadians(bearing)); + const offsetAtInitialZoom = offset.add(rotatedPaddingOffset); + const offsetAtFinalZoom = offsetAtInitialZoom.mult(tr.scale / zoomScale(zoom)); + + const center = unprojectFromWorldCoordinates( + tr.worldSize, + // either world diagonal can be used (NW-SE or NE-SW) + nwWorld.add(seWorld).div(2).sub(offsetAtFinalZoom) + ); + + const result = { + center, + zoom, + bearing + }; + + return result; +} diff --git a/src/geo/projection/covering_tiles.test.ts b/src/geo/projection/covering_tiles.test.ts index 74b70efca8..c9686654e3 100644 --- a/src/geo/projection/covering_tiles.test.ts +++ b/src/geo/projection/covering_tiles.test.ts @@ -1,7 +1,6 @@ import {beforeEach, describe, expect, test} from 'vitest'; import {GlobeTransform} from './globe_transform'; -import {globeConstants, type VerticalPerspectiveProjection} from './vertical_perspective_projection'; -import {getGlobeProjectionMock} from '../../util/test/util'; +import {globeConstants} from './vertical_perspective_projection'; import {LngLat} from '../lng_lat'; import {coveringTiles, coveringZoomLevel, type CoveringZoomOptions} from './covering_tiles'; import {OverscaledTileID} from '../../source/tile_id'; @@ -9,17 +8,14 @@ import {MercatorTransform} from './mercator_transform'; describe('coveringTiles', () => { describe('globe', () => { - let globeProjectionMock: VerticalPerspectiveProjection; 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); + const transform = new GlobeTransform(); transform.resize(128, 128); transform.setCenter(new LngLat(0.0, 0.0)); transform.setZoom(-1); @@ -34,7 +30,7 @@ describe('coveringTiles', () => { }); test('zoomed in', () => { - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(128, 128); transform.setCenter(new LngLat(-0.02, 0.01)); transform.setZoom(3); @@ -52,7 +48,7 @@ describe('coveringTiles', () => { }); test('zoomed in 512x512', () => { - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(512, 512); transform.setCenter(new LngLat(-0.02, 0.01)); transform.setZoom(3); @@ -78,7 +74,7 @@ describe('coveringTiles', () => { }); test('pitched', () => { - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(128, 128); transform.setCenter(new LngLat(-0.002, 0.001)); transform.setZoom(8); @@ -98,7 +94,7 @@ describe('coveringTiles', () => { }); test('pitched+rotated', () => { - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(128, 128); transform.setCenter(new LngLat(-0.002, 0.001)); transform.setZoom(8); @@ -123,7 +119,7 @@ describe('coveringTiles', () => { }); test('antimeridian1', () => { - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(128, 128); transform.setCenter(new LngLat(179.99, -0.001)); transform.setZoom(5); @@ -141,7 +137,7 @@ describe('coveringTiles', () => { }); test('antimeridian2', () => { - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(128, 128); transform.setCenter(new LngLat(-179.99, 0.001)); transform.setZoom(5); @@ -159,7 +155,7 @@ describe('coveringTiles', () => { }); test('zoom < 0', () => { - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(128, 128); transform.setCenter(new LngLat(0.0, 80.0)); transform.setZoom(-0.5); diff --git a/src/geo/projection/globe_camera_helper.ts b/src/geo/projection/globe_camera_helper.ts new file mode 100644 index 0000000000..083f6c916c --- /dev/null +++ b/src/geo/projection/globe_camera_helper.ts @@ -0,0 +1,66 @@ +import type Point from '@mapbox/point-geometry'; +import {type CameraForBoxAndBearingHandlerResult, type EaseToHandlerResult, type EaseToHandlerOptions, type FlyToHandlerResult, type FlyToHandlerOptions, type ICameraHelper, type MapControlsDeltas} from './camera_helper'; +import {type LngLat, type LngLatLike} from '../lng_lat'; +import {MercatorCameraHelper} from './mercator_camera_helper'; + +import type {IReadonlyTransform, ITransform} from '../transform_interface'; +import type {CameraForBoundsOptions} from '../../ui/camera'; +import type {LngLatBounds} from '../lng_lat_bounds'; +import type {PaddingOptions} from '../edge_insets'; +import {type GlobeProjection} from './globe_projection'; +import {VerticalPerspectiveCameraHelper} from './vertical_perspective_camera_helper'; + +/** + * @internal + */ +export class GlobeCameraHelper implements ICameraHelper { + private _globe: GlobeProjection; + private _mercatorCameraHelper: MercatorCameraHelper; + private _verticalPerspectiveCameraHelper: VerticalPerspectiveCameraHelper; + + constructor(globe: GlobeProjection) { + this._globe = globe; + this._mercatorCameraHelper = new MercatorCameraHelper(); + this._verticalPerspectiveCameraHelper = new VerticalPerspectiveCameraHelper(); + } + + get useGlobeControls(): boolean { return this._globe.useGlobeControls; } + + get currentHelper(): ICameraHelper { + return this.useGlobeControls ? this._verticalPerspectiveCameraHelper : this._mercatorCameraHelper; + } + + handlePanInertia(pan: Point, transform: IReadonlyTransform): { + easingCenter: LngLat; + easingOffset: Point; + } { + return this.currentHelper.handlePanInertia(pan, transform); + } + + handleMapControlsRollPitchBearingZoom(deltas: MapControlsDeltas, tr: ITransform): void { + return this.currentHelper.handleMapControlsRollPitchBearingZoom(deltas, tr); + } + + handleMapControlsPan(deltas: MapControlsDeltas, tr: ITransform, preZoomAroundLoc: LngLat): void { + this.currentHelper.handleMapControlsPan(deltas, tr, preZoomAroundLoc); + } + + cameraForBoxAndBearing(options: CameraForBoundsOptions, padding: PaddingOptions, bounds: LngLatBounds, bearing: number, tr: ITransform): CameraForBoxAndBearingHandlerResult { + return this.currentHelper.cameraForBoxAndBearing(options, padding, bounds, bearing, tr); + } + + /** + * Handles the zoom and center change during camera jumpTo. + */ + handleJumpToCenterZoom(tr: ITransform, options: { zoom?: number; center?: LngLatLike }): void { + this.currentHelper.handleJumpToCenterZoom(tr, options); + } + + handleEaseTo(tr: ITransform, options: EaseToHandlerOptions): EaseToHandlerResult { + return this.currentHelper.handleEaseTo(tr, options); + } + + handleFlyTo(tr: ITransform, options: FlyToHandlerOptions): FlyToHandlerResult { + return this.currentHelper.handleFlyTo(tr, options); + } +} diff --git a/src/geo/projection/globe_projection.ts b/src/geo/projection/globe_projection.ts index a7089111e8..f4102a5e6b 100644 --- a/src/geo/projection/globe_projection.ts +++ b/src/geo/projection/globe_projection.ts @@ -41,12 +41,27 @@ export class GlobeProjection extends Evented implements Projection { this._verticalPerspectiveProjection = new VerticalPerspectiveProjection(); } + public get transitionState(): number { + const currentProjectionSpecValue = this.properties.get('type'); + if (typeof currentProjectionSpecValue === 'string' && currentProjectionSpecValue === 'mercator') { + return 0; + } + if (typeof currentProjectionSpecValue === 'string' && currentProjectionSpecValue === 'vertical-perspective') { + return 1; + } + // HM TODO: check this! + if ('transition' in (currentProjectionSpecValue as any)) { + return (currentProjectionSpecValue as any).transition; + }; + return 1; + } + private get currentProjection(): Projection { return this.useGlobeControls ? this._verticalPerspectiveProjection : this._mercatorProjection; } setProjection(projection?: ProjectionSpecification) { - this._transitionable.setValue('type', projection.type); + this._transitionable.setValue('type', projection?.type || 'mercator'); } updateTransitions(parameters: TransitionParameters) { @@ -90,11 +105,7 @@ export class GlobeProjection extends Evented implements Projection { } get useGlobeControls(): boolean { - const currentProjectionSpecValue = this.properties.get('type'); - if (typeof currentProjectionSpecValue === 'string' && currentProjectionSpecValue === 'mercator') { - return false; - } - return true; + return this.transitionState > 0; } public destroy(): void { @@ -103,7 +114,7 @@ export class GlobeProjection extends Evented implements Projection { } public isRenderingDirty(): boolean { - return this.currentProjection.isRenderingDirty(); + return this.hasTransition() || this.currentProjection.isRenderingDirty(); } public updateGPUdependent(context: ProjectionGPUContext): void { diff --git a/src/geo/projection/globe_transform.test.ts b/src/geo/projection/globe_transform.test.ts index d9161d6869..9b613402d9 100644 --- a/src/geo/projection/globe_transform.test.ts +++ b/src/geo/projection/globe_transform.test.ts @@ -1,12 +1,12 @@ import {describe, expect, test} from 'vitest'; -import {globeConstants, type VerticalPerspectiveProjection} from './vertical_perspective_projection'; +import {globeConstants} from './vertical_perspective_projection'; import {EXTENT} from '../../data/extent'; import Point from '@mapbox/point-geometry'; import {LngLat} from '../lng_lat'; import {GlobeTransform} from './globe_transform'; import {CanonicalTileID, OverscaledTileID, UnwrappedTileID} from '../../source/tile_id'; import {angularCoordinatesRadiansToVector, mercatorCoordinatesToAngularCoordinatesRadians, sphereSurfacePointToCoordinates} from './globe_utils'; -import {expectToBeCloseToArray, getGlobeProjectionMock, sleep} from '../../util/test/util'; +import {expectToBeCloseToArray} from '../../util/test/util'; import {MercatorCoordinate} from '../mercator_coordinate'; import {tileCoordinatesToLocation} from './mercator_utils'; import {MercatorTransform} from './mercator_transform'; @@ -27,21 +27,19 @@ function planeDistance(point: Array, plane: Array) { return point[0] * plane[0] + point[1] * plane[1] + point[2] * plane[2] + plane[3]; } -function createGlobeTransform(globeProjection: VerticalPerspectiveProjection) { - const globeTransform = new GlobeTransform(globeProjection); +function createGlobeTransform() { + const globeTransform = new GlobeTransform(); globeTransform.resize(640, 480); globeTransform.setFov(45); return globeTransform; } describe('GlobeTransform', () => { - const globeProjectionMock = getGlobeProjectionMock(); // Force faster animations so we can use shorter sleeps when testing them - globeConstants.globeTransitionTimeSeconds = 0.1; globeConstants.errorTransitionTimeSeconds = 0.1; describe('getProjectionData', () => { - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); test('mercator tile extents are set', () => { const projectionData = globeTransform.getProjectionData({overscaledTileID: new OverscaledTileID(1, 0, 1, 1, 0)}); expectToBeCloseToArray(projectionData.tileMercatorCoords, [0.5, 0, 0.5 / EXTENT, 0.5 / EXTENT]); @@ -59,7 +57,7 @@ describe('GlobeTransform', () => { }); describe('clipping plane', () => { - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); describe('general plane properties', () => { const projectionData = globeTransform.getProjectionData({overscaledTileID: new OverscaledTileID(0, 0, 0, 0, 0)}); @@ -130,7 +128,7 @@ describe('GlobeTransform', () => { test('camera position', () => { const precisionDigits = 10; - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); expectToBeCloseToArray(globeTransform.cameraPosition as Array, [0, 0, 8.110445867263898], precisionDigits); globeTransform.resize(512, 512); @@ -174,7 +172,7 @@ describe('GlobeTransform', () => { describe('project location to coordinates', () => { const precisionDigits = 10; - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); test('basic test', () => { globeTransform.setCenter(new LngLat(0, 0)); @@ -208,7 +206,7 @@ describe('GlobeTransform', () => { describe('unproject', () => { test('unproject screen center', () => { const precisionDigits = 10; - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); let unprojected = globeTransform.screenPointToLocation(screenCenter); expect(unprojected.lng).toBeCloseTo(globeTransform.center.lng, precisionDigits); expect(unprojected.lat).toBeCloseTo(globeTransform.center.lat, precisionDigits); @@ -226,7 +224,7 @@ describe('GlobeTransform', () => { test('unproject point to the side', () => { const precisionDigits = 10; - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); let coords: LngLat; let projected: Point; let unprojected: LngLat; @@ -256,7 +254,7 @@ describe('GlobeTransform', () => { // This particular case turned out to be problematic, hence this test. const precisionDigits = 10; - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); // Transform settings from the render test projection/globe/fill-planet-pole // See the expected result for how the globe should look with this transform. globeTransform.resize(512, 512); @@ -287,7 +285,7 @@ describe('GlobeTransform', () => { test('unproject outside of sphere', () => { const precisionDigits = 10; - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); // Try unprojection a point somewhere above the western horizon globeTransform.setPitch(60); globeTransform.setBearing(-90); @@ -299,7 +297,7 @@ describe('GlobeTransform', () => { describe('setLocationAtPoint', () => { const precisionDigits = 10; - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); globeTransform.setZoom(1); let coords: LngLat; let point: Point; @@ -398,7 +396,7 @@ describe('GlobeTransform', () => { }); describe('isPointOnMapSurface', () => { - const globeTransform = new GlobeTransform(globeProjectionMock); + const globeTransform = new GlobeTransform(); globeTransform.resize(640, 480); globeTransform.setZoom(1); @@ -438,7 +436,7 @@ describe('GlobeTransform', () => { test('pointCoordinate', () => { const precisionDigits = 10; - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); let coords: LngLat; let coordsMercator: MercatorCoordinate; let projected: Point; @@ -462,7 +460,7 @@ describe('GlobeTransform', () => { describe('getBounds', () => { const precisionDigits = 10; - const globeTransform = new GlobeTransform(globeProjectionMock); + const globeTransform = new GlobeTransform(); globeTransform.resize(640, 480); test('basic', () => { @@ -508,7 +506,7 @@ describe('GlobeTransform', () => { describe('projectTileCoordinates', () => { const precisionDigits = 10; - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(512, 512); transform.setCenter(new LngLat(10.0, 50.0)); transform.setZoom(-1); @@ -546,7 +544,7 @@ describe('GlobeTransform', () => { }); describe('isLocationOccluded', () => { - const transform = new GlobeTransform(globeProjectionMock); + const transform = new GlobeTransform(); transform.resize(512, 512); transform.setCenter(new LngLat(0.0, 0.0)); transform.setZoom(-1); @@ -576,36 +574,16 @@ describe('GlobeTransform', () => { }); }); - test('transform and projection instance are synchronized properly', async () => { - const projectionMock = getGlobeProjectionMock(); - const globeTransform = createGlobeTransform(projectionMock); - // projectionMock.useGlobeRendering and globeTransform.isGlobeRendering must have the same value - expect(projectionMock.useGlobeRendering).toBe(true); - expect(globeTransform.isGlobeRendering).toBe(projectionMock.useGlobeRendering); - globeTransform.setZoom(15); - globeTransform.newFrameUpdate(); - await sleep(150); - globeTransform.newFrameUpdate(); - expect(projectionMock.useGlobeRendering).toBe(false); - expect(globeTransform.isGlobeRendering).toBe(projectionMock.useGlobeRendering); - globeTransform.setZoom(1); - globeTransform.newFrameUpdate(); - await sleep(150); - globeTransform.newFrameUpdate(); - expect(globeTransform.isGlobeRendering).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); + const globeTransform = createGlobeTransform(); globeTransform.setRenderWorldCopies(true); expect(globeTransform.renderWorldCopies).toBeTruthy(); }); test('change transform and make sure render world copies is kept', () => { - const globeTransform = createGlobeTransform(globeProjectionMock); + const globeTransform = createGlobeTransform(); globeTransform.setRenderWorldCopies(true); const mercator = new MercatorTransform(0, 1, 2, 3, false); mercator.apply(globeTransform); diff --git a/src/geo/projection/globe_transform.ts b/src/geo/projection/globe_transform.ts index 7fd0bd8d9f..151d2c75e6 100644 --- a/src/geo/projection/globe_transform.ts +++ b/src/geo/projection/globe_transform.ts @@ -3,10 +3,9 @@ import {TransformHelper} from '../transform_helper'; import {MercatorTransform} from './mercator_transform'; import {VerticalPerspectiveTransform} from './vertical_perspective_transform'; import {LngLat, type LngLatLike,} from '../lng_lat'; -import {createMat4f32, createMat4f64, differenceOfAnglesDegrees, easeCubicInOut, lerp, warnOnce} from '../../util/util'; +import {createMat4f32, createMat4f64, differenceOfAnglesDegrees, lerp, warnOnce} from '../../util/util'; import {OverscaledTileID, type UnwrappedTileID, type CanonicalTileID} from '../../source/tile_id'; import {browser} from '../../util/browser'; -import {globeConstants, type VerticalPerspectiveProjection} from './vertical_perspective_projection'; import {EXTENT} from '../../data/extent'; import type Point from '@mapbox/point-geometry'; @@ -15,7 +14,7 @@ import type {LngLatBounds} from '../lng_lat_bounds'; import type {Frustum} from '../../util/primitives/frustum'; import type {Terrain} from '../../render/terrain'; import type {PointProjection} from '../../symbol/projection'; -import type {IReadonlyTransform, ITransform, TransformUpdateResult} from '../transform_interface'; +import type {IReadonlyTransform, ITransform} from '../transform_interface'; import type {PaddingOptions} from '../edge_insets'; import type {ProjectionData, ProjectionDataParams} from './projection_data'; import type {CoveringTilesDetailsProvider} from './covering_tiles_details_provider'; @@ -200,6 +199,27 @@ export class GlobeTransform implements ITransform { return this._helper.cameraToCenterDistance; } + /** + * True when globe render path should be used instead of the old but simpler mercator rendering. + * Globe automatically transitions to mercator at high zoom levels, which causes a switch from + * globe to mercator render path. + */ + get useGlobeControls(): boolean { return this._globeness > 0; } + setGlobeness(globeness: number): void { + if (this._globeness !== globeness) { + this.setCenter(new LngLat( + this._mercatorTransform.center.lng + differenceOfAnglesDegrees(this._mercatorTransform.center.lng, this.center.lng) * globeness, + lerp(this._mercatorTransform.center.lat, this.center.lat, globeness) + )); + this.setZoom(lerp(this._mercatorTransform.zoom, this.zoom, globeness)); + } + this._globeness = globeness; + this._updateErrorCorrectionValue(); + this._calcMatrices(); + this._verticalPerspectiveTransform.getCoveringTilesDetailsProvider().newFrame(); + this._mercatorTransform.getCoveringTilesDetailsProvider().newFrame(); + } + // // Implementation of globe transform // @@ -221,24 +241,10 @@ export class GlobeTransform implements ITransform { private _skipNextAnimation: boolean = true; - /** - * Note: projection instance should only be accessed in the {@link newFrameUpdate} function. - * to ensure the transform's state isn't unintentionally changed. - */ - private _projectionInstance: VerticalPerspectiveProjection; private _globeLatitudeErrorCorrectionRadians: number = 0; - /** - * True when globe render path should be used instead of the old but simpler mercator rendering. - * Globe automatically transitions to mercator at high zoom levels, which causes a switch from - * globe to mercator render path. - */ - get isGlobeRendering(): boolean { - return this._globeness > 0; - } - get currentTransform(): ITransform { - return this.isGlobeRendering ? this._verticalPerspectiveTransform : this._mercatorTransform; + return this.useGlobeControls ? this._verticalPerspectiveTransform : this._mercatorTransform; } /** @@ -249,20 +255,19 @@ export class GlobeTransform implements ITransform { private _mercatorTransform: MercatorTransform; private _verticalPerspectiveTransform: VerticalPerspectiveTransform; - public constructor(globeProjection: VerticalPerspectiveProjection) { + public constructor() { this._helper = new TransformHelper({ calcMatrices: () => { this._calcMatrices(); }, getConstrained: (center, zoom) => { return this.getConstrained(center, zoom); } }); this._globeness = 1; // When transform is cloned for use in symbols, `_updateAnimation` function which usually sets this value never gets called. - this._projectionInstance = globeProjection; this._mercatorTransform = new MercatorTransform(); this._verticalPerspectiveTransform = new VerticalPerspectiveTransform(); } clone(): ITransform { - const clone = new GlobeTransform(null); + const clone = new GlobeTransform(); clone._globeness = this._globeness; clone._globeLatitudeErrorCorrectionRadians = this._globeLatitudeErrorCorrectionRadians; clone.apply(this); @@ -287,89 +292,18 @@ export class GlobeTransform implements ITransform { public get farZ(): number { return this.currentTransform.farZ; } - /** - * Should be called at the beginning of every frame to synchronize the transform with the underlying projection. - */ - newFrameUpdate(): TransformUpdateResult { - this._lastUpdateTimeSeconds = browser.now() / 1000.0; - const oldGlobeRendering = this.isGlobeRendering; - - this._globeness = this._computeGlobenessAnimation(); - // Everything below this comment must happen AFTER globeness update - this._updateErrorCorrectionValue(); - this._calcMatrices(); - this._verticalPerspectiveTransform.getCoveringTilesDetailsProvider().newFrame(); - this._mercatorTransform.getCoveringTilesDetailsProvider().newFrame(); - - if (oldGlobeRendering === this.isGlobeRendering) { - return { - forcePlacementUpdate: false, - }; - } else { - return { - forcePlacementUpdate: true, - fireProjectionEvent: { - type: 'projectiontransition', - newProjection: this.isGlobeRendering ? 'globe' : 'globe-mercator', - }, - forceSourceUpdate: true, - }; - } - } - /** * This function should never be called on a cloned transform, thus ensuring that * the state of a cloned transform is never changed after creation. */ private _updateErrorCorrectionValue(): void { - if (!this._projectionInstance) { - return; - } - this._projectionInstance.useGlobeRendering = this.isGlobeRendering; - this._projectionInstance.errorQueryLatitudeDegrees = this.center.lat; - this._globeLatitudeErrorCorrectionRadians = this._projectionInstance.latitudeErrorCorrectionRadians; - } - - /** - * Compute new globeness, if needed. - */ - private _computeGlobenessAnimation(): number { - // Update globe transition animation - const globeState = this.zoom < globeConstants.maxGlobeZoom; - const currentTimeSeconds = this._lastUpdateTimeSeconds; - if (globeState !== this._lastGlobeStateEnabled) { - this._lastGlobeChangeTimeSeconds = currentTimeSeconds; - this._lastGlobeStateEnabled = globeState; - } - - const oldGlobeness = this._globeness; - - // Transition parameter, where 0 is the start and 1 is end. - const globeTransition = Math.min(Math.max((currentTimeSeconds - this._lastGlobeChangeTimeSeconds) / globeConstants.globeTransitionTimeSeconds, 0.0), 1.0); - let newGlobeness = globeState ? globeTransition : (1.0 - globeTransition); - - if (this._skipNextAnimation) { - newGlobeness = globeState ? 1.0 : 0.0; - this._lastGlobeChangeTimeSeconds = currentTimeSeconds - globeConstants.globeTransitionTimeSeconds * 2.0; - this._skipNextAnimation = false; - } - - newGlobeness = easeCubicInOut(newGlobeness); // Smooth animation - - if (oldGlobeness !== newGlobeness) { - this.setCenter(new LngLat( - this._mercatorTransform.center.lng + differenceOfAnglesDegrees(this._mercatorTransform.center.lng, this.center.lng) * newGlobeness, - lerp(this._mercatorTransform.center.lat, this.center.lat, newGlobeness) - )); - this.setZoom(lerp(this._mercatorTransform.zoom, this.zoom, newGlobeness)); - } - - return newGlobeness; - } - - isRenderingDirty(): boolean { - // Globe transition - return (this._lastUpdateTimeSeconds - this._lastGlobeChangeTimeSeconds) < globeConstants.globeTransitionTimeSeconds; + // HM TODO: need to re apply this + //if (!this._projectionInstance) { + // return; + //} + //this._projectionInstance.useGlobeRendering = this.isGlobeRendering; + //this._projectionInstance.errorQueryLatitudeDegrees = this.center.lat; + //this._globeLatitudeErrorCorrectionRadians = this._projectionInstance.latitudeErrorCorrectionRadians; } getProjectionData(params: ProjectionDataParams): ProjectionData { @@ -377,7 +311,7 @@ export class GlobeTransform implements ITransform { const verticalPerspectiveProjectionData = this._verticalPerspectiveTransform.getProjectionData(params); return { - mainMatrix: this.isGlobeRendering ? verticalPerspectiveProjectionData.mainMatrix : mercatorProjectionData.mainMatrix, + mainMatrix: this.useGlobeControls ? verticalPerspectiveProjectionData.mainMatrix : mercatorProjectionData.mainMatrix, clippingPlane: verticalPerspectiveProjectionData.clippingPlane, tileMercatorCoords: verticalPerspectiveProjectionData.tileMercatorCoords, projectionTransition: params.applyGlobeMatrix ? this._globeness : 0, @@ -492,7 +426,7 @@ export class GlobeTransform implements ITransform { * (same size before and after a {@link setLocationAtPoint} call). */ setLocationAtPoint(lnglat: LngLat, point: Point): void { - if (!this.isGlobeRendering) { + if (!this.useGlobeControls) { this._mercatorTransform.setLocationAtPoint(lnglat, point); this.apply(this._mercatorTransform); return; diff --git a/src/geo/projection/mercator_camera_helper.ts b/src/geo/projection/mercator_camera_helper.ts index 1295815a49..7b506284e5 100644 --- a/src/geo/projection/mercator_camera_helper.ts +++ b/src/geo/projection/mercator_camera_helper.ts @@ -1,8 +1,8 @@ -import Point from '@mapbox/point-geometry'; +import type Point from '@mapbox/point-geometry'; import {LngLat, type LngLatLike} from '../lng_lat'; -import {cameraBoundsWarning, type CameraForBoxAndBearingHandlerResult, type EaseToHandlerResult, type EaseToHandlerOptions, type FlyToHandlerResult, type FlyToHandlerOptions, type ICameraHelper, type MapControlsDeltas, updateRotation, type UpdateRotationArgs} from './camera_helper'; +import {cameraForBoxAndBearing, type CameraForBoxAndBearingHandlerResult, type EaseToHandlerResult, type EaseToHandlerOptions, type FlyToHandlerResult, type FlyToHandlerOptions, type ICameraHelper, type MapControlsDeltas, updateRotation, type UpdateRotationArgs} from './camera_helper'; import {normalizeCenter} from '../transform_helper'; -import {degreesToRadians, rollPitchBearingEqual, scaleZoom, zoomScale} from '../../util/util'; +import {rollPitchBearingEqual, scaleZoom, zoomScale} from '../../util/util'; import {projectToWorldCoordinates, unprojectFromWorldCoordinates} from './mercator_utils'; import {interpolates} from '@maplibre/maplibre-gl-style-spec'; @@ -45,70 +45,7 @@ export class MercatorCameraHelper implements ICameraHelper { } cameraForBoxAndBearing(options: CameraForBoundsOptions, padding: PaddingOptions, bounds: LngLatBounds, bearing: number, tr: IReadonlyTransform): CameraForBoxAndBearingHandlerResult { - const edgePadding = tr.padding; - - // Consider all corners of the rotated bounding box derived from the given points - // when find the camera position that fits the given points. - - const nwWorld = projectToWorldCoordinates(tr.worldSize, bounds.getNorthWest()); - const neWorld = projectToWorldCoordinates(tr.worldSize, bounds.getNorthEast()); - const seWorld = projectToWorldCoordinates(tr.worldSize, bounds.getSouthEast()); - const swWorld = projectToWorldCoordinates(tr.worldSize, bounds.getSouthWest()); - - const bearingRadians = degreesToRadians(-bearing); - - const nwRotatedWorld = nwWorld.rotate(bearingRadians); - const neRotatedWorld = neWorld.rotate(bearingRadians); - const seRotatedWorld = seWorld.rotate(bearingRadians); - const swRotatedWorld = swWorld.rotate(bearingRadians); - - const upperRight = new Point( - Math.max(nwRotatedWorld.x, neRotatedWorld.x, swRotatedWorld.x, seRotatedWorld.x), - Math.max(nwRotatedWorld.y, neRotatedWorld.y, swRotatedWorld.y, seRotatedWorld.y) - ); - - const lowerLeft = new Point( - Math.min(nwRotatedWorld.x, neRotatedWorld.x, swRotatedWorld.x, seRotatedWorld.x), - Math.min(nwRotatedWorld.y, neRotatedWorld.y, swRotatedWorld.y, seRotatedWorld.y) - ); - - // Calculate zoom: consider the original bbox and padding. - const size = upperRight.sub(lowerLeft); - - const availableWidth = (tr.width - (edgePadding.left + edgePadding.right + padding.left + padding.right)); - const availableHeight = (tr.height - (edgePadding.top + edgePadding.bottom + padding.top + padding.bottom)); - const scaleX = availableWidth / size.x; - const scaleY = availableHeight / size.y; - - if (scaleY < 0 || scaleX < 0) { - cameraBoundsWarning(); - return undefined; - } - - const zoom = Math.min(scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom); - - // Calculate center: apply the zoom, the configured offset, as well as offset that exists as a result of padding. - const offset = Point.convert(options.offset); - const paddingOffsetX = (padding.left - padding.right) / 2; - const paddingOffsetY = (padding.top - padding.bottom) / 2; - const paddingOffset = new Point(paddingOffsetX, paddingOffsetY); - const rotatedPaddingOffset = paddingOffset.rotate(degreesToRadians(bearing)); - const offsetAtInitialZoom = offset.add(rotatedPaddingOffset); - const offsetAtFinalZoom = offsetAtInitialZoom.mult(tr.scale / zoomScale(zoom)); - - const center = unprojectFromWorldCoordinates( - tr.worldSize, - // either world diagonal can be used (NW-SE or NE-SW) - nwWorld.add(seWorld).div(2).sub(offsetAtFinalZoom) - ); - - const result = { - center, - zoom, - bearing - }; - - return result; + return cameraForBoxAndBearing(options, padding, bounds, bearing, tr); } handleJumpToCenterZoom(tr: ITransform, options: { zoom?: number; center?: LngLatLike }): void { diff --git a/src/geo/projection/mercator_projection.ts b/src/geo/projection/mercator_projection.ts index 76d5cdafca..a3f3d49229 100644 --- a/src/geo/projection/mercator_projection.ts +++ b/src/geo/projection/mercator_projection.ts @@ -49,6 +49,10 @@ export class MercatorProjection implements Projection { return false; } + get transitionState(): number { + return 0; + } + public destroy(): void { // Do nothing. } diff --git a/src/geo/projection/mercator_transform.ts b/src/geo/projection/mercator_transform.ts index 3772ba5327..f4a737153c 100644 --- a/src/geo/projection/mercator_transform.ts +++ b/src/geo/projection/mercator_transform.ts @@ -200,6 +200,11 @@ export class MercatorTransform implements ITransform { // Implementation of mercator transform // + get useGlobeControls(): boolean { return false; } + setGlobeness(_globeness: number): void { + // Do nothing + } + private _cameraPosition: vec3; private _mercatorMatrix: mat4; @@ -696,10 +701,6 @@ export class MercatorTransform implements ITransform { return (p[2] / p[3]); } - isRenderingDirty(): boolean { - return false; - } - getProjectionData(params: ProjectionDataParams): ProjectionData { const {overscaledTileID, aligned, applyTerrainMatrix} = params; const mercatorTileCoordinates = this._helper.getMercatorTileCoordinates(overscaledTileID); diff --git a/src/geo/projection/projection.ts b/src/geo/projection/projection.ts index a9a9abacee..b167ee0844 100644 --- a/src/geo/projection/projection.ts +++ b/src/geo/projection/projection.ts @@ -89,6 +89,8 @@ export interface Projection { */ get subdivisionGranularity(): SubdivisionGranularitySetting; + get transitionState(): number; + /** * @internal * Cleans up any resources the projection created, especially GPU buffers. diff --git a/src/geo/projection/projection_factory.ts b/src/geo/projection/projection_factory.ts index addc09d6f0..09ff839dcf 100644 --- a/src/geo/projection/projection_factory.ts +++ b/src/geo/projection/projection_factory.ts @@ -1,4 +1,3 @@ -import {type ProjectionSpecification} from '@maplibre/maplibre-gl-style-spec'; import {warnOnce} from '../../util/util'; import {MercatorProjection} from './mercator_projection'; import {MercatorTransform} from './mercator_transform'; @@ -7,6 +6,10 @@ import {VerticalPerspectiveProjection} from './vertical_perspective_projection'; import {GlobeTransform} from './globe_transform'; import {VerticalPerspectiveCameraHelper} from './vertical_perspective_camera_helper'; import {VerticalPerspectiveTransform} from './vertical_perspective_transform'; +import {GlobeProjection} from './globe_projection'; +import {GlobeCameraHelper} from './globe_camera_helper'; + +import type {ProjectionSpecification} from '@maplibre/maplibre-gl-style-spec'; import type {Projection} from './projection'; import type {ITransform} from '../transform_interface'; import type {ICameraHelper} from './camera_helper'; @@ -27,20 +30,27 @@ export function createProjectionFromName(name: ProjectionSpecification['type']): } case 'globe': { - const proj = new VerticalPerspectiveProjection(); + const globeProjection = new GlobeProjection({type: [ + "interpolate", + ["linear"], + ["zoom"], + 10, + "vertical-perspective", + 12, + "mercator" + ]}); return { - projection: proj, - transform: new GlobeTransform(proj), - cameraHelper: new VerticalPerspectiveCameraHelper(proj), + projection: globeProjection, + transform: new GlobeTransform(), + cameraHelper: new GlobeCameraHelper(globeProjection), }; } case 'vertical-perspective': { - const proj = new VerticalPerspectiveProjection(); return { - projection: proj, + projection: new VerticalPerspectiveProjection(), transform: new VerticalPerspectiveTransform(), - cameraHelper: new VerticalPerspectiveCameraHelper(proj), + cameraHelper: new VerticalPerspectiveCameraHelper(), }; } default: diff --git a/src/geo/projection/vertical_perspective_camera_helper.ts b/src/geo/projection/vertical_perspective_camera_helper.ts index 1ab7f69deb..715ee7eaea 100644 --- a/src/geo/projection/vertical_perspective_camera_helper.ts +++ b/src/geo/projection/vertical_perspective_camera_helper.ts @@ -1,7 +1,6 @@ import Point from '@mapbox/point-geometry'; -import {cameraBoundsWarning, type CameraForBoxAndBearingHandlerResult, type EaseToHandlerResult, type EaseToHandlerOptions, type FlyToHandlerResult, type FlyToHandlerOptions, type ICameraHelper, type MapControlsDeltas, updateRotation, type UpdateRotationArgs} from './camera_helper'; +import {cameraBoundsWarning, type CameraForBoxAndBearingHandlerResult, type EaseToHandlerResult, type EaseToHandlerOptions, type FlyToHandlerResult, type FlyToHandlerOptions, type ICameraHelper, type MapControlsDeltas, updateRotation, type UpdateRotationArgs, cameraForBoxAndBearing} from './camera_helper'; import {LngLat, type LngLatLike} from '../lng_lat'; -import {MercatorCameraHelper} from './mercator_camera_helper'; import {angularCoordinatesToSurfaceVector, computeGlobePanCenter, getGlobeRadiusPixels, getZoomAdjustment, globeDistanceOfLocationsPixels, interpolateLngLatForGlobe} from './globe_utils'; import {clamp, createVec3f64, differenceOfAnglesDegrees, MAX_VALID_LATITUDE, remapSaturate, rollPitchBearingEqual, scaleZoom, warnOnce, zoomScale} from '../../util/util'; import {type mat4, vec3} from 'gl-matrix'; @@ -9,7 +8,6 @@ import {normalizeCenter} from '../transform_helper'; import {interpolates} from '@maplibre/maplibre-gl-style-spec'; import type {IReadonlyTransform, ITransform} from '../transform_interface'; -import type {VerticalPerspectiveProjection} from './vertical_perspective_projection'; import type {CameraForBoundsOptions} from '../../ui/camera'; import type {LngLatBounds} from '../lng_lat_bounds'; import type {PaddingOptions} from '../edge_insets'; @@ -18,24 +16,13 @@ import type {PaddingOptions} from '../edge_insets'; * @internal */ export class VerticalPerspectiveCameraHelper implements ICameraHelper { - private _globe: VerticalPerspectiveProjection; - private _mercatorCameraHelper: MercatorCameraHelper; - constructor(globe: VerticalPerspectiveProjection) { - this._globe = globe; - this._mercatorCameraHelper = new MercatorCameraHelper(); - } - - get useGlobeControls(): boolean { return this._globe.useGlobeRendering; } + get useGlobeControls(): boolean { return true; } handlePanInertia(pan: Point, transform: IReadonlyTransform): { easingCenter: LngLat; easingOffset: Point; } { - if (!this.useGlobeControls) { - return this._mercatorCameraHelper.handlePanInertia(pan, transform); - } - const panCenter = computeGlobePanCenter(pan, transform); if (Math.abs(panCenter.lng - transform.center.lng) > 180) { // If easeTo target would be over 180° distant, the animation would move @@ -50,11 +37,6 @@ export class VerticalPerspectiveCameraHelper implements ICameraHelper { } handleMapControlsRollPitchBearingZoom(deltas: MapControlsDeltas, tr: ITransform): void { - if (!this.useGlobeControls) { - this._mercatorCameraHelper.handleMapControlsRollPitchBearingZoom(deltas, tr); - return; - } - const zoomPixel = deltas.around; const zoomLoc = tr.screenPointToLocation(zoomPixel); @@ -141,12 +123,7 @@ export class VerticalPerspectiveCameraHelper implements ICameraHelper { tr.setZoom(oldZoom + getZoomAdjustment(oldCenterLat, tr.center.lat)); } - handleMapControlsPan(deltas: MapControlsDeltas, tr: ITransform, preZoomAroundLoc: LngLat): void { - if (!this.useGlobeControls) { - this._mercatorCameraHelper.handleMapControlsPan(deltas, tr, preZoomAroundLoc); - return; - } - + handleMapControlsPan(deltas: MapControlsDeltas, tr: ITransform, _preZoomAroundLoc: LngLat): void { if (!deltas.panDelta) { return; } @@ -162,12 +139,7 @@ export class VerticalPerspectiveCameraHelper implements ICameraHelper { } cameraForBoxAndBearing(options: CameraForBoundsOptions, padding: PaddingOptions, bounds: LngLatBounds, bearing: number, tr: ITransform): CameraForBoxAndBearingHandlerResult { - const result = this._mercatorCameraHelper.cameraForBoxAndBearing(options, padding, bounds, bearing, tr); - - if (!this.useGlobeControls) { - return result; - } - + const result = cameraForBoxAndBearing(options, padding, bounds, bearing, tr); // If globe is enabled, we use the parameters computed for mercator, and just update the zoom to fit the bounds. // Get clip space bounds including padding @@ -238,11 +210,6 @@ export class VerticalPerspectiveCameraHelper implements ICameraHelper { * Handles the zoom and center change during camera jumpTo. */ handleJumpToCenterZoom(tr: ITransform, options: { zoom?: number; center?: LngLatLike }): void { - if (!this.useGlobeControls) { - this._mercatorCameraHelper.handleJumpToCenterZoom(tr, options); - return; - } - // Special zoom & center handling for globe: // Globe constrained center isn't dependent on zoom level const startingLat = tr.center.lat; @@ -257,10 +224,6 @@ export class VerticalPerspectiveCameraHelper implements ICameraHelper { } handleEaseTo(tr: ITransform, options: EaseToHandlerOptions): EaseToHandlerResult { - if (!this.useGlobeControls) { - return this._mercatorCameraHelper.handleEaseTo(tr, options); - } - const startZoom = tr.zoom; const startCenter = tr.center; const startPadding = tr.padding; @@ -362,9 +325,6 @@ export class VerticalPerspectiveCameraHelper implements ICameraHelper { } handleFlyTo(tr: ITransform, options: FlyToHandlerOptions): FlyToHandlerResult { - if (!this.useGlobeControls) { - return this._mercatorCameraHelper.handleFlyTo(tr, options); - } const optionsZoom = typeof options.zoom !== 'undefined'; const startCenter = tr.center; diff --git a/src/geo/projection/vertical_perspective_projection.ts b/src/geo/projection/vertical_perspective_projection.ts index d459d418e8..97352a2f75 100644 --- a/src/geo/projection/vertical_perspective_projection.ts +++ b/src/geo/projection/vertical_perspective_projection.ts @@ -16,8 +16,6 @@ export const VerticalPerspectiveShaderDefine = '#define GLOBE'; export const VerticalPerspectiveShaderVariantKey = 'globe'; export const globeConstants = { - globeTransitionTimeSeconds: 0.5, - maxGlobeZoom: 12.0, errorTransitionTimeSeconds: 0.5 }; @@ -67,6 +65,10 @@ export class VerticalPerspectiveProjection implements Projection { return this._useGlobeRendering; } + get transitionState(): number { + return 1; + } + /** * @internal * Intended for internal use, only called from GlobeTransform. diff --git a/src/geo/projection/vertical_perspective_transform.ts b/src/geo/projection/vertical_perspective_transform.ts index 83bdb6cad7..e2929143fb 100644 --- a/src/geo/projection/vertical_perspective_transform.ts +++ b/src/geo/projection/vertical_perspective_transform.ts @@ -218,6 +218,11 @@ export class VerticalPerspectiveTransform implements ITransform { // Implementation of globe transform // + get useGlobeControls(): boolean { return true; } + setGlobeness(_globeness: number): void { + // Do nothing + } + private _cachedClippingPlane: vec4 = createVec4f64(); private _cachedFrustum: Frustum; private _projectionMatrix: mat4 = createIdentityMat4f64(); @@ -288,10 +293,6 @@ export class VerticalPerspectiveTransform implements ITransform { return {}; } - isRenderingDirty(): boolean { - return false; - } - getProjectionData(params: ProjectionDataParams): ProjectionData { const {overscaledTileID, applyGlobeMatrix} = params; const mercatorTileCoordinates = this._helper.getMercatorTileCoordinates(overscaledTileID); diff --git a/src/geo/transform_interface.ts b/src/geo/transform_interface.ts index 047c9d7682..bdf7258cad 100644 --- a/src/geo/transform_interface.ts +++ b/src/geo/transform_interface.ts @@ -186,13 +186,6 @@ interface ITransformMutators { */ setMaxBounds(bounds?: LngLatBounds | null): void; - /** - * @internal - * Signals to the transform that a new frame is starting. - * The transform might update some of its internal variables and animations based on this. - */ - newFrameUpdate(): TransformUpdateResult; - /** * @internal * Called before rendering to allow the transform implementation @@ -201,6 +194,13 @@ interface ITransformMutators { * @param coords - Array of tile IDs that will be rendered. */ precacheTiles(coords: Array): void; + + /** + * @internal + * Sets the transform's globeness to all transitioning from mercator to vertical-perspective projection. + * @param globeness - A number between 0 and 1, where 0 is mercator and 1 is vertical-perspective. + */ + setGlobeness(globeness: number): void; } /** @@ -249,6 +249,8 @@ export interface IReadonlyTransform extends ITransformGetters { get nearZ(): number; get farZ(): number; + get useGlobeControls(): boolean; + /** * Returns if the padding params match * @@ -404,13 +406,6 @@ export interface IReadonlyTransform extends ITransformGetters { */ calculateFogMatrix(unwrappedTileID: UnwrappedTileID): mat4; - /** - * @internal - * True when an animation handled by the transform is in progress, - * requiring MapLibre to keep rendering new frames. - */ - isRenderingDirty(): boolean; - /** * @internal * Generates a `ProjectionData` instance to be used while rendering the supplied tile. diff --git a/src/ui/camera.test.ts b/src/ui/camera.test.ts index 481b134e72..33b5f92e05 100644 --- a/src/ui/camera.test.ts +++ b/src/ui/camera.test.ts @@ -13,7 +13,6 @@ import {getZoomAdjustment} from '../geo/projection/globe_utils'; import {VerticalPerspectiveCameraHelper} from '../geo/projection/vertical_perspective_camera_helper'; import {MercatorCameraHelper} from '../geo/projection/mercator_camera_helper'; -import type {VerticalPerspectiveProjection} from '../geo/projection/vertical_perspective_projection'; import type {Terrain} from '../render/terrain'; beforeEach(() => { @@ -43,7 +42,7 @@ function attachSimulateFrame(camera) { function createCamera(options?): Camera & { simulateFrame: () => void } { options = options || {}; - const transform = options.globe ? new GlobeTransform({} as any) : new MercatorTransform(); + const transform = options.globe ? new GlobeTransform() : new MercatorTransform(); transform.setMinZoom(0); transform.setMaxZoom(20); transform.setMinPitch(0); @@ -53,7 +52,7 @@ function createCamera(options?): Camera & { simulateFrame: () => void } { const camera = attachSimulateFrame(new CameraMock(transform, new MercatorCameraHelper(), {} as any)); if (options.globe) { - camera.cameraHelper = new VerticalPerspectiveCameraHelper({useGlobeRendering: true} as VerticalPerspectiveProjection); + camera.cameraHelper = new VerticalPerspectiveCameraHelper(); } camera.jumpTo(options); diff --git a/src/ui/control/navigation_control.ts b/src/ui/control/navigation_control.ts index ed0d89d5ca..4e0d3de159 100644 --- a/src/ui/control/navigation_control.ts +++ b/src/ui/control/navigation_control.ts @@ -214,7 +214,6 @@ class MouseRotateWrapper { move(e: MouseEvent | TouchEvent, point: Point) { const map = this.map; const {bearingDelta, pitchDelta} = this._rotatePitchHanlder.dragMove(e, point) || {}; - console.log(bearingDelta, pitchDelta, point); if (bearingDelta) map.setBearing(map.getBearing() + bearingDelta); if (pitchDelta) map.setPitch(map.getPitch() + pitchDelta); } diff --git a/src/ui/map.ts b/src/ui/map.ts index b8d51d2e20..6b07196fcb 100644 --- a/src/ui/map.ts +++ b/src/ui/map.ts @@ -3167,6 +3167,7 @@ export class Map extends Camera { */ _render(paintStartTimeStamp: number) { const fadeDuration = this._idleTriggered ? this._fadeDuration : 0; + const initialGlobeControls = this.transform.useGlobeControls; // A custom layer may have used the context asynchronously. Mark the state as dirty. this.painter.context.setDirty(); @@ -3203,13 +3204,14 @@ export class Map extends Camera { this.style.update(parameters); } - - const transformUpdateResult = this.transform.newFrameUpdate(); + this.transform.setGlobeness(this.style.projection.transitionState); + // HM TODO: solve this + //const transformUpdateResult = this.transform.newFrameUpdate(); // If we are in _render for any reason other than an in-progress paint // transition, update source caches to check for and load any tiles we // need for the current transform - if (this.style && (this._sourcesDirty || transformUpdateResult.forceSourceUpdate)) { + if (this.style && (this._sourcesDirty || initialGlobeControls !== this.transform.useGlobeControls)) { this._sourcesDirty = false; this.style._updateSources(this.transform); } @@ -3228,11 +3230,13 @@ export class Map extends Camera { } } - this._placementDirty = this.style && this.style._updatePlacement(this.transform, this.showCollisionBoxes, fadeDuration, this._crossSourceCollisions, transformUpdateResult.forcePlacementUpdate); + this._placementDirty = this.style && this.style._updatePlacement(this.transform, this.showCollisionBoxes, fadeDuration, this._crossSourceCollisions, initialGlobeControls !== this.transform.useGlobeControls); + /* HM TODO: solve this if (transformUpdateResult.fireProjectionEvent) { this.fire(new Event('projectiontransition', transformUpdateResult.fireProjectionEvent)); } + */ // Actually draw this.painter.render(this.style, { @@ -3269,7 +3273,7 @@ export class Map extends Camera { // Even though `_styleDirty` and `_sourcesDirty` are reset in this // method, synchronous events fired during Style#update or // Style#_updateSources could have caused them to be set again. - const somethingDirty = this._sourcesDirty || this._styleDirty || this._placementDirty || this.style.projection.isRenderingDirty() || this.transform.isRenderingDirty(); + const somethingDirty = this._sourcesDirty || this._styleDirty || this._placementDirty || this.style.projection.isRenderingDirty(); if (somethingDirty || this._repaint) { this.triggerRepaint(); } else if (!this.isMoving() && this.loaded()) { diff --git a/test/bench/benchmarks/covering_tiles_globe.ts b/test/bench/benchmarks/covering_tiles_globe.ts index 836376252f..a6f1941944 100644 --- a/test/bench/benchmarks/covering_tiles_globe.ts +++ b/test/bench/benchmarks/covering_tiles_globe.ts @@ -1,8 +1,7 @@ import Benchmark from '../lib/benchmark'; -import { GlobeTransform } from '../../../src/geo/projection/globe_transform'; -import { VerticalPerspectiveProjection } from '../../../src/geo/projection/vertical_perspective_projection'; -import { LngLat } from '../styles'; -import { coveringTiles } from '../../../src/geo/projection/covering_tiles'; +import {GlobeTransform} from '../../../src/geo/projection/globe_transform'; +import {LngLat} from '../styles'; +import {coveringTiles} from '../../../src/geo/projection/covering_tiles'; export default class CoveringTilesGlobe extends Benchmark { _pitch: number; @@ -13,8 +12,7 @@ export default class CoveringTilesGlobe extends Benchmark { } bench() { - const projection = new VerticalPerspectiveProjection(); - const transform = new GlobeTransform(projection); + const transform = new GlobeTransform(); transform.setCenter(new LngLat(0, 0)); transform.setZoom(4); transform.resize(4096, 4096); diff --git a/test/bench/benchmarks/symbol_collision_box.ts b/test/bench/benchmarks/symbol_collision_box.ts index eee48e2209..ab48ddaf2f 100644 --- a/test/bench/benchmarks/symbol_collision_box.ts +++ b/test/bench/benchmarks/symbol_collision_box.ts @@ -51,9 +51,8 @@ export default class SymbolCollisionBox extends Benchmark { private _createTransform() { if (this._useGlobeProjection) { - const projection = new VerticalPerspectiveProjection(); return { - transform: new GlobeTransform(projection), + transform: new GlobeTransform(), calculatePosMatrix: (_tileID: UnwrappedTileID) => { return undefined; }, }; } else {