Files
wireless-docs/camera.js

144 lines
5.3 KiB
JavaScript

// 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!
});
}
}