| import * as THREE from "three"; |
|
|
| const LOOK_SPEED = 0.003; |
| const _yawQ = new THREE.Quaternion(); |
| const _pitQ = new THREE.Quaternion(); |
| const raycaster = new THREE.Raycaster(); |
| const _ndc = new THREE.Vector2(); |
|
|
| function raycastAt(mesh, camera, clientX, clientY) { |
| _ndc.set((clientX / innerWidth) * 2 - 1, -(clientY / innerHeight) * 2 + 1); |
| raycaster.setFromCamera(_ndc, camera); |
| const hits = []; |
| mesh.raycast(raycaster, hits); |
| hits.sort((a, b) => a.distance - b.distance); |
| return hits[0] ?? null; |
| } |
|
|
| export function setupControls(domElement, camera, mesh) { |
| let dragging = false, |
| dragButton = 0, |
| lastX = 0, |
| lastY = 0; |
|
|
| domElement.addEventListener("pointerdown", (e) => { |
| dragging = true; |
| dragButton = e.button; |
| lastX = e.clientX; |
| lastY = e.clientY; |
| domElement.setPointerCapture(e.pointerId); |
| }); |
|
|
| domElement.addEventListener("pointermove", (e) => { |
| if (!dragging) return; |
| const dx = e.clientX - lastX, |
| dy = e.clientY - lastY; |
| lastX = e.clientX; |
| lastY = e.clientY; |
|
|
| if (dragButton === 0) { |
| |
| const camUp = new THREE.Vector3(0, 1, 0).applyQuaternion( |
| camera.quaternion, |
| ); |
| camera.quaternion.premultiply( |
| _yawQ.setFromAxisAngle(camUp, -dx * LOOK_SPEED), |
| ); |
| const camRight = new THREE.Vector3(1, 0, 0).applyQuaternion( |
| camera.quaternion, |
| ); |
| camera.quaternion.premultiply( |
| _pitQ.setFromAxisAngle(camRight, -dy * LOOK_SPEED), |
| ); |
| camera.quaternion.normalize(); |
| } else if (dragButton === 2) { |
| |
| const hit = raycastAt(mesh, camera, lastX, lastY); |
| const panSpeed = (hit ? hit.distance : 5) * 0.001; |
| const right = new THREE.Vector3(1, 0, 0).applyQuaternion( |
| camera.quaternion, |
| ); |
| const up = new THREE.Vector3(0, 1, 0).applyQuaternion(camera.quaternion); |
| camera.position.addScaledVector(right, -dx * panSpeed); |
| camera.position.addScaledVector(up, dy * panSpeed); |
| } |
| }); |
|
|
| domElement.addEventListener("pointerup", () => { |
| dragging = false; |
| }); |
| domElement.addEventListener("contextmenu", (e) => e.preventDefault()); |
|
|
| domElement.addEventListener( |
| "wheel", |
| (e) => { |
| const hit = raycastAt(mesh, camera, e.clientX, e.clientY); |
| const dist = hit ? hit.distance : 10; |
| camera.position.addScaledVector( |
| raycaster.ray.direction, |
| -(dist * e.deltaY * 0.001), |
| ); |
| }, |
| { passive: true }, |
| ); |
| } |
|
|