From 7ec9258de2327a2492239e1b871fdb9a3de2598b Mon Sep 17 00:00:00 2001 From: kpal Date: Thu, 18 Apr 2024 12:52:23 +0100 Subject: [PATCH] added back zoom slowing and pointer move only occurs on canvas --- src/cameras/base-camera.ts | 22 +++----- src/cameras/multi-camera.ts | 71 ++++++++++++------------ src/viewer.ts | 108 +++++++++++++++++++----------------- 3 files changed, 99 insertions(+), 102 deletions(-) diff --git a/src/cameras/base-camera.ts b/src/cameras/base-camera.ts index 47969e6..e61050b 100644 --- a/src/cameras/base-camera.ts +++ b/src/cameras/base-camera.ts @@ -10,6 +10,8 @@ type PointerMoveEvent = PointerEvent & { const LOOK_MAX_ANGLE = 90; abstract class BaseCamera { + target: HTMLElement = document.documentElement; + entity: Entity = new Entity(); sceneSize: number = 100; @@ -30,11 +32,8 @@ abstract class BaseCamera { protected _angles: Vec3 = new Vec3(); - protected _zoom: number = 0; - - protected _focusDist: number = 0; - - constructor(options: Record = {}) { + constructor(target: HTMLElement, options: Record = {}) { + this.target = target; this.sceneSize = options.sceneSize ?? this.sceneSize; this.lookSensitivity = options.lookSensitivity ?? this.lookSensitivity; this.lookDamping = options.lookDamping ?? this.lookDamping; @@ -45,14 +44,6 @@ abstract class BaseCamera { this._onPointerUp = this._onPointerUp.bind(this); } - abstract get point(): Vec3 - - abstract get start(): Vec3 - - get dir() { - return this._dir; - } - private _smoothLook(dt: number) { const lerpRate = 1 - Math.pow(this.lookDamping, dt * 1000); this._angles.x = math.lerp(this._angles.x, this._dir.x, lerpRate); @@ -76,13 +67,16 @@ abstract class BaseCamera { protected abstract _onPointerUp(event: PointerEvent): void protected _look(event: PointerMoveEvent) { + if (event.target !== this.target) { + return; + } const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; this._dir.x = math.clamp(this._dir.x - movementY * this.lookSensitivity, -LOOK_MAX_ANGLE, LOOK_MAX_ANGLE); this._dir.y -= movementX * this.lookSensitivity; } - abstract focus(point: Vec3, start?: Vec3, dir?: Vec2, snap?: boolean): void + abstract focus(point: Vec3, start?: Vec3): void attach(camera: Entity) { this._camera = camera; diff --git a/src/cameras/multi-camera.ts b/src/cameras/multi-camera.ts index 813b422..235e88b 100644 --- a/src/cameras/multi-camera.ts +++ b/src/cameras/multi-camera.ts @@ -29,9 +29,11 @@ class MultiCamera extends BaseCamera { wheelSpeed: number = 0.005; - zoomThreshold: number = 0.01; + zoomAbsMin: number = 0.01; - zoomExp: number = 0.5; + zoomScaleMax: number = 10; + + zoomSpeedMin: number = 0.01; moveSpeed: number = 2; @@ -39,6 +41,12 @@ class MultiCamera extends BaseCamera { crouchSpeed: number = 1; + private _zoomDist: number = 0; + + private _zoomStart: number = 0; + + private _cameraDist: number = 0; + private _pointerEvents: Map = new Map(); private _lastPinchDist: number = -1; @@ -60,15 +68,15 @@ class MultiCamera extends BaseCamera { crouch: false }; - constructor(options: Record = {}) { - super(options); + constructor(target: HTMLElement, options: Record = {}) { + super(target, options); this.mousePanSpeed = options.mousePanSpeed ?? this.mousePanSpeed; this.mobilePanSpeed = options.mobilePanSpeed ?? this.mobilePanSpeed; this.pinchSpeed = options.pinchSpeed ?? this.pinchSpeed; this.wheelSpeed = options.wheelSpeed ?? this.wheelSpeed; - this.zoomThreshold = options.zoomThreshold ?? this.zoomThreshold; - this.zoomExp = options.zoomExp ?? this.zoomExp; + this.zoomAbsMin = options.zoomAbsMin ?? this.zoomAbsMin; + this.zoomScaleMax = options.zoomScaleMax ?? this.zoomScaleMax; this.moveSpeed = options.moveSpeed ?? this.moveSpeed; this.sprintSpeed = options.sprintSpeed ?? this.sprintSpeed; this.crouchSpeed = options.crouchSpeed ?? this.crouchSpeed; @@ -78,14 +86,6 @@ class MultiCamera extends BaseCamera { this._onKeyUp = this._onKeyUp.bind(this); } - get point() { - return this._origin; - } - - get start() { - return this._camera.getPosition(); - } - protected _onPointerDown(event: PointerEvent) { if (!this._camera) { return; @@ -101,7 +101,7 @@ class MultiCamera extends BaseCamera { this._panning = true; } if (event.button === 2) { - this._zoom = this._focusDist; + this._zoomDist = this._cameraDist; this._origin.copy(this._camera.getPosition()); this._position.copy(this._origin); this._camera.setLocalPosition(0, 0, 0); @@ -133,7 +133,7 @@ class MultiCamera extends BaseCamera { // pinch zoom const pinchDist = this._getPinchDist(); if (this._lastPinchDist > 0) { - this._focusZoom(this._lastPinchDist - pinchDist); + this._zoom(this._lastPinchDist - pinchDist); } this._lastPinchDist = pinchDist; } @@ -150,7 +150,7 @@ class MultiCamera extends BaseCamera { this._panning = false; } if (event.button === 2) { - tmpV1.copy(this.entity.forward).mulScalar(this._zoom); + tmpV1.copy(this.entity.forward).mulScalar(this._zoomDist); this._origin.add(tmpV1); this._position.add(tmpV1); this._flying = false; @@ -159,7 +159,7 @@ class MultiCamera extends BaseCamera { private _onWheel(event: WheelEvent) { event.preventDefault(); - this._focusZoom(event.deltaY); + this._zoom(event.deltaY); } private _onKeyDown(event: KeyboardEvent) { @@ -273,12 +273,15 @@ class MultiCamera extends BaseCamera { this._lastPosition.copy(pos); } - private _focusZoom(delta: number) { - const zoomMult = delta * this.sceneSize * this.wheelSpeed; - this._zoom = Math.max(this._zoom + zoomMult, this.zoomThreshold); + private _zoom(delta: number) { + const min = this.zoomAbsMin; + const max = this.zoomScaleMax * this.sceneSize; + const speed = math.clamp(this._zoomDist / (max - min), this.zoomSpeedMin, 1); + this._zoomDist += (delta * this.wheelSpeed * this.sceneSize * speed); + this._zoomDist = math.clamp(this._zoomDist, min, max); } - focus(point: Vec3, start?: Vec3, dir?: Vec2, snap?: boolean) { + focus(point: Vec3, start?: Vec3) { if (!this._camera) { return; } @@ -288,23 +291,19 @@ class MultiCamera extends BaseCamera { } tmpV1.sub2(start, point); - if (dir) { - this._dir.copy(dir); - } else { - const elev = Math.atan2(tmpV1.y, tmpV1.z) * math.RAD_TO_DEG; - const azim = Math.atan2(tmpV1.x, tmpV1.z) * math.RAD_TO_DEG; - this._dir.set(-elev, -azim); - } + const elev = Math.atan2(tmpV1.y, tmpV1.z) * math.RAD_TO_DEG; + const azim = Math.atan2(tmpV1.x, tmpV1.z) * math.RAD_TO_DEG; + this._dir.set(-elev, -azim); this._origin.copy(point); this._camera.setPosition(start); - if (snap) { - this._angles.set(this._dir.x, this._dir.y, 0); - this._position.copy(this._origin); - } + this._zoomDist = tmpV1.length(); + this._zoomStart = this._zoomDist; + } - this._zoom = tmpV1.length(); + resetZoom() { + this._zoomDist = this._zoomStart; } attach(camera: Entity) { @@ -343,8 +342,8 @@ class MultiCamera extends BaseCamera { } if (!this._flying) { - this._focusDist = math.lerp(this._focusDist, this._zoom, 1 - Math.pow(this.moveDamping, dt * 1000)); - this._camera.setLocalPosition(0, 0, this._focusDist); + this._cameraDist = math.lerp(this._cameraDist, this._zoomDist, 1 - Math.pow(this.moveDamping, dt * 1000)); + this._camera.setLocalPosition(0, 0, this._cameraDist); } this._move(dt); diff --git a/src/viewer.ts b/src/viewer.ts index 6f54e2b..03df18e 100644 --- a/src/viewer.ts +++ b/src/viewer.ts @@ -69,7 +69,6 @@ import { MorphTargetData, File, HierarchyNode } from './types'; import { DebugLines } from './debug-lines'; import { Multiframe } from './multiframe'; import { ReadDepth } from './read-depth'; -import { BaseCamera } from './cameras/base-camera'; import { MultiCamera } from './cameras/multi-camera'; import { PngExporter } from './png-exporter'; import { ProjectiveSkybox } from './projective-skybox'; @@ -88,9 +87,9 @@ const defaultSceneBounds = new BoundingBox(new Vec3(0, 1, 0), new Vec3(1, 1, 1)) const vec = new Vec3(); const bbox = new BoundingBox(); -const FOCUS_SECTOR_MULT = 0.25; -const FOCUS_SCALE_MULT = 0.25; -const FOCUS_START_DIR = new Vec3(0, 1, 3); +const FOCUS_SECTOR_MULT = 0.5; +const FOCUS_SCALE_MULT = 1; +const FOCUS_START_DIR = new Vec3(0, 1, 5); class Viewer { canvas: HTMLCanvasElement; @@ -155,7 +154,7 @@ class Viewer { canvasResize = true; - controlCamera: BaseCamera; + multiCamera: MultiCamera; constructor(canvas: HTMLCanvasElement, graphicsDevice: GraphicsDevice, observer: Observer, skyboxUrls: Map) { this.canvas = canvas; @@ -234,14 +233,15 @@ class Viewer { camera.camera.requestSceneColorMap(true); // create camera controls - this.controlCamera = new MultiCamera(); - app.root.addChild(this.controlCamera.entity); - this.controlCamera.attach(camera); + this.multiCamera = new MultiCamera(canvas); + app.root.addChild(this.multiCamera.entity); + this.multiCamera.attach(camera); app.keyboard.on(EVENT_KEYDOWN, (event) => { switch (event.key) { case KEY_F: { this.focusSelection(false); + this.multiCamera.resetZoom(); break; } } @@ -380,7 +380,7 @@ class Viewer { this.camera.getWorldTransform().transformPoint(this.cursorWorld, this.cursorWorld); // world space // focus on cursor - this.controlCamera.focus(this.cursorWorld); + this.multiCamera.focus(this.cursorWorld); } }); @@ -701,6 +701,52 @@ class Viewer { }; } + private getFocusPosition(bbox: BoundingBox) { + const focus = new Vec3(); + if (this.initialCameraFocus) { + focus.copy(this.initialCameraFocus); + this.initialCameraFocus = null; + } else { + const entityAsset = this.entityAssets[0]; + const splatData = entityAsset?.asset?.resource?.splatData; + if (splatData) { + splatData.calcFocalPoint(focus); + entityAsset.entity.getWorldTransform().transformPoint(focus, focus); + } else { + focus.copy(bbox.center); + } + } + return focus; + } + + private focusSelection(calcStart = true) { + const camera = this.camera.camera; + + // calculate scene bounding box + this.calcSceneBounds(bbox, this.selectedNode as Entity); + + // calculate the camera focus point + const focus = this.getFocusPosition(bbox); + + const sceneSize = bbox.halfExtents.length(); + let start: Vec3 | null = null; + if (calcStart) { + start = new Vec3(); + if (this.initialCameraPosition) { + start.copy(this.initialCameraPosition); + this.initialCameraPosition = null; + } else { + start.copy(focus); + const scale = FOCUS_SCALE_MULT / Math.sin(FOCUS_SECTOR_MULT * camera.fov * math.DEG_TO_RAD); + start.add(vec.copy(FOCUS_START_DIR).normalize().mulScalar(sceneSize * scale)); + } + } + + // focus orbit camera on object and set focus and sceneSize + this.multiCamera.sceneSize = sceneSize; + this.multiCamera.focus(focus, start); + } + destroyRenderTargets() { const rt = this.camera.camera.renderTarget; if (rt) { @@ -873,48 +919,6 @@ class Viewer { } } - focusSelection(calcStart = true) { - const camera = this.camera.camera; - - // calculate scene bounding box - this.calcSceneBounds(bbox, this.selectedNode as Entity); - - const sceneSize = bbox.halfExtents.length(); - - // calculate the camera focus point - const focus = new Vec3(); - if (this.initialCameraFocus) { - focus.copy(this.initialCameraFocus); - this.initialCameraFocus = null; - } else { - const entityAsset = this.entityAssets[0]; - const splatData = entityAsset?.asset?.resource?.splatData; - if (splatData) { - splatData.calcFocalPoint(focus); - entityAsset.entity.getWorldTransform().transformPoint(focus, focus); - } else { - focus.copy(bbox.center); - } - } - - let start: Vec3 | null = null; - if (calcStart) { - start = new Vec3(); - if (this.initialCameraPosition) { - start.copy(this.initialCameraPosition); - this.initialCameraPosition = null; - } else { - start.copy(focus); - const scale = FOCUS_SCALE_MULT / Math.tan(FOCUS_SECTOR_MULT * camera.fov * math.DEG_TO_RAD); - start.add(vec.copy(FOCUS_START_DIR).mulScalar(sceneSize * scale)); - } - } - - // focus orbit camera on object and set focus and sceneSize - this.controlCamera.sceneSize = sceneSize; - this.controlCamera.focus(focus, start); - } - // adjust camera clipping planes to fit the scene fitCameraClipPlanes() { if (this.xrMode?.active) { @@ -1410,7 +1414,7 @@ class Viewer { update(deltaTime: number) { // update the orbit camera if (!this.xrMode?.active) { - this.controlCamera.update(deltaTime); + this.multiCamera.update(deltaTime); } const maxdiff = (a: Mat4, b: Mat4) => {