// import React, { // useEffect, // useMemo, // useState, // useCallback, // useRef, // } from "react"; // import { useSimulationStates } 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 PointTrigger { // uuid: string; // bufferTime: number; // name: string; // type: string; // isUsed: boolean; // } // export interface PathPoint { // uuid: string; // position: [number, number, number]; // actions: PointAction[]; // triggers: PointTrigger[]; // connections: { // targets: Array<{ modelUUID: string }>; // }; // } // export interface SimulationPath { // type: string; // modeluuid: string; // points: PathPoint[]; // pathPosition: [number, number, number]; // speed?: number; // } // export interface Process { // id: string; // paths: SimulationPath[]; // animationPath: THREE.Vector3[]; // pointActions: PointAction[][]; // pointTriggers: PointTrigger[][]; // speed: number; // isActive: boolean; // } // interface ProcessCreatorProps { // onProcessesCreated: (processes: Process[]) => void; // } // // Convert event schemas to SimulationPath // function convertToSimulationPath( // path: ConveyorEventsSchema | VehicleEventsSchema // ): SimulationPath { // const { modeluuid } = path; // // Normalized action handler // const normalizeAction = (action: any): PointAction => { // return { ...action }; // Return exact copy with no modifications // }; // // Normalized trigger handler // const normalizeTrigger = (trigger: any): PointTrigger => { // return { ...trigger }; // Return exact copy with no modifications // }; // if (path.type === "Conveyor") { // return { // type: path.type, // modeluuid, // points: path.points.map((point) => ({ // uuid: point.uuid, // position: point.position, // actions: Array.isArray(point.actions) // ? point.actions.map(normalizeAction) // : point.actions // ? [normalizeAction(point.actions)] // : [], // triggers: Array.isArray(point.triggers) // ? point.triggers.map(normalizeTrigger) // : point.triggers // ? [normalizeTrigger(point.triggers)] // : [], // connections: { // targets: point.connections.targets.map((target) => ({ // modelUUID: target.modelUUID, // })), // }, // })), // pathPosition: path.position, // speed: // typeof path.speed === "string" // ? parseFloat(path.speed) || 1 // : path.speed || 1, // }; // } else { // // For vehicle paths, handle the case where triggers might not exist // return { // type: path.type, // modeluuid, // points: [ // { // uuid: path.points.uuid, // position: path.points.position, // actions: Array.isArray(path.points.actions) // ? path.points.actions.map(normalizeAction) // : path.points.actions // ? [normalizeAction(path.points.actions)] // : [], // // For vehicle paths, since triggers might not exist in the schema, // // we always define default to an empty array // triggers: [], // connections: { // targets: path.points.connections.targets.map((target) => ({ // modelUUID: target.modelUUID, // })), // }, // }, // ], // pathPosition: path.position, // speed: path.points.speed || 1, // }; // } // } // // Helper function to create an empty process // const createEmptyProcess = (): Process => ({ // id: `process-${Math.random().toString(36).substring(2, 11)}`, // paths: [], // animationPath: [], // pointActions: [], // pointTriggers: [], // Added point triggers array // speed: 1, // isActive: false, // }); // // 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.modelUUID === nextPath.modeluuid && // nextLastPoint.connections.targets.some( // (t) => t.modelUUID === currentPath.modeluuid // ) // ); // // Check if current last connects to next first (no reversal needed) // const connectsToFirst = currentLastPoint.connections.targets.some( // (target) => // target.modelUUID === nextPath.modeluuid && // nextFirstPoint.connections.targets.some( // (t) => t.modelUUID === 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 => { // if (path.type !== "Conveyor") return false; // 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 pointTriggers: PointTrigger[][] = []; // Added point triggers collection // const processSpeed = paths[0]?.speed || 1; // for (const path of paths) { // for (const point of path.points) { // if (path.type === "Conveyor") { // 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); // pointTriggers.push(point.triggers); // Collect triggers for each point // } // } // } // return { // id: `process-${Math.random().toString(36).substring(2, 11)}`, // paths, // animationPath, // pointActions, // pointTriggers, // speed: processSpeed, // isActive: false, // }; // }, // [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.modelUUID)) { // const targetPath = pathMap.get(target.modelUUID); // if (targetPath) { // visited.add(target.modelUUID); // 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.modelUUID === 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 { simulationStates } = useSimulationStates(); // const { createProcessesFromPaths } = useProcessCreation(); // const prevPathsRef = useRef([]); // const prevProcessesRef = useRef([]); // const convertedPaths = useMemo((): SimulationPath[] => { // if (!simulationStates) return []; // return simulationStates.map((path) => // convertToSimulationPath( // path as ConveyorEventsSchema | VehicleEventsSchema // ) // ); // }, [simulationStates]); // // Enhanced dependency tracking that includes action and trigger 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(","), // // Track all trigger types for each point // triggerSignature: path.points // .map((point, index) => // point.triggers // .map((trigger) => `${index}-${trigger.type}`) // .join("|") // ) // .join(","), // connections: path.points // .flatMap((p: PathPoint) => // p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID) // ) // .join(","), // isActive: false, // })); // }, [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 and trigger type changes will be detected // const newProcesses = createProcessesFromPaths(convertedPaths); // prevPathsRef.current = convertedPaths; // // Always update processes when action or trigger types change // onProcessesCreated(newProcesses); // prevProcessesRef.current = newProcesses; // }, [ // pathsDependency, // This now includes action and trigger types // onProcessesCreated, // convertedPaths, // createProcessesFromPaths, // ]); // return null; // } // ); // export default ProcessCreator; import React, { useEffect, useMemo, useState, useCallback, useRef, } from "react"; import { useSimulationStates } from "../../../store/store"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; import { ConveyorEventsSchema, VehicleEventsSchema, } from "../../../types/simulation"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; // Type definitions export interface PointAction { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: string | number; isUsed: boolean; } export interface PointTrigger { uuid: string; bufferTime: number; name: string; type: string; isUsed: boolean; } export interface PathPoint { uuid: string; position: [number, number, number]; actions: PointAction[]; triggers: PointTrigger[]; connections: { targets: Array<{ modelUUID: string }>; }; } export interface SimulationPath { type: string; modeluuid: string; points: PathPoint[]; pathPosition: [number, number, number]; speed?: number; isActive: boolean; } export interface Process { id: string; paths: SimulationPath[]; animationPath: THREE.Vector3[]; pointActions: PointAction[][]; pointTriggers: PointTrigger[][]; speed: number; isActive: boolean; } interface ProcessCreatorProps { onProcessesCreated: (processes: Process[]) => void; } // Convert event schemas to SimulationPath function convertToSimulationPath( path: ConveyorEventsSchema | VehicleEventsSchema ): SimulationPath { const { modeluuid } = path; // Normalized action handler const normalizeAction = (action: any): PointAction => { return { ...action }; // Return exact copy with no modifications }; // Normalized trigger handler const normalizeTrigger = (trigger: any): PointTrigger => { return { ...trigger }; // Return exact copy with no modifications }; if (path.type === "Conveyor") { return { type: path.type, modeluuid, points: path.points.map((point) => ({ uuid: point.uuid, position: point.position, actions: Array.isArray(point.actions) ? point.actions.map(normalizeAction) : point.actions ? [normalizeAction(point.actions)] : [], triggers: Array.isArray(point.triggers) ? point.triggers.map(normalizeTrigger) : point.triggers ? [normalizeTrigger(point.triggers)] : [], connections: { targets: point.connections.targets.map((target) => ({ modelUUID: target.modelUUID, })), }, })), pathPosition: path.position, speed: typeof path.speed === "string" ? parseFloat(path.speed) || 1 : path.speed || 1, isActive: false, // Added missing property }; } else { // For vehicle paths, handle the case where triggers might not exist return { type: path.type, modeluuid, points: [ { uuid: path.points.uuid, position: path.points.position, actions: Array.isArray(path.points.actions) ? path.points.actions.map(normalizeAction) : path.points.actions ? [normalizeAction(path.points.actions)] : [], triggers: [], connections: { targets: path.points.connections.targets.map((target) => ({ modelUUID: target.modelUUID, })), }, }, ], pathPosition: path.position, speed: path.points.speed || 1, isActive: false, }; } } // Helper function to create an empty process const createEmptyProcess = (): Process => ({ id: `process-${Math.random().toString(36).substring(2, 11)}`, paths: [], animationPath: [], pointActions: [], pointTriggers: [], // Added point triggers array speed: 1, isActive: false, }); // 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.modelUUID === nextPath.modeluuid && nextLastPoint.connections.targets.some( (t) => t.modelUUID === currentPath.modeluuid ) ); // Check if current last connects to next first (no reversal needed) const connectsToFirst = currentLastPoint.connections.targets.some( (target) => target.modelUUID === nextPath.modeluuid && nextFirstPoint.connections.targets.some( (t) => t.modelUUID === 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 => { if (path.type !== "Conveyor") return false; 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 pointTriggers: PointTrigger[][] = []; // Added point triggers collection const processSpeed = paths[0]?.speed || 1; for (const path of paths) { for (const point of path.points) { if (path.type === "Conveyor") { 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); pointTriggers.push(point.triggers); // Collect triggers for each point } } } return { id: `process-${Math.random().toString(36).substring(2, 11)}`, paths, animationPath, pointActions, pointTriggers, speed: processSpeed, isActive: false, }; }, [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.modelUUID)) { const targetPath = pathMap.get(target.modelUUID); if (targetPath) { visited.add(target.modelUUID); 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.modelUUID === 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 { simulationStates } = useSimulationStates(); const { createProcessesFromPaths } = useProcessCreation(); const prevPathsRef = useRef([]); const prevProcessesRef = useRef([]); const { isPlaying } = usePlayButtonStore(); const convertedPaths = useMemo((): SimulationPath[] => { if (!simulationStates) return []; return simulationStates.map((path) => convertToSimulationPath( path as ConveyorEventsSchema | VehicleEventsSchema ) ); }, [simulationStates]); // Enhanced dependency tracking that includes action and trigger 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(","), // Track all trigger types for each point triggerSignature: path.points .map((point, index) => point.triggers .map((trigger) => `${index}-${trigger.type}`) .join("|") ) .join(","), connections: path.points .flatMap((p: PathPoint) => p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID) ) .join(","), isActive: false, })); }, [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 and trigger type changes will be detected const newProcesses = createProcessesFromPaths(convertedPaths); prevPathsRef.current = convertedPaths; // Always update processes when action or trigger types change onProcessesCreated(newProcesses); prevProcessesRef.current = newProcesses; }, [ pathsDependency, // This now includes action and trigger types onProcessesCreated, convertedPaths, createProcessesFromPaths, ]); return null; } ); export default ProcessCreator;