144 lines
5.3 KiB
JavaScript
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!
|
|
});
|
|
}
|
|
} |