Skip to content

Commit

Permalink
feat: use bearing in render camera
Browse files Browse the repository at this point in the history
Use bearings instead if basic coordinates to handle cases
where fov is >180 deg.
  • Loading branch information
oscarlorentzon committed Apr 1, 2024
1 parent bf4262f commit 4c4693a
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 77 deletions.
2 changes: 1 addition & 1 deletion examples/debug/fisheye.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@

function generateChunk() {
const cameraType = CAMERA_TYPE_FISHEYE;
const height = 2495;
const height = 2496;
const width = 2496;
const focal = 0.2212;
const k1 = 0.1282;
Expand Down
4 changes: 2 additions & 2 deletions src/component/bearing/BearingComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,9 @@ export class BearingComponent extends Component<BearingConfiguration> {
transform, vertices, directions, pointsPerLine, this._viewportCoords);
const projections = bearings
.map(b => this._spatial.projectToPlane(b, [0, 1, 0]))
.map(p => [p[0], p[2]]);
.map(p => [p[0], -p[2]]);

const angles = projections.map(p => Math.atan2(p[0], -p[1]));
const angles = projections.map(p => Math.abs(Math.atan2(p[0], p[1])));
const fov = 2 * Math.min(...angles);

return fov;
Expand Down
23 changes: 20 additions & 3 deletions src/geo/Geo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,23 @@ export function computeBearings(
camera.up.copy(transform.upVector());
camera.position.copy(new THREE.Vector3().fromArray(transform.unprojectSfM([0, 0], 0)));
camera.lookAt(new THREE.Vector3().fromArray(transform.unprojectSfM([0, 0], 10)));
camera.updateMatrix();
camera.updateMatrixWorld(true);

return computeCameraBearings(
camera,
transform,
basicVertices,
basicDirections,
pointsPerLine,
viewportCoords);
}

export function computeCameraBearings(
camera: THREE.Camera,
transform: Transform,
basicVertices: number[][],
basicDirections: number[][],
pointsPerLine: number,
viewportCoords: ViewportCoords): number[][] {

const basicPoints: number[][] = [];
for (let side: number = 0; side < basicVertices.length; ++side) {
Expand All @@ -59,8 +74,10 @@ export function computeBearings(
}
}

camera.updateMatrix();
camera.updateMatrixWorld(true);
const bearings: number[][] = [];
for (const [index, basicPoint] of basicPoints.entries()) {
for (const basicPoint of basicPoints) {
const worldPoint = transform.unprojectBasic(basicPoint, 10000);
const cameraPoint = new THREE.Vector3()
.fromArray(viewportCoords.worldToCamera(worldPoint, camera));
Expand Down
133 changes: 62 additions & 71 deletions src/render/RenderCamera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { State } from "../state/State";
import { AnimationFrame } from "../state/interfaces/AnimationFrame";
import { EulerRotation } from "../state/interfaces/EulerRotation";
import { isSpherical } from "../geo/Geo";
import { MathUtils, Matrix3, Quaternion, Vector2, Vector3 } from "three";
import { MathUtils, Vector3 } from "three";

export class RenderCamera {
private _spatial: Spatial;
Expand Down Expand Up @@ -47,6 +47,8 @@ export class RenderCamera {
private _currentProjectedPoints: number[][];
private _previousProjectedPoints: number[][];

private _currentBearings: number[][];

private _currentFov: number;
private _previousFov: number;

Expand Down Expand Up @@ -95,6 +97,8 @@ export class RenderCamera {
this._currentProjectedPoints = [];
this._previousProjectedPoints = [];

this._currentBearings = [];

this._currentFov = this._initialFov;
this._previousFov = this._initialFov;

Expand Down Expand Up @@ -193,8 +197,7 @@ export class RenderCamera {
this.setSize(this._size);
}
if (this._state === State.Earth) {
const y = this._fovToY(this._perspective.fov, this._zoom);
this._stateTransitionFov = this._yToFov(y, 0);
this._stateTransitionFov = this._zoomedFovToFov(this._perspective.fov, this._zoom);
}

this._changed = true;
Expand All @@ -207,6 +210,7 @@ export class RenderCamera {
this._currentImageId = currentImageId;
this._currentSpherical = isSpherical(state.currentTransform.cameraType);
this._currentProjectedPoints = this._computeProjectedPoints(state.currentTransform);
this._currentBearings = this._computeBearings(state.currentTransform);
this._currentCameraUp.copy(state.currentCamera.up);
this._currentTransformUp.copy(state.currentTransform.upVector());
this._currentTransformForward.copy(
Expand Down Expand Up @@ -247,6 +251,7 @@ export class RenderCamera {

if (this._changed) {
this._currentFov = this._computeCurrentFov(zoom);
this._computeCurrentBearingFov(zoom);
this._previousFov = this._computePreviousFov(zoom);
}

Expand All @@ -264,8 +269,7 @@ export class RenderCamera {
const startFov = this._stateTransitionFov;
const endFov = this._focalToFov(state.camera.focal);
const fov = MathUtils.lerp(startFov, endFov, sta);
const y = this._fovToY(fov, 0);
this._perspective.fov = this._yToFov(y, zoom);
this._perspective.fov = this._fovToZoomedFov(fov, zoom);
break;
}
case State.Custom:
Expand Down Expand Up @@ -350,19 +354,22 @@ export class RenderCamera {
return elementWidth === 0 ? 0 : elementWidth / elementHeight;
}

private _computeUpRotation(up: Vector3, refForward: Vector3, refUp: Vector3): number {
const right = new Vector3().crossVectors(refForward, refUp).normalize();
const normal = new Vector3().crossVectors(refUp, right).normalize();

const xzFromNormal = new Quaternion()
.setFromUnitVectors(normal, new Vector3(0, 1, 0));
const upXz = up.clone().applyQuaternion(xzFromNormal);
const refUpXz = refUp.clone().applyQuaternion(xzFromNormal);
private _computeCurrentBearingFov(zoom: number): number {
if (this._perspective.aspect === 0) {
return 0;
}

const upRad = Math.acos(new Vector2(upXz.x, upXz.z).normalize().x);
const refUpRad = Math.acos(new Vector2(refUpXz.x, refUpXz.z).normalize().x);
if (!this._currentImageId) {
return this._initialFov;
}

return refUpRad - upRad;
return this._currentSpherical ?
this._fovToZoomedFov(90, zoom) :
this._computeVerticalBearingFov(
this._currentBearings,
this._renderMode,
zoom,
this.perspective.aspect);
}

private _computeCurrentFov(zoom: number): number {
Expand All @@ -375,16 +382,12 @@ export class RenderCamera {
}

return this._currentSpherical ?
this._yToFov(1, zoom) :
this._computeVerticalFov(
this._fovToZoomedFov(90, zoom) :
this._computeVerticalBearingFov(
this._currentProjectedPoints,
this._renderMode,
zoom,
this.perspective.aspect,
this._computeUpRotation(
this._currentTransformUp,
this._currentTransformForward,
this._currentCameraUp));
this.perspective.aspect);
}

private _computeFov(): number {
Expand All @@ -406,16 +409,25 @@ export class RenderCamera {
return !this._previousImageId ?
this._currentFov :
this._previousSpherical ?
this._yToFov(1, zoom) :
this._computeVerticalFov(
this._fovToZoomedFov(90, zoom) :
this._computeVerticalBearingFov(
this._previousProjectedPoints,
this._renderMode,
zoom,
this.perspective.aspect,
this._computeUpRotation(
this._previousTransformUp,
this._previousTransformForward,
this._previousCameraUp));
this.perspective.aspect);
}

private _computeBearings(transform: Transform): number[][] {
const vertices = [[0, 0], [1, 0], [1, 1], [0, 1]];
const directions = [[1, 0], [0, 1], [-1, 0], [0, -1]];
const pointsPerLine = 5;

return Geo.computeBearings(
transform,
vertices,
directions,
pointsPerLine,
this._viewportCoords);
}

private _computeProjectedPoints(transform: Transform): number[][] {
Expand All @@ -431,17 +443,6 @@ export class RenderCamera {
this._viewportCoords);
}

private _computeRequiredVerticalFov(
projectedPoint: number[],
zoom: number,
aspect: number): number {
const maxY = Math.max(
Math.abs(projectedPoint[0]) / aspect,
Math.abs(projectedPoint[1]));

return this._yToFov(maxY, zoom);
}

private _computeRotation(camera: Camera): EulerRotation {
let direction: THREE.Vector3 = camera.lookat.clone().sub(camera.position);
let up: THREE.Vector3 = camera.up.clone();
Expand All @@ -452,32 +453,22 @@ export class RenderCamera {
return { phi: phi, theta: theta };
}

private _computeVerticalFov(
projectedPoints: number[][],
private _computeVerticalBearingFov(
bearings: number[][],
renderMode: RenderMode,
zoom: number,
aspect: number,
theta: number): number {

const sinTheta = Math.sin(theta);
const cosTheta = Math.cos(theta);

const fovs = projectedPoints
.map(
([x, y]: number[]): number => {
const rotatedPoint = [
cosTheta * x - sinTheta * y,
sinTheta * x + cosTheta * y,
];
return this._computeRequiredVerticalFov(
rotatedPoint,
zoom,
aspect);
});

const fovMax = this._yToFov(this._fovToY(125, 0), zoom);
const minFov = Math.min(...fovs) * 0.995;
const vFovFill = Math.min(minFov, fovMax);
aspect: number): number {

const projections = bearings
.map(b => this._spatial.projectToPlane(b, [1, 0, 0]))
.map(p => [p[1], -p[2]]);

const fovs = projections.map(p => Math.abs(Math.atan2(p[0], p[1])));
const vFovMin = fovs.length > 0 ? this._spatial.radToDeg(0.995 * 2 * Math.min(...fovs)) : 125;
const fovMin = this._fovToZoomedFov(vFovMin, zoom);
const fovMax = this._fovToZoomedFov(125, zoom);
console.log(zoom, vFovMin, fovMin, fovMax);
const vFovFill = Math.min(fovMin, fovMax);
if (renderMode === RenderMode.Fill) {
return vFovFill;
}
Expand All @@ -501,18 +492,18 @@ export class RenderCamera {
return vFov;
}

private _yToFov(y: number, zoom: number): number {
return 2 * Math.atan(y / Math.pow(2, zoom)) * 180 / Math.PI;
private _fovToZoomedFov(fov: number, zoom: number): number {
return fov / Math.pow(2, zoom);
}

private _zoomedFovToFov(zoomedFov: number, zoom: number): number {
return Math.pow(2, zoom) * zoomedFov;
}

private _focalToFov(focal: number): number {
return 2 * Math.atan2(1, 2 * focal) * 180 / Math.PI;
}

private _fovToY(fov: number, zoom: number): number {
return Math.pow(2, zoom) * Math.tan(Math.PI * fov / 360);
}

private _interpolateFov(v1: number, v2: number, alpha: number): number {
return alpha * v1 + (1 - alpha) * v2;
}
Expand Down

0 comments on commit 4c4693a

Please sign in to comment.