import * as THREE from 'three'; import { useState, useEffect, useRef, useMemo } from "react"; import { useLoader, useFrame } from "@react-three/fiber"; import { GLTFLoader } from "three-stdlib"; import crate from "../../../../assets/gltf-glb/crate_box.glb"; import { useOrganization } from '../../../../store/store'; import { useControls } from 'leva'; type PathPoint = { position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string; }; type PathFlowProps = { path: PathPoint[]; connections: { start: PathPoint; end: PathPoint }[]; }; export default function PathFlow({ path, connections }: PathFlowProps) { const { organization } = useOrganization(); const [isPaused, setIsPaused] = useState(false); const [isStopped, setIsStopped] = useState(false); const { spawnInterval, speed, pauseResume, startStop } = useControls({ spawnInterval: { value: 1000, min: 500, max: 5000, step: 100 }, speed: { value: 2, min: 1, max: 20, step: 0.5 }, pauseResume: { value: false, label: "Pause/Resume" }, startStop: { value: false, label: "Start/Stop" }, }); const [meshes, setMeshes] = useState<{ id: number }[]>([]); const gltf = useLoader(GLTFLoader, crate); const meshIdRef = useRef(0); const lastSpawnTime = useRef(performance.now()); const totalPausedTime = useRef(0); const pauseStartTime = useRef(null); useEffect(() => { setIsPaused(pauseResume); setIsStopped(startStop); }, [pauseResume, startStop]); const removeMesh = (id: number) => { setMeshes((prev) => prev.filter((m) => m.id !== id)); }; useFrame(() => { if (isStopped || !path) return; const now = performance.now(); if (isPaused) { if (pauseStartTime.current === null) { pauseStartTime.current = now; } return; } if (pauseStartTime.current !== null) { totalPausedTime.current += now - pauseStartTime.current; pauseStartTime.current = null; } const adjustedTime = now - totalPausedTime.current; if (adjustedTime - lastSpawnTime.current >= spawnInterval) { setMeshes((prev) => [...prev, { id: meshIdRef.current++ }]); lastSpawnTime.current = adjustedTime; } }); return ( <> {meshes.map((mesh) => ( ))} ); } function MovingMesh({ meshId, points, speed, gltf, removeMesh, isPaused }: any) { const meshRef = useRef(); const startTime = useRef(null); // Initialize as null const pausedTime = useRef(0); const pauseStartTime = useRef(null); const distances = useMemo(() => { if (!points || points.length < 2) return []; return points.slice(1).map((point: any, i: number) => points[i].position.distanceTo(point.position)); }, [points]); useFrame(() => { if (!points || points.length < 2) return; if (startTime.current === null && points.length > 0) { startTime.current = performance.now(); } if (!meshRef.current) return; if (isPaused) { if (pauseStartTime.current === null) { pauseStartTime.current = performance.now(); } return; } if (pauseStartTime.current !== null) { pausedTime.current += performance.now() - pauseStartTime.current; pauseStartTime.current = null; } if (startTime.current === null) return; const elapsed = performance.now() - startTime.current - pausedTime.current; const distanceTraveled = elapsed / 1000 * speed; let remainingDistance = distanceTraveled; let currentSegmentIndex = 0; while (currentSegmentIndex < distances.length && remainingDistance > distances[currentSegmentIndex]) { remainingDistance -= distances[currentSegmentIndex]; currentSegmentIndex++; } if (currentSegmentIndex >= distances.length) { removeMesh(meshId); return; } const progress = remainingDistance / distances[currentSegmentIndex]; const start = points[currentSegmentIndex].position; const end = points[currentSegmentIndex + 1].position; meshRef.current.position.lerpVectors(start, end, Math.min(progress, 1)); const startRotation = points[currentSegmentIndex].rotation; const endRotation = points[currentSegmentIndex + 1].rotation; const interpolatedRotation = new THREE.Quaternion().slerpQuaternions(startRotation, endRotation, Math.min(progress, 1)); meshRef.current.quaternion.copy(interpolatedRotation); }); return ( <> {points && points.length > 0 && } ); }