import React, { useEffect, useState, useRef } from "react"; import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { NavMeshQuery } from "@recast-navigation/core"; import { Line } from "@react-three/drei"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; interface PathNavigatorProps { navMesh: any; pathPoints: any; id: string; speed: number; bufferTime: number; hitCount: number; } export default function PathNavigator({ navMesh, pathPoints, id, speed, bufferTime, hitCount }: PathNavigatorProps) { const [path, setPath] = useState<[number, number, number][]>([]); const [currentPhase, setCurrentPhase] = useState<'initial' | 'loop'>('initial'); const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>([]); const [pickupDropPath, setPickupDropPath] = useState<[number, number, number][]>([]); const [dropPickupPath, setDropPickupPath] = useState<[number, number, number][]>([]); const [initialPosition, setInitialPosition] = useState(null); const [initialRotation, setInitialRotation] = useState(null); const distancesRef = useRef([]); const totalDistanceRef = useRef(0); const progressRef = useRef(0); const isWaiting = useRef(false); const timeoutRef = useRef(null); const { scene } = useThree(); const { isPlaying } = usePlayButtonStore(); useEffect(() => { const object = scene.getObjectByProperty("uuid", id); if (object) { setInitialPosition(object.position.clone()); setInitialRotation(object.rotation.clone()); } }, [scene, id]); const computePath = (start: any, end: any) => { try { const navMeshQuery = new NavMeshQuery(navMesh); const { path: segmentPath } = navMeshQuery.computePath(start, end); return segmentPath?.map(({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]) || []; } catch { return []; } }; const resetState = () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } setPath([]); setCurrentPhase('initial'); setPickupDropPath([]); setDropPickupPath([]); distancesRef.current = []; totalDistanceRef.current = 0; progressRef.current = 0; isWaiting.current = false; if (initialPosition && initialRotation) { const object = scene.getObjectByProperty("uuid", id); if (object) { object.position.copy(initialPosition); object.rotation.copy(initialRotation); } } }; useEffect(() => { if (!isPlaying) { resetState(); } if (!navMesh || pathPoints.length < 2) return; const [pickup, drop] = pathPoints.slice(-2); const object = scene.getObjectByProperty("uuid", id); if (!object) return; const currentPosition = { x: object.position.x, y: object.position.y, z: object.position.z }; const toPickupPath = computePath(currentPosition, pickup); const pickupToDropPath = computePath(pickup, drop); const dropToPickupPath = computePath(drop, pickup); if (toPickupPath.length && pickupToDropPath.length && dropToPickupPath.length) { setPickupDropPath(pickupToDropPath); setDropPickupPath(dropToPickupPath); setToPickupPath(toPickupPath); setPath(toPickupPath); setCurrentPhase('initial'); } }, [navMesh, pathPoints, hitCount, isPlaying]); useEffect(() => { if (path.length < 2) return; let total = 0; const segmentDistances = path.slice(0, -1).map((point, i) => { const dist = new THREE.Vector3(...point).distanceTo(new THREE.Vector3(...path[i + 1])); total += dist; return dist; }); distancesRef.current = segmentDistances; totalDistanceRef.current = total; progressRef.current = 0; isWaiting.current = false; }, [path]); useFrame((_, delta) => { if (!isPlaying || path.length < 2 || !scene || !id) return; const object = scene.getObjectByProperty("uuid", id); if (!object) return; const speedFactor = speed; progressRef.current += delta * speedFactor; let covered = progressRef.current; let accumulated = 0; let index = 0; while ( index < distancesRef.current.length && covered > accumulated + distancesRef.current[index] ) { accumulated += distancesRef.current[index]; index++; } if (index >= distancesRef.current.length) { progressRef.current = totalDistanceRef.current; if (!isWaiting.current) { isWaiting.current = true; timeoutRef.current = setTimeout(() => { if (currentPhase === 'initial') { setPath(pickupDropPath); setCurrentPhase('loop'); } else { setPath(prevPath => prevPath === pickupDropPath ? dropPickupPath : pickupDropPath ); } progressRef.current = 0; isWaiting.current = false; }, bufferTime * 1000); } return; } const start = new THREE.Vector3(...path[index]); const end = new THREE.Vector3(...path[index + 1]); const dist = distancesRef.current[index]; const t = THREE.MathUtils.clamp((covered - accumulated) / dist, 0, 1); const position = start.clone().lerp(end, t); object.position.copy(position); const direction = new THREE.Vector3().subVectors(end, start).normalize(); const targetRotationY = Math.atan2(direction.x, direction.z); let angleDifference = targetRotationY - object.rotation.y; angleDifference = ((angleDifference + Math.PI) % (Math.PI * 2)) - Math.PI; object.rotation.y += angleDifference * 0.1; }); useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); return ( {toPickupPath.length > 0 && ( )} {pickupDropPath.length > 0 && ( )} {dropPickupPath.length > 0 && ( )} ); }