Skip to content

Commit

Permalink
Respect padding in cameraForBounds on Globe (#13126) (h/t @jonasnoki)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasnoki authored Apr 23, 2024
1 parent 541c310 commit 360bfd2
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 32 deletions.
73 changes: 41 additions & 32 deletions src/ui/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,8 @@ class Camera extends Evented {

aabb = Aabb.applyTransform(aabb, mat4.multiply([], worldToCamera, aabbOrientation));

aabb = this._extendAABBWithPaddings(aabb, eOptions, tr, bearing);

vec3.transformMat4(center, center, worldToCamera);

const aabbHalfExtentZ = (aabb.max[2] - aabb.min[2]) * 0.5;
Expand Down Expand Up @@ -740,6 +742,42 @@ class Camera extends Evented {
return {center: tr.center, zoom, bearing, pitch};
}

_extendAABBWithPaddings(aabb: Aabb, eOptions: FullCameraOptions, tr: Transform, bearing: number): Aabb {
const size = vec3.sub([], aabb.max, aabb.min);

const screenPadL = tr.padding.left || 0;
const screenPadR = tr.padding.right || 0;
const screenPadB = tr.padding.bottom || 0;
const screenPadT = tr.padding.top || 0;

const {left: padL, right: padR, top: padT, bottom: padB} = eOptions.padding;

const halfScreenPadX = (screenPadL + screenPadR) * 0.5;
const halfScreenPadY = (screenPadT + screenPadB) * 0.5;

const scaleX = (tr.width - (screenPadL + screenPadR + padL + padR)) / size[0];
const scaleY = (tr.height - (screenPadB + screenPadT + padB + padT)) / size[1];

const zoomRef = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), eOptions.maxZoom);

const scaleRatio = tr.scale / tr.zoomScale(zoomRef);

aabb = new Aabb(
[aabb.min[0] - (padL + halfScreenPadX) * scaleRatio, aabb.min[1] - (padB + halfScreenPadY) * scaleRatio, aabb.min[2]],
[aabb.max[0] + (padR + halfScreenPadX) * scaleRatio, aabb.max[1] + (padT + halfScreenPadY) * scaleRatio, aabb.max[2]]);

const centerOffset = (typeof eOptions.offset.x === 'number' && typeof eOptions.offset.y === 'number') ?
new Point(eOptions.offset.x, eOptions.offset.y) :
Point.convert(eOptions.offset);

const rotatedOffset = centerOffset.rotate(-degToRad(bearing));

aabb.center[0] -= rotatedOffset.x * scaleRatio;
aabb.center[1] += rotatedOffset.y * scaleRatio;

return aabb;
}

/** @section {Querying features} */

/**
Expand Down Expand Up @@ -773,6 +811,7 @@ class Camera extends Evented {
* the highest zoom level up to and including `Map#getMaxZoom()` that fits
* the points in the viewport at the specified bearing.
* @memberof Map#
* @param transform The current transform
* @param {LngLatLike} p0 First point
* @param {LngLatLike} p1 Second point
* @param {number} bearing Desired map bearing at end of animation, in degrees
Expand All @@ -799,7 +838,6 @@ class Camera extends Evented {

const tr = transform.clone();
const eOptions = this._extendCameraOptions(options);
const edgePadding = tr.padding;

tr.bearing = bearing;
tr.pitch = pitch;
Expand Down Expand Up @@ -829,29 +867,9 @@ class Camera extends Evented {

aabb = Aabb.applyTransform(aabb, worldToCamera);

const size = vec3.sub([], aabb.max, aabb.min);

const screenPadL = edgePadding.left || 0;
const screenPadR = edgePadding.right || 0;
const screenPadB = edgePadding.bottom || 0;
const screenPadT = edgePadding.top || 0;

const {left: padL, right: padR, top: padT, bottom: padB} = eOptions.padding;

const halfScreenPadX = (screenPadL + screenPadR) * 0.5;
const halfScreenPadY = (screenPadT + screenPadB) * 0.5;

const scaleX = (tr.width - (screenPadL + screenPadR + padL + padR)) / size[0];
const scaleY = (tr.height - (screenPadB + screenPadT + padB + padT)) / size[1];

const zoomRef = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), eOptions.maxZoom);

const scaleRatio = tr.scale / tr.zoomScale(zoomRef);

aabb = new Aabb(
[aabb.min[0] - (padL + halfScreenPadX) * scaleRatio, aabb.min[1] - (padB + halfScreenPadY) * scaleRatio, aabb.min[2]],
[aabb.max[0] + (padR + halfScreenPadX) * scaleRatio, aabb.max[1] + (padT + halfScreenPadY) * scaleRatio, aabb.max[2]]);
aabb = this._extendAABBWithPaddings(aabb, eOptions, tr, bearing);

const size = vec3.sub([], aabb.max, aabb.min);
const aabbHalfExtentZ = size[2] * 0.5;
const frustumDistance = this._minimumAABBFrustumDistance(tr, aabb);

Expand All @@ -863,15 +881,6 @@ class Camera extends Evented {
const offset = vec3.scale([], normalZ, frustumDistance + aabbHalfExtentZ);
const cameraPosition = vec3.add([], aabb.center, offset);

const centerOffset = (typeof eOptions.offset.x === 'number' && typeof eOptions.offset.y === 'number') ?
new Point(eOptions.offset.x, eOptions.offset.y) :
Point.convert(eOptions.offset);

const rotatedOffset = centerOffset.rotate(-degToRad(bearing));

aabb.center[0] -= rotatedOffset.x * scaleRatio;
aabb.center[1] += rotatedOffset.y * scaleRatio;

vec3.transformMat4(aabb.center, aabb.center, cameraToWorld);
vec3.transformMat4(cameraPosition, cameraPosition, cameraToWorld);

Expand Down
9 changes: 9 additions & 0 deletions test/unit/ui/camera.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2299,6 +2299,15 @@ describe('camera', () => {
expect(fixedLngLat(transform.center, 4)).toEqual({lng: 180, lat: 80});
expect(fixedNum(transform.zoom, 3)).toEqual(1.072);
});

test('entire longitude range: -180 to 180 with asymmetrical padding', () => {
const camera = createCamera({projection: {name: 'globe'}});
const bb = [[-180, 10], [180, 50]];

const transform = camera.cameraForBounds(bb, {padding:{top: 10, right: 75, bottom: 50, left: 25}});
expect(fixedLngLat(transform.center, 4)).toEqual({lng: 180, lat: 80});
expect(fixedNum(transform.zoom, 3)).toEqual(0.892);
});
});

describe('#fitBounds', () => {
Expand Down

0 comments on commit 360bfd2

Please sign in to comment.