|
import type { Camera } from "../cameras/Camera"; |
|
import { Matrix3 } from "../math/Matrix3"; |
|
import { Vector3 } from "../math/Vector3"; |
|
|
|
class OrbitControls { |
|
minBeta: number = (5 * Math.PI) / 180; |
|
maxBeta: number = (85 * Math.PI) / 180; |
|
minZoom: number = 0.1; |
|
maxZoom: number = 30; |
|
orbitSpeed: number = 1; |
|
panSpeed: number = 1; |
|
zoomSpeed: number = 1; |
|
dampening: number = 0.3; |
|
|
|
update: () => void; |
|
dispose: () => void; |
|
|
|
constructor(camera: Camera, domElement: HTMLElement) { |
|
let target = new Vector3(); |
|
let alpha = 0; |
|
let beta = 0; |
|
let radius = 5; |
|
|
|
let desiredTarget = target.clone(); |
|
let desiredAlpha = alpha; |
|
let desiredBeta = beta; |
|
let desiredRadius = radius; |
|
|
|
let dragging = false; |
|
let panning = false; |
|
let lastX = 0; |
|
let lastY = 0; |
|
|
|
const computeZoomNorm = () => { |
|
return 0.1 + (0.9 * (desiredRadius - this.minZoom)) / (this.maxZoom - this.minZoom); |
|
}; |
|
|
|
const onPointerDown = (e: PointerEvent) => { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
|
|
dragging = true; |
|
if (e.pointerType === "touch") { |
|
panning = e.pointerId === 2; |
|
} else { |
|
panning = e.button === 2; |
|
} |
|
lastX = e.clientX; |
|
lastY = e.clientY; |
|
window.addEventListener("pointerup", onPointerUp); |
|
window.addEventListener("pointercancel", onPointerUp); |
|
}; |
|
|
|
const onPointerUp = (e: PointerEvent) => { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
|
|
dragging = false; |
|
panning = false; |
|
window.removeEventListener("pointerup", onPointerUp); |
|
window.removeEventListener("pointercancel", onPointerUp); |
|
}; |
|
|
|
const onPointerMove = (e: PointerEvent) => { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
|
|
if (!dragging) return; |
|
|
|
const dx = e.clientX - lastX; |
|
const dy = e.clientY - lastY; |
|
const zoomNorm = computeZoomNorm(); |
|
|
|
if (panning) { |
|
const panX = -dx * this.panSpeed * 0.01 * zoomNorm; |
|
const panY = -dy * this.panSpeed * 0.01 * zoomNorm; |
|
const R = camera.rotation.buffer; |
|
const right = new Vector3(R[0], R[3], R[6]); |
|
const up = new Vector3(R[1], R[4], R[7]); |
|
desiredTarget.add(right.multiply(panX)); |
|
desiredTarget.add(up.multiply(panY)); |
|
} else { |
|
desiredAlpha -= dx * this.orbitSpeed * 0.005; |
|
desiredBeta += dy * this.orbitSpeed * 0.005; |
|
desiredBeta = Math.min(Math.max(desiredBeta, this.minBeta), this.maxBeta); |
|
} |
|
|
|
lastX = e.clientX; |
|
lastY = e.clientY; |
|
}; |
|
|
|
const onWheel = (e: WheelEvent) => { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
|
|
const zoomNorm = computeZoomNorm(); |
|
desiredRadius += e.deltaY * this.zoomSpeed * 0.02 * zoomNorm; |
|
desiredRadius = Math.min(Math.max(desiredRadius, this.minZoom), this.maxZoom); |
|
}; |
|
|
|
const lerp = (a: number, b: number, t: number) => { |
|
return (1 - t) * a + t * b; |
|
}; |
|
|
|
this.update = () => { |
|
alpha = lerp(alpha, desiredAlpha, this.dampening); |
|
beta = lerp(beta, desiredBeta, this.dampening); |
|
radius = lerp(radius, desiredRadius, this.dampening); |
|
target = target.lerp(desiredTarget, this.dampening); |
|
|
|
const x = target.x + radius * Math.sin(alpha) * Math.cos(beta); |
|
const y = target.y - radius * Math.sin(beta); |
|
const z = target.z - radius * Math.cos(alpha) * Math.cos(beta); |
|
camera.position.set(x, y, z); |
|
|
|
const direction = target.clone().subtract(camera.position).normalize(); |
|
const rx = Math.asin(-direction.y); |
|
const ry = Math.atan2(direction.x, direction.z); |
|
camera.rotation = Matrix3.RotationFromEuler(new Vector3(rx, ry, 0)); |
|
}; |
|
|
|
this.dispose = () => { |
|
domElement.removeEventListener("pointerdown", onPointerDown); |
|
domElement.removeEventListener("pointermove", onPointerMove); |
|
domElement.removeEventListener("wheel", onWheel); |
|
}; |
|
|
|
domElement.addEventListener("pointerdown", onPointerDown); |
|
domElement.addEventListener("pointermove", onPointerMove); |
|
domElement.addEventListener("wheel", onWheel); |
|
|
|
this.update(); |
|
} |
|
} |
|
|
|
export { OrbitControls }; |
|
|