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

Commit

Permalink
MAPSJS-2660: Move camera functions from MapViewUtils to CameraUtils. (#…
Browse files Browse the repository at this point in the history
…2263)

* MAPSJS-2660: Move camera functions from MapViewUtils to CameraUtils.

- Deprecate calculateDepthFromClipDistance() and cameraToWorldDistance()
- Move getCameraFrustumPlanes(), convertWorldToScreenSize(),
  convertScreenToWorldSize() and all functions related to fov and
  focalLength computations to CameraUtils.
- Extract FovCalculation to a separate file.
- Move MapView's limitFov code to CameraUtils' setVerticalFov().

Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>

* MAPSJS-2660: Address review comments.

Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>
  • Loading branch information
atomicsulfate committed Aug 6, 2021
1 parent 9e25396 commit c7f303e
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 161 deletions.
2 changes: 2 additions & 0 deletions @here/harp-mapview/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from "./lib/AnimatedExtrusionHandler";
export * from "./lib/BaseTileLoader";
export * from "./lib/BoundsGenerator";
export * from "./lib/CameraMovementDetector";
export * from "./lib/CameraUtils";
export * from "./lib/ClipPlanesEvaluator";
export * from "./lib/ColorCache";
export * from "./lib/composing";
Expand All @@ -29,6 +30,7 @@ export * from "./lib/copyrights/UrlCopyrightProvider";
export * from "./lib/DataSource";
export * from "./lib/EventDispatcher";
export * from "./lib/FixedClipPlanesEvaluator";
export * from "./lib/FovCalculation";
export * from "./lib/PolarTileDataSource";
export * from "./lib/DecodedTileHelpers";
export * from "./lib/DepthPrePass";
Expand Down
110 changes: 110 additions & 0 deletions @here/harp-mapview/lib/CameraUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2021 HERE Europe B.V.
* Licensed under Apache 2.0, see full license in LICENSE
* SPDX-License-Identifier: Apache-2.0
*/

import * as THREE from "three";

import { MAX_FOV_RAD, MIN_FOV_RAD } from "./FovCalculation";

export namespace CameraUtils {
/**
* Computes a camera's vertical field of view for given focal length and viewport height.
* @beta
*
* @param focalLength - Focal length in pixels (see {@link computeFocalLength})
* @param height - Viewport height in pixels.
* @returns Vertical field of view in radians.
*/
export function computeVerticalFov(focalLength: number, height: number): number {
return 2 * Math.atan(height / 2 / focalLength);
}

/**
* Computes a camera's horizontal field of view.
* @beta
*
* @param camera
* @returns Horizontal field of view in radians.
*/
export function computeHorizontalFov(camera: THREE.PerspectiveCamera): number {
const vFov = THREE.MathUtils.degToRad(camera.fov);
return 2 * Math.atan(Math.tan(vFov / 2) * camera.aspect);
}

/**
* Set a camera's horizontal field of view.
* @internal
*
* @param camera
* @param hFov - The horizontal field of view in radians.
*/
export function setHorizontalFov(camera: THREE.PerspectiveCamera, hFov: number): void {
camera.fov = THREE.MathUtils.radToDeg(2 * Math.atan(Math.tan(hFov / 2) / camera.aspect));
}

/**
* Sets a camera's vertical field of view.
* @internal
*
* @param camera
* @param fov - The vertical field of view in radians.
*/
export function setVerticalFov(camera: THREE.PerspectiveCamera, fov: number): void {
camera.fov = THREE.MathUtils.radToDeg(THREE.MathUtils.clamp(fov, MIN_FOV_RAD, MAX_FOV_RAD));

let hFov = computeHorizontalFov(camera);

if (hFov > MAX_FOV_RAD || hFov < MIN_FOV_RAD) {
hFov = THREE.MathUtils.clamp(hFov, MIN_FOV_RAD, MAX_FOV_RAD);
setHorizontalFov(camera, hFov);
}
}

/**
* Computes a camera's focal length for a given viewport height.
* @beta
*
* @param vFov - Vertical field of view in radians.
* @param height - Viewport height in pixels.
* @returns focal length in pixels.
*/
export function computeFocalLength(vFov: number, height: number): number {
return height / 2 / Math.tan(vFov / 2);
}

/**
* Calculates object's screen size based on the focal length and it's camera distance.
* @beta
*
* @param focalLength - Focal length in pixels (see {@link computeFocalLength})
* @param distance - Object distance in world space.
* @param worldSize - Object size in world space.
* @return object size in screen space.
*/
export function convertWorldToScreenSize(
focalLength: number,
distance: number,
worldSize: number
): number {
return (focalLength * worldSize) / distance;
}

/**
* Calculates object's world size based on the focal length and it's camera distance.
* @beta
*
* @param focalLength - Focal length in pixels (see {@link computeFocalLength})
* @param distance - Object distance in world space.
* @param screenSize - Object size in screen space.
* @return object size in world space.
*/
export function convertScreenToWorldSize(
focalLength: number,
distance: number,
screenSize: number
): number {
return (distance * screenSize) / focalLength;
}
}
6 changes: 2 additions & 4 deletions @here/harp-mapview/lib/ClipPlanesEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { EarthConstants, Projection, ProjectionType } from "@here/harp-geoutils"
import { assert } from "@here/harp-utils";
import * as THREE from "three";

import { CameraUtils } from "./CameraUtils";
import { ElevationProvider } from "./ElevationProvider";
import { MapViewUtils } from "./Utils";

Expand Down Expand Up @@ -476,10 +477,7 @@ export class TopViewClipPlanesEvaluator extends ElevationBasedClipPlanesEvaluato
let halfFovAngle = THREE.MathUtils.degToRad(camera.fov / 2);
// If width > height, then we have to compute the horizontal FOV.
if (camera.aspect > 1) {
halfFovAngle = MapViewUtils.calculateHorizontalFovByVerticalFov(
THREE.MathUtils.degToRad(camera.fov),
camera.aspect
);
halfFovAngle = CameraUtils.computeHorizontalFov(camera);
}

const maxR = r + this.maxElevation;
Expand Down
48 changes: 48 additions & 0 deletions @here/harp-mapview/lib/FovCalculation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 HERE Europe B.V.
* Licensed under Apache 2.0, see full license in LICENSE
* SPDX-License-Identifier: Apache-2.0
*/

import * as THREE from "three";

/**
* Specifies how the FOV (Field of View) should be calculated.
*/
export interface FovCalculation {
/**
* How to interpret the [[fov]], can be either `fixed` or `dynamic`.
*
* `fixed` means that the FOV is fixed regardless of the [[viewportHeight]], such that shrinking
* the height causes the map to shrink to keep the content in view. The benefit is that,
* regardless of any resizes, the field of view is constant, which means there is no change in
* the distortion of buildings near the edges. However the trade off is that the zoom level
* changes, which means that the map will pull in new tiles, hence causing some flickering.
*
* `dynamic` means that the focal length is calculated based on the supplied [[fov]] and
* [[viewportHeight]], this means that the map doesn't scale (the image is essentially cropped
* but not shrunk) when the [[viewportHeight]] or [[viewportWidth]] is changed. The benefit is
* that the zoom level is (currently) stable during resize, because the focal length is used,
* however the tradeoff is that changing from a small to a big height will cause the fov to
* change a lot, and thus introduce distortion.
*/
type: "fixed" | "dynamic";

/**
* Vertical field of view in degrees.
* If [[type]] is `fixed` then the supplied [[fov]] is fixed regardless of
* [[viewportHeight]] or [[viewportWidth]].
*
* If [[type]] is `dynamic` then the supplied [[fov]] is applied to the
* first frame, and the focal length calculated. Changes to the viewport
* height no longer shrink the content because the field of view is updated
* dynamically.
*/
fov: number;
}

export const DEFAULT_FOV_CALCULATION: FovCalculation = { type: "dynamic", fov: 40 };
export const MIN_FOV_DEG = 10;
export const MAX_FOV_DEG = 140;
export const MIN_FOV_RAD = THREE.MathUtils.degToRad(MIN_FOV_DEG);
export const MAX_FOV_RAD = THREE.MathUtils.degToRad(MAX_FOV_DEG);
90 changes: 21 additions & 69 deletions @here/harp-mapview/lib/MapView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import * as THREE from "three";
import { AnimatedExtrusionHandler } from "./AnimatedExtrusionHandler";
import { BackgroundDataSource } from "./BackgroundDataSource";
import { CameraMovementDetector } from "./CameraMovementDetector";
import { CameraUtils } from "./CameraUtils";
import { ClipPlanesEvaluator, createDefaultClipPlanesEvaluator } from "./ClipPlanesEvaluator";
import { IMapAntialiasSettings, IMapRenderingManager, MapRenderingManager } from "./composing";
import { ConcurrentDecoderFacade } from "./ConcurrentDecoderFacade";
Expand All @@ -54,6 +55,12 @@ import { DataSource } from "./DataSource";
import { ElevationProvider } from "./ElevationProvider";
import { ElevationRangeSource } from "./ElevationRangeSource";
import { EventDispatcher } from "./EventDispatcher";
import {
DEFAULT_FOV_CALCULATION,
FovCalculation,
MAX_FOV_DEG,
MIN_FOV_DEG
} from "./FovCalculation";
import { FrustumIntersection } from "./FrustumIntersection";
import { overlayOnElevation } from "./geometry/overlayOnElevation";
import { TileGeometryManager } from "./geometry/TileGeometryManager";
Expand Down Expand Up @@ -138,12 +145,8 @@ export enum MapViewEventNames {
}

const logger = LoggerManager.instance.create("MapView");
const DEFAULT_FOV_CALCULATION: FovCalculation = { type: "dynamic", fov: 40 };
const DEFAULT_CAM_NEAR_PLANE = 0.1;
const DEFAULT_CAM_FAR_PLANE = 4000000;
const MAX_FIELD_OF_VIEW = 140;
const MIN_FIELD_OF_VIEW = 10;

const DEFAULT_MIN_ZOOM_LEVEL = 1;

/**
Expand Down Expand Up @@ -187,40 +190,6 @@ const cache = {
color: new THREE.Color()
};

/**
* Specifies how the FOV (Field of View) should be calculated.
*/
export interface FovCalculation {
/**
* How to interpret the [[fov]], can be either `fixed` or `dynamic`.
*
* `fixed` means that the FOV is fixed regardless of the [[viewportHeight]], such that shrinking
* the height causes the map to shrink to keep the content in view. The benefit is that,
* regardless of any resizes, the field of view is constant, which means there is no change in
* the distortion of buildings near the edges. However the trade off is that the zoom level
* changes, which means that the map will pull in new tiles, hence causing some flickering.
*
* `dynamic` means that the focal length is calculated based on the supplied [[fov]] and
* [[viewportHeight]], this means that the map doesn't scale (the image is essentially cropped
* but not shrunk) when the [[viewportHeight]] or [[viewportWidth]] is changed. The benefit is
* that the zoom level is (currently) stable during resize, because the focal length is used,
* however the tradeoff is that changing from a small to a big height will cause the fov to
* change a lot, and thus introduce distortion.
*/
type: "fixed" | "dynamic";

/**
* If [[type]] is `fixed` then the supplied [[fov]] is fixed regardless of
* [[viewportHeight]] or [[viewportWidth]].
*
* If [[type]] is `dynamic` then the supplied [[fov]] is applied to the
* first frame, and the focal length calculated. Changes to the viewport
* height no longer shrink the content because the field of view is updated
* dynamically.
*/
fov: number;
}

/**
* Hint for the WebGL implementation on which power mode to prefer.
*
Expand Down Expand Up @@ -1047,8 +1016,8 @@ export class MapView extends EventDispatcher {
: this.m_options.fovCalculation;
this.m_options.fovCalculation.fov = THREE.MathUtils.clamp(
this.m_options.fovCalculation!.fov,
MIN_FIELD_OF_VIEW,
MAX_FIELD_OF_VIEW
MIN_FOV_DEG,
MAX_FOV_DEG
);
// Initialization of mCamera and mVisibleTiles
const { width, height } = this.getCanvasClientSize();
Expand Down Expand Up @@ -2072,7 +2041,7 @@ export class MapView extends EventDispatcher {
*/
setFovCalculation(fovCalculation: FovCalculation) {
this.m_options.fovCalculation = fovCalculation;
this.calculateFocalLength(this.m_renderer.getSize(cache.vector2[0]).height);
this.updateFocalLength(this.m_renderer.getSize(cache.vector2[0]).height);
this.updateCameras();
}

Expand Down Expand Up @@ -2460,7 +2429,7 @@ export class MapView extends EventDispatcher {
const lookAtDistance = this.m_targetDistance;

// Find world space object size that corresponds to one pixel on screen.
this.m_pixelToWorld = MapViewUtils.calculateWorldSizeByFocalLength(
this.m_pixelToWorld = CameraUtils.convertScreenToWorldSize(
this.m_focalLength,
lookAtDistance,
1
Expand Down Expand Up @@ -3659,7 +3628,7 @@ export class MapView extends EventDispatcher {

const { width, height } = this.getCanvasClientSize();

this.calculateFocalLength(height);
this.updateFocalLength(height);

this.m_options.target = GeoCoordinates.fromObject(
getOptionValue(this.m_options.target, MapViewDefaults.target)
Expand Down Expand Up @@ -3886,40 +3855,23 @@ export class MapView extends EventDispatcher {
logger.warn("WebGL context restored", event);
};

private limitFov(fov: number, aspect: number): number {
fov = THREE.MathUtils.clamp(fov, MIN_FIELD_OF_VIEW, MAX_FIELD_OF_VIEW);

let hFov = THREE.MathUtils.radToDeg(
MapViewUtils.calculateHorizontalFovByVerticalFov(THREE.MathUtils.degToRad(fov), aspect)
);

if (hFov > MAX_FIELD_OF_VIEW || hFov < MIN_FIELD_OF_VIEW) {
hFov = THREE.MathUtils.clamp(hFov, MIN_FIELD_OF_VIEW, MAX_FIELD_OF_VIEW);
fov = THREE.MathUtils.radToDeg(
MapViewUtils.calculateVerticalFovByHorizontalFov(
THREE.MathUtils.degToRad(hFov),
aspect
)
);
}
return fov as number;
}

/**
* Sets the field of view calculation, and applies it immediately to the camera.
*
* @param type - How to calculate the FOV
* @param fovCalculation - How to calculate the FOV
* @param height - Viewport height.
*/
private setFovOnCamera(fovCalculation: FovCalculation, height: number) {
let fov = 0;
if (fovCalculation.type === "fixed") {
this.calculateFocalLength(height);
fov = fovCalculation.fov;
this.updateFocalLength(height);
fov = THREE.MathUtils.degToRad(fovCalculation.fov);
} else {
assert(this.m_focalLength !== 0);
fov = MapViewUtils.calculateFovByFocalLength(this.m_focalLength, height);
fov = CameraUtils.computeVerticalFov(this.m_focalLength, height);
}
this.m_camera.fov = this.limitFov(fov, this.m_camera.aspect);

CameraUtils.setVerticalFov(this.m_camera, fov);
}

/**
Expand All @@ -3930,9 +3882,9 @@ export class MapView extends EventDispatcher {
* fixed but the FOV changes.
* @param height - Height of the canvas in css / client pixels.
*/
private calculateFocalLength(height: number) {
private updateFocalLength(height: number) {
assert(this.m_options.fovCalculation !== undefined);
this.m_focalLength = MapViewUtils.calculateFocalLengthByVerticalFov(
this.m_focalLength = CameraUtils.computeFocalLength(
THREE.MathUtils.degToRad(this.m_options.fovCalculation!.fov),
height
);
Expand Down
Loading

0 comments on commit c7f303e

Please sign in to comment.