Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Facilitate for globe transition expression refactoring #5139

Merged
merged 20 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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;
}

Check warning on line 195 in src/geo/projection/camera_helper.ts

View check run for this annotation

Codecov / codecov/patch

src/geo/projection/camera_helper.ts#L193-L195

Added lines #L193 - L195 were not covered by tests

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
Loading