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 2, 2024
1 parent bf4262f commit c074a6c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 95 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: 46 additions & 87 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 @@ -44,8 +44,8 @@ export class RenderCamera {

private _state: State;

private _currentProjectedPoints: number[][];
private _previousProjectedPoints: number[][];
private _currentBearings: number[][];
private _previousBearings: number[][];

private _currentFov: number;
private _previousFov: number;
Expand Down Expand Up @@ -92,8 +92,8 @@ export class RenderCamera {

this._state = null;

this._currentProjectedPoints = [];
this._previousProjectedPoints = [];
this._currentBearings = [];
this._previousBearings = [];

this._currentFov = this._initialFov;
this._previousFov = this._initialFov;
Expand Down Expand Up @@ -193,8 +193,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 @@ -206,7 +205,7 @@ export class RenderCamera {
if (currentImageId !== this._currentImageId || this._changed) {
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 All @@ -224,7 +223,7 @@ export class RenderCamera {
this._previousImageId = previousImageId;
this._previousSpherical =
isSpherical(state.previousTransform.cameraType);
this._previousProjectedPoints = this._computeProjectedPoints(state.previousTransform);
this._previousBearings = this._computeBearings(state.previousTransform);
this._previousCameraUp.copy(state.previousCamera.up);
this._previousTransformUp.copy(state.previousTransform.upVector());
this._previousTransformForward.copy(
Expand Down Expand Up @@ -264,8 +263,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,21 +348,6 @@ 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);

const upRad = Math.acos(new Vector2(upXz.x, upXz.z).normalize().x);
const refUpRad = Math.acos(new Vector2(refUpXz.x, refUpXz.z).normalize().x);

return refUpRad - upRad;
}

private _computeCurrentFov(zoom: number): number {
if (this._perspective.aspect === 0) {
return 0;
Expand All @@ -375,16 +358,12 @@ export class RenderCamera {
}

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

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

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

return Geo.computeProjectedPointsSafe(
return Geo.computeBearings(
transform,
vertices,
directions,
pointsPerLine,
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 +416,27 @@ 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 {_spatial} = this;

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

console.error(projections, bearings)

const fovs = projections.map(p => Math.abs(Math.atan2(p[0], p[1])));
const vFovMin = fovs.length > 0 ? _spatial.radToDeg(0.995 * 2 * Math.min(...fovs)) : 125;

console.error(fovs.map(f => _spatial.radToDeg(0.995 * 2 * f)));
const fovMin = this._fovToZoomedFov(vFovMin, zoom);
const fovMax = this._fovToZoomedFov(125, zoom);
const vFovFill = Math.min(fovMin, fovMax);
if (renderMode === RenderMode.Fill) {
return vFovFill;
}
Expand All @@ -501,18 +460,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
7 changes: 5 additions & 2 deletions test/render/RenderCamera.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,15 @@ describe("RenderCamera.perspective.fov", () => {

it("should increase when changing from fill to letterbox", () => {
const renderCamera = new RenderCamera(1, 1, RenderMode.Fill);
console.error(renderCamera.perspective.fov);
const frame = new FrameHelper().createFrame();
console.error(frame);
renderCamera.setFrame(new FrameHelper().createFrame());
const fov = renderCamera.perspective.fov;

renderCamera.setRenderMode(RenderMode.Letterbox);

expect(renderCamera.perspective.fov).toBeGreaterThan(fov);
expect(renderCamera.perspective.fov).toBeGreaterThanOrEqual(fov);
});

it("should decrease when increasing aspect", () => {
Expand All @@ -170,7 +173,7 @@ describe("RenderCamera.perspective.fov", () => {

renderCamera.setSize({ width: 5, height: 1 });

expect(renderCamera.perspective.fov).toBeLessThan(fov);
expect(renderCamera.perspective.fov).toBeLessThanOrEqual(fov);
});

it("should be constant when decreasing aspect", () => {
Expand Down

0 comments on commit c074a6c

Please sign in to comment.