Files
Dwinzo_dev/app/src/modules/simulation/process/processCreator.tsx

399 lines
11 KiB
TypeScript
Raw Normal View History

2025-04-01 18:54:04 +05:30
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<Process[]>([]);
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<string> = new Set()
): SimulationPath[] => {
const connectedPaths: SimulationPath[] = [];
const queue: SimulationPath[] = [initialPath];
visited.add(initialPath.modeluuid);
const pathMap = new Map<string, SimulationPath>();
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<string>();
const processes: Process[] = [];
const pathMap = new Map<string, SimulationPath>();
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<ProcessCreatorProps> = React.memo(
({ onProcessesCreated }) => {
const { simulationPaths } = useSimulationPaths();
const { createProcessesFromPaths } = useProcessCreation();
const prevPathsRef = useRef<SimulationPath[]>([]);
const prevProcessesRef = useRef<Process[]>([]);
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;