implemented touch controls for the camera

This commit is contained in:
csawatzky 2026-06-12 11:43:58 -06:00
parent e8c31b23ee
commit a6b31c30c9

View file

@ -73,6 +73,7 @@ export default function OrbitCameraControls(props: Props) {
const isPanning = useRef(false); const isPanning = useRef(false);
const lastPos = useRef({ x: 0, y: 0 }); const lastPos = useRef({ x: 0, y: 0 });
const panCameraPosition = useRef(new Vector3()); const panCameraPosition = useRef(new Vector3());
const lastPinchDist = useRef(0);
// Spherical coords // Spherical coords
const spherical = useRef({ const spherical = useRef({
@ -222,16 +223,105 @@ export default function OrbitCameraControls(props: Props) {
updateCamera(); updateCamera();
}; };
const handleTouchStart = (e: TouchEvent) => {
if (e.target !== canvas) return;
e.preventDefault();
if (e.touches.length === 1) {
isDragging.current = true;
isPanning.current = false;
lastPos.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
} else if (e.touches.length === 2) {
isDragging.current = false;
isPanning.current = true;
lastPos.current = {
x: (e.touches[0].clientX + e.touches[1].clientX) / 2,
y: (e.touches[0].clientY + e.touches[1].clientY) / 2,
};
// Store initial pinch distance for zoom
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
lastPinchDist.current = Math.sqrt(dx * dx + dy * dy);
}
};
const handleTouchMove = (e: TouchEvent) => {
e.preventDefault();
if (e.touches.length === 1 && isDragging.current) {
const deltaX = e.touches[0].clientX - lastPos.current.x;
const deltaY = e.touches[0].clientY - lastPos.current.y;
spherical.current.theta -= deltaX * 0.005;
spherical.current.phi -= deltaY * 0.005;
if (clampVerticalRotation) {
spherical.current.phi = Math.max(minPhi, Math.min(maxPhi, spherical.current.phi));
}
lastPos.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
updateCamera();
} else if (e.touches.length === 2) {
// Pinch zoom
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const dist = Math.sqrt(dx * dx + dy * dy);
const pinchDelta = lastPinchDist.current - dist;
spherical.current.radius += pinchDelta * 0.05;
spherical.current.radius = Math.max(3, Math.min(maxRadius ?? 30, spherical.current.radius));
lastPinchDist.current = dist;
// Two-finger pan
const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
const panDeltaX = midX - lastPos.current.x;
const panDeltaY = midY - lastPos.current.y;
const distance = spherical.current.radius;
const panSpeed = 0.002 * distance;
const forward = new Vector3();
camera.getWorldDirection(forward);
const right = new Vector3().crossVectors(forward, new Vector3(0, 1, 0)).normalize();
if (right.lengthSq() < 0.001) right.crossVectors(forward, camera.up).normalize();
const screenUp = new Vector3().crossVectors(right, forward).normalize();
right.multiplyScalar(-panDeltaX * panSpeed);
screenUp.multiplyScalar(panDeltaY * panSpeed);
const pan = new Vector3().addVectors(right, screenUp);
targetRef.current.x += pan.x;
targetRef.current.y += pan.y;
targetRef.current.z += pan.z;
lastPos.current = { x: midX, y: midY };
updateCamera();
}
};
const handleTouchEnd = (e: TouchEvent) => {
if (e.touches.length === 0) {
isDragging.current = false;
isPanning.current = false;
} else if (e.touches.length === 1) {
// Transitioned from pinch back to single finger
isDragging.current = true;
isPanning.current = false;
lastPos.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
};
window.addEventListener("mousedown", handleMouseDown); window.addEventListener("mousedown", handleMouseDown);
window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp); window.addEventListener("mouseup", handleMouseUp);
canvas.addEventListener("wheel", handleWheel, { passive: false }); canvas.addEventListener("wheel", handleWheel, { passive: false });
canvas.addEventListener("touchstart", handleTouchStart, { passive: false });
canvas.addEventListener("touchmove", handleTouchMove, { passive: false });
canvas.addEventListener("touchend", handleTouchEnd);
return () => { return () => {
window.removeEventListener("mousedown", handleMouseDown); window.removeEventListener("mousedown", handleMouseDown);
window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp); window.removeEventListener("mouseup", handleMouseUp);
canvas.removeEventListener("wheel", handleWheel); canvas.removeEventListener("wheel", handleWheel);
canvas.removeEventListener("touchstart", handleTouchStart);
canvas.removeEventListener("touchmove", handleTouchMove);
canvas.removeEventListener("touchend", handleTouchEnd);
}; };
}, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi, initialRadius, maxRadius]); }, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi, initialRadius, maxRadius]);