From b4f5d44b9ddea7f0af304f8e80a9f01ee62a3acf Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 31 Mar 2026 04:24:06 +0200 Subject: [PATCH] Add camera drag and zoom functionality in viewport-host.ts --- src/viewport-three/viewport-host.ts | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index 0f391787..1b14fad5 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -470,6 +470,21 @@ export class ViewportHost { } private handlePointerDown = (event: PointerEvent) => { + if (event.button === 1) { + event.preventDefault(); + this.activeCameraDragPointerId = event.pointerId; + this.lastCameraDragClientPosition = { + x: event.clientX, + y: event.clientY + }; + this.renderer.domElement.setPointerCapture(event.pointerId); + return; + } + + if (event.button !== 0) { + return; + } + if (this.toolMode === "box-create") { const previewCenter = this.getBoxCreatePreviewCenter(event); @@ -541,6 +556,24 @@ export class ViewportHost { }; private handlePointerMove = (event: PointerEvent) => { + if (this.activeCameraDragPointerId === event.pointerId && this.lastCameraDragClientPosition !== null) { + const deltaX = event.clientX - this.lastCameraDragClientPosition.x; + const deltaY = event.clientY - this.lastCameraDragClientPosition.y; + + this.lastCameraDragClientPosition = { + x: event.clientX, + y: event.clientY + }; + + if (event.shiftKey) { + this.panCamera(deltaX, deltaY); + } else { + this.orbitCamera(deltaX, deltaY); + } + + return; + } + if (this.toolMode !== "box-create") { return; } @@ -548,7 +581,24 @@ export class ViewportHost { this.setBoxCreatePreview(this.getBoxCreatePreviewCenter(event)); }; + private handlePointerUp = (event: PointerEvent) => { + if (this.activeCameraDragPointerId !== event.pointerId) { + return; + } + + if (this.renderer.domElement.hasPointerCapture(event.pointerId)) { + this.renderer.domElement.releasePointerCapture(event.pointerId); + } + + this.activeCameraDragPointerId = null; + this.lastCameraDragClientPosition = null; + }; + private handlePointerLeave = () => { + if (this.activeCameraDragPointerId !== null) { + return; + } + if (this.toolMode !== "box-create") { return; } @@ -556,6 +606,48 @@ export class ViewportHost { this.setBoxCreatePreview(null); }; + private handleWheel = (event: WheelEvent) => { + event.preventDefault(); + this.cameraSpherical.radius = Math.min( + MAX_CAMERA_DISTANCE, + Math.max(MIN_CAMERA_DISTANCE, this.cameraSpherical.radius * Math.exp(event.deltaY * ZOOM_SPEED)) + ); + this.applyCameraOrbitPose(); + }; + + private handleAuxClick = (event: MouseEvent) => { + if (event.button === 1) { + event.preventDefault(); + } + }; + + private orbitCamera(deltaX: number, deltaY: number) { + this.cameraSpherical.theta -= deltaX * ORBIT_ROTATION_SPEED; + this.cameraSpherical.phi += deltaY * ORBIT_ROTATION_SPEED; + this.applyCameraOrbitPose(); + } + + private panCamera(deltaX: number, deltaY: number) { + if (this.container === null) { + return; + } + + const width = Math.max(1, this.container.clientWidth); + const height = Math.max(1, this.container.clientHeight); + const visibleHeight = 2 * Math.tan((this.camera.fov * Math.PI) / 360) * this.cameraSpherical.radius; + const visibleWidth = visibleHeight * Math.max(this.camera.aspect, 0.0001); + + this.camera.getWorldDirection(this.cameraForward); + this.cameraRight.crossVectors(this.cameraForward, this.camera.up).normalize(); + this.cameraUp.crossVectors(this.cameraRight, this.cameraForward).normalize(); + + this.cameraTarget + .addScaledVector(this.cameraRight, (-deltaX / width) * visibleWidth) + .addScaledVector(this.cameraUp, (deltaY / height) * visibleHeight); + + this.applyCameraOrbitPose(); + } + private getBoxCreatePreviewCenter(event: PointerEvent): Vec3 | null { const bounds = this.renderer.domElement.getBoundingClientRect();