Skip to content

Commit

Permalink
Facilitate for globe transition expression refactoring (#5139)
Browse files Browse the repository at this point in the history
* Initial commit to move the logic of transition out to a new file

* Remove one line method

* Fix build test

* Fix projection data bad refactoring

* Fix render and size tests

* Fix failing unit test

* Uncomment unit tests

* Fix typo, remove unneeded details provider initialization. Added current transfrom getter

* Fix typo

* Add back error correction for latitude.

* Refactors the `Projection` classed only (#5163)

* This refactors the projection class only, without touching other parts of the code.

* remove mercator from the code, revert more changes

* Refactor camera helper to split the logic for vertical-perspective and mercator (#5162)

* Refactor camera helper

* Use the helper in the factory.

* Fix build test

* Fix according to code review

* Use `Transitionalable` infrastructure for transfrom globeness changes. (#5164)

* Remove duplicate code

* Fix lint, add some methods for original branch

* Add projection definition in factory

* Add has transition to projection

* Fix transition value

* Remove isRenderingDirty and use hasTransition when needed

* Remove the last part of the animation handling in the transform class

* More clean-up

* Remove some "TODO"s.

* Remove reference to globe projection in globe transform

* Rename newFrame with recalculateCache

* Remove unneeded ifs

* Add support for arbitrary projection definitions

* Uses mercator matrix when globe is using mercator transform

* Improve handling of fog martix in globe transform

---------

Co-authored-by: Isaac Besora Vilardaga <[email protected]>

* Globe custom layer fixes (#5150)

* Simplify custom layer ProjectionData code in mercator transform

* Fix globe tiles example

* Fix globe projection shader

* Add custom layer 3D model after globe->mercator transition render test

* Fix custom layer 3D models not rendering when globe transitions to mercator

* Move camera to center distance to helper

# Conflicts:
#	src/geo/projection/globe_transform.ts
#	src/geo/projection/mercator_transform.ts
#	src/geo/transform_helper.ts
#	src/geo/transform_interface.ts

* Synchronize globe+mercator near and far Z to prevent 3D model disappearing during projection transition

* Expose getProjectionData and getMatrixForModel for custom layers, don't use internal API in examples

* VerticalPerspectiveTransform should have a valid getProjectionDataForCustomLayer implementation

* Add changelog entry

* Fix missing docs

* Fix docs again

* Review feedback

* Move nearZfarZoverride to transform helper

* Update build size

* Review feedback

* Change near/far Z override API

* Custom layers use internal API

* Fix globe custom tiles example

* Update build size

* Fix typo

---------

Co-authored-by: HarelM <[email protected]>

* Final removal of TODOs and some minor clean up

* Update CHANGELOG.md

---------

Co-authored-by: Isaac Besora Vilardaga <[email protected]>
Co-authored-by: Jakub Pelc <[email protected]>
  • Loading branch information
3 people authored Dec 12, 2024
1 parent a3e98c4 commit 551ba95
Show file tree
Hide file tree
Showing 54 changed files with 2,469 additions and 1,957 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
## main

### ✨ Features and improvements
- Add support for projection type expression as part of a refactoring of the transfrom and projection classes ([#5139](https://github.com/maplibre/maplibre-gl-js/pull/5139))
- _...Add new stuff here..._

### 🐞 Bug fixes
- Fix globe custom layers being supplied incorrect matrices after projection transition to mercator ([#5150](https://github.com/maplibre/maplibre-gl-js/pull/5150))
- Fix custom 3D models disappearing during projection transition ([#5150](https://github.com/maplibre/maplibre-gl-js/pull/5150))
- _...Add new stuff here..._

## 5.0.0-pre.9
Expand Down
72 changes: 70 additions & 2 deletions src/geo/projection/camera_helper.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
}
22 changes: 9 additions & 13 deletions src/geo/projection/covering_tiles.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import {beforeEach, describe, expect, test} from 'vitest';
import {GlobeTransform} from './globe_transform';
import {globeConstants, type GlobeProjection} from './globe';
import {getGlobeProjectionMock} from '../../util/test/util';
import {LngLat} from '../lng_lat';
import {coveringTiles, coveringZoomLevel, type CoveringZoomOptions} from './covering_tiles';
import {OverscaledTileID} from '../../source/tile_id';
import {MercatorTransform} from './mercator_transform';
import {globeConstants} from './vertical_perspective_projection';

describe('coveringTiles', () => {
describe('globe', () => {
let globeProjectionMock: GlobeProjection;

beforeEach(() => {
globeProjectionMock = getGlobeProjectionMock();
// Force faster animations so we can use shorter sleeps when testing them
globeConstants.globeTransitionTimeSeconds = 0.1;
globeConstants.errorTransitionTimeSeconds = 0.1;
});

test('zoomed out', () => {
const transform = new GlobeTransform(globeProjectionMock);
const transform = new GlobeTransform();
transform.resize(128, 128);
transform.setCenter(new LngLat(0.0, 0.0));
transform.setZoom(-1);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
10 changes: 5 additions & 5 deletions src/geo/projection/covering_tiles.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {OverscaledTileID} from '../../source/tile_id';
import {vec2, type vec4} from 'gl-matrix';
import {type IReadonlyTransform} from '../transform_interface';
import {MercatorCoordinate} from '../mercator_coordinate';
import {scaleZoom} from '../transform_helper';
import {clamp, degreesToRadians} from '../../util/util';
import {type Terrain} from '../../render/terrain';
import {type Frustum} from '../../util/primitives/frustum';
import {clamp, degreesToRadians, scaleZoom} from '../../util/util';
import {type Aabb, IntersectionResult} from '../../util/primitives/aabb';

import type {IReadonlyTransform} from '../transform_interface';
import type {Terrain} from '../../render/terrain';
import type {Frustum} from '../../util/primitives/frustum';

type CoveringTilesResult = {
tileID: OverscaledTileID;
distanceSq: number;
Expand Down
5 changes: 5 additions & 0 deletions src/geo/projection/covering_tiles_details_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ export interface CoveringTilesDetailsProvider {
* Whether to allow world copies to be rendered.
*/
allowWorldCopies: () => boolean;

/**
* Prepare cache for the next frame.
*/
recalculateCache(): void;
}
Loading

0 comments on commit 551ba95

Please sign in to comment.