import { useRef, useEffect } from "react"; import { useThree } from "@react-three/fiber"; import { Vector2, Euler } from "three"; import { Touch, DefaultTouch, getCurrentTouch, tappedNipple, } from "../utils/touch"; const DRAG_SENSITIVITY = new Vector2(0.7, 0.7); /** * TouchFPSCamera controls the camera rotation by detecting * touch drag on the screen. Unlike MouseFPSCamera, this component * does not have a way to pause, that must be done externally. * * @param props * @constructor */ export default function TouchFPSCamera() { const touchStartPos = useRef<Touch>(DefaultTouch); const originEuler = useRef<Euler>(new Euler(0, 0, 0, "YXZ")); const camera = useThree((state) => state.camera); const getNewEuler = (dragX: number, dragY: number): Euler => { const newEuler = originEuler.current.clone(); const moveX = dragX - touchStartPos.current.pos.x; const moveY = dragY - touchStartPos.current.pos.y; newEuler.setFromQuaternion(camera.quaternion); newEuler.y = originEuler.current.y - (moveX * DRAG_SENSITIVITY.x) / 100; newEuler.x = originEuler.current.x - (moveY * DRAG_SENSITIVITY.y) / 100; newEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, newEuler.x)); return newEuler; }; // touch move scripts const onTouchStart = (ev: TouchEvent) => { if (touchStartPos.current.id !== -1) { return; } if (tappedNipple(ev)) { touchStartPos.current = DefaultTouch; return; } // get last in list (most recent touch) to not confuse with movement const touchIndex = ev.touches.length - 1; const { clientX, clientY, identifier: id } = ev.touches[touchIndex]; touchStartPos.current = { pos: new Vector2(clientX, clientY), id }; originEuler.current.setFromQuaternion(camera.quaternion); }; const onTouchMove = (ev: TouchEvent) => { const touch = getCurrentTouch(touchStartPos.current.id, ev.touches); if (!touch) { return; } const { clientX, clientY } = touch; const newEuler = getNewEuler(clientX, clientY); camera.quaternion.setFromEuler(newEuler); }; const onTouchEnd = (ev: TouchEvent) => { const touch = getCurrentTouch(touchStartPos.current.id, ev.changedTouches); if (!touch) { return; } const { clientX, clientY } = touch; originEuler.current = getNewEuler(clientX, clientY); touchStartPos.current.id = -1; }; useEffect(() => { document.addEventListener("touchstart", onTouchStart); document.addEventListener("touchmove", onTouchMove); document.addEventListener("touchend", onTouchEnd); return () => { document.removeEventListener("touchstart", onTouchStart); document.removeEventListener("touchmove", onTouchMove); document.removeEventListener("touchend", onTouchEnd); }; }, []); return null; }