// 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; 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; } // Check if a point has a spawn action function hasSpawnAction(point: PathPoint): boolean { return point.actions.some((action) => action.type.toLowerCase() === "spawn"); } // Ensure spawn point is always at the beginning of the path function ensureSpawnPointIsFirst(path: SimulationPath): SimulationPath { if (path.points.length !== 3) return path; // If the third point has spawn action and first doesn't, reverse the array if (hasSpawnAction(path.points[2]) && !hasSpawnAction(path.points[0])) { return { ...path, points: [...path.points].reverse(), }; } return path; } // Updated path adjustment function function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] { if (paths.length < 1) return paths; const adjustedPaths = [...paths]; // First ensure all paths have spawn points at the beginning for (let i = 0; i < adjustedPaths.length; i++) { adjustedPaths[i] = ensureSpawnPointIsFirst(adjustedPaths[i]); } // Then handle connections between 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]); // Enhanced dependency tracking that includes action types const pathsDependency = useMemo(() => { if (!convertedPaths) return null; return convertedPaths.map((path) => ({ id: path.modeluuid, // Track all action types for each point actionSignature: path.points .map((point, index) => point.actions.map((action) => `${index}-${action.type}`).join("|") ) .join(","), connections: path.points .flatMap((p: PathPoint) => p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID) ) .join(","), })); }, [convertedPaths]); // Force process recreation when paths change useEffect(() => { if (!convertedPaths || convertedPaths.length === 0) { if (prevProcessesRef.current.length > 0) { onProcessesCreated([]); prevProcessesRef.current = []; } return; } // Always regenerate processes if the pathsDependency has changed // This ensures action type changes will be detected const newProcesses = createProcessesFromPaths(convertedPaths); prevPathsRef.current = convertedPaths; // Always update processes when action types change onProcessesCreated(newProcesses); prevProcessesRef.current = newProcesses; }, [ pathsDependency, // This now includes action types onProcessesCreated, convertedPaths, createProcessesFromPaths, ]); return null; } ); export default ProcessCreator;