1
0
Files
VR-Web-Player/src/vr180player/fallback-camera-controls.ts
2026-06-10 11:26:29 +10:00

169 lines
5.2 KiB
TypeScript

import type { ProjectionMode } from './config.js';
type CameraControlsCallbacks = {
hideControls: () => void;
isEnabled: () => boolean;
showControls: () => void;
};
const MOUSE_SENSITIVITY = 0.002;
const TOUCH_SENSITIVITY = 0.003;
const MOMENTUM_DAMPING = 0.8;
const MAX_PITCH = Math.PI * (45 / 180);
const MAX_YAW = Math.PI * (45 / 180);
export class FallbackCameraControls {
private camera: any;
private readonly callbacks: CameraControlsCallbacks;
private cameraRotation = { yaw: 0, pitch: 0 };
private cameraVelocity = { yaw: 0, pitch: 0 };
private dragging = false;
private lastMouseX = 0;
private lastMouseY = 0;
private lastTouchX = 0;
private lastTouchY = 0;
constructor(camera: any, callbacks: CameraControlsCallbacks) {
this.camera = camera;
this.callbacks = callbacks;
}
get isDragging(): boolean {
return this.dragging;
}
reset(): void {
this.cameraRotation = { yaw: 0, pitch: 0 };
this.cameraVelocity = { yaw: 0, pitch: 0 };
this.dragging = false;
if (this.camera) {
this.camera.rotation.set(0, 0, 0);
}
}
updateCameraRotation(): void {
if (!this.camera) return;
this.cameraRotation.yaw += this.cameraVelocity.yaw;
this.cameraRotation.pitch += this.cameraVelocity.pitch;
this.cameraRotation.pitch = Math.max(-MAX_PITCH, Math.min(MAX_PITCH, this.cameraRotation.pitch));
this.cameraRotation.yaw = Math.max(-MAX_YAW, Math.min(MAX_YAW, this.cameraRotation.yaw));
this.cameraVelocity.yaw *= MOMENTUM_DAMPING;
this.cameraVelocity.pitch *= MOMENTUM_DAMPING;
if (Math.abs(this.cameraVelocity.yaw) < 0.001) this.cameraVelocity.yaw = 0;
if (Math.abs(this.cameraVelocity.pitch) < 0.001) this.cameraVelocity.pitch = 0;
this.camera.rotation.set(this.cameraRotation.pitch, this.cameraRotation.yaw, 0);
}
addEventListeners(canvas: HTMLElement, projectionMode: ProjectionMode): void {
canvas.addEventListener('mousemove', this.onCanvasMouseMove);
if (projectionMode === 'vr180') {
canvas.addEventListener('mousedown', this.onMouseDown);
canvas.addEventListener('mousemove', this.onMouseMove);
canvas.addEventListener('mouseup', this.onMouseUp);
canvas.addEventListener('touchstart', this.onTouchStart, { passive: false });
canvas.addEventListener('touchmove', this.onTouchMove, { passive: false });
canvas.addEventListener('touchend', this.onTouchEnd, { passive: false });
} else {
canvas.addEventListener('touchstart', this.onCanvasTouchStart, { passive: true });
}
}
removeEventListeners(canvas: HTMLElement): void {
canvas.removeEventListener('mousemove', this.onCanvasMouseMove);
canvas.removeEventListener('mousedown', this.onMouseDown);
canvas.removeEventListener('mousemove', this.onMouseMove);
canvas.removeEventListener('mouseup', this.onMouseUp);
canvas.removeEventListener('touchstart', this.onTouchStart);
canvas.removeEventListener('touchmove', this.onTouchMove);
canvas.removeEventListener('touchend', this.onTouchEnd);
canvas.removeEventListener('touchstart', this.onCanvasTouchStart);
}
private readonly onCanvasMouseMove = (): void => {
if (this.callbacks.isEnabled() && !this.dragging) {
this.callbacks.showControls();
}
};
private readonly onCanvasTouchStart = (): void => {
if (this.callbacks.isEnabled()) {
this.callbacks.showControls();
}
};
private readonly onMouseDown = (event: MouseEvent): void => {
if (!this.callbacks.isEnabled()) return;
this.dragging = true;
this.lastMouseX = event.clientX;
this.lastMouseY = event.clientY;
this.cameraVelocity.yaw = 0;
this.cameraVelocity.pitch = 0;
this.callbacks.hideControls();
};
private readonly onMouseMove = (event: MouseEvent): void => {
if (!this.callbacks.isEnabled() || !this.dragging) return;
const deltaX = event.clientX - this.lastMouseX;
const deltaY = event.clientY - this.lastMouseY;
this.cameraVelocity.yaw = deltaX * MOUSE_SENSITIVITY;
this.cameraVelocity.pitch = deltaY * MOUSE_SENSITIVITY;
this.lastMouseX = event.clientX;
this.lastMouseY = event.clientY;
};
private readonly onMouseUp = (): void => {
if (!this.callbacks.isEnabled()) return;
this.dragging = false;
this.callbacks.showControls();
};
private readonly onTouchStart = (event: TouchEvent): void => {
if (!this.callbacks.isEnabled()) return;
event.preventDefault();
if (event.touches.length === 1) {
this.dragging = true;
this.lastTouchX = event.touches[0].clientX;
this.lastTouchY = event.touches[0].clientY;
this.cameraVelocity.yaw = 0;
this.cameraVelocity.pitch = 0;
this.callbacks.hideControls();
}
};
private readonly onTouchMove = (event: TouchEvent): void => {
if (!this.callbacks.isEnabled() || !this.dragging) return;
event.preventDefault();
if (event.touches.length === 1) {
const deltaX = event.touches[0].clientX - this.lastTouchX;
const deltaY = event.touches[0].clientY - this.lastTouchY;
this.cameraVelocity.yaw = deltaX * TOUCH_SENSITIVITY;
this.cameraVelocity.pitch = deltaY * TOUCH_SENSITIVITY;
this.lastTouchX = event.touches[0].clientX;
this.lastTouchY = event.touches[0].clientY;
}
};
private readonly onTouchEnd = (event: TouchEvent): void => {
if (!this.callbacks.isEnabled()) return;
event.preventDefault();
this.dragging = false;
this.callbacks.showControls();
};
}