From d012877e8a3ea4efc783a67ad8658fe526a229ca Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Thu, 21 Nov 2024 12:11:57 +0200 Subject: [PATCH 01/18] Updated the fly controller to use the z up basis --- packages/viewer-sandbox/src/main.ts | 5 +++-- .../modules/extensions/HybridCameraController.ts | 1 + .../src/modules/extensions/controls/FlyControls.ts | 14 +++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index b8e50f6775..445d5c8f9d 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -5,7 +5,8 @@ import { ViewerEvent, Viewer, CameraController, - ViewModes + ViewModes, + HybridCameraController } from '@speckle/viewer' import './style.css' @@ -43,7 +44,7 @@ const createViewer = async (containerName: string, stream: string) => { const viewer: Viewer = new Viewer(container, params) await viewer.init() - const cameraController = viewer.createExtension(CameraController) + const cameraController = viewer.createExtension(HybridCameraController) const selection = viewer.createExtension(SelectionExtension) const sections = viewer.createExtension(SectionTool) viewer.createExtension(SectionOutlines) diff --git a/packages/viewer/src/modules/extensions/HybridCameraController.ts b/packages/viewer/src/modules/extensions/HybridCameraController.ts index b8ebf500c9..914d039fa5 100644 --- a/packages/viewer/src/modules/extensions/HybridCameraController.ts +++ b/packages/viewer/src/modules/extensions/HybridCameraController.ts @@ -15,6 +15,7 @@ export class HybridCameraController extends CameraController { public constructor(viewer: IViewer) { super(viewer) document.addEventListener('keydown', this.onKeyDown.bind(this)) + document.addEventListener('keyup', this.onKeyUp.bind(this)) } protected onKeyDown(event: KeyboardEvent) { diff --git a/packages/viewer/src/modules/extensions/controls/FlyControls.ts b/packages/viewer/src/modules/extensions/controls/FlyControls.ts index cc4fa8ee81..5e630ae8c0 100644 --- a/packages/viewer/src/modules/extensions/controls/FlyControls.ts +++ b/packages/viewer/src/modules/extensions/controls/FlyControls.ts @@ -192,14 +192,14 @@ class FlyControls extends SpeckleControls { this.goalPosition.copy(pos) } - /** The input position and target will be in a basis with (0,1,0) as up */ + /** The input position and target will be in a basis with (0,0,1) as up */ public fromPositionAndTarget(position: Vector3, target: Vector3): void { const cPos = this.getPosition() const cTarget = this.getTarget() if (cPos.equals(position) && cTarget.equals(target)) return - const tPosition = new Vector3().copy(position).applyMatrix4(this._basisTransform) - const tTarget = new Vector3().copy(target).applyMatrix4(this._basisTransform) + const tPosition = new Vector3().copy(position) + const tTarget = new Vector3().copy(target) const matrix = new Matrix4() .lookAt(tPosition, tTarget, this._up) .premultiply(this._basisTransformInv) @@ -208,7 +208,7 @@ class FlyControls extends SpeckleControls { this.goalPosition.copy(tPosition) } - /** The returned vector needs to be in a basis with (0,1,0) as up */ + /** The returned vector needs to be in a basis with (0,0,1) as up */ public getTarget(): Vector3 { const target = new Vector3().copy(this.goalPosition) const matrix = new Matrix4().makeRotationFromEuler(this.goalEuler) @@ -217,12 +217,12 @@ class FlyControls extends SpeckleControls { .applyMatrix4(this._basisTransform) .normalize() target.addScaledVector(forward, -this.world.getRelativeOffset(0.2)) - return target.applyMatrix4(this._basisTransformInv) + return target } - /** The returned vector needs to be in a basis with (0,1,0) as up */ + /** The returned vector needs to be in a basis with (0,0,1) as up */ public getPosition(): Vector3 { - return new Vector3().copy(this.goalPosition).applyMatrix4(this._basisTransformInv) + return new Vector3().copy(this.goalPosition) } /** From e3a20bd9ce66391218f546899df7e10715aebabe Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Thu, 21 Nov 2024 13:03:23 +0200 Subject: [PATCH 02/18] Fixed very important compiler error --- packages/viewer-sandbox/src/main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 445d5c8f9d..a9c6157c30 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -4,7 +4,6 @@ import { SelectionEvent, ViewerEvent, Viewer, - CameraController, ViewModes, HybridCameraController } from '@speckle/viewer' From 83063b048dbc2c62ce27af76a93997868986b6d6 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Thu, 21 Nov 2024 17:30:42 +0200 Subject: [PATCH 03/18] Removed the annoying delay when first holding down WASD keys before movement started --- packages/viewer/src/modules/Viewer.ts | 2 +- .../modules/extensions/CameraController.ts | 4 ++-- .../extensions/controls/FlyControls.ts | 22 ++++++++++++------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index 47a3f2954a..c814436439 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -204,7 +204,7 @@ export class Viewer extends EventEmitter implements IViewer { } private update() { - const delta = this.clock.getDelta() + const delta = this.clock.getDelta() * 1000 // turn to miliseconds const extensions = Object.values(this.extensions) extensions.forEach((ext: Extension) => { ext.onEarlyUpdate(delta) diff --git a/packages/viewer/src/modules/extensions/CameraController.ts b/packages/viewer/src/modules/extensions/CameraController.ts index 2f7adf2f45..ed18eed5ed 100644 --- a/packages/viewer/src/modules/extensions/CameraController.ts +++ b/packages/viewer/src/modules/extensions/CameraController.ts @@ -295,8 +295,8 @@ export class CameraController extends Extension implements SpeckleCamera { this.emit(CameraEvent.Dynamic) } - public onEarlyUpdate() { - const changed = this._activeControls.update() + public onEarlyUpdate(_delta?: number) { + const changed = this._activeControls.update(_delta) if (changed !== this._lastCameraChanged) { this.emit(changed ? CameraEvent.Dynamic : CameraEvent.Stationary) } diff --git a/packages/viewer/src/modules/extensions/controls/FlyControls.ts b/packages/viewer/src/modules/extensions/controls/FlyControls.ts index 5e630ae8c0..ebe8ef0623 100644 --- a/packages/viewer/src/modules/extensions/controls/FlyControls.ts +++ b/packages/viewer/src/modules/extensions/controls/FlyControls.ts @@ -63,8 +63,6 @@ class FlyControls extends SpeckleControls { } public set enabled(value: boolean) { - if (value) this.connect() - else this.disconnect() this._enabled = value } @@ -109,6 +107,8 @@ class FlyControls extends SpeckleControls { this.container = container this.world = world this._options = Object.assign({}, options) + + this.connect() } public isStationary(): boolean { @@ -123,9 +123,12 @@ class FlyControls extends SpeckleControls { const now = performance.now() delta = delta !== undefined ? delta : now - this._lastTick this._lastTick = now - const deltaSeconds = delta / 1000 + if (!this._enabled) return false + + const deltaSeconds = delta / 1000 const scaledWalkingSpeed = this.world.getRelativeOffset(0.2) * walkingSpeed + if (this.keyMap.forward) this.velocity.z = -scaledWalkingSpeed * this._options.moveSpeed * deltaSeconds if (this.keyMap.back) @@ -145,9 +148,14 @@ class FlyControls extends SpeckleControls { if (!this.keyMap.down && !this.keyMap.up) this.velocity.y = 0 if (this.isStationary()) return false - this.moveBy(this.velocity) + this.updatePositionRotation(delta) + + return true + } + + protected updatePositionRotation(delta: number) { const diagonal = this.world.worldBox.min.distanceTo(this.world.worldBox.max) const minMaxRange = diagonal < 1 ? diagonal : 1 this.position.x = this.positionXDamper.update( @@ -175,12 +183,10 @@ class FlyControls extends SpeckleControls { this.rotate(this.euler) this._targetCamera.position.copy(this.position) - - return true } public jumpToGoal(): void { - this.update(SETTLING_TIME) + this.updatePositionRotation(SETTLING_TIME) } public fitToSphere(sphere: Sphere): void { @@ -292,7 +298,7 @@ class FlyControls extends SpeckleControls { // event listeners protected onMouseMove = (event: PointerEvent) => { - if (event.buttons !== 1) return + if (event.buttons !== 1 || !this._enabled) return const movementX = event.movementX || 0 const movementY = event.movementY || 0 From 59ae40d4aadbca9e6bdb062f5d050c0740b59f54 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 22 Nov 2024 17:15:55 +0200 Subject: [PATCH 04/18] Updated LegacyViewer to use the hybrid camera controls --- packages/viewer/src/modules/LegacyViewer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/viewer/src/modules/LegacyViewer.ts b/packages/viewer/src/modules/LegacyViewer.ts index c9c6b2f177..4af3f9a499 100644 --- a/packages/viewer/src/modules/LegacyViewer.ts +++ b/packages/viewer/src/modules/LegacyViewer.ts @@ -44,6 +44,7 @@ import { BatchObject } from './batching/BatchObject.js' import { SpeckleLoader } from './loaders/Speckle/SpeckleLoader.js' import Logger from './utils/Logger.js' import { ViewModes } from './extensions/ViewModes.js' +import { HybridCameraController } from './extensions/HybridCameraController.js' class LegacySelectionExtension extends SelectionExtension { /** FE2 'manually' selects objects pon it's own, so we're disabling the extension's event handler @@ -120,7 +121,7 @@ export class LegacyViewer extends Viewer { params: ViewerParams = DefaultViewerParams ) { super(container, params) - this.cameraController = this.createExtension(CameraController) + this.cameraController = this.createExtension(HybridCameraController) this.selection = this.createExtension(LegacySelectionExtension) this.sections = this.createExtension(SectionTool) this.createExtension(SectionOutlines) From d1da6a7ee606b350a3c5d2e903440b98d469dde1 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Tue, 26 Nov 2024 13:23:40 +0200 Subject: [PATCH 05/18] Added big baker --- packages/viewer-sandbox/src/main.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index a9c6157c30..a3f97cece6 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -450,6 +450,9 @@ const getStream = () => { // Perfectly flat // 'https://app.speckle.systems/projects/344f803f81/models/5582ab673e' + + // big baker + // 'https://latest.speckle.systems/projects/126cd4b7bb/models/032d09f716' ) } From 0ad6c4466f87b705245ae667a312b03a6f00dd66 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Wed, 11 Dec 2024 10:34:33 +0200 Subject: [PATCH 06/18] Trying to figure out the essence of this --- packages/viewer-sandbox/src/main.ts | 12 +- .../controls/SmoothOrbitControls.ts | 275 ++++++++++++++++-- 2 files changed, 265 insertions(+), 22 deletions(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index a3f97cece6..eda7e8e990 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -5,7 +5,8 @@ import { ViewerEvent, Viewer, ViewModes, - HybridCameraController + HybridCameraController, + CameraController } from '@speckle/viewer' import './style.css' @@ -44,7 +45,7 @@ const createViewer = async (containerName: string, stream: string) => { await viewer.init() const cameraController = viewer.createExtension(HybridCameraController) - const selection = viewer.createExtension(SelectionExtension) + // const selection = viewer.createExtension(SelectionExtension) const sections = viewer.createExtension(SectionTool) viewer.createExtension(SectionOutlines) const measurements = viewer.createExtension(MeasurementsExtension) @@ -56,7 +57,7 @@ const createViewer = async (containerName: string, stream: string) => { // const boxSelect = viewer.createExtension(BoxSelection) // const rotateCamera = viewer.createExtension(RotateCamera) cameraController // use it - selection // use it + // selection // use it sections // use it measurements // use it filtering // use it @@ -80,6 +81,7 @@ const createViewer = async (containerName: string, stream: string) => { Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize) Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin) sandbox.refresh() + viewer.getExtension(CameraController).setCameraView('front', false) }) viewer.on(ViewerEvent.UnloadComplete, () => { @@ -108,7 +110,7 @@ const getStream = () => { // prettier-ignore // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' // Revit sample house (good for bim-like stuff with many display meshes) - 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' + // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.systems/streams/c1faab5c62/commits/ab1a1ab2b6' // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.systems/streams/58b5648c4d/commits/60371ecb2d' @@ -182,7 +184,7 @@ const getStream = () => { // Alex cubes // 'https://latest.speckle.systems/streams/4658eb53b9/commits/d8ec9cccf7' // Alex more cubes - // 'https://latest.speckle.systems/streams/4658eb53b9/commits/31a8d5ff2b' + 'https://latest.speckle.systems/streams/4658eb53b9/commits/31a8d5ff2b' // Tekla // 'https://latest.speckle.systems/streams/caec6d6676/commits/588c731104' // Purple market square diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index ed254e6bbe..274d7f2c2b 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /* @license * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. @@ -24,7 +25,11 @@ import { OrthographicCamera, Quaternion, Euler, - Scene + Scene, + Mesh, + MeshBasicMaterial, + SphereGeometry, + Object3D } from 'three' import { Damper, SETTLING_TIME } from '../../utils/Damper.js' @@ -34,6 +39,8 @@ import { SpeckleControls } from './SpeckleControls.js' import { Intersections } from '../../Intersections.js' import { lerp } from 'three/src/math/MathUtils.js' import { computeOrthographicSize } from '../CameraController.js' +import { ObjectLayers } from '../../../IViewer.js' +import { multiply } from 'lodash-es' /** * @param {Number} value @@ -167,6 +174,13 @@ export class SmoothOrbitControls extends SpeckleControls { private world: World private intersections: Intersections + private orbitSphere: Mesh + private originSphere: Mesh + private pivotPoint: Vector3 = new Vector3() + private lastCameraPos: Vector3 + + private forceUpdate = false + public get enabled(): boolean { return this._enabled } @@ -206,8 +220,20 @@ export class SmoothOrbitControls extends SpeckleControls { this.scene = scene this._options = Object.assign({}, options) this.setDamperDecayTime(this._options.damperDecay) - this.scene - this.intersections + + this.orbitSphere = new Mesh( + new SphereGeometry(0.5, 32, 16), + new MeshBasicMaterial({ color: 0xff00000 }) + ) + this.orbitSphere.layers.set(ObjectLayers.OVERLAY) + this.scene.add(this.orbitSphere) + + this.originSphere = new Mesh( + new SphereGeometry(0.5, 32, 16), + new MeshBasicMaterial({ color: 0x00ff00 }) + ) + this.originSphere.layers.set(ObjectLayers.OVERLAY) + this.scene.add(this.originSphere) } /** @@ -285,7 +311,8 @@ export class SmoothOrbitControls extends SpeckleControls { this.goalSpherical.phi === this.spherical.phi && this.goalSpherical.radius === this.spherical.radius && this.goalLogFov === this.logFov && - this.goalOrigin.equals(this.origin) + this.goalOrigin.equals(this.origin) && + !this.forceUpdate ) } @@ -592,18 +619,206 @@ export class SmoothOrbitControls extends SpeckleControls { normalization ) this.origin.set(x, y, z) - + const v = new Vector3().set(x, y, z) + this.originSphere.position.copy(v.applyMatrix4(this._basisTransform)) this.moveCamera() + this.forceUpdate = false return true } + protected transformTo(outParentToTarget: Matrix4, parent: Matrix4, target: Matrix4) { + outParentToTarget.copy(parent) + outParentToTarget.invert() + outParentToTarget.multiply(target) + } + + protected rotateAboutPoint( + obj: Object3D, + point: Vector3, + axis: Vector3, + theta: number + ) { + obj.position.sub(point) // remove the offset + obj.position.applyAxisAngle(axis, theta) // rotate the POSITION + obj.position.add(point) // re-add the offset + + obj.rotateOnAxis(axis, theta) // rotate the OBJECT + } + + protected hereToThere = new Vector3() + protected hereToThereMat = new Matrix4() + protected lastPivot = new Vector3() + protected relativeCamPos = new Vector3() + + protected getPivotTransform(pivot: Vector3, quaternion: Quaternion) { + const translateToOrigin = new Matrix4().makeTranslation( + -pivot.x, + -pivot.y, + -pivot.z + ) + const translateBack = new Matrix4().makeTranslation(pivot.x, pivot.y, pivot.z) + + // Rotation matrix from quaternion + const rotationMatrix = new Matrix4().makeRotationFromQuaternion(quaternion) + + // Combine the matrices + return new Matrix4() + .multiply(translateBack) // Translate back to the pivot + .multiply(rotationMatrix) // Apply rotation + .multiply(translateToOrigin) // Translate pivot to origin + // .multiply(new Matrix4().makeScale(1, 1, this.spherical.radius)) + } + + protected change = false protected moveCamera() { + const pivotPoint = new Vector3() + .copy(this.pivotPoint) + .applyMatrix4(this._basisTransformInv) + const prevPivotPoint = new Vector3() + .copy(this.lastPivot) + .applyMatrix4(this._basisTransformInv) + const camPos = new Vector3() + .copy(this._targetCamera.position) + .applyMatrix4(this._basisTransformInv) + // const position = new Vector3(0, 50, this.spherical.radius) + // const quaternion = new Quaternion().setFromEuler( + // new Euler(0, this.spherical.theta, 0) + // ) + + // position.sub(pivotPoint) + // position.applyQuaternion(quaternion) + // position.add(pivotPoint) + + // this._targetCamera.position.copy(position) + // this._targetCamera.quaternion.copy(quaternion) + + // this._targetCamera.position.set(0, 50, this.spherical.radius) + // this._targetCamera.quaternion.identity() + // this._targetCamera.updateMatrixWorld(true) + + // this.rotateAboutPoint( + // this._targetCamera, + // pivotPoint, + // new Vector3(0, 1, 0), + // this.spherical.theta + // ) + + // // Step 1: Calculate the camera's position relative to the pivot using spherical coordinates + // const cameraOffset = new Vector3(0, 50, this.spherical.radius) // Camera offset (distance and height) + // const rotationMatrix = new Matrix4().makeRotationFromEuler( + // new Euler(0, this.spherical.theta, 0) + // ) // Apply yaw rotation + + // cameraOffset.sub(pivotPoint) + // cameraOffset.applyMatrix4(rotationMatrix) // Apply the rotation + // cameraOffset.add(pivotPoint) + // this._targetCamera.position.copy(cameraOffset) // Set the camera position relative to the pivot + + // // Step 2: Update the camera orientation so it faces away from the pivot point + // const direction = new Vector3() + // .subVectors(this._targetCamera.position, pivotPoint) + // .normalize() // Camera direction from pivot + // const up = new Vector3(0, 1, 0) // Keep the 'up' vector stable + // const right = new Vector3().crossVectors(up, direction).normalize() // Right vector (cross product of up and direction) + // const newUp = new Vector3().crossVectors(direction, right).normalize() // Recompute up vector based on new direction + + // // Step 3: Create a new quaternion based on the updated right, up, and direction vectors + // const rotationMatrixFinal = new Matrix4().makeBasis(right, newUp, direction) + // this._targetCamera.quaternion.setFromRotationMatrix(rotationMatrixFinal) + + // this._targetCamera.position.applyMatrix4(this._basisTransform) + // this._targetCamera.quaternion.premultiply( + // new Quaternion().setFromRotationMatrix(this._basisTransform) + // ) + // Derive the new camera position from the updated spherical: this.spherical.makeSafe() - const position = this.positionFromSpherical(this.spherical, this.origin) + + // // Compute direction vector: from camera to target (origin) + // const pos = new Vector3().copy(this._targetCamera.position) + // const direction = new Vector3().subVectors(pos, new Vector3(0, 0, 0)).normalize() + + // // Compute a "right" vector using cross product with world up (0, 1, 0) + // const worldUp = new Vector3(0, 1, 0) + // const right = new Vector3().crossVectors(worldUp, direction).normalize() + + // // Recompute the up vector to ensure orthogonality + // const up = new Vector3().crossVectors(direction, right).normalize() + + // // Construct the rotation matrix using the orthogonal basis vectors + // const rotationMatrix = new Matrix4().makeBasis(right, up, direction.negate()) + + // // Extract the quaternion from the rotation matrix + // const fakeQuaternion = new Quaternion().setFromRotationMatrix(rotationMatrix) + // // const sphericalPosition = this.positionFromSpherical(this.spherical, this.origin) + const quaternion = this.quaternionFromSpherical(this.spherical) + const position = new Vector3() + const tPivot = this.getPivotTransform(pivotPoint, quaternion) + const invTPivot = new Matrix4().copy(tPivot).invert() + + const deltaPivot = new Vector3().copy(pivotPoint).sub(prevPivotPoint) + + // const tPrevPivot = this.getPivotTransform(prevPivotPoint, quaternion) + // const tPrevPointInv = new Matrix4().copy(tPrevPivot).invert() + // const objectToPrevPiot = new Vector3().copy(camPos).sub(prevPivotPoint) + // const objectToNewPivot = new Vector3().copy(camPos).sub(pivotPoint) + // const offset = new Vector3().copy(objectToPrevPiot).sub(objectToNewPivot) + const delta = new Vector3() + if (deltaPivot.length() > 0) { + /** New pos */ + const newPos = new Vector3() + newPos.sub(pivotPoint) + newPos.applyQuaternion(quaternion) + newPos.add(pivotPoint) + + const dir = new Vector3().setFromMatrixColumn( + new Matrix4().makeRotationFromQuaternion(quaternion), + 2 + ) + dir.multiplyScalar(this.spherical.radius) + newPos.add(dir) + + /** Old Pos */ + const oldPos = new Vector3() + oldPos.sub(prevPivotPoint) + oldPos.applyQuaternion(quaternion) + oldPos.add(prevPivotPoint) + + const dir2 = new Vector3().setFromMatrixColumn( + new Matrix4().makeRotationFromQuaternion(quaternion), + 2 + ) + dir2.multiplyScalar(this.spherical.radius) + oldPos.add(dir2) + delta.copy(oldPos.sub(newPos)) + console.warn('Delta -> ', delta) + console.warn('Delta pivot -> ', deltaPivot) + } + position.copy(new Vector3(0, 0, 0)) + position.sub(pivotPoint) + position.applyQuaternion(quaternion) + position.add(pivotPoint) + + const dir = new Vector3().setFromMatrixColumn( + new Matrix4().makeRotationFromQuaternion(quaternion), + 2 + ) + dir.multiplyScalar(this.spherical.radius) + position.add(dir) + position.sub(delta) + + // } else { + // position.copy(this.positionFromSpherical(this.spherical, this.origin)) + // } + + position.applyQuaternion( + new Quaternion().setFromRotationMatrix(this._basisTransform) + ) + quaternion.premultiply(new Quaternion().setFromRotationMatrix(this._basisTransform)) + if (this._targetCamera instanceof OrthographicCamera) { const cameraDirection = new Vector3() .setFromSpherical(this.spherical) @@ -619,7 +834,6 @@ export class SmoothOrbitControls extends SpeckleControls { } this._targetCamera.position.copy(position) this._targetCamera.quaternion.copy(quaternion) - if (this._targetCamera instanceof PerspectiveCamera) if (this._targetCamera.fov !== Math.exp(this.logFov)) { this._targetCamera.fov = Math.exp(this.logFov) @@ -638,6 +852,8 @@ export class SmoothOrbitControls extends SpeckleControls { this._targetCamera.bottom = orthographicSize.y / -2 this._targetCamera.updateProjectionMatrix() } + + this.lastPivot.copy(this.pivotPoint) } /* Ortho height to distance functions @@ -654,9 +870,6 @@ export class SmoothOrbitControls extends SpeckleControls { position.setFromSpherical(spherical) if (origin) position.add(origin) - position.applyQuaternion( - new Quaternion().setFromRotationMatrix(this._basisTransform) - ) return position } @@ -666,7 +879,7 @@ export class SmoothOrbitControls extends SpeckleControls { quaternion.setFromEuler( new Euler(spherical.phi - Math.PI / 2, spherical.theta, 0, 'YXZ') ) - quaternion.premultiply(new Quaternion().setFromRotationMatrix(this._basisTransform)) + return quaternion } @@ -836,6 +1049,40 @@ export class SmoothOrbitControls extends SpeckleControls { } protected onPointerDown = (event: PointerEvent) => { + const x = + ((event.clientX - this._container.offsetLeft) / this._container.offsetWidth) * 2 - + 1 + + const y = + ((event.clientY - this._container.offsetTop) / this._container.offsetHeight) * + -2 + + 1 + const res = this.intersections.intersect( + this.scene, + this._targetCamera as PerspectiveCamera, + new Vector2(x, y), + ObjectLayers.STREAM_CONTENT_MESH, + true, + this.world.worldBox, + true, + false + ) + if (res) { + this.lastPivot.copy(this.pivotPoint) + this.pivotPoint.copy(res[0].point) + this.orbitSphere.position.copy(res[0].point) + // console.log('Radius -> ', this.spherical.radius) + // console.log('Distance -> ', this._targetCamera.position.distanceTo(res[0].point)) + // this.goalSpherical.radius += + // this.goalSpherical.radius - this._targetCamera.position.distanceTo(res[0].point) + + this.hereToThere + .copy(this._targetCamera.position) + .applyMatrix4(this._basisTransformInv) + this.forceUpdate = true + this.change = true + } + if (this.pointers.length > 2) { return } @@ -849,11 +1096,6 @@ export class SmoothOrbitControls extends SpeckleControls { this.startPointerPosition.clientY = event.clientY } - // try { - // this._container.setPointerCapture(event.pointerId) - // } catch (e) { - // e - // } this.pointers.push({ clientX: event.clientX, clientY: event.clientY, @@ -971,7 +1213,6 @@ export class SmoothOrbitControls extends SpeckleControls { (event.button === 2 || event.ctrlKey || event.metaKey || event.shiftKey) ) { this.initializePan() - // ;(this.scene.element as any)[$panElement].style.opacity = 1 } // this.element.style.cursor = 'grabbing' } From d3d6d5205b0018f340a2136d78da0a517e3b30fd Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Wed, 11 Dec 2024 23:51:30 +0200 Subject: [PATCH 07/18] Partly works --- .../controls/SmoothOrbitControls.ts | 64 +++++++------------ 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index 274d7f2c2b..d4fa0c0b4a 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -650,6 +650,9 @@ export class SmoothOrbitControls extends SpeckleControls { protected hereToThereMat = new Matrix4() protected lastPivot = new Vector3() protected relativeCamPos = new Vector3() + protected lastInitialPosition = new Vector3() + protected lastQuat = new Quaternion() + protected lastDelta = new Vector3() protected getPivotTransform(pivot: Vector3, quaternion: Quaternion) { const translateToOrigin = new Matrix4().makeTranslation( @@ -759,60 +762,36 @@ export class SmoothOrbitControls extends SpeckleControls { const tPivot = this.getPivotTransform(pivotPoint, quaternion) const invTPivot = new Matrix4().copy(tPivot).invert() - const deltaPivot = new Vector3().copy(pivotPoint).sub(prevPivotPoint) - + const deltaPivot = new Vector3().copy(prevPivotPoint).sub(pivotPoint) + // console.log('Pivot -> ', pivotPoint) // const tPrevPivot = this.getPivotTransform(prevPivotPoint, quaternion) // const tPrevPointInv = new Matrix4().copy(tPrevPivot).invert() // const objectToPrevPiot = new Vector3().copy(camPos).sub(prevPivotPoint) // const objectToNewPivot = new Vector3().copy(camPos).sub(pivotPoint) // const offset = new Vector3().copy(objectToPrevPiot).sub(objectToNewPivot) - const delta = new Vector3() + const dir = new Vector3().setFromMatrixColumn( + new Matrix4().makeRotationFromQuaternion(quaternion), + 2 + ) + dir.multiplyScalar(this.spherical.radius) + if (deltaPivot.length() > 0) { - /** New pos */ - const newPos = new Vector3() - newPos.sub(pivotPoint) - newPos.applyQuaternion(quaternion) - newPos.add(pivotPoint) - - const dir = new Vector3().setFromMatrixColumn( - new Matrix4().makeRotationFromQuaternion(quaternion), - 2 + this.lastDelta.copy(pivotPoint) + this.lastDelta.add( + new Vector3() + .copy(pivotPoint) + .negate() + .applyQuaternion(new Quaternion().copy(quaternion).invert()) ) - dir.multiplyScalar(this.spherical.radius) - newPos.add(dir) - - /** Old Pos */ - const oldPos = new Vector3() - oldPos.sub(prevPivotPoint) - oldPos.applyQuaternion(quaternion) - oldPos.add(prevPivotPoint) - - const dir2 = new Vector3().setFromMatrixColumn( - new Matrix4().makeRotationFromQuaternion(quaternion), - 2 - ) - dir2.multiplyScalar(this.spherical.radius) - oldPos.add(dir2) - delta.copy(oldPos.sub(newPos)) - console.warn('Delta -> ', delta) - console.warn('Delta pivot -> ', deltaPivot) + this.lastInitialPosition.copy(this.lastDelta) } - position.copy(new Vector3(0, 0, 0)) + + console.warn(this.lastInitialPosition) + position.copy(this.lastInitialPosition) position.sub(pivotPoint) position.applyQuaternion(quaternion) position.add(pivotPoint) - - const dir = new Vector3().setFromMatrixColumn( - new Matrix4().makeRotationFromQuaternion(quaternion), - 2 - ) - dir.multiplyScalar(this.spherical.radius) position.add(dir) - position.sub(delta) - - // } else { - // position.copy(this.positionFromSpherical(this.spherical, this.origin)) - // } position.applyQuaternion( new Quaternion().setFromRotationMatrix(this._basisTransform) @@ -834,6 +813,7 @@ export class SmoothOrbitControls extends SpeckleControls { } this._targetCamera.position.copy(position) this._targetCamera.quaternion.copy(quaternion) + this._targetCamera.updateMatrixWorld(true) if (this._targetCamera instanceof PerspectiveCamera) if (this._targetCamera.fov !== Math.exp(this.logFov)) { this._targetCamera.fov = Math.exp(this.logFov) From dabeefc824efc7288e35a2d799261fa77e4a6464 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Wed, 18 Dec 2024 14:16:58 +0200 Subject: [PATCH 08/18] Pivotal coordinates now work --- packages/viewer-sandbox/src/main.ts | 4 +- .../extensions/controls/PivotalControls.ts | 53 +++++ .../controls/SmoothOrbitControls.ts | 204 ++++++++---------- 3 files changed, 140 insertions(+), 121 deletions(-) create mode 100644 packages/viewer/src/modules/extensions/controls/PivotalControls.ts diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index eda7e8e990..1ec2f06a61 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -110,7 +110,7 @@ const getStream = () => { // prettier-ignore // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' // Revit sample house (good for bim-like stuff with many display meshes) - // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' + 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.systems/streams/c1faab5c62/commits/ab1a1ab2b6' // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.systems/streams/58b5648c4d/commits/60371ecb2d' @@ -184,7 +184,7 @@ const getStream = () => { // Alex cubes // 'https://latest.speckle.systems/streams/4658eb53b9/commits/d8ec9cccf7' // Alex more cubes - 'https://latest.speckle.systems/streams/4658eb53b9/commits/31a8d5ff2b' + // 'https://latest.speckle.systems/streams/4658eb53b9/commits/31a8d5ff2b' // Tekla // 'https://latest.speckle.systems/streams/caec6d6676/commits/588c731104' // Purple market square diff --git a/packages/viewer/src/modules/extensions/controls/PivotalControls.ts b/packages/viewer/src/modules/extensions/controls/PivotalControls.ts new file mode 100644 index 0000000000..5806ef33bb --- /dev/null +++ b/packages/viewer/src/modules/extensions/controls/PivotalControls.ts @@ -0,0 +1,53 @@ +// import { PerspectiveCamera, OrthographicCamera, Sphere, Vector3 } from 'three' +// import { SpeckleControls } from './SpeckleControls.js' + +// export interface PivotalControlsOptions {} + +// export class PivotalControls extends SpeckleControls { +// private _enabled: boolean = false +// private _options: Required = {} + +// get options(): Partial { +// return this._options +// } +// set options(value: Partial) { +// Object.assign(this._options, value) +// } + +// get enabled(): boolean { +// return this._enabled +// } +// set enabled(value: boolean) { +// this._enabled = value +// } + +// set targetCamera(target: PerspectiveCamera | OrthographicCamera) { +// throw new Error('Method not implemented.') +// } + +// isStationary(): boolean { +// throw new Error('Method not implemented.') +// } + +// update(delta?: number): boolean { +// throw new Error('Method not implemented.') +// } +// jumpToGoal(): void { +// throw new Error('Method not implemented.') +// } +// fitToSphere(sphere: Sphere): void { +// throw new Error('Method not implemented.') +// } +// dispose(): void { +// throw new Error('Method not implemented.') +// } +// fromPositionAndTarget(position: Vector3, target: Vector3): void { +// throw new Error('Method not implemented.') +// } +// getTarget(): Vector3 { +// throw new Error('Method not implemented.') +// } +// getPosition(): Vector3 { +// throw new Error('Method not implemented.') +// } +// } diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index d4fa0c0b4a..5556b596c9 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -40,7 +40,6 @@ import { Intersections } from '../../Intersections.js' import { lerp } from 'three/src/math/MathUtils.js' import { computeOrthographicSize } from '../CameraController.js' import { ObjectLayers } from '../../../IViewer.js' -import { multiply } from 'lodash-es' /** * @param {Number} value @@ -651,147 +650,110 @@ export class SmoothOrbitControls extends SpeckleControls { protected lastPivot = new Vector3() protected relativeCamPos = new Vector3() protected lastInitialPosition = new Vector3() - protected lastQuat = new Quaternion() + protected lastQuat: Quaternion = new Quaternion() protected lastDelta = new Vector3() - protected getPivotTransform(pivot: Vector3, quaternion: Quaternion) { - const translateToOrigin = new Matrix4().makeTranslation( - -pivot.x, - -pivot.y, - -pivot.z + protected polarFromPivotal() { + const quaternion = this.quaternionFromSpherical(this.spherical) + const dir = new Vector3().setFromMatrixColumn( + new Matrix4().makeRotationFromQuaternion(quaternion), + 2 ) - const translateBack = new Matrix4().makeTranslation(pivot.x, pivot.y, pivot.z) + const camPos = new Vector3() + .copy(this._targetCamera.position) + .applyMatrix4(this._basisTransformInv) - // Rotation matrix from quaternion - const rotationMatrix = new Matrix4().makeRotationFromQuaternion(quaternion) + const pivotPoint = new Vector3() + .copy(this.pivotPoint) + .applyMatrix4(this._basisTransformInv) - // Combine the matrices - return new Matrix4() - .multiply(translateBack) // Translate back to the pivot - .multiply(rotationMatrix) // Apply rotation - .multiply(translateToOrigin) // Translate pivot to origin - // .multiply(new Matrix4().makeScale(1, 1, this.spherical.radius)) + const cameraPivotDist = camPos.distanceTo(pivotPoint) + const cameraPivotDir = new Vector3().copy(camPos).sub(pivotPoint) + cameraPivotDir.normalize() + + const dot = Math.min(Math.max(dir.dot(cameraPivotDir), -1), 1) + const angle = Math.acos(dot) + const polarRadius = cameraPivotDist //cameraPivotDist * Math.cos(angle) + const polarOrigin = new Vector3().copy(dir).multiplyScalar(polarRadius) + this.setTarget(polarOrigin.x, polarOrigin.y, polarOrigin.z) + this.setRadius(polarRadius) + this.jumpToGoal() } - protected change = false - protected moveCamera() { + protected positionFromPivotal(origin: Vector3, quaternion: Quaternion) { const pivotPoint = new Vector3() .copy(this.pivotPoint) .applyMatrix4(this._basisTransformInv) - const prevPivotPoint = new Vector3() - .copy(this.lastPivot) - .applyMatrix4(this._basisTransformInv) - const camPos = new Vector3() - .copy(this._targetCamera.position) - .applyMatrix4(this._basisTransformInv) - // const position = new Vector3(0, 50, this.spherical.radius) - // const quaternion = new Quaternion().setFromEuler( - // new Euler(0, this.spherical.theta, 0) - // ) - // position.sub(pivotPoint) - // position.applyQuaternion(quaternion) - // position.add(pivotPoint) + const dir = new Vector3().setFromMatrixColumn( + new Matrix4().makeRotationFromQuaternion(quaternion), + 2 + ) + dir.multiplyScalar(this.spherical.radius) - // this._targetCamera.position.copy(position) - // this._targetCamera.quaternion.copy(quaternion) + const position = new Vector3() + position.copy(origin) - // this._targetCamera.position.set(0, 50, this.spherical.radius) - // this._targetCamera.quaternion.identity() - // this._targetCamera.updateMatrixWorld(true) + position.sub(pivotPoint) + position.applyQuaternion(quaternion) + position.add(pivotPoint) + position.add(dir) - // this.rotateAboutPoint( - // this._targetCamera, - // pivotPoint, - // new Vector3(0, 1, 0), - // this.spherical.theta - // ) + return position + } - // // Step 1: Calculate the camera's position relative to the pivot using spherical coordinates - // const cameraOffset = new Vector3(0, 50, this.spherical.radius) // Camera offset (distance and height) - // const rotationMatrix = new Matrix4().makeRotationFromEuler( - // new Euler(0, this.spherical.theta, 0) - // ) // Apply yaw rotation - - // cameraOffset.sub(pivotPoint) - // cameraOffset.applyMatrix4(rotationMatrix) // Apply the rotation - // cameraOffset.add(pivotPoint) - // this._targetCamera.position.copy(cameraOffset) // Set the camera position relative to the pivot - - // // Step 2: Update the camera orientation so it faces away from the pivot point - // const direction = new Vector3() - // .subVectors(this._targetCamera.position, pivotPoint) - // .normalize() // Camera direction from pivot - // const up = new Vector3(0, 1, 0) // Keep the 'up' vector stable - // const right = new Vector3().crossVectors(up, direction).normalize() // Right vector (cross product of up and direction) - // const newUp = new Vector3().crossVectors(direction, right).normalize() // Recompute up vector based on new direction - - // // Step 3: Create a new quaternion based on the updated right, up, and direction vectors - // const rotationMatrixFinal = new Matrix4().makeBasis(right, newUp, direction) - // this._targetCamera.quaternion.setFromRotationMatrix(rotationMatrixFinal) - - // this._targetCamera.position.applyMatrix4(this._basisTransform) - // this._targetCamera.quaternion.premultiply( - // new Quaternion().setFromRotationMatrix(this._basisTransform) - // ) + protected isPivotal = false + protected pivotalOrigin: Vector3 = new Vector3() - // Derive the new camera position from the updated spherical: + protected change = false + protected moveCamera() { this.spherical.makeSafe() - // // Compute direction vector: from camera to target (origin) - // const pos = new Vector3().copy(this._targetCamera.position) - // const direction = new Vector3().subVectors(pos, new Vector3(0, 0, 0)).normalize() - - // // Compute a "right" vector using cross product with world up (0, 1, 0) - // const worldUp = new Vector3(0, 1, 0) - // const right = new Vector3().crossVectors(worldUp, direction).normalize() - - // // Recompute the up vector to ensure orthogonality - // const up = new Vector3().crossVectors(direction, right).normalize() - - // // Construct the rotation matrix using the orthogonal basis vectors - // const rotationMatrix = new Matrix4().makeBasis(right, up, direction.negate()) - - // // Extract the quaternion from the rotation matrix - // const fakeQuaternion = new Quaternion().setFromRotationMatrix(rotationMatrix) - // // const sphericalPosition = this.positionFromSpherical(this.spherical, this.origin) + const pivotPoint = new Vector3() + .copy(this.pivotPoint) + .applyMatrix4(this._basisTransformInv) + const prevPivotPoint = new Vector3() + .copy(this.lastPivot) + .applyMatrix4(this._basisTransformInv) const quaternion = this.quaternionFromSpherical(this.spherical) - const position = new Vector3() - const tPivot = this.getPivotTransform(pivotPoint, quaternion) - const invTPivot = new Matrix4().copy(tPivot).invert() - - const deltaPivot = new Vector3().copy(prevPivotPoint).sub(pivotPoint) - // console.log('Pivot -> ', pivotPoint) - // const tPrevPivot = this.getPivotTransform(prevPivotPoint, quaternion) - // const tPrevPointInv = new Matrix4().copy(tPrevPivot).invert() - // const objectToPrevPiot = new Vector3().copy(camPos).sub(prevPivotPoint) - // const objectToNewPivot = new Vector3().copy(camPos).sub(pivotPoint) - // const offset = new Vector3().copy(objectToPrevPiot).sub(objectToNewPivot) - const dir = new Vector3().setFromMatrixColumn( - new Matrix4().makeRotationFromQuaternion(quaternion), - 2 - ) - dir.multiplyScalar(this.spherical.radius) + const prevQuaternion = new Quaternion() + .copy(this.lastQuat) + .premultiply(new Quaternion().setFromRotationMatrix(this._basisTransformInv)) + const deltaPivot = prevPivotPoint.sub(pivotPoint) + let deltaQuat = prevQuaternion.angleTo(quaternion) + + if (deltaQuat === 2.3948158271083724) deltaQuat = 0 if (deltaPivot.length() > 0) { - this.lastDelta.copy(pivotPoint) - this.lastDelta.add( - new Vector3() - .copy(pivotPoint) - .negate() - .applyQuaternion(new Quaternion().copy(quaternion).invert()) + const camPos = new Vector3() + .copy(this._targetCamera.position) + .applyMatrix4(this._basisTransformInv) + const dir = new Vector3().setFromMatrixColumn( + new Matrix4().makeRotationFromQuaternion(quaternion), + 2 ) - this.lastInitialPosition.copy(this.lastDelta) + dir.multiplyScalar(this.spherical.radius) + + camPos.sub(dir) + camPos.sub(pivotPoint) + camPos.applyQuaternion(new Quaternion().copy(quaternion).invert()) + camPos.add(pivotPoint) + this.pivotalOrigin.copy(camPos) } - console.warn(this.lastInitialPosition) - position.copy(this.lastInitialPosition) - position.sub(pivotPoint) - position.applyQuaternion(quaternion) - position.add(pivotPoint) - position.add(dir) + if (deltaQuat > 0.00001) { + // pivotalOrigin.add(pivotPoint) + // pivotalOrigin.add( + // new Vector3() + // .copy(pivotPoint) + // .negate() + // .applyQuaternion(new Quaternion().copy(quaternion).invert()) + // ) + } + + const position = this.positionFromPivotal(this.pivotalOrigin, quaternion) position.applyQuaternion( new Quaternion().setFromRotationMatrix(this._basisTransform) @@ -834,6 +796,7 @@ export class SmoothOrbitControls extends SpeckleControls { } this.lastPivot.copy(this.pivotPoint) + this.lastQuat.copy(quaternion) } /* Ortho height to distance functions @@ -1043,9 +1006,7 @@ export class SmoothOrbitControls extends SpeckleControls { new Vector2(x, y), ObjectLayers.STREAM_CONTENT_MESH, true, - this.world.worldBox, - true, - false + this.world.worldBox ) if (res) { this.lastPivot.copy(this.pivotPoint) @@ -1061,6 +1022,7 @@ export class SmoothOrbitControls extends SpeckleControls { .applyMatrix4(this._basisTransformInv) this.forceUpdate = true this.change = true + this.isPivotal = true } if (this.pointers.length > 2) { @@ -1157,6 +1119,10 @@ export class SmoothOrbitControls extends SpeckleControls { if (this.isUserPointing) { this.emit(PointerChangeEvent.PointerChangeEnd) } + // this.polarFromPivotal() + // this.isPivotal = false + // console.log('Pivotal -> ', this.positionFromPivotal()) + // console.log('Polar -> ', this.positionFromSpherical(this.spherical, this.origin)) } protected onTouchChange(event: PointerEvent) { From fbab733b21204cfa9f3a38d0087235ab416f4072 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Thu, 19 Dec 2024 22:13:40 +0200 Subject: [PATCH 09/18] Smoothened out the math abit --- .../modules/extensions/CameraController.ts | 1 + .../controls/SmoothOrbitControls.ts | 148 ++++++------------ 2 files changed, 53 insertions(+), 96 deletions(-) diff --git a/packages/viewer/src/modules/extensions/CameraController.ts b/packages/viewer/src/modules/extensions/CameraController.ts index ed18eed5ed..87a3231695 100644 --- a/packages/viewer/src/modules/extensions/CameraController.ts +++ b/packages/viewer/src/modules/extensions/CameraController.ts @@ -94,6 +94,7 @@ export const DefaultOrbitControlsOptions: Required = { touchAction: 'none', infiniteZoom: true, zoomToCursor: true, + orbitAroundCursor: true, lookSpeed: 1, moveSpeed: 1, damperDecay: 30, diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index 5556b596c9..309bd064e4 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ /* @license * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. @@ -28,8 +27,7 @@ import { Scene, Mesh, MeshBasicMaterial, - SphereGeometry, - Object3D + SphereGeometry } from 'three' import { Damper, SETTLING_TIME } from '../../utils/Damper.js' @@ -99,6 +97,8 @@ export interface SmoothOrbitControlsOptions { infiniteZoom?: boolean // Zoom to cursor zoomToCursor?: boolean + // Orbit around cursor + orbitAroundCursor?: boolean // Dampening damperDecay?: number } @@ -143,6 +143,7 @@ export class SmoothOrbitControls extends SpeckleControls { public spherical = new Spherical() private goalSpherical = new Spherical() private origin = new Vector3() + private pivotalOrigin: Vector3 = new Vector3() private goalOrigin = new Vector3() private targetDamperX = new Damper() private targetDamperY = new Damper() @@ -176,7 +177,7 @@ export class SmoothOrbitControls extends SpeckleControls { private orbitSphere: Mesh private originSphere: Mesh private pivotPoint: Vector3 = new Vector3() - private lastCameraPos: Vector3 + private lastPivot: Vector3 = new Vector3() private forceUpdate = false @@ -294,7 +295,9 @@ export class SmoothOrbitControls extends SpeckleControls { * Gets the current goal position */ public getPosition(): Vector3 { - return this.positionFromSpherical(this.goalSpherical, this.goalOrigin) + return this.positionFromSpherical(this.goalSpherical, this.goalOrigin).applyMatrix4( + this._basisTransform + ) } /** @@ -626,33 +629,6 @@ export class SmoothOrbitControls extends SpeckleControls { return true } - protected transformTo(outParentToTarget: Matrix4, parent: Matrix4, target: Matrix4) { - outParentToTarget.copy(parent) - outParentToTarget.invert() - outParentToTarget.multiply(target) - } - - protected rotateAboutPoint( - obj: Object3D, - point: Vector3, - axis: Vector3, - theta: number - ) { - obj.position.sub(point) // remove the offset - obj.position.applyAxisAngle(axis, theta) // rotate the POSITION - obj.position.add(point) // re-add the offset - - obj.rotateOnAxis(axis, theta) // rotate the OBJECT - } - - protected hereToThere = new Vector3() - protected hereToThereMat = new Matrix4() - protected lastPivot = new Vector3() - protected relativeCamPos = new Vector3() - protected lastInitialPosition = new Vector3() - protected lastQuat: Quaternion = new Quaternion() - protected lastDelta = new Vector3() - protected polarFromPivotal() { const quaternion = this.quaternionFromSpherical(this.spherical) const dir = new Vector3().setFromMatrixColumn( @@ -671,41 +647,52 @@ export class SmoothOrbitControls extends SpeckleControls { const cameraPivotDir = new Vector3().copy(camPos).sub(pivotPoint) cameraPivotDir.normalize() - const dot = Math.min(Math.max(dir.dot(cameraPivotDir), -1), 1) - const angle = Math.acos(dot) - const polarRadius = cameraPivotDist //cameraPivotDist * Math.cos(angle) - const polarOrigin = new Vector3().copy(dir).multiplyScalar(polarRadius) - this.setTarget(polarOrigin.x, polarOrigin.y, polarOrigin.z) - this.setRadius(polarRadius) - this.jumpToGoal() + // const dot = Math.min(Math.max(dir.dot(cameraPivotDir), -1), 1) + // const angle = Math.acos(dot) + // console.log('Angle -> ', angle) + const polarRadius = cameraPivotDist // * Math.cos(angle) + const polarOrigin = camPos.sub(new Vector3().copy(dir).multiplyScalar(polarRadius)) + + this.goalOrigin.copy(polarOrigin) + this.origin.copy(polarOrigin) + this.goalSpherical.radius = polarRadius + this.spherical.radius = polarRadius + + this.originSphere.position.copy(polarOrigin) + // console.log('Origin -> ', polarOrigin) + // console.log('Dist -> ', cameraPivotDist, ' Radius -> ', polarRadius) } + /** Function expects the origin in a CS where Y is up */ protected positionFromPivotal(origin: Vector3, quaternion: Quaternion) { const pivotPoint = new Vector3() .copy(this.pivotPoint) .applyMatrix4(this._basisTransformInv) - const dir = new Vector3().setFromMatrixColumn( - new Matrix4().makeRotationFromQuaternion(quaternion), - 2 - ) - dir.multiplyScalar(this.spherical.radius) - const position = new Vector3() position.copy(origin) position.sub(pivotPoint) position.applyQuaternion(quaternion) position.add(pivotPoint) - position.add(dir) return position } - protected isPivotal = false - protected pivotalOrigin: Vector3 = new Vector3() + /** Function expects the pivotPoint in a CS where Y is up */ + protected getPivotalOrigin(pivotPoint: Vector3, quaternion: Quaternion) { + const pivotalOrigin = new Vector3() + .copy(this._targetCamera.position) + .applyMatrix4(this._basisTransformInv) + + pivotalOrigin.sub(pivotPoint) + pivotalOrigin.applyQuaternion(new Quaternion().copy(quaternion).invert()) + pivotalOrigin.add(pivotPoint) - protected change = false + return pivotalOrigin + } + + protected usePivotal = false protected moveCamera() { this.spherical.makeSafe() @@ -716,44 +703,24 @@ export class SmoothOrbitControls extends SpeckleControls { .copy(this.lastPivot) .applyMatrix4(this._basisTransformInv) - const quaternion = this.quaternionFromSpherical(this.spherical) - - const prevQuaternion = new Quaternion() - .copy(this.lastQuat) - .premultiply(new Quaternion().setFromRotationMatrix(this._basisTransformInv)) const deltaPivot = prevPivotPoint.sub(pivotPoint) - let deltaQuat = prevQuaternion.angleTo(quaternion) - - if (deltaQuat === 2.3948158271083724) deltaQuat = 0 + const quaternion = this.quaternionFromSpherical(this.spherical) if (deltaPivot.length() > 0) { - const camPos = new Vector3() - .copy(this._targetCamera.position) - .applyMatrix4(this._basisTransformInv) - const dir = new Vector3().setFromMatrixColumn( - new Matrix4().makeRotationFromQuaternion(quaternion), - 2 - ) - dir.multiplyScalar(this.spherical.radius) - - camPos.sub(dir) - camPos.sub(pivotPoint) - camPos.applyQuaternion(new Quaternion().copy(quaternion).invert()) - camPos.add(pivotPoint) - this.pivotalOrigin.copy(camPos) + this.pivotalOrigin.copy(this.getPivotalOrigin(pivotPoint, quaternion)) } - if (deltaQuat > 0.00001) { - // pivotalOrigin.add(pivotPoint) - // pivotalOrigin.add( - // new Vector3() - // .copy(pivotPoint) - // .negate() - // .applyQuaternion(new Quaternion().copy(quaternion).invert()) + let position + if (this.usePivotal) { + position = this.positionFromPivotal(this.pivotalOrigin, quaternion) + this.polarFromPivotal() + // position = this.positionFromSpherical(this.spherical, this.origin) + // console.log( + // 'Pivotal -> ', + // this.positionFromPivotal(this.pivotalOrigin, quaternion) // ) - } - - const position = this.positionFromPivotal(this.pivotalOrigin, quaternion) + // console.log('Polar -> ', this.positionFromSpherical(this.spherical, this.origin)) + } else position = this.positionFromSpherical(this.spherical, this.origin) position.applyQuaternion( new Quaternion().setFromRotationMatrix(this._basisTransform) @@ -796,7 +763,6 @@ export class SmoothOrbitControls extends SpeckleControls { } this.lastPivot.copy(this.pivotPoint) - this.lastQuat.copy(quaternion) } /* Ortho height to distance functions @@ -989,6 +955,7 @@ export class SmoothOrbitControls extends SpeckleControls { const target = this.getTarget().applyMatrix4(this._basisTransformInv) target.add(dxy.applyMatrix3(this.panProjection)) this.setTarget(target.x, target.y, target.z) + this.usePivotal = false } protected onPointerDown = (event: PointerEvent) => { @@ -1011,18 +978,10 @@ export class SmoothOrbitControls extends SpeckleControls { if (res) { this.lastPivot.copy(this.pivotPoint) this.pivotPoint.copy(res[0].point) + this.polarFromPivotal() this.orbitSphere.position.copy(res[0].point) - // console.log('Radius -> ', this.spherical.radius) - // console.log('Distance -> ', this._targetCamera.position.distanceTo(res[0].point)) - // this.goalSpherical.radius += - // this.goalSpherical.radius - this._targetCamera.position.distanceTo(res[0].point) - - this.hereToThere - .copy(this._targetCamera.position) - .applyMatrix4(this._basisTransformInv) this.forceUpdate = true - this.change = true - this.isPivotal = true + this.usePivotal = this._options.orbitAroundCursor && true } if (this.pointers.length > 2) { @@ -1119,10 +1078,7 @@ export class SmoothOrbitControls extends SpeckleControls { if (this.isUserPointing) { this.emit(PointerChangeEvent.PointerChangeEnd) } - // this.polarFromPivotal() - // this.isPivotal = false - // console.log('Pivotal -> ', this.positionFromPivotal()) - // console.log('Polar -> ', this.positionFromSpherical(this.spherical, this.origin)) + this.usePivotal = false } protected onTouchChange(event: PointerEvent) { From 8fab92e472db501853c6837f8c95ab721685ebe7 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Thu, 19 Dec 2024 23:35:26 +0200 Subject: [PATCH 10/18] Fixed sandbox error --- packages/viewer-sandbox/src/main.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 48257dc911..da35d00e35 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -6,7 +6,6 @@ import { Viewer, ViewModes, HybridCameraController, - CameraController, SelectionExtension } from '@speckle/viewer' @@ -21,9 +20,8 @@ import { import { SectionTool } from '@speckle/viewer' import { SectionOutlines } from '@speckle/viewer' import { ViewModesKeys } from './Extensions/ViewModesKeys' -import { JSONSpeckleStream } from './JSONSpeckleStream' +// import { JSONSpeckleStream } from './JSONSpeckleStream' import { BoxSelection } from './Extensions/BoxSelection' -import { ExtendedSelection } from './Extensions/ExtendedSelection' const createViewer = async (containerName: string, _stream: string) => { const container = document.querySelector(containerName) From 28fff50fb4cfb57b91ffd36fafb475e5f87bae8b Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 20 Dec 2024 12:04:30 +0200 Subject: [PATCH 11/18] Enabled the pivot sphere --- .../src/modules/extensions/controls/SmoothOrbitControls.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index 821241c9d2..dbbfc68401 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -223,10 +223,11 @@ export class SmoothOrbitControls extends SpeckleControls { this.orbitSphere = new Mesh( new SphereGeometry(0.5, 32, 16), - new MeshBasicMaterial({ color: 0xff00000 }) + new MeshBasicMaterial({ color: 0x43af11 }) ) this.orbitSphere.layers.set(ObjectLayers.OVERLAY) - // this.scene.add(this.orbitSphere) + this.orbitSphere.visible = false + this.scene.add(this.orbitSphere) this.originSphere = new Mesh( new SphereGeometry(0.5, 32, 16), @@ -982,6 +983,7 @@ export class SmoothOrbitControls extends SpeckleControls { this.orbitSphere.position.copy(res[0].point) this.forceUpdate = true this.usePivotal = this._options.orbitAroundCursor && true + this.orbitSphere.visible = true } if (this.pointers.length > 2) { @@ -1079,6 +1081,7 @@ export class SmoothOrbitControls extends SpeckleControls { this.emit(PointerChangeEvent.PointerChangeEnd) } this.usePivotal = false + this.orbitSphere.visible = false } protected onTouchChange(event: PointerEvent) { From 3ded61feb58801b8c1a0c0aa40efa2a7c34152a6 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Thu, 9 Jan 2025 22:33:04 +0200 Subject: [PATCH 12/18] feat(viewer-lib): Fixed some issues with orbiting around cursor --- packages/viewer-sandbox/src/main.ts | 4 +- .../modules/extensions/CameraController.ts | 1 + .../controls/SmoothOrbitControls.ts | 158 +++++++++--------- .../modules/materials/SpeckleBasicMaterial.ts | 1 + 4 files changed, 86 insertions(+), 78 deletions(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index a687096d02..1022615ba3 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -112,7 +112,7 @@ const getStream = () => { // prettier-ignore // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' // Revit sample house (good for bim-like stuff with many display meshes) - // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' + 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.systems/streams/c1faab5c62/commits/ab1a1ab2b6' // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.systems/streams/58b5648c4d/commits/60371ecb2d' @@ -462,7 +462,7 @@ const getStream = () => { // 'https://speckle.xyz/streams/27e89d0ad6/commits/5ed4b74252' //Gingerbread - 'https://latest.speckle.systems/projects/387050bffe/models/48f7eb26fb' + // 'https://latest.speckle.systems/projects/387050bffe/models/48f7eb26fb' // DUI3 Mesh Colors // 'https://app.speckle.systems/projects/93200a735d/models/cbacd3eaeb@344a397239' diff --git a/packages/viewer/src/modules/extensions/CameraController.ts b/packages/viewer/src/modules/extensions/CameraController.ts index 1cf97813d0..92b066edc2 100644 --- a/packages/viewer/src/modules/extensions/CameraController.ts +++ b/packages/viewer/src/modules/extensions/CameraController.ts @@ -95,6 +95,7 @@ export const DefaultOrbitControlsOptions: Required = { infiniteZoom: true, zoomToCursor: true, orbitAroundCursor: true, + showOrbitPoint: true, lookSpeed: 1, moveSpeed: 1, damperDecay: 30, diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index dbbfc68401..14c5862306 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -26,7 +26,6 @@ import { Euler, Scene, Mesh, - MeshBasicMaterial, SphereGeometry } from 'three' @@ -38,6 +37,7 @@ import { Intersections } from '../../Intersections.js' import { lerp } from 'three/src/math/MathUtils.js' import { computeOrthographicSize } from '../CameraController.js' import { ObjectLayers } from '../../../IViewer.js' +import SpeckleBasicMaterial from '../../materials/SpeckleBasicMaterial.js' /** * @param {Number} value @@ -99,6 +99,8 @@ export interface SmoothOrbitControlsOptions { zoomToCursor?: boolean // Orbit around cursor orbitAroundCursor?: boolean + // Show orbit point + showOrbitPoint?: boolean // Dampening damperDecay?: number } @@ -175,11 +177,8 @@ export class SmoothOrbitControls extends SpeckleControls { private intersections: Intersections private orbitSphere: Mesh - private originSphere: Mesh private pivotPoint: Vector3 = new Vector3() - private lastPivot: Vector3 = new Vector3() - - private forceUpdate = false + private lastPivotPoint: Vector3 = new Vector3() public get enabled(): boolean { return this._enabled @@ -221,20 +220,20 @@ export class SmoothOrbitControls extends SpeckleControls { this._options = Object.assign({}, options) this.setDamperDecayTime(this._options.damperDecay) - this.orbitSphere = new Mesh( - new SphereGeometry(0.5, 32, 16), - new MeshBasicMaterial({ color: 0x43af11 }) - ) + const billboardMaterial = new SpeckleBasicMaterial({ color: 0x43af11 }, [ + 'BILLBOARD_FIXED' + ]) + billboardMaterial.opacity = 0.75 + billboardMaterial.transparent = true + billboardMaterial.color.convertSRGBToLinear() + billboardMaterial.toneMapped = false + billboardMaterial.depthTest = false + billboardMaterial.billboardPixelHeight = 15 * window.devicePixelRatio + + this.orbitSphere = new Mesh(new SphereGeometry(0.5, 32, 16), billboardMaterial) this.orbitSphere.layers.set(ObjectLayers.OVERLAY) this.orbitSphere.visible = false this.scene.add(this.orbitSphere) - - this.originSphere = new Mesh( - new SphereGeometry(0.5, 32, 16), - new MeshBasicMaterial({ color: 0x00ff00 }) - ) - this.originSphere.layers.set(ObjectLayers.OVERLAY) - // this.scene.add(this.originSphere) } /** @@ -315,7 +314,7 @@ export class SmoothOrbitControls extends SpeckleControls { this.goalSpherical.radius === this.spherical.radius && this.goalLogFov === this.logFov && this.goalOrigin.equals(this.origin) && - !this.forceUpdate + this.pivotPoint.equals(this.lastPivotPoint) ) } @@ -331,6 +330,7 @@ export class SmoothOrbitControls extends SpeckleControls { // polar, azimuth and radius: this.setOrbit() this.setFieldOfView(Math.exp(this.goalLogFov)) + this.orbitSphere.visible = this._options.showOrbitPoint } /** Computes min/max radius values based on the current world size */ @@ -622,11 +622,8 @@ export class SmoothOrbitControls extends SpeckleControls { normalization ) this.origin.set(x, y, z) - const v = new Vector3().set(x, y, z) - this.originSphere.position.copy(v.applyMatrix4(this._basisTransform)) this.moveCamera() - this.forceUpdate = false return true } @@ -644,24 +641,27 @@ export class SmoothOrbitControls extends SpeckleControls { .copy(this.pivotPoint) .applyMatrix4(this._basisTransformInv) + // let cameraPivotDist + // if (this._targetCamera instanceof OrthographicCamera) { + // const offset = dir.multiplyScalar( + // this._options.maximumRadius - this.options.minimumRadius - this.spherical.radius + // ) + // const plm = new Vector3().copy(camPos).sub(offset) + // cameraPivotDist = camPos.distanceTo(pivotPoint) + // } else cameraPivotDist = camPos.distanceTo(pivotPoint) const cameraPivotDist = camPos.distanceTo(pivotPoint) const cameraPivotDir = new Vector3().copy(camPos).sub(pivotPoint) cameraPivotDir.normalize() - // const dot = Math.min(Math.max(dir.dot(cameraPivotDir), -1), 1) - // const angle = Math.acos(dot) - // console.log('Angle -> ', angle) - const polarRadius = cameraPivotDist // * Math.cos(angle) + const dot = Math.min(Math.max(dir.dot(cameraPivotDir), -1), 1) + const angle = Math.acos(dot) + const polarRadius = cameraPivotDist * Math.cos(angle) const polarOrigin = camPos.sub(new Vector3().copy(dir).multiplyScalar(polarRadius)) this.goalOrigin.copy(polarOrigin) this.origin.copy(polarOrigin) this.goalSpherical.radius = polarRadius this.spherical.radius = polarRadius - - this.originSphere.position.copy(polarOrigin) - // console.log('Origin -> ', polarOrigin) - // console.log('Dist -> ', cameraPivotDist, ' Radius -> ', polarRadius) } /** Function expects the origin in a CS where Y is up */ @@ -694,6 +694,11 @@ export class SmoothOrbitControls extends SpeckleControls { } protected usePivotal = false + /** This flag decides if full pivotal movement is going to be used or a 'softer' inbetween pivotal and polar one, + * where the polar origin is moved at the same depth as the pivot point. We don't expose this (yet) + */ + protected fullPivotal = true + protected moveCamera() { this.spherical.makeSafe() @@ -701,7 +706,7 @@ export class SmoothOrbitControls extends SpeckleControls { .copy(this.pivotPoint) .applyMatrix4(this._basisTransformInv) const prevPivotPoint = new Vector3() - .copy(this.lastPivot) + .copy(this.lastPivotPoint) .applyMatrix4(this._basisTransformInv) const deltaPivot = prevPivotPoint.sub(pivotPoint) @@ -712,15 +717,9 @@ export class SmoothOrbitControls extends SpeckleControls { } let position - if (this.usePivotal) { + if (this.usePivotal && this.fullPivotal) { position = this.positionFromPivotal(this.pivotalOrigin, quaternion) this.polarFromPivotal() - // position = this.positionFromSpherical(this.spherical, this.origin) - // console.log( - // 'Pivotal -> ', - // this.positionFromPivotal(this.pivotalOrigin, quaternion) - // ) - // console.log('Polar -> ', this.positionFromSpherical(this.spherical, this.origin)) } else position = this.positionFromSpherical(this.spherical, this.origin) position.applyQuaternion( @@ -728,19 +727,19 @@ export class SmoothOrbitControls extends SpeckleControls { ) quaternion.premultiply(new Quaternion().setFromRotationMatrix(this._basisTransform)) - if (this._targetCamera instanceof OrthographicCamera) { - const cameraDirection = new Vector3() - .setFromSpherical(this.spherical) - .applyQuaternion(new Quaternion().setFromRotationMatrix(this._basisTransform)) - .normalize() - position.add( - cameraDirection.multiplyScalar( - this._options.maximumRadius - - this.options.minimumRadius - - this.spherical.radius - ) - ) - } + // if (this._targetCamera instanceof OrthographicCamera) { + // const cameraDirection = new Vector3() + // .setFromSpherical(this.spherical) + // .applyQuaternion(new Quaternion().setFromRotationMatrix(this._basisTransform)) + // .normalize() + // position.add( + // cameraDirection.multiplyScalar( + // this._options.maximumRadius - + // this.options.minimumRadius - + // this.spherical.radius + // ) + // ) + // } this._targetCamera.position.copy(position) this._targetCamera.quaternion.copy(quaternion) this._targetCamera.updateMatrixWorld(true) @@ -763,16 +762,22 @@ export class SmoothOrbitControls extends SpeckleControls { this._targetCamera.updateProjectionMatrix() } - this.lastPivot.copy(this.pivotPoint) + this.lastPivotPoint.copy(this.pivotPoint) + + this.orbitSphere.position.copy( + this._options.orbitAroundCursor + ? this.pivotPoint + : new Vector3().copy(this.origin).applyMatrix4(this._basisTransform) + ) } - /* Ortho height to distance functions + // Ortho height to distance functions private orthographicHeightToDistance(height: number) { if (!(this._targetCamera instanceof OrthographicCamera)) return this.spherical.radius return height / (Math.tan(MathUtils.DEG2RAD * Math.exp(this.logFov) * 0.5) * 2) - }*/ + } /** Three.js Spherical assumes (0, 1, 0) as up... */ protected positionFromSpherical(spherical: Spherical, origin?: Vector3) { @@ -960,31 +965,32 @@ export class SmoothOrbitControls extends SpeckleControls { } protected onPointerDown = (event: PointerEvent) => { - const x = - ((event.clientX - this._container.offsetLeft) / this._container.offsetWidth) * 2 - - 1 + if (this._options.orbitAroundCursor) { + const x = + ((event.clientX - this._container.offsetLeft) / this._container.offsetWidth) * + 2 - + 1 + + const y = + ((event.clientY - this._container.offsetTop) / this._container.offsetHeight) * + -2 + + 1 + const res = this.intersections.intersect( + this.scene, + this._targetCamera as PerspectiveCamera, + new Vector2(x, y), + ObjectLayers.STREAM_CONTENT_MESH, + true, + this.world.worldBox // TO DO: This does not account for transformed objects + ) + if (res) { + this.pivotPoint.copy(res[0].point) + this.polarFromPivotal() - const y = - ((event.clientY - this._container.offsetTop) / this._container.offsetHeight) * - -2 + - 1 - const res = this.intersections.intersect( - this.scene, - this._targetCamera as PerspectiveCamera, - new Vector2(x, y), - ObjectLayers.STREAM_CONTENT_MESH, - true, - this.world.worldBox // TO DO: This does not account for transformed objects - ) - if (res) { - this.lastPivot.copy(this.pivotPoint) - this.pivotPoint.copy(res[0].point) - this.polarFromPivotal() - this.orbitSphere.position.copy(res[0].point) - this.forceUpdate = true - this.usePivotal = this._options.orbitAroundCursor && true - this.orbitSphere.visible = true + this.usePivotal = true + } } + this.orbitSphere.visible = this._options.showOrbitPoint if (this.pointers.length > 2) { return @@ -1080,7 +1086,6 @@ export class SmoothOrbitControls extends SpeckleControls { if (this.isUserPointing) { this.emit(PointerChangeEvent.PointerChangeEnd) } - this.usePivotal = false this.orbitSphere.visible = false } @@ -1143,6 +1148,7 @@ export class SmoothOrbitControls extends SpeckleControls { 60 this.userAdjustOrbit(0, 0, deltaZoom) event.preventDefault() + this.usePivotal = false // TO DO // this.dispatchEvent({ type: 'user-interaction' }) } diff --git a/packages/viewer/src/modules/materials/SpeckleBasicMaterial.ts b/packages/viewer/src/modules/materials/SpeckleBasicMaterial.ts index f336252047..d9868d5c20 100644 --- a/packages/viewer/src/modules/materials/SpeckleBasicMaterial.ts +++ b/packages/viewer/src/modules/materials/SpeckleBasicMaterial.ts @@ -95,6 +95,7 @@ class SpeckleBasicMaterial extends ExtendedMeshBasicMaterial { this.userData.billboardSize.value.copy(SpeckleBasicMaterial.vecBuff) SpeckleBasicMaterial.matBuff.copy(camera.projectionMatrix).invert() this.userData.invProjection.value.copy(SpeckleBasicMaterial.matBuff) + this.userData.billboardPos.value.copy(object.position) } if (this.defines && this.defines['USE_RTE']) { From a4dbf462240eeb1195530093ae3a2988051b5bad Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 10 Jan 2025 15:30:40 +0200 Subject: [PATCH 13/18] feat(viewer-lib): Updates to WEB-2313, orbiting around mouse cursor Orbiting around mouse cursor now works correctly with an orthographic projection as well as when toggling between orthographic and perspective. Disabled WASD navigation for now. SmoothOrbitControls now has protected members instead of private allowing extension Documented the important parts of the pivotal navigation code --- packages/viewer-sandbox/src/main.ts | 5 +- .../controls/SmoothOrbitControls.ts | 223 ++++++++++-------- 2 files changed, 126 insertions(+), 102 deletions(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 1022615ba3..62ec7ffff2 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -6,7 +6,8 @@ import { Viewer, HybridCameraController, ViewModes, - SelectionExtension + SelectionExtension, + CameraController } from '@speckle/viewer' import './style.css' @@ -45,7 +46,7 @@ const createViewer = async (containerName: string, _stream: string) => { const viewer: Viewer = new Viewer(container, params) await viewer.init() - const cameraController = viewer.createExtension(HybridCameraController) + const cameraController = viewer.createExtension(CameraController) const selection = viewer.createExtension(SelectionExtension) const sections = viewer.createExtension(SectionTool) viewer.createExtension(SectionOutlines) diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index 14c5862306..a7dea36384 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -131,54 +131,55 @@ export enum PointerChangeEvent { * ensure that the camera's matrixWorld is in sync before using SmoothControls. */ export class SmoothOrbitControls extends SpeckleControls { - private _enabled: boolean = false - private _options: Required - private isUserPointing = false + protected _enabled: boolean = false + protected _options: Required + protected isUserPointing = false // Pan state public enablePan = true public enableTap = true - private panProjection = new Matrix3() - private panPerPixel = 0 + protected panProjection = new Matrix3() + protected panPerPixel = 0 // Internal orbital position state public spherical = new Spherical() - private goalSpherical = new Spherical() - private origin = new Vector3() - private pivotalOrigin: Vector3 = new Vector3() - private goalOrigin = new Vector3() - private targetDamperX = new Damper() - private targetDamperY = new Damper() - private targetDamperZ = new Damper() - private thetaDamper = new Damper() - private phiDamper = new Damper() - private radiusDamper = new Damper() - private logFov = Math.log(55) - private goalLogFov = this.logFov - private fovDamper = new Damper() + protected goalSpherical = new Spherical() + protected origin = new Vector3() + protected pivotalOrigin: Vector3 = new Vector3() + protected goalOrigin = new Vector3() + protected targetDamperX = new Damper() + protected targetDamperY = new Damper() + protected targetDamperZ = new Damper() + protected thetaDamper = new Damper() + protected phiDamper = new Damper() + protected radiusDamper = new Damper() + protected logFov = Math.log(55) + protected goalLogFov = this.logFov + protected fovDamper = new Damper() // Pointer state - private touchMode: TouchMode = null - private pointers: Pointer[] = [] - private startPointerPosition = { clientX: 0, clientY: 0 } - private lastSeparation = 0 - private touchDecided = false - private zoomControlCoord: Vector2 = new Vector2() - - private _targetCamera: PerspectiveCamera | OrthographicCamera - private _container: HTMLElement - private _lastTick: number = 0 - private _basisTransform: Matrix4 = new Matrix4() - private _basisTransformInv: Matrix4 = new Matrix4() - private _radiusDelta: number = 0 - - private scene: Scene - private world: World - private intersections: Intersections - - private orbitSphere: Mesh - private pivotPoint: Vector3 = new Vector3() - private lastPivotPoint: Vector3 = new Vector3() + protected touchMode: TouchMode = null + protected pointers: Pointer[] = [] + protected startPointerPosition = { clientX: 0, clientY: 0 } + protected lastSeparation = 0 + protected touchDecided = false + protected zoomControlCoord: Vector2 = new Vector2() + + protected _targetCamera: PerspectiveCamera | OrthographicCamera + protected _container: HTMLElement + protected _lastTick: number = 0 + protected _basisTransform: Matrix4 = new Matrix4() + protected _basisTransformInv: Matrix4 = new Matrix4() + protected _radiusDelta: number = 0 + + protected scene: Scene + protected world: World + protected intersections: Intersections + + protected orbitSphere: Mesh + protected pivotPoint: Vector3 = new Vector3() + protected lastPivotPoint: Vector3 = new Vector3() + protected usePivotal = false public get enabled(): boolean { return this._enabled @@ -249,6 +250,14 @@ export class SmoothOrbitControls extends SpeckleControls { set targetCamera(value: PerspectiveCamera | OrthographicCamera) { this._targetCamera = value + this.usePivotal = this._options.orbitAroundCursor + + /** We move the lat pivot point somwhere outside of world bounds, in order to force a pivotal origin recompute */ + this.lastPivotPoint.set( + this.world.worldOrigin.x + this.world.worldSize.x, + this.world.worldOrigin.y + this.world.worldSize.y, + this.world.worldOrigin.z + this.world.worldSize.z + ) this.moveCamera() } @@ -627,44 +636,43 @@ export class SmoothOrbitControls extends SpeckleControls { return true } - protected polarFromPivotal() { + /** Function expects the position argument to be in a CS where Y is up */ + protected polarFromPivotal(position: Vector3) { const quaternion = this.quaternionFromSpherical(this.spherical) + /** Forward direction */ const dir = new Vector3().setFromMatrixColumn( new Matrix4().makeRotationFromQuaternion(quaternion), 2 ) - const camPos = new Vector3() - .copy(this._targetCamera.position) - .applyMatrix4(this._basisTransformInv) + const camPos = new Vector3().copy(position) + /** Pivot needs to be transformed in a Y up CS */ const pivotPoint = new Vector3() .copy(this.pivotPoint) .applyMatrix4(this._basisTransformInv) - // let cameraPivotDist - // if (this._targetCamera instanceof OrthographicCamera) { - // const offset = dir.multiplyScalar( - // this._options.maximumRadius - this.options.minimumRadius - this.spherical.radius - // ) - // const plm = new Vector3().copy(camPos).sub(offset) - // cameraPivotDist = camPos.distanceTo(pivotPoint) - // } else cameraPivotDist = camPos.distanceTo(pivotPoint) const cameraPivotDist = camPos.distanceTo(pivotPoint) const cameraPivotDir = new Vector3().copy(camPos).sub(pivotPoint) cameraPivotDir.normalize() const dot = Math.min(Math.max(dir.dot(cameraPivotDir), -1), 1) const angle = Math.acos(dot) + /** We compute a new distanced based on the pivot point */ const polarRadius = cameraPivotDist * Math.cos(angle) + /** We compute a new origin based on the pivot point, but keeping it along the camera's current forward direction */ const polarOrigin = camPos.sub(new Vector3().copy(dir).multiplyScalar(polarRadius)) this.goalOrigin.copy(polarOrigin) this.origin.copy(polarOrigin) - this.goalSpherical.radius = polarRadius - this.spherical.radius = polarRadius + + /** For orthographica camera's we don't need to update the radius because it will break their orthographic size */ + if (this._targetCamera instanceof PerspectiveCamera) { + this.goalSpherical.radius = polarRadius + this.spherical.radius = polarRadius + } } - /** Function expects the origin in a CS where Y is up */ + /** Function expects the origin argument to be in a CS where Y is up */ protected positionFromPivotal(origin: Vector3, quaternion: Quaternion) { const pivotPoint = new Vector3() .copy(this.pivotPoint) @@ -680,11 +688,13 @@ export class SmoothOrbitControls extends SpeckleControls { return position } - /** Function expects the pivotPoint in a CS where Y is up */ - protected getPivotalOrigin(pivotPoint: Vector3, quaternion: Quaternion) { - const pivotalOrigin = new Vector3() - .copy(this._targetCamera.position) - .applyMatrix4(this._basisTransformInv) + /** Function expects the pivotPoint and position arguments to be in a CS where Y is up */ + protected getPivotalOrigin( + pivotPoint: Vector3, + position: Vector3, + quaternion: Quaternion + ) { + const pivotalOrigin = new Vector3().copy(position) pivotalOrigin.sub(pivotPoint) pivotalOrigin.applyQuaternion(new Quaternion().copy(quaternion).invert()) @@ -693,61 +703,76 @@ export class SmoothOrbitControls extends SpeckleControls { return pivotalOrigin } - protected usePivotal = false - /** This flag decides if full pivotal movement is going to be used or a 'softer' inbetween pivotal and polar one, - * where the polar origin is moved at the same depth as the pivot point. We don't expose this (yet) - */ - protected fullPivotal = true - protected moveCamera() { this.spherical.makeSafe() - const pivotPoint = new Vector3() - .copy(this.pivotPoint) - .applyMatrix4(this._basisTransformInv) - const prevPivotPoint = new Vector3() - .copy(this.lastPivotPoint) - .applyMatrix4(this._basisTransformInv) - - const deltaPivot = prevPivotPoint.sub(pivotPoint) + /** We get the current position and rotation based off the latest polar params + * The ground truth is going to always be the polar CS! + */ const quaternion = this.quaternionFromSpherical(this.spherical) + let position = this.positionFromSpherical(this.spherical, this.origin) + + if (this.usePivotal) { + /** We transform both current and previous pivots in a CS where Y us up */ + const pivotPoint = new Vector3() + .copy(this.pivotPoint) + .applyMatrix4(this._basisTransformInv) + const prevPivotPoint = new Vector3() + .copy(this.lastPivotPoint) + .applyMatrix4(this._basisTransformInv) + + const deltaPivot = prevPivotPoint.sub(pivotPoint) + + /** We recompute the pivotal origin/pivotal offset, but only when required! */ + if (deltaPivot.length() > 0) { + this.pivotalOrigin.copy(this.getPivotalOrigin(pivotPoint, position, quaternion)) + } - if (deltaPivot.length() > 0) { - this.pivotalOrigin.copy(this.getPivotalOrigin(pivotPoint, quaternion)) - } - - let position - if (this.usePivotal && this.fullPivotal) { + /** We get a new position in the pivotal CS */ position = this.positionFromPivotal(this.pivotalOrigin, quaternion) - this.polarFromPivotal() - } else position = this.positionFromSpherical(this.spherical, this.origin) + /** We update the polar CS based off the new pivotal camera position, + * essentially creating a virtual pair polar CS which can reproduce the pivotal position */ + this.polarFromPivotal(position) + /** Update the last pivot */ + this.lastPivotPoint.copy(this.pivotPoint) + } + /** We transform both position and quaternion in the required basis */ position.applyQuaternion( new Quaternion().setFromRotationMatrix(this._basisTransform) ) quaternion.premultiply(new Quaternion().setFromRotationMatrix(this._basisTransform)) - // if (this._targetCamera instanceof OrthographicCamera) { - // const cameraDirection = new Vector3() - // .setFromSpherical(this.spherical) - // .applyQuaternion(new Quaternion().setFromRotationMatrix(this._basisTransform)) - // .normalize() - // position.add( - // cameraDirection.multiplyScalar( - // this._options.maximumRadius - - // this.options.minimumRadius - - // this.spherical.radius - // ) - // ) - // } + /** This is a trick we do for ortographic projection which stops the near plane from clipping into geometry + * In orthographic projection the camera's 'depth' along it's forward does not matter. Zoooming is achieved by + * varying the orthographic size, not by moving the camera. + */ + if (this._targetCamera instanceof OrthographicCamera) { + const cameraDirection = new Vector3() + .setFromSpherical(this.spherical) + .applyQuaternion(new Quaternion().setFromRotationMatrix(this._basisTransform)) + .normalize() + position.add( + cameraDirection.multiplyScalar( + this._options.maximumRadius - + this.options.minimumRadius - + this.spherical.radius + ) + ) + } + /** Apply values and update transform */ this._targetCamera.position.copy(position) this._targetCamera.quaternion.copy(quaternion) this._targetCamera.updateMatrixWorld(true) + + /** Fov update */ if (this._targetCamera instanceof PerspectiveCamera) if (this._targetCamera.fov !== Math.exp(this.logFov)) { this._targetCamera.fov = Math.exp(this.logFov) this._targetCamera.updateProjectionMatrix() } + + /** Compute the correct orthographic size based on the polar radius */ if (this._targetCamera instanceof OrthographicCamera) { const orthographicSize = computeOrthographicSize( this.spherical.radius, @@ -762,8 +787,7 @@ export class SmoothOrbitControls extends SpeckleControls { this._targetCamera.updateProjectionMatrix() } - this.lastPivotPoint.copy(this.pivotPoint) - + /** Update the debug origin sphere */ this.orbitSphere.position.copy( this._options.orbitAroundCursor ? this.pivotPoint @@ -771,13 +795,14 @@ export class SmoothOrbitControls extends SpeckleControls { ) } - // Ortho height to distance functions + /* + // Ortho height to distance function. Keeping for reference private orthographicHeightToDistance(height: number) { if (!(this._targetCamera instanceof OrthographicCamera)) return this.spherical.radius return height / (Math.tan(MathUtils.DEG2RAD * Math.exp(this.logFov) * 0.5) * 2) - } + }*/ /** Three.js Spherical assumes (0, 1, 0) as up... */ protected positionFromSpherical(spherical: Spherical, origin?: Vector3) { @@ -985,8 +1010,6 @@ export class SmoothOrbitControls extends SpeckleControls { ) if (res) { this.pivotPoint.copy(res[0].point) - this.polarFromPivotal() - this.usePivotal = true } } From fc1cf444dca3fbab8cbccb911fdb7dbe2aeba361 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 10 Jan 2025 17:03:33 +0200 Subject: [PATCH 14/18] feat(viewer-lib): Mouse orbiting now takes clipping planes into consideration --- packages/viewer/src/modules/LegacyViewer.ts | 3 +-- .../modules/extensions/CameraController.ts | 3 +-- .../controls/SmoothOrbitControls.ts | 23 ++++++++----------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/viewer/src/modules/LegacyViewer.ts b/packages/viewer/src/modules/LegacyViewer.ts index 4af3f9a499..c9c6b2f177 100644 --- a/packages/viewer/src/modules/LegacyViewer.ts +++ b/packages/viewer/src/modules/LegacyViewer.ts @@ -44,7 +44,6 @@ import { BatchObject } from './batching/BatchObject.js' import { SpeckleLoader } from './loaders/Speckle/SpeckleLoader.js' import Logger from './utils/Logger.js' import { ViewModes } from './extensions/ViewModes.js' -import { HybridCameraController } from './extensions/HybridCameraController.js' class LegacySelectionExtension extends SelectionExtension { /** FE2 'manually' selects objects pon it's own, so we're disabling the extension's event handler @@ -121,7 +120,7 @@ export class LegacyViewer extends Viewer { params: ViewerParams = DefaultViewerParams ) { super(container, params) - this.cameraController = this.createExtension(HybridCameraController) + this.cameraController = this.createExtension(CameraController) this.selection = this.createExtension(LegacySelectionExtension) this.sections = this.createExtension(SectionTool) this.createExtension(SectionOutlines) diff --git a/packages/viewer/src/modules/extensions/CameraController.ts b/packages/viewer/src/modules/extensions/CameraController.ts index 92b066edc2..579b080fda 100644 --- a/packages/viewer/src/modules/extensions/CameraController.ts +++ b/packages/viewer/src/modules/extensions/CameraController.ts @@ -196,8 +196,7 @@ export class CameraController extends Extension implements SpeckleCamera { this.perspectiveCamera, this.viewer.getContainer(), this.viewer.World, - this.viewer.getRenderer().scene, - this.viewer.getRenderer().intersections, + this.viewer.getRenderer(), this._options ) orbitControls.enabled = true diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index a7dea36384..9ce4be6545 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -24,7 +24,6 @@ import { OrthographicCamera, Quaternion, Euler, - Scene, Mesh, SphereGeometry } from 'three' @@ -33,11 +32,11 @@ import { Damper, SETTLING_TIME } from '../../utils/Damper.js' import { World } from '../../World.js' import { SpeckleControls } from './SpeckleControls.js' -import { Intersections } from '../../Intersections.js' import { lerp } from 'three/src/math/MathUtils.js' import { computeOrthographicSize } from '../CameraController.js' import { ObjectLayers } from '../../../IViewer.js' import SpeckleBasicMaterial from '../../materials/SpeckleBasicMaterial.js' +import SpeckleRenderer from '../../SpeckleRenderer.js' /** * @param {Number} value @@ -172,9 +171,8 @@ export class SmoothOrbitControls extends SpeckleControls { protected _basisTransformInv: Matrix4 = new Matrix4() protected _radiusDelta: number = 0 - protected scene: Scene protected world: World - protected intersections: Intersections + protected renderer: SpeckleRenderer protected orbitSphere: Mesh protected pivotPoint: Vector3 = new Vector3() @@ -208,16 +206,14 @@ export class SmoothOrbitControls extends SpeckleControls { camera: PerspectiveCamera | OrthographicCamera, container: HTMLElement, world: World, - scene: Scene, - intersections: Intersections, + renderer: SpeckleRenderer, options: Required ) { super() this._targetCamera = camera this._container = container this.world = world - this.intersections = intersections - this.scene = scene + this.renderer = renderer this._options = Object.assign({}, options) this.setDamperDecayTime(this._options.damperDecay) @@ -234,7 +230,7 @@ export class SmoothOrbitControls extends SpeckleControls { this.orbitSphere = new Mesh(new SphereGeometry(0.5, 32, 16), billboardMaterial) this.orbitSphere.layers.set(ObjectLayers.OVERLAY) this.orbitSphere.visible = false - this.scene.add(this.orbitSphere) + this.renderer.scene.add(this.orbitSphere) } /** @@ -339,7 +335,6 @@ export class SmoothOrbitControls extends SpeckleControls { // polar, azimuth and radius: this.setOrbit() this.setFieldOfView(Math.exp(this.goalLogFov)) - this.orbitSphere.visible = this._options.showOrbitPoint } /** Computes min/max radius values based on the current world size */ @@ -1000,15 +995,15 @@ export class SmoothOrbitControls extends SpeckleControls { ((event.clientY - this._container.offsetTop) / this._container.offsetHeight) * -2 + 1 - const res = this.intersections.intersect( - this.scene, + const res = this.renderer.intersections.intersect( + this.renderer.scene, this._targetCamera as PerspectiveCamera, new Vector2(x, y), ObjectLayers.STREAM_CONTENT_MESH, true, - this.world.worldBox // TO DO: This does not account for transformed objects + this.renderer.clippingVolume ) - if (res) { + if (res && res.length) { this.pivotPoint.copy(res[0].point) this.usePivotal = true } From 87208740e5305d18520493f2fa27693b272fa780 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 10 Jan 2025 17:30:35 +0200 Subject: [PATCH 15/18] chore(viewer-lib): Fixed sandbox build error --- packages/viewer-sandbox/src/main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 62ec7ffff2..1a4cbf3749 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -4,7 +4,6 @@ import { SelectionEvent, ViewerEvent, Viewer, - HybridCameraController, ViewModes, SelectionExtension, CameraController From b40e99e779e6881be0532522f525bb84a0a85e04 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Mon, 13 Jan 2025 15:15:00 +0200 Subject: [PATCH 16/18] fix(viewer-lib): Handled WEB-2449 and WEB-2450 Additionally fixed an issue where changing the orbit pivot would trigger a hard render, adding the unneeded noise of AO re-convergence --- .../extensions/controls/SmoothOrbitControls.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index 9ce4be6545..0728e8171d 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -48,6 +48,7 @@ const clamp = (value: number, lowerLimit: number, upperLimit: number): number => Math.max(lowerLimit, Math.min(upperLimit, value)) const PAN_SENSITIVITY = 0.018 +const MOVEMENT_EPSILON = 1e-5 const vector3 = new Vector3() export type TouchMode = null | ((dx: number, dy: number) => void) @@ -626,9 +627,8 @@ export class SmoothOrbitControls extends SpeckleControls { normalization ) this.origin.set(x, y, z) - this.moveCamera() - return true + return this.moveCamera() } /** Function expects the position argument to be in a CS where Y is up */ @@ -698,7 +698,10 @@ export class SmoothOrbitControls extends SpeckleControls { return pivotalOrigin } - protected moveCamera() { + protected moveCamera(): boolean { + const lastCameraPos = new Vector3().copy(this._targetCamera.position) + const lastCameraQuat = new Quaternion().copy(this._targetCamera.quaternion) + this.spherical.makeSafe() /** We get the current position and rotation based off the latest polar params @@ -784,10 +787,15 @@ export class SmoothOrbitControls extends SpeckleControls { /** Update the debug origin sphere */ this.orbitSphere.position.copy( - this._options.orbitAroundCursor + this._options.orbitAroundCursor && this.usePivotal ? this.pivotPoint : new Vector3().copy(this.origin).applyMatrix4(this._basisTransform) ) + + return ( + lastCameraPos.sub(this._targetCamera.position).length() > MOVEMENT_EPSILON || + lastCameraQuat.angleTo(this._targetCamera.quaternion) > MOVEMENT_EPSILON + ) } /* From 5de1bc5883b2bf26fabbc9e7df0cbce7a9aee444 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Mon, 13 Jan 2025 19:33:18 +0200 Subject: [PATCH 17/18] fix(viewer-lib): Fixed the issue with focusing and other camera animations caused by the introduction on the pivotal CS. Pivot sphere now shows only on orbit --- .../src/modules/extensions/controls/SmoothOrbitControls.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index 0728e8171d..841351578c 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -277,6 +277,7 @@ export class SmoothOrbitControls extends SpeckleControls { /** Three.js Spherical assumes (0, 1, 0) as up... */ v1.applyMatrix4(this._basisTransformInv) this.setTarget(v1.x, v1.y, v1.z) + this.usePivotal = false } /** @@ -295,6 +296,7 @@ export class SmoothOrbitControls extends SpeckleControls { this.setTarget(nativeOrigin.x, nativeOrigin.y, nativeOrigin.z) this.setRadius(sphere.radius) + this.usePivotal = false } /** @@ -990,6 +992,7 @@ export class SmoothOrbitControls extends SpeckleControls { target.add(dxy.applyMatrix3(this.panProjection)) this.setTarget(target.x, target.y, target.z) this.usePivotal = false + this.orbitSphere.visible = false } protected onPointerDown = (event: PointerEvent) => { @@ -1149,6 +1152,7 @@ export class SmoothOrbitControls extends SpeckleControls { (event.button === 2 || event.ctrlKey || event.metaKey || event.shiftKey) ) { this.initializePan() + this.orbitSphere.visible = false } // this.element.style.cursor = 'grabbing' } @@ -1175,6 +1179,7 @@ export class SmoothOrbitControls extends SpeckleControls { this.userAdjustOrbit(0, 0, deltaZoom) event.preventDefault() this.usePivotal = false + this.orbitSphere.visible = false // TO DO // this.dispatchEvent({ type: 'user-interaction' }) } From 572ae38cccb07461a6065469bffb32163774386c Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Tue, 14 Jan 2025 12:17:23 +0200 Subject: [PATCH 18/18] feat(viewer-lib): Updates on mouse orbiting: - When clicking outside of the model, oribitig will switch to polar and use the last computed origin (which is still going to be based on the last pivot point) Made the pivot sphere speckle blue The pivot sphere now only shows when clicking on objects, when clicking outside of the model it will not show --- .../src/modules/extensions/controls/SmoothOrbitControls.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts index 841351578c..b1931bdb11 100644 --- a/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts +++ b/packages/viewer/src/modules/extensions/controls/SmoothOrbitControls.ts @@ -218,7 +218,7 @@ export class SmoothOrbitControls extends SpeckleControls { this._options = Object.assign({}, options) this.setDamperDecayTime(this._options.damperDecay) - const billboardMaterial = new SpeckleBasicMaterial({ color: 0x43af11 }, [ + const billboardMaterial = new SpeckleBasicMaterial({ color: 0x047efb }, [ 'BILLBOARD_FIXED' ]) billboardMaterial.opacity = 0.75 @@ -1017,9 +1017,12 @@ export class SmoothOrbitControls extends SpeckleControls { if (res && res.length) { this.pivotPoint.copy(res[0].point) this.usePivotal = true + this.orbitSphere.visible = this._options.showOrbitPoint + } else { + this.usePivotal = false + this.orbitSphere.visible = false } } - this.orbitSphere.visible = this._options.showOrbitPoint if (this.pointers.length > 2) { return