Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
MAPSJS-2660: Support off-center projection in getFitBoundsDistance.
Browse files Browse the repository at this point in the history
Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>
  • Loading branch information
atomicsulfate committed Aug 24, 2021
1 parent 387d3c5 commit 1577629
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 23 deletions.
44 changes: 30 additions & 14 deletions @here/harp-mapview/lib/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ export namespace MapViewUtils {
// |<-------->| Ps
// constD pEyeZ| /| ^
// |<-->|<--->| / | |
// | | | / | | ndcY*h/2
// | | | / | | |ndcY-O.y|*h/2
// | | | / | |
// <---T----P'----C0----O v
// camZ |_| /| |
Expand All @@ -961,7 +961,7 @@ export namespace MapViewUtils {
// camY
// constD newPEyeZ ^ Ps
// |<-->|<--------------->| _-`| ^
// | | | _-` | | h/2
// | | | _-` | | |sign(ndcY)-O.y|h/2
// | | | _-` | |
// <---T----P'----C0----------C1---------O v
// camZ |_| _-`| | C0 - Initial camera position
Expand All @@ -976,27 +976,43 @@ export namespace MapViewUtils {
// initial camera position, but calculations are equivalent for points beyond the target
// (pEyeZ negative) or behind the camera (constD negative).
// Right triangles PP'C0 and PsOC0 are equivalent, as well as PP'C1 and Ps0C1, that means:
// |ndcY|*h/(2*f) = PcamY / |pEyeZ| (1) (ndcY,pEyeZ may be negative so take abs vals).
// h/(2*f) = PcamY / newPEyeZ (2)
// Dividing (1) by (2) and solving for newPEyeZ we get: newPEyeZ = |ndcY| * |pEyeZ|
// |ndcY-O.y|*h/(2*f) = PcamY / |pEyeZ| (1) (ndcY-O.y,pEyeZ may be negative, take abs vals).
// |sign(ndcY)-O.y|h/(2*f) = PcamY / newPEyeZ (2)
// Dividing (1) by (2) and solving for newPEyeZ we get:
// newPEyeZ = | pEyeZ || ndcY - O.y | / |sign(ndcY)-O.y|
// The target distance to project P at the top/bottom border of the viewport is then:
// constD + newPEyeZ = targetDist - pEyeZ + |ndcY|*|pEyeZ|
// constD + newPEyeZ = targetDist - pEyeZ + |pEyeZ||ndcY-O.y| / |sign(ndcY)-O.y|
// The target distance to project P at the left/right border of the viewport is similarly:
// targetDist - pEyeZ + |ndcX|*|pEyeZ|
// Take the largest of both distances to ensure the point is inside the viewport, i.e., take
// the largest ndc coordinate value:
// newDistance = targetDist - pEyeZ + max(|ndcX|, |ndcY|)*|pEyeZ|
// targetDist - pEyeZ + |pEyeZ||ndcX-O.x| / |sign(ndcX)-O.x|
// Take the largest of both distances to ensure the point is inside the viewport:
// newDistance = targetDist - pEyeZ +
// max(| ndcX - O.x | /|sign(ndcX)-O.x|, |ndcY-O.y|/sign(ndcY) - O.y |) *| pEyeZ |

const targetDist = cache.vector3[0].copy(worldTarget).sub(camera.position).length();
const ppalPoint = CameraUtils.getPrincipalPoint(camera);
let newDistance = targetDist;

const getDistanceFactor = (pointNDC: number, ppNDC: number) => {
// Use as maximum NDC a value slightly smaller than 1 to ensure the point is visible
// with the final camera distance. Otherwise any precision loss might leave it just
// outside of the viewport.
const maxNDC = 0.99;
return Math.abs(pointNDC) > 1
? Math.abs((pointNDC - ppNDC) / (maxNDC * Math.sign(pointNDC) - ppNDC))
: 1;
};
for (const point of points) {
const pEyeZ = -cache.vector3[0].copy(point).applyMatrix4(camera.matrixWorldInverse).z;
const pointNDC = cache.vector3[0].applyMatrix4(camera.projectionMatrix);
const constDist = targetDist - pEyeZ;
const newPEyeZ =
Math.abs(pEyeZ) * Math.max(Math.abs(pointNDC.x), Math.abs(pointNDC.y)) + constDist;
newDistance = Math.max(newDistance, newPEyeZ);
const maxFactor = Math.max(
getDistanceFactor(pointNDC.x, ppalPoint.x),
getDistanceFactor(pointNDC.y, ppalPoint.y)
);
if (maxFactor > 1) {
const constDist = targetDist - pEyeZ;
const newPEyeZ = Math.abs(pEyeZ) * maxFactor + constDist;
newDistance = Math.max(newDistance, newPEyeZ);
}
}
return newDistance;
}
Expand Down
66 changes: 57 additions & 9 deletions @here/harp-mapview/test/MapViewTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import * as sinon from "sinon";
import * as THREE from "three";

import { BackgroundDataSource } from "../lib/BackgroundDataSource";
import { CameraUtils } from "../lib/CameraUtils";
import { DataSource } from "../lib/DataSource";
import { ElevationProvider } from "../lib/ElevationProvider";
import { CalculationStatus, ElevationRangeSource } from "../lib/ElevationRangeSource";
Expand Down Expand Up @@ -140,6 +141,7 @@ describe("MapView", function () {
const tests: Array<{
testName: string;
lookAtParams: Partial<LookAtParams>;
ppalPoint?: { x: number; y: number };
expectAllInView?: boolean;
}> = [
{
Expand Down Expand Up @@ -199,6 +201,16 @@ describe("MapView", function () {
)
}
},
{
testName: "berlin bounds only, off-center projection",
lookAtParams: {
bounds: new GeoBox(
new GeoCoordinates(52.438917, 13.275001),
new GeoCoordinates(52.590844, 13.522331)
)
},
ppalPoint: { x: 0.5, y: -0.3 }
},
{
testName: "berlin bounds + zoomLevel",
lookAtParams: {
Expand Down Expand Up @@ -231,6 +243,18 @@ describe("MapView", function () {
heading: 45
}
},
{
testName: "berlin bounds + angles, off-center projection",
lookAtParams: {
bounds: new GeoBox(
new GeoCoordinates(52.438917, 13.275001),
new GeoCoordinates(52.590844, 13.522331)
),
tilt: 45,
heading: 45
},
ppalPoint: { x: 0.5, y: -0.3 }
},
{
testName: "berlin polygon bounds only",
lookAtParams: {
Expand Down Expand Up @@ -266,6 +290,20 @@ describe("MapView", function () {
heading: 30
}
},
{
testName: "large polygon bounds + angles, off-center projection",
lookAtParams: {
bounds: new GeoPolygon([
new GeoCoordinates(40.0, 13.0),
new GeoCoordinates(40.0, 20.0),
new GeoCoordinates(52.0, 20.0),
new GeoCoordinates(52.0, 13.0)
]),
tilt: 80,
heading: 30
},
ppalPoint: { x: -0.8, y: 0.1 }
},
{
testName: "large polygon with spike bounds only",
lookAtParams: {
Expand Down Expand Up @@ -336,7 +374,7 @@ describe("MapView", function () {
function getCenterAndGeoPoints(
bounds: GeoBox | GeoPolygon | GeoBoxExtentLike | GeoCoordLike[]
): [GeoCoordinates, GeoCoordinatesLike[]] {
let center: GeoCoordinates | undefined;
let center: GeoCoordinates;
let geoPoints: GeoCoordinatesLike[] = [];

if (bounds instanceof GeoBox) {
Expand All @@ -345,9 +383,12 @@ describe("MapView", function () {
geoPoints.push(bounds.southWest);
geoPoints.push(new GeoCoordinates(bounds.south, bounds.east));
geoPoints.push(new GeoCoordinates(bounds.north, bounds.west));
} else if (bounds instanceof GeoPolygon) {
center = bounds.getCentroid();
geoPoints = bounds.coordinates as GeoCoordinatesLike[];
} else {
expect(bounds).instanceOf(GeoPolygon);
const polygon = bounds as GeoPolygon;
center = polygon.getCentroid()!;
expect(center).not.undefined;
geoPoints = polygon.coordinates as GeoCoordinatesLike[];
}
return [center, geoPoints];
}
Expand All @@ -357,11 +398,15 @@ describe("MapView", function () {
[mercatorProjection, 1e-13]
] as Array<[Projection, number]>) {
describe(`${getProjectionName(projection)} projection`, function () {
for (const { testName, lookAtParams, expectAllInView } of tests) {
for (const { testName, lookAtParams, ppalPoint, expectAllInView } of tests) {
const target = lookAtParams.target as GeoCoordinates | undefined;

it(`obeys constructor params - ${testName}`, function () {
mapView = new MapView({ ...mapViewOptions, projection, ...lookAtParams });
if (ppalPoint) {
CameraUtils.setPrincipalPoint(mapView.camera, ppalPoint);
mapView.camera.updateProjectionMatrix();
}
if (lookAtParams.zoomLevel !== undefined) {
expect(mapView.zoomLevel).to.be.closeTo(lookAtParams.zoomLevel, eps);
}
Expand All @@ -378,7 +423,10 @@ describe("MapView", function () {
});
it(`obeys #lookAt params - ${testName}`, function () {
mapView = new MapView({ ...mapViewOptions, projection });

if (ppalPoint) {
CameraUtils.setPrincipalPoint(mapView.camera, ppalPoint);
mapView.camera.updateProjectionMatrix();
}
mapView.lookAt(lookAtParams);

if (lookAtParams.zoomLevel !== undefined) {
Expand All @@ -391,8 +439,8 @@ describe("MapView", function () {
);
}
if (lookAtParams.target !== undefined) {
expect(mapView.target.lat).to.be.closeTo(target.lat, eps);
expect(mapView.target.lng).to.be.closeTo(target.lng, eps);
expect(mapView.target.lat).to.be.closeTo(target!.lat, eps);
expect(mapView.target.lng).to.be.closeTo(target!.lng, eps);
}
if (lookAtParams.tilt !== undefined) {
expect(mapView.tilt).to.be.closeTo(lookAtParams.tilt, eps);
Expand Down Expand Up @@ -422,7 +470,7 @@ describe("MapView", function () {
MapViewUtils.closeToFrustum(
worldPoint as THREE.Vector3,
mapView!.camera,
0.00001
0
)
).to.be.true;
});
Expand Down

0 comments on commit 1577629

Please sign in to comment.