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 MapControls. (#2281)
Browse files Browse the repository at this point in the history
* MAPSJS-2660: Support oblique perspective projection in orbitAroundScreenPoint.

Deprecate old orbitAroundScreenPoint signature.
Create new signature where orbit center is optional, defaulting to
camera principal point.

Signed-off-by: Andres Mandado <[email protected]>

* MAPSJS-2660: Address review comments.

Signed-off-by: Andres Mandado <[email protected]>
  • Loading branch information
atomicsulfate authored Aug 23, 2021
1 parent c31e662 commit a3b58b6
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 110 deletions.
72 changes: 36 additions & 36 deletions @here/harp-map-controls/lib/MapControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
*/

import * as geoUtils from "@here/harp-geoutils";
import { EventDispatcher, MapView, MapViewEventNames, MapViewUtils } from "@here/harp-mapview";
import {
CameraUtils,
EventDispatcher,
MapView,
MapViewEventNames,
MapViewUtils
} from "@here/harp-mapview";
import * as THREE from "three";

import * as utils from "./Utils";
Expand Down Expand Up @@ -618,14 +624,10 @@ export class MapControls extends EventDispatcher {

const deltaAzimuth = this.m_currentAzimuth - this.m_lastAzimuth;

MapViewUtils.orbitAroundScreenPoint(
this.mapView,
0,
0,
MapViewUtils.orbitAroundScreenPoint(this.mapView, {
deltaAzimuth,
0,
this.m_maxTiltAngle
);
maxTiltAngle: this.m_maxTiltAngle
});
this.updateMapView();
}

Expand Down Expand Up @@ -671,9 +673,12 @@ export class MapControls extends EventDispatcher {
: this.targetedTilt;

const initialTilt = this.currentTilt;
const deltaAngle = this.m_currentTilt - initialTilt;
const deltaTilt = this.m_currentTilt - initialTilt;

MapViewUtils.orbitAroundScreenPoint(this.mapView, 0, 0, 0, deltaAngle, this.m_maxTiltAngle);
MapViewUtils.orbitAroundScreenPoint(this.mapView, {
deltaTilt,
maxTiltAngle: this.m_maxTiltAngle
});
this.updateMapView();

if (tiltAnimationFinished) {
Expand Down Expand Up @@ -941,7 +946,7 @@ export class MapControls extends EventDispatcher {
utils.calculateNormalizedDeviceCoordinates(mousePos.x, mousePos.y, width, height)
);
} else {
this.m_initialMousePosition.set(0, 0);
CameraUtils.getPrincipalPoint(this.mapView.camera, this.m_initialMousePosition);
}

const onMouseMove = this.mouseMove.bind(this);
Expand Down Expand Up @@ -990,14 +995,15 @@ export class MapControls extends EventDispatcher {
} else if (this.m_state === State.ORBIT) {
this.stopExistingAnimations();

MapViewUtils.orbitAroundScreenPoint(
this.mapView,
this.m_initialMousePosition.x,
this.m_initialMousePosition.y,
this.orbitingMouseDeltaFactor * this.m_mouseDelta.x,
-this.orbitingMouseDeltaFactor * this.m_mouseDelta.y,
this.m_maxTiltAngle
);
MapViewUtils.orbitAroundScreenPoint(this.mapView, {
center: this.m_tmpVector2.set(
this.m_initialMousePosition.x,
this.m_initialMousePosition.y
),
deltaAzimuth: this.orbitingMouseDeltaFactor * this.m_mouseDelta.x,
deltaTilt: -this.orbitingMouseDeltaFactor * this.m_mouseDelta.y,
maxTiltAngle: this.m_maxTiltAngle
});
}

this.m_lastMousePosition.set(mousePos.x, mousePos.y);
Expand Down Expand Up @@ -1296,18 +1302,15 @@ export class MapControls extends EventDispatcher {

if (this.rotateEnabled) {
this.updateCurrentRotation();
const deltaRotation =
const deltaAzimuth =
this.m_touchState.currentRotation - this.m_touchState.initialRotation;
this.stopExistingAnimations();

MapViewUtils.orbitAroundScreenPoint(
this.mapView,
center.x,
center.y,
deltaRotation,
0,
this.m_maxTiltAngle
);
MapViewUtils.orbitAroundScreenPoint(this.mapView, {
center: this.m_tmpVector2.set(center.x, center.y),
deltaAzimuth,
maxTiltAngle: this.m_maxTiltAngle
});
}
}

Expand All @@ -1319,14 +1322,11 @@ export class MapControls extends EventDispatcher {
firstTouch.lastTouchPoint
);
this.stopExistingAnimations();
MapViewUtils.orbitAroundScreenPoint(
this.mapView,
0,
0,
this.orbitingTouchDeltaFactor * diff.x,
-this.orbitingTouchDeltaFactor * diff.y,
this.m_maxTiltAngle
);
MapViewUtils.orbitAroundScreenPoint(this.mapView, {
deltaAzimuth: this.orbitingTouchDeltaFactor * diff.x,
deltaTilt: -this.orbitingTouchDeltaFactor * diff.y,
maxTiltAngle: this.m_maxTiltAngle
});
}

this.m_zoomAnimationStartTime = performance.now();
Expand Down
92 changes: 74 additions & 18 deletions @here/harp-mapview/lib/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ProjectionType,
sphereProjection,
TileKey,
Vector2Like,
Vector3Like
} from "@here/harp-geoutils";
import { GeoCoordLike } from "@here/harp-geoutils/lib/coordinates/GeoCoordLike";
Expand Down Expand Up @@ -65,7 +66,7 @@ const cache = {
box3: [new THREE.Box3()],
obox3: [new OrientedBox3()],
quaternions: [new THREE.Quaternion(), new THREE.Quaternion()],
vector2: [new THREE.Vector2()],
vector2: [new THREE.Vector2(), new THREE.Vector2()],
vector3: [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()],
matrix4: [new THREE.Matrix4(), new THREE.Matrix4()],
transforms: [
Expand Down Expand Up @@ -198,16 +199,15 @@ export namespace MapViewUtils {
.multiplyScalar(-newCameraDistance)
.add(cameraTarget);

// In sphere, we may have to also orbit the camera around the position located at the
// center of the screen, in order to limit the tilt to `maxTiltAngle`, as we change
// this tilt by changing the camera's height above.
// In sphere, we may have to also orbit the camera around the target, in order to limit the
// the tilt to `maxTiltAngle`, as we change this tilt by changing the camera's height above.
if (projection.type === ProjectionType.Spherical) {
// FIXME: We cannot use mapView.tilt here b/c it does not reflect the latest camera
// changes.
const tilt = extractCameraTilt(camera, projection);
const deltaTilt = tilt - maxTiltAngle;
if (deltaTilt > 0) {
orbitAroundScreenPoint(mapView, 0, 0, 0, deltaTilt, maxTiltAngle);
orbitAroundScreenPoint(mapView, { deltaTilt, maxTiltAngle });
}
}

Expand All @@ -228,15 +228,39 @@ export namespace MapViewUtils {
return true;
}

/**
* Parameters for {@link orbitAroundScreenPoint}.
*/
export interface OrbitParams {
/**
* Delta azimuth in radians (default 0).
*/
deltaAzimuth?: number;
/**
* Delta tilt in radians (default 0);
*/
deltaTilt?: number;
/**
* Maximum tilt between the camera and its target in radians.
*/
maxTiltAngle: number;
/**
* Orbiting center in NDC coordinates, defaults to camera's principal point.
* @see {@link CameraUtils.getPrincipalPoint}.
*/
center?: Vector2Like;
}

/**
* Orbits the camera around a given point on the screen.
*
* @param mapView - The {@link MapView} instance to manipulate.
* @param offsetX - Orbit point in NDC space.
* @param offsetY - Orbit point in NDC space.
* @param deltaAzimuth - Delta azimuth in radians.
* @param deltaTil - Delta tilt in radians.
* @param deltaTilt - Delta tilt in radians.
* @param maxTiltAngle - The maximum tilt between the camera and its target in radian.
* @deprecated Use overload with {@link OrbitParams} object parameter.
*/
export function orbitAroundScreenPoint(
mapView: MapView,
Expand All @@ -245,28 +269,60 @@ export namespace MapViewUtils {
deltaAzimuth: number,
deltaTilt: number,
maxTiltAngle: number
) {
const rotationTargetWorld = MapViewUtils.rayCastWorldCoordinates(mapView, offsetX, offsetY);
if (rotationTargetWorld === null) {
): void;

/**
* Orbits the camera around a given point on the screen.
*
* @param mapView - The {@link MapView} instance to manipulate.
* @param orbitParams - {@link OrbitParams}.
*/
export function orbitAroundScreenPoint(mapView: MapView, orbitParams: OrbitParams): void;

export function orbitAroundScreenPoint(
mapView: MapView,
offsetXOrOrbitParams: number | OrbitParams,
offsetY?: number,
deltaAzimuth?: number,
deltaTilt?: number,
maxTiltAngle?: number
): void {
const ppalPoint = CameraUtils.getPrincipalPoint(mapView.camera, cache.vector2[0]);
const mapTargetWorld = MapViewUtils.rayCastWorldCoordinates(
mapView,
ppalPoint.x,
ppalPoint.y
);
if (mapTargetWorld === null) {
return;
}

const mapTargetWorld =
offsetX === 0 && offsetY === 0
? rotationTargetWorld
: MapViewUtils.rayCastWorldCoordinates(mapView, 0, 0);
if (mapTargetWorld === null) {
let orbitCenter: Vector2Like | undefined;
if (typeof offsetXOrOrbitParams === "number") {
orbitCenter = cache.vector2[1].set(offsetXOrOrbitParams, offsetY!);
} else {
const params = offsetXOrOrbitParams;
orbitCenter = params.center ?? ppalPoint;
deltaAzimuth = params.deltaAzimuth ?? 0;
deltaTilt = params.deltaTilt ?? 0;
maxTiltAngle = params.maxTiltAngle;
}
const orbitAroundPpalPoint = orbitCenter.x === ppalPoint.x && orbitCenter.y === ppalPoint.y;
const rotationTargetWorld = orbitAroundPpalPoint
? mapTargetWorld
: MapViewUtils.rayCastWorldCoordinates(mapView, orbitCenter.x, orbitCenter.y);
if (rotationTargetWorld === null) {
return;
}

applyAzimuthAroundTarget(mapView, rotationTargetWorld, -deltaAzimuth);
applyAzimuthAroundTarget(mapView, rotationTargetWorld, -deltaAzimuth!);

const tiltAxis = new THREE.Vector3(1, 0, 0).applyQuaternion(mapView.camera.quaternion);
const clampedDeltaTilt = computeClampedDeltaTilt(
mapView,
offsetY,
deltaTilt,
maxTiltAngle,
orbitCenter.y - ppalPoint.y,
deltaTilt!,
maxTiltAngle!,
mapTargetWorld,
rotationTargetWorld,
tiltAxis
Expand Down
Loading

0 comments on commit a3b58b6

Please sign in to comment.