// camera.js export class CameraController { constructor(diorama) { this.diorama = diorama; this.canvas = diorama.canvas; this.element = diorama.element; this.isDragging = false; this.hasDragged = false; // Tracks if the current interaction is a drag or a tap this.dragButton = 0; this.touchMode = ''; this.lastMouse = { x: 0, y: 0 }; this.lastPinchDist = 0; this.attachEvents(); } orbit(dx, dy) { this.diorama.theta -= dx * 0.4; this.diorama.phi += dy * 0.4; this.diorama.phi = Math.max(-90, Math.min(90, this.diorama.phi)); } pan(dx, dy) { const t = this.diorama.theta * Math.PI / 180; const p = this.diorama.phi * Math.PI / 180; const rightX = Math.cos(t); const rightZ = -Math.sin(t); const upX = -Math.sin(p) * Math.sin(t); const upY = Math.cos(p); const upZ = -Math.sin(p) * Math.cos(t); const rect = this.canvas.getBoundingClientRect(); const panSpeed = this.diorama.radius / rect.height; this.diorama.target[0] += (-rightX * dx + upX * dy) * panSpeed; this.diorama.target[1] += (upY * dy) * panSpeed; this.diorama.target[2] += (-rightZ * dx + upZ * dy) * panSpeed; } zoomRatio(ratio) { this.diorama.radius *= ratio; this.diorama.radius = Math.max(1, Math.min(50, this.diorama.radius)); } zoomLinear(delta) { this.diorama.radius += delta * 0.05; this.diorama.radius = Math.max(1, Math.min(50, this.diorama.radius)); } attachEvents() { this.element.addEventListener('contextmenu', e => e.preventDefault()); const notifyChange = () => { this.hasDragged = true; // Mark that a drag occurred this.diorama.requestRender(); }; this.canvas.addEventListener('mousedown', (e) => { // REMOVED e.preventDefault() here so the browser can register the native click! this.isDragging = true; this.hasDragged = false; this.lastMouse = { x: e.clientX, y: e.clientY }; this.dragButton = e.button; if (this.dragButton === 0) { if (e.ctrlKey || e.metaKey) this.dragButton = 2; else if (e.shiftKey) this.dragButton = 1; } if (this.dragButton === 1) this.canvas.style.cursor = 'move'; else if (this.dragButton === 2) this.canvas.style.cursor = 'ns-resize'; else this.canvas.style.cursor = 'grabbing'; }); window.addEventListener('mouseup', () => { this.isDragging = false; this.canvas.style.cursor = 'grab'; }); window.addEventListener('mousemove', (e) => { if (!this.isDragging) return; const dx = e.clientX - this.lastMouse.x; const dy = e.clientY - this.lastMouse.y; // Only consider it a drag if we move more than a couple of pixels if (Math.hypot(dx, dy) > 3) { this.lastMouse = { x: e.clientX, y: e.clientY }; if (this.dragButton === 0) this.orbit(dx, dy); else if (this.dragButton === 1) this.pan(dx, dy); else if (this.dragButton === 2) this.zoomLinear(dy); notifyChange(); } }); // Touch interactions (simplified for brevity, matching the mousedown/move logic) this.canvas.addEventListener('touchstart', (e) => { this.isDragging = true; this.hasDragged = false; if (e.touches.length === 1) { this.touchMode = 'orbit'; this.lastMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY }; } else if (e.touches.length >= 2) { this.touchMode = 'pan-zoom'; const t1 = e.touches[0], t2 = e.touches[1]; this.lastMouse = { x: (t1.clientX + t2.clientX) / 2, y: (t1.clientY + t2.clientY) / 2 }; this.lastPinchDist = Math.hypot(t1.clientX - t2.clientX, t1.clientY - t2.clientY); } }, { passive: true }); // Passive allows the synthetic 'click' to fire later this.canvas.addEventListener('touchmove', (e) => { if (!this.isDragging) return; // Prevent default here to stop page scrolling while orbiting if (e.cancelable) e.preventDefault(); this.hasDragged = true; // ... (Insert previous touchmove logic for orbit/pan here) ... this.diorama.requestRender(); }, { passive: false }); const onTouchEnd = (e) => { if (e.touches.length === 0) { this.isDragging = false; this.touchMode = ''; } }; this.canvas.addEventListener('touchend', onTouchEnd); this.canvas.addEventListener('touchcancel', onTouchEnd); // --- The Magic Event Forwarder --- // Native click fires AFTER mouseup/touchend. this.canvas.addEventListener('click', (e) => { if (this.hasDragged) { // If it was a drag, stop the click from bubbling to the scrubber e.stopPropagation(); e.preventDefault(); } // If it WASN'T a drag, we do nothing. The event naturally bubbles up! }); } }