import React, { useEffect, useMemo, useState, useCallback, useRef, } from "react"; import { useSimulationPaths } from "../../../store/store"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; import { ConveyorEventsSchema, VehicleEventsSchema, } from "../../../types/world/worldTypes"; // Type definitions export interface PointAction { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: string | number; isUsed: boolean; } export interface PathPoint { uuid: string; position: [number, number, number]; actions: PointAction[]; connections: { targets: Array<{ pathUUID: string }>; }; } export interface SimulationPath { modeluuid: string; points: PathPoint[]; pathPosition: [number, number, number]; speed?: number; } export interface Process { id: string; paths: SimulationPath[]; animationPath: THREE.Vector3[]; pointActions: PointAction[][]; speed: number; } interface ProcessCreatorProps { onProcessesCreated: (processes: Process[]) => void; } // Convert event schemas to SimulationPath function convertToSimulationPath( path: ConveyorEventsSchema | VehicleEventsSchema ): SimulationPath { const { modeluuid } = path; // Simplified normalizeAction function that preserves exact original properties const normalizeAction = (action: any): PointAction => { return { ...action }; // Return exact copy with no modifications }; if (path.type === "Conveyor") { return { modeluuid, points: path.points.map((point) => ({ uuid: point.uuid, position: point.position, actions: point.actions.map(normalizeAction), // Preserve exact actions connections: { targets: point.connections.targets.map((target) => ({ pathUUID: target.pathUUID, })), }, })), pathPosition: path.position, speed: typeof path.speed === "string" ? parseFloat(path.speed) || 1 : path.speed || 1, }; } else { return { modeluuid, points: [ { uuid: path.point.uuid, position: path.point.position, actions: Array.isArray(path.point.actions) ? path.point.actions.map(normalizeAction) : [normalizeAction(path.point.actions)], connections: { targets: path.point.connections.targets.map((target) => ({ pathUUID: target.pathUUID, })), }, }, ], pathPosition: path.position, speed: path.point.speed || 1, }; } } // Custom shallow comparison for arrays const areArraysEqual = (a: any[], b: any[]) => { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; }; // Helper function to create an empty process const createEmptyProcess = (): Process => ({ id: `process-${Math.random().toString(36).substring(2, 11)}`, paths: [], animationPath: [], pointActions: [], speed: 1, }); // Enhanced connection checking function function shouldReverseNextPath( currentPath: SimulationPath, nextPath: SimulationPath ): boolean { if (nextPath.points.length !== 3) return false; const currentLastPoint = currentPath.points[currentPath.points.length - 1]; const nextFirstPoint = nextPath.points[0]; const nextLastPoint = nextPath.points[nextPath.points.length - 1]; // Check if current last connects to next last (requires reversal) const connectsToLast = currentLastPoint.connections.targets.some( (target) => target.pathUUID === nextPath.modeluuid && nextLastPoint.connections.targets.some( (t) => t.pathUUID === currentPath.modeluuid ) ); // Check if current last connects to next first (no reversal needed) const connectsToFirst = currentLastPoint.connections.targets.some( (target) => target.pathUUID === nextPath.modeluuid && nextFirstPoint.connections.targets.some( (t) => t.pathUUID === currentPath.modeluuid ) ); // Only reverse if connected to last point and not to first point return connectsToLast && !connectsToFirst; } // Updated path adjustment function function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] { if (paths.length < 2) return paths; const adjustedPaths = [...paths]; for (let i = 0; i < adjustedPaths.length - 1; i++) { const currentPath = adjustedPaths[i]; const nextPath = adjustedPaths[i + 1]; if (shouldReverseNextPath(currentPath, nextPath)) { const reversedPoints = [ nextPath.points[2], nextPath.points[1], nextPath.points[0], ]; adjustedPaths[i + 1] = { ...nextPath, points: reversedPoints, }; } } return adjustedPaths; } // Main hook for process creation export function useProcessCreation() { const { scene } = useThree(); const [processes, setProcesses] = useState([]); const hasSpawnAction = useCallback((path: SimulationPath): boolean => { return path.points.some((point) => point.actions.some((action) => action.type.toLowerCase() === "spawn") ); }, []); const createProcess = useCallback( (paths: SimulationPath[]): Process => { if (!paths || paths.length === 0) { return createEmptyProcess(); } const animationPath: THREE.Vector3[] = []; const pointActions: PointAction[][] = []; const processSpeed = paths[0]?.speed || 1; for (const path of paths) { for (const point of path.points) { const obj = scene.getObjectByProperty("uuid", point.uuid); if (!obj) { console.warn(`Object with UUID ${point.uuid} not found in scene`); continue; } const position = obj.getWorldPosition(new THREE.Vector3()); animationPath.push(position.clone()); pointActions.push(point.actions); } } return { id: `process-${Math.random().toString(36).substring(2, 11)}`, paths, animationPath, pointActions, speed: processSpeed, }; }, [scene] ); const getAllConnectedPaths = useCallback( ( initialPath: SimulationPath, allPaths: SimulationPath[], visited: Set = new Set() ): SimulationPath[] => { const connectedPaths: SimulationPath[] = []; const queue: SimulationPath[] = [initialPath]; visited.add(initialPath.modeluuid); const pathMap = new Map(); allPaths.forEach((path) => pathMap.set(path.modeluuid, path)); while (queue.length > 0) { const currentPath = queue.shift()!; connectedPaths.push(currentPath); // Process outgoing connections for (const point of currentPath.points) { for (const target of point.connections.targets) { if (!visited.has(target.pathUUID)) { const targetPath = pathMap.get(target.pathUUID); if (targetPath) { visited.add(target.pathUUID); queue.push(targetPath); } } } } // Process incoming connections for (const [uuid, path] of pathMap) { if (!visited.has(uuid)) { const hasConnectionToCurrent = path.points.some((point) => point.connections.targets.some( (t) => t.pathUUID === currentPath.modeluuid ) ); if (hasConnectionToCurrent) { visited.add(uuid); queue.push(path); } } } } return connectedPaths; }, [] ); const createProcessesFromPaths = useCallback( (paths: SimulationPath[]): Process[] => { if (!paths || paths.length === 0) return []; const visited = new Set(); const processes: Process[] = []; const pathMap = new Map(); paths.forEach((path) => pathMap.set(path.modeluuid, path)); for (const path of paths) { if (!visited.has(path.modeluuid) && hasSpawnAction(path)) { const connectedPaths = getAllConnectedPaths(path, paths, visited); const adjustedPaths = adjustPathPointsOrder(connectedPaths); const process = createProcess(adjustedPaths); processes.push(process); } } return processes; }, [createProcess, getAllConnectedPaths, hasSpawnAction] ); return { processes, createProcessesFromPaths, setProcesses, }; } const ProcessCreator: React.FC = React.memo( ({ onProcessesCreated }) => { const { simulationPaths } = useSimulationPaths(); const { createProcessesFromPaths } = useProcessCreation(); const prevPathsRef = useRef([]); const prevProcessesRef = useRef([]); const convertedPaths = useMemo((): SimulationPath[] => { if (!simulationPaths) return []; return simulationPaths.map((path) => convertToSimulationPath( path as ConveyorEventsSchema | VehicleEventsSchema ) ); }, [simulationPaths]); const pathsDependency = useMemo(() => { if (!convertedPaths) return null; return convertedPaths.map((path) => ({ id: path.modeluuid, hasSpawn: path.points.some((p: PathPoint) => p.actions.some((a: PointAction) => a.type.toLowerCase() === "spawn") ), connections: path.points .flatMap((p: PathPoint) => p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID) ) .join(","), })); }, [convertedPaths]); useEffect(() => { if (!convertedPaths || convertedPaths.length === 0) { if (prevProcessesRef.current.length > 0) { onProcessesCreated([]); prevProcessesRef.current = []; } return; } if (areArraysEqual(prevPathsRef.current, convertedPaths)) { return; } prevPathsRef.current = convertedPaths; const newProcesses = createProcessesFromPaths(convertedPaths); // console.log("--- Action Types in Paths ---"); // convertedPaths.forEach((path) => { // path.points.forEach((point) => { // point.actions.forEach((action) => { // console.log( // `Path ${path.modeluuid}, Point ${point.uuid}: ${action.type}` // ); // }); // }); // }); // console.log("New processes:", newProcesses); if ( newProcesses.length !== prevProcessesRef.current.length || !newProcesses.every( (proc, i) => proc.paths.length === prevProcessesRef.current[i]?.paths.length && proc.paths.every( (path, j) => path.modeluuid === prevProcessesRef.current[i]?.paths[j]?.modeluuid ) ) ) { onProcessesCreated(newProcesses); // prevProcessesRef.current = newProcesses; } }, [ pathsDependency, onProcessesCreated, convertedPaths, createProcessesFromPaths, ]); return null; } ); export default ProcessCreator;