diff --git a/examples/debug/fisheye.html b/examples/debug/fisheye.html index 47be0f75..a6841c9a 100644 --- a/examples/debug/fisheye.html +++ b/examples/debug/fisheye.html @@ -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; diff --git a/src/component/bearing/BearingComponent.ts b/src/component/bearing/BearingComponent.ts index 115041a3..212ca65c 100644 --- a/src/component/bearing/BearingComponent.ts +++ b/src/component/bearing/BearingComponent.ts @@ -473,9 +473,9 @@ export class BearingComponent extends Component { 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; diff --git a/src/geo/Geo.ts b/src/geo/Geo.ts index 1ae63036..e6e1a300 100644 --- a/src/geo/Geo.ts +++ b/src/geo/Geo.ts @@ -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) { @@ -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)); diff --git a/src/render/RenderCamera.ts b/src/render/RenderCamera.ts index 3bfef717..813e3883 100644 --- a/src/render/RenderCamera.ts +++ b/src/render/RenderCamera.ts @@ -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; @@ -47,6 +47,8 @@ export class RenderCamera { private _currentProjectedPoints: number[][]; private _previousProjectedPoints: number[][]; + private _currentBearings: number[][]; + private _currentFov: number; private _previousFov: number; @@ -95,6 +97,8 @@ export class RenderCamera { this._currentProjectedPoints = []; this._previousProjectedPoints = []; + this._currentBearings = []; + this._currentFov = this._initialFov; this._previousFov = this._initialFov; @@ -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; @@ -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( @@ -247,6 +251,7 @@ export class RenderCamera { if (this._changed) { this._currentFov = this._computeCurrentFov(zoom); + this._computeCurrentBearingFov(zoom); this._previousFov = this._computePreviousFov(zoom); } @@ -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: @@ -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 { @@ -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 { @@ -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[][] { @@ -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(); @@ -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; } @@ -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; }