diff --git a/viewer/packages/camera-manager/src/Flexible/DampedSpherical.ts b/viewer/packages/camera-manager/src/Flexible/DampedSpherical.ts index 2886f3a2c85..c4ca5bcc07d 100644 --- a/viewer/packages/camera-manager/src/Flexible/DampedSpherical.ts +++ b/viewer/packages/camera-manager/src/Flexible/DampedSpherical.ts @@ -77,6 +77,28 @@ export class DampedSpherical { DampedSpherical.dampSphericalVectors(this.value, this.end, dampeningFactor); } + static correctPhi(value: Spherical): void { + // https://github.com/mrdoob/three.js/blob/master/src/math/Spherical.js + // When phi is outside the range (-Pi/2, Pi/2), correct it so it is inside the range by + // rotatation the theta Pi radians and flipping the phi. + + // First normalize so Phi is in the range (-Pi, Pi) + while (value.phi < -Math.PI) { + value.phi += 2 * Math.PI; + } + while (value.phi >= Math.PI) { + value.phi -= 2 * Math.PI; + } + // Flipping phi + if (value.phi < -Math.PI / 2) { + value.theta += Math.PI; + value.phi = -Math.PI - value.phi; + } else if (value.phi > Math.PI / 2) { + value.theta += Math.PI; + value.phi = Math.PI - value.phi; + } + } + static dampSphericalVectors(value: Spherical, end: Spherical, dampeningFactor: number): void { const deltaPhi = end.phi - value.phi; const deltaTheta = getShortestDeltaTheta(end.theta, value.theta); diff --git a/viewer/packages/camera-manager/src/Flexible/DampedVector3.ts b/viewer/packages/camera-manager/src/Flexible/DampedVector3.ts index 4181df9873c..729dac779e6 100644 --- a/viewer/packages/camera-manager/src/Flexible/DampedVector3.ts +++ b/viewer/packages/camera-manager/src/Flexible/DampedVector3.ts @@ -41,6 +41,10 @@ export class DampedVector3 { this.value.copy(this.end); } + synchronizeEnd(): void { + this.end.copy(this.value); + } + damp(dampeningFactor: number): void { this.value.lerp(this.end, dampeningFactor); } diff --git a/viewer/packages/camera-manager/src/Flexible/FlexibleControls.ts b/viewer/packages/camera-manager/src/Flexible/FlexibleControls.ts index f4b5db6f630..9efe15dca52 100644 --- a/viewer/packages/camera-manager/src/Flexible/FlexibleControls.ts +++ b/viewer/packages/camera-manager/src/Flexible/FlexibleControls.ts @@ -55,7 +55,6 @@ export class FlexibleControls extends EventDispatcher { private _scrollDistance = 0; // When using the wheel this is the distance to the picked point private _tempTarget: Vector3 | undefined = undefined; - private readonly _accumulatedMouseRotation: Vector2 = new Vector2(); private readonly _keyboard: Keyboard; private readonly _pointEventCache: Array = []; @@ -293,19 +292,12 @@ export class FlexibleControls extends EventDispatcher { if (!forceUpdate && !this.isEnabled) { return false; } - const isRotated = this._accumulatedMouseRotation.lengthSq() > 1; - if (isRotated) { - this.rotate(this._accumulatedMouseRotation); - this._accumulatedMouseRotation.set(0, 0); - } - const isKeyPressed = this.handleKeyboard(deltaTimeS); const epsilon = this._options.EPSILON; - const isChanged = - this._target.isChanged(epsilon) || - this._cameraVector.isChanged(epsilon) || - this._cameraPosition.isChanged(epsilon); - + const isKeyPressed = this.handleKeyboard(deltaTimeS); + const isRotated = this._cameraVector.isChanged(epsilon); + const isChanged = isRotated || this._target.isChanged(epsilon) || this._cameraPosition.isChanged(epsilon); const dampeningFactor = isKeyPressed ? 1 : this.getDampingFactor(deltaTimeS); + if (isChanged && dampeningFactor < 1) { this._cameraVector.damp(dampeningFactor); if (isRotated) { @@ -386,9 +378,6 @@ export class FlexibleControls extends EventDispatcher { private readonly onPointerUp = (event: PointerEvent) => { if (!this.isEnabled) return; switch (event.pointerType) { - case 'mouse': - this.onMouseUp(); - break; case 'touch': remove(this._pointEventCache, ev => ev.pointerId === event.pointerId); break; @@ -397,11 +386,6 @@ export class FlexibleControls extends EventDispatcher { } }; - private readonly onMouseUp = () => { - if (!this.isEnabled) return; - this._accumulatedMouseRotation.set(0, 0); - }; - private readonly onMouseWheel = (event: WheelEvent) => { if (!this.isEnabled) return; event.preventDefault(); @@ -489,11 +473,13 @@ export class FlexibleControls extends EventDispatcher { const position = getMousePosition(this._domElement, event.clientX, event.clientY); const deltaPosition = position.clone().sub(prevPosition); if (this._keyboard.isShiftPressed()) { - this.pan(0, 0, deltaPosition.y * this.options.mouseDollySpeed); + deltaPosition.multiplyScalar(this._options.mouseDollySpeed); + this.pan(0, 0, deltaPosition.y); } else if (this._keyboard.isCtrlPressed()) { - this.pan(deltaPosition.x * this.options.mousePanSpeed, deltaPosition.y * this.options.mousePanSpeed, 0); + deltaPosition.multiplyScalar(this._options.mousePanSpeed); + this.pan(deltaPosition.x, deltaPosition.y, 0); } else { - this._accumulatedMouseRotation.sub(deltaPosition); + this.rotate(deltaPosition); } prevPosition = position; }; @@ -516,8 +502,7 @@ export class FlexibleControls extends EventDispatcher { return; } const position = getMousePosition(this._domElement, event.clientX, event.clientY); - prevPosition.sub(position); - this.rotate(new Vector2().subVectors(prevPosition, position)); + this.rotate(new Vector2().subVectors(position, prevPosition)); prevPosition = position; }; @@ -549,10 +534,13 @@ export class FlexibleControls extends EventDispatcher { return; } let deltaAzimuthAngle = this._options.mouseRotationSpeedAzimuth * delta.x; - const deltaPolarAngle = this._options.mouseRotationSpeedPolar * delta.y; + let deltaPolarAngle = this._options.mouseRotationSpeedPolar * delta.y; + // It is more natural that the first persion rotate slower then the other modes if (this.controlsType == FlexibleControlsType.FirstPerson) { - deltaAzimuthAngle *= this.getAzimuthCompensationFactor(); + deltaAzimuthAngle *= 0.5; + deltaPolarAngle *= 0.5; } + deltaAzimuthAngle *= this.getAzimuthCompensationFactor(); this.rotateByAngles(deltaAzimuthAngle, deltaPolarAngle); } @@ -561,7 +549,7 @@ export class FlexibleControls extends EventDispatcher { // to make it feel more natural when looking straight up or straight down. const deviationFromEquator = Math.abs(this._cameraVector.end.phi - Math.PI / 2); const azimuthCompensationFactor = Math.sin(Math.PI / 2 - deviationFromEquator); - return azimuthCompensationFactor; + return 0.5 + 0.5 * azimuthCompensationFactor; } private rotateByAngles(deltaAzimuth: number, deltaPolar: number) { @@ -570,7 +558,7 @@ export class FlexibleControls extends EventDispatcher { } if (this.controlsType === FlexibleControlsType.OrbitInCenter) { - this.rawRotateByAngles(deltaAzimuth, -deltaPolar); + this.rawRotateByAngles(-deltaAzimuth, deltaPolar); // Adust the camera position by // CameraPosition = Target - DistanceToTarget * CameraVector @@ -583,7 +571,7 @@ export class FlexibleControls extends EventDispatcher { const oldOffset = this.newVector3().subVectors(this._target.end, this._cameraPosition.end); const oldCameraVectorEnd = this._cameraVector.end.clone(); - this.rawRotateByAngles(deltaAzimuth, -deltaPolar); + this.rawRotateByAngles(-deltaAzimuth, deltaPolar); // Adust the camera position so the target point is the same on the screen const oldQuat = new Quaternion().multiplyQuaternions( @@ -600,7 +588,7 @@ export class FlexibleControls extends EventDispatcher { const newCameraPosition = this.newVector3().subVectors(this._target.end, newOffset); this._cameraPosition.end.copy(newCameraPosition); } else { - this.rawRotateByAngles(deltaAzimuth, -deltaPolar); + this.rawRotateByAngles(-deltaAzimuth, deltaPolar); } } @@ -738,11 +726,11 @@ export class FlexibleControls extends EventDispatcher { } private dollyWithWheelScroll(pixelCoordinates: Vector2, deltaDistance: number) { - const translation = this.getRadiusAndTranslation(pixelCoordinates, deltaDistance); + const translation = this.getTranslation(pixelCoordinates, deltaDistance); this.translate(translation); } - private getRadiusAndTranslation(pixelCoordinates: Vector2, deltaDistance: number): Vector3 { + private getTranslation(pixelCoordinates: Vector2, deltaDistance: number): Vector3 { if (this.options.shouldPick) { return this.getTranslationByScrollCursor(pixelCoordinates, deltaDistance); } @@ -762,29 +750,33 @@ export class FlexibleControls extends EventDispatcher { if (this._scrollDirection === undefined) { return this.getTranslationByDirection(pixelCoordinates, deltaDistance); } - let step = this.options.zoomFraction * this._scrollDistance * Math.sign(deltaDistance); - if ( - this.options.realMouseWheelAction === FlexibleWheelZoomType.PastCursor && - Math.abs(step) < Math.abs(deltaDistance) - ) { - // If past or near the scroll cursor, go in equal steps - step = deltaDistance; - } - const prevScrollDistance = this._scrollDistance; + const step = this.getStep(deltaDistance); this._scrollDistance -= step; - if ( - this.options.realMouseWheelAction === FlexibleWheelZoomType.ToCursor && - this._scrollDistance < this.options.sensitivity - ) { - // This avoid to close to the scroll cursor - this._scrollDistance = this.options.sensitivity; - step = prevScrollDistance - this._scrollDistance; - } const translation = this._scrollDirection.clone(); + translation.multiplyScalar(step); return translation; } + private getStep(deltaDistance: number): number { + const step = this.options.zoomFraction * this._scrollDistance * Math.sign(deltaDistance); + if (this.options.realMouseWheelAction === FlexibleWheelZoomType.PastCursor) { + const minStep = Math.abs(deltaDistance * 0.5); + if (Math.abs(step) < minStep) { + // If past or near the scroll cursor, go in equal steps + return minStep * Math.sign(deltaDistance); + } + } + if (this.options.realMouseWheelAction === FlexibleWheelZoomType.ToCursor) { + if (this._scrollDistance - step < this.options.sensitivity) { + // This avoid to close to the scroll cursor + // Can not get closer than options.sensitivity + return Math.max(0, this._scrollDistance - this.options.sensitivity); + } + } + return step; + } + //================================================ // INSTANCE METHODS: Keyboard //================================================ @@ -821,7 +813,7 @@ export class FlexibleControls extends EventDispatcher { return false; } this.setControlsType(FlexibleControlsType.FirstPerson); - this.rotateByAngles(deltaAzimuthAngle, deltaPolarAngle); + this.rotateByAngles(-deltaAzimuthAngle, -deltaPolarAngle); return true; } diff --git a/viewer/packages/camera-manager/src/Flexible/FlexibleControlsOptions.ts b/viewer/packages/camera-manager/src/Flexible/FlexibleControlsOptions.ts index 81219dfedbb..a62cf38b2bb 100644 --- a/viewer/packages/camera-manager/src/Flexible/FlexibleControlsOptions.ts +++ b/viewer/packages/camera-manager/src/Flexible/FlexibleControlsOptions.ts @@ -8,7 +8,7 @@ import { FlexibleMouseActionType } from './FlexibleMouseActionType'; import { FlexibleWheelZoomType } from './FlexibleWheelZoomType'; const DEFAULT_POINTER_ROTATION_SPEED = (0.5 * Math.PI) / 360; // half degree per pixel -const DEFAULT_KEYBOARD_ROTATION_SPEED = DEFAULT_POINTER_ROTATION_SPEED * 5; +const DEFAULT_KEYBOARD_ROTATION_SPEED = DEFAULT_POINTER_ROTATION_SPEED * 2.5; const DEFAULT_MIN_POLAR_ANGLE = 0.0001; /** @@ -69,7 +69,7 @@ export class FlexibleControlsOptions { public maxOrthographicZoom = Infinity; // Mouse speed for dolly and pan - public wheelDollySpeed = 1; + public wheelDollySpeed = 0.5; public mousePanSpeed = 25; public mouseDollySpeed = 100;