From cf23e2293110c065f32be73ba4c1bef50d1ed9b6 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 13 Feb 2026 17:11:25 -0800 Subject: [PATCH] Add zoom, pan, and camera reset to 3D editors Enable zoom (scroll/pinch) and pan (right-click/two-finger drag) on all OrbitControls. Add resetView() to BaseRenderer that restores initial camera state and auto-rotate via OrbitControls.saveState/reset. Add reset camera button to EditorTooltip with mobile-friendly touch targets and hover-only highlight to avoid sticky state on touch. --- src/core/rendering/ActorRenderer.js | 1 + src/core/rendering/BaseRenderer.js | 13 ++++- src/lib/EditorTooltip.svelte | 63 ++++++++++++++++++++++-- src/lib/save-editor/ActorEditor.svelte | 2 +- src/lib/save-editor/ScoreCube.svelte | 2 +- src/lib/save-editor/VehicleEditor.svelte | 2 +- 6 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/core/rendering/ActorRenderer.js b/src/core/rendering/ActorRenderer.js index 90542d7..34de012 100644 --- a/src/core/rendering/ActorRenderer.js +++ b/src/core/rendering/ActorRenderer.js @@ -96,6 +96,7 @@ export class ActorRenderer extends BaseRenderer { this.setupControls(new THREE.Vector3(0, 0.2, 0)); this.controls.autoRotate = false; + this._initialAutoRotate = false; this.raycaster = new THREE.Raycaster(); } diff --git a/src/core/rendering/BaseRenderer.js b/src/core/rendering/BaseRenderer.js index 9ce7f84..4cb55ad 100644 --- a/src/core/rendering/BaseRenderer.js +++ b/src/core/rendering/BaseRenderer.js @@ -41,8 +41,8 @@ export class BaseRenderer { setupControls(target) { this.controls = new OrbitControls(this.camera, this.canvas); - this.controls.enableZoom = false; - this.controls.enablePan = false; + this.controls.enableZoom = true; + this.controls.enablePan = true; this.controls.enableDamping = true; this.controls.dampingFactor = 0.1; this.controls.autoRotate = true; @@ -66,6 +66,15 @@ export class BaseRenderer { this.canvas.addEventListener('pointerdown', this._onPointerDown); this.canvas.addEventListener('pointermove', this._onPointerMove); + + this._initialAutoRotate = this.controls.autoRotate; + this.controls.saveState(); + } + + resetView() { + if (!this.controls) return; + this.controls.reset(); + this.controls.autoRotate = this._initialAutoRotate; } wasDragged() { diff --git a/src/lib/EditorTooltip.svelte b/src/lib/EditorTooltip.svelte index c178b3d..4918415 100644 --- a/src/lib/EditorTooltip.svelte +++ b/src/lib/EditorTooltip.svelte @@ -1,11 +1,26 @@
- ? - {text} - +
+ {#if onResetCamera} + + {/if} + ? + {text} + +
@@ -17,11 +32,51 @@ width: 100%; } - .tooltip-trigger { + .tooltip-icons { position: absolute; top: 0; right: 0; z-index: 1; + display: flex; + align-items: center; + gap: 8px; + } + + .reset-camera-btn { + position: relative; + width: 16px; + height: 16px; + border-radius: 50%; + background-color: var(--color-border-medium); + color: #eee; + display: inline-flex; + align-items: center; + justify-content: center; + border: none; + padding: 0; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + } + + /* Expand touch target on mobile */ + @media (pointer: coarse) { + .reset-camera-btn::before { + content: ''; + position: absolute; + inset: -10px; + } + } + + @media (hover: hover) { + .reset-camera-btn:hover { + background-color: var(--color-primary); + color: var(--color-bg-panel); + } + } + + .reset-camera-btn:active { + background-color: var(--color-primary); + color: var(--color-bg-panel); } .editor-tooltip-content { diff --git a/src/lib/save-editor/ActorEditor.svelte b/src/lib/save-editor/ActorEditor.svelte index 2909d97..d92df58 100644 --- a/src/lib/save-editor/ActorEditor.svelte +++ b/src/lib/save-editor/ActorEditor.svelte @@ -333,7 +333,7 @@ } - + renderer?.resetView()}>
- + renderer?.resetView()}>
- + renderer?.resetView()}>