import { useRef, useEffect, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { config, useSpring } from "react-spring"; import { Vector2, Euler } from "three"; import { useEnvironment } from "../layers/environment"; const DRAG_SENSITIVITY = new Vector2(0.16, 0.16); const HOVER_SENSITIVITY = new Vector2(0.02, 0.02); /** * DragControls allows for a click-and-drag control * of the camera * * @param props * @constructor */ export function DragControls() { const originEuler = useRef<Euler>(new Euler(0, 0, 0, "YXZ")); const setEuler = useRef<Euler>(new Euler(0, 0, 0, "YXZ")); const mouseDownPos = useRef<Vector2>(new Vector2(0, 0)); const dragging = useRef(false); const camera = useThree((state) => state.camera); const { containerRef } = useEnvironment(); const [xyz, setXYZ] = useState([0, 0, 0]); const { x, y, z } = useSpring({ x: xyz[0], y: xyz[1], z: xyz[2], config: { ...config.default, precision: 0.0001 }, }); useFrame(() => { if (setEuler.current) { setEuler.current.set(x.get(), y.get(), z.get()); camera.quaternion.setFromEuler(setEuler.current); } }); const getNewEuler = ( dragX: number, dragY: number, isHover?: boolean ): Euler => { const newEuler = originEuler.current.clone(); const moveX = dragX - mouseDownPos.current.x; const moveY = dragY - mouseDownPos.current.y; const SENSITIVITY = isHover ? HOVER_SENSITIVITY : DRAG_SENSITIVITY; newEuler.setFromQuaternion(camera.quaternion); newEuler.y = originEuler.current.y - (moveX * SENSITIVITY.x) / 100; newEuler.x = originEuler.current.x - (moveY * SENSITIVITY.y) / 100; // newEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, newEuler.x)); return newEuler; }; // touch move scripts const onMouseDown = (ev: MouseEvent) => { dragging.current = true; mouseDownPos.current.set(ev.clientX, ev.clientY); containerRef?.current?.classList.add("grabbing"); }; const onMouseMove = (ev: MouseEvent) => { const newEuler = getNewEuler(ev.clientX, ev.clientY, !dragging.current); setXYZ(newEuler.toArray()); }; const onMouseUp = (ev: MouseEvent) => { dragging.current = false; originEuler.current = getNewEuler(ev.clientX, ev.clientY); mouseDownPos.current.set(ev.clientX, ev.clientY); containerRef?.current?.classList.remove("grabbing"); }; useEffect(() => { document.addEventListener("mousedown", onMouseDown); document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); return () => { document.removeEventListener("mousedown", onMouseDown); document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); }; }, [containerRef]); return null; }