164 lines
5.4 KiB
TypeScript
164 lines
5.4 KiB
TypeScript
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<number | null>(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) => (
|
|
<MovingMesh
|
|
key={mesh.id}
|
|
meshId={mesh.id}
|
|
points={path}
|
|
speed={speed}
|
|
gltf={gltf}
|
|
removeMesh={removeMesh}
|
|
isPaused={isPaused}
|
|
/>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function MovingMesh({ meshId, points, speed, gltf, removeMesh, isPaused }: any) {
|
|
const meshRef = useRef<any>();
|
|
const startTime = useRef<number | null>(null); // Initialize as null
|
|
const pausedTime = useRef(0);
|
|
const pauseStartTime = useRef<number | null>(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 &&
|
|
<mesh ref={meshRef}>
|
|
<primitive object={gltf.scene.clone()} />
|
|
</mesh>
|
|
}
|
|
</>
|
|
);
|
|
} |