diff --git a/app/.env b/app/.env index 5740621..c50d174 100644 --- a/app/.env +++ b/app/.env @@ -11,7 +11,7 @@ REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:5000 REACT_APP_SERVER_MARKETPLACE_URL=185.100.212.76:50011 # Base URL for the asset library server, used for asset library images and model blob id. -REACT_APP_SERVER_ASSET_LIBRARY_URL=192.168.0.111:3501 +REACT_APP_SERVER_ASSET_LIBRARY_URL=185.100.212.76:50011 # base url for IoT socket server REACT_APP_IOT_SOCKET_SERVER_URL =185.100.212.76:5010 diff --git a/app/src/assets/gltf-glb/box.glb b/app/src/assets/gltf-glb/box.glb new file mode 100644 index 0000000..92ef9d8 Binary files /dev/null and b/app/src/assets/gltf-glb/box.glb differ diff --git a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx index 913bdd1..8ad0886 100644 --- a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx @@ -14,6 +14,7 @@ import { useSelectedActionSphere, useSelectedPath, useSimulationPaths, + useSocketStore, } from "../../../../store/store"; import * as THREE from "three"; import * as Types from "../../../../types/world/worldTypes"; @@ -26,6 +27,7 @@ const ConveyorMechanics: React.FC = () => { const { selectedPath, setSelectedPath } = useSelectedPath(); const { simulationPaths, setSimulationPaths } = useSimulationPaths(); const { floorItems, setFloorItems } = useFloorItems(); + const { socket } = useSocketStore(); const actionsContainerRef = useRef<HTMLDivElement>(null); const triggersContainerRef = useRef<HTMLDivElement>(null); @@ -37,20 +39,28 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" ) .flatMap((path) => path.points) - .find((point) => point.uuid === selectedActionSphere.point.uuid); + .find((point) => point.uuid === selectedActionSphere.points.uuid); }, [selectedActionSphere, simulationPaths]); const updateBackend = async (updatedPath: Types.ConveyorEventsSchema | undefined) => { if (!updatedPath) return; - // const email = localStorage.getItem("email"); - // const organization = email ? email.split("@")[1].split(".")[0] : ""; - // console.log('updatedPath: ', updatedPath); - // const a = await setEventApi( + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + // await setEventApi( // organization, // updatedPath.modeluuid, - // updatedPath.points + // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } // ); - // console.log('a: ', a); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } + } + + socket.emit('v2:model-asset:updateEventData', data); + } const handleAddAction = () => { @@ -61,7 +71,7 @@ const ConveyorMechanics: React.FC = () => { return { ...path, points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.point.uuid) { + if (point.uuid === selectedActionSphere.points.uuid) { const actionIndex = point.actions.length; const newAction = { uuid: THREE.MathUtils.generateUUID(), @@ -86,7 +96,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -102,7 +112,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.filter( @@ -119,7 +129,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -135,7 +145,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => @@ -167,7 +177,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -181,7 +191,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" ) .flatMap((path) => path.points) - .find((p) => p.uuid === selectedActionSphere.point.uuid) + .find((p) => p.uuid === selectedActionSphere.points.uuid) ?.actions.find((a) => a.uuid === uuid); if (updatedAction) { @@ -202,7 +212,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => @@ -222,7 +232,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -249,7 +259,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => @@ -266,7 +276,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -285,7 +295,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => @@ -304,7 +314,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -323,7 +333,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -340,7 +350,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.point.uuid) { + if (point.uuid === selectedActionSphere.points.uuid) { const triggerIndex = point.triggers.length; const newTrigger = { uuid: THREE.MathUtils.generateUUID(), @@ -362,7 +372,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -378,7 +388,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, triggers: point.triggers.filter( @@ -395,7 +405,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -411,7 +421,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, triggers: point.triggers.map((trigger) => @@ -430,7 +440,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -456,7 +466,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => ({ @@ -474,7 +484,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -502,7 +512,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, triggers: point.triggers.map((trigger) => ({ @@ -520,7 +530,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); @@ -547,7 +557,7 @@ const ConveyorMechanics: React.FC = () => { ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, triggers: point.triggers.map((trigger) => @@ -566,7 +576,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); diff --git a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx index bf0b112..e9bd6a2 100644 --- a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx @@ -1,9 +1,10 @@ import React, { useRef, useMemo } from "react"; import { InfoIcon } from "../../../icons/ExportCommonIcons"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; -import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionSphere, useSimulationPaths } from "../../../../store/store"; +import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionSphere, useSimulationPaths, useSocketStore } from "../../../../store/store"; import * as Types from '../../../../types/world/worldTypes'; import PositionInput from "../customInput/PositionInputs"; +import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; const VehicleMechanics: React.FC = () => { const { selectedActionSphere } = useSelectedActionSphere(); @@ -11,46 +12,68 @@ const VehicleMechanics: React.FC = () => { const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); const { editingPoint, setEditingPoint } = useEditingPoint(); const { previewPosition, setPreviewPosition } = usePreviewPosition(); + const { socket } = useSocketStore(); const propertiesContainerRef = useRef<HTMLDivElement>(null); const { selectedPoint, connectedPointUuids } = useMemo(() => { - if (!selectedActionSphere?.point?.uuid) return { selectedPoint: null, connectedPointUuids: [] }; + if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null, connectedPointUuids: [] }; const vehiclePaths = simulationPaths.filter( (path): path is Types.VehicleEventsSchema => path.type === "Vehicle" ); - const point = vehiclePaths.find( - (path) => path.point.uuid === selectedActionSphere.point.uuid - )?.point; + const points = vehiclePaths.find( + (path) => path.points.uuid === selectedActionSphere.points.uuid + )?.points; - if (!point) return { selectedPoint: null, connectedPointUuids: [] }; + if (!points) return { selectedPoint: null, connectedPointUuids: [] }; const connectedUuids: string[] = []; - if (point.connections?.targets) { - point.connections.targets.forEach(target => { + if (points.connections?.targets) { + points.connections.targets.forEach(target => { connectedUuids.push(target.pointUUID); }); } return { - selectedPoint: point, + selectedPoint: points, connectedPointUuids: connectedUuids }; }, [selectedActionSphere, simulationPaths]); - const handleActionUpdate = React.useCallback((updatedAction: Partial<Types.VehicleEventsSchema['point']['actions']>) => { - if (!selectedActionSphere?.point?.uuid) return; + const updateBackend = async (updatedPath: Types.VehicleEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "Vehicle", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Vehicle", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } + + const handleActionUpdate = React.useCallback((updatedAction: Partial<Types.VehicleEventsSchema['points']['actions']>) => { + if (!selectedActionSphere?.points?.uuid) return; const updatedPaths = simulationPaths.map((path) => { - if (path.type === "Vehicle" && path.point.uuid === selectedActionSphere.point.uuid) { + if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, actions: { - ...path.point.actions, + ...path.points.actions, ...updatedAction } } @@ -59,14 +82,15 @@ const VehicleMechanics: React.FC = () => { return path; }); + const updatedPath = updatedPaths.find( + (path): path is Types.VehicleEventsSchema => + path.type === "Vehicle" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); - }, [selectedActionSphere?.point?.uuid, simulationPaths, setSimulationPaths]); - - const handleStartPointChange = React.useCallback((position: { x: number, y: number }) => { - }, [handleActionUpdate]); - - const handleEndPointChange = React.useCallback((position: { x: number, y: number }) => { - }, [handleActionUpdate]); + }, [selectedActionSphere?.points?.uuid, simulationPaths, setSimulationPaths]); const handleHitCountChange = React.useCallback((hitCount: number) => { handleActionUpdate({ hitCount }); @@ -77,14 +101,14 @@ const VehicleMechanics: React.FC = () => { }, [handleActionUpdate]); const handleSpeedChange = React.useCallback((speed: number) => { - if (!selectedActionSphere?.point?.uuid) return; + if (!selectedActionSphere?.points?.uuid) return; const updatedPaths = simulationPaths.map((path) => { - if (path.type === "Vehicle" && path.point.uuid === selectedActionSphere.point.uuid) { + if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, speed: speed } }; @@ -92,8 +116,15 @@ const VehicleMechanics: React.FC = () => { return path; }); + const updatedPath = updatedPaths.find( + (path): path is Types.VehicleEventsSchema => + path.type === "Vehicle" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); - }, [selectedActionSphere?.point?.uuid, simulationPaths, setSimulationPaths]); + }, [selectedActionSphere?.points?.uuid, simulationPaths, setSimulationPaths]); const handleStartEyeDropClick = () => { setEditingPoint('start'); diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index d74d9ca..528fbc2 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -1,21 +1,30 @@ import React, { useState, useRef, useEffect } from "react"; import { ExitIcon, PlayStopIcon, ResetIcon } from "../../icons/SimulationIcons"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import { useActiveTool } from "../../../store/store"; +import { + useAnimationPlaySpeed, + usePauseButtonStore, + usePlayButtonStore, + useResetButtonStore, +} from "../../../store/usePlayButtonStore"; const SimulationPlayer: React.FC = () => { - const [speed, setSpeed] = useState<number>(1); + const { speed, setSpeed } = useAnimationPlaySpeed(); const [playSimulation, setPlaySimulation] = useState(false); const { setIsPlaying } = usePlayButtonStore(); const sliderRef = useRef<HTMLDivElement>(null); const isDragging = useRef(false); const { setActiveTool } = useActiveTool(); + const { isPaused, setIsPaused } = usePauseButtonStore(); + const { isReset, setReset } = useResetButtonStore(); // Button functions const handleReset = () => { + setReset(true); setSpeed(1); }; const handlePlayStop = () => { + setIsPaused(!isPaused); setPlaySimulation(!playSimulation); }; const handleExit = () => { @@ -30,7 +39,7 @@ const SimulationPlayer: React.FC = () => { }; const calculateHandlePosition = () => { - return ((speed - 0.5) / (50 - 0.5)) * 100; + return ((speed - 0.5) / (8 - 0.5)) * 100; }; const handleMouseDown = () => { @@ -118,7 +127,7 @@ const SimulationPlayer: React.FC = () => { <input type="range" min="0.5" - max="50" + max="8" step="0.1" value={speed} onChange={handleSpeedChange} @@ -126,7 +135,7 @@ const SimulationPlayer: React.FC = () => { /> </div> </div> - <div className="max-value">50x</div> + <div className="max-value">8x</div> </div> </div> </div> diff --git a/app/src/modules/builder/agv/agv.tsx b/app/src/modules/builder/agv/agv.tsx index 6218662..24003ea 100644 --- a/app/src/modules/builder/agv/agv.tsx +++ b/app/src/modules/builder/agv/agv.tsx @@ -9,13 +9,12 @@ import { useSelectedActionSphere, useSimulationPaths, } from "../../../store/store"; +import * as CONSTANTS from "../../../types/world/worldConstants"; const Agv = ({ lines, - plane, }: { lines: Types.RefLines; - plane: Types.RefMesh; }) => { const [pathPoints, setPathPoints] = useState< { @@ -23,6 +22,7 @@ const Agv = ({ modelSpeed: number; bufferTime: number; points: { x: number; y: number; z: number }[]; + hitCount: number; }[] >([]); const { simulationPaths } = useSimulationPaths(); @@ -34,7 +34,7 @@ const Agv = ({ (val: any) => val.modelName === "agv" ); - console.log("agvModels: ", agvModels); + let findMesh = agvModels.filter( (val: any) => val.modeluuid === selectedActionSphere?.path?.modeluuid && @@ -43,18 +43,19 @@ const Agv = ({ const result = findMesh.length > 0 && - findMesh[0].type === "Vehicle" && - typeof findMesh[0].point?.actions.start === "object" && - typeof findMesh[0].point?.actions.end === "object" && - "x" in findMesh[0].point.actions.start && - "y" in findMesh[0].point.actions.start && - "x" in findMesh[0].point.actions.end && - "y" in findMesh[0].point.actions.end + findMesh[0].type === "Vehicle" && + typeof findMesh[0].points?.actions.start === "object" && + typeof findMesh[0].points?.actions.end === "object" && + "x" in findMesh[0].points.actions.start && + "y" in findMesh[0].points.actions.start && + "x" in findMesh[0].points.actions.end && + "y" in findMesh[0].points.actions.end ? [ { modelUuid: findMesh[0].modeluuid, // Ensure it's a number - modelSpeed: findMesh[0].point.speed, - bufferTime: findMesh[0].point.actions.buffer, + modelSpeed: findMesh[0].points.speed, + bufferTime: findMesh[0].points.actions.buffer, + hitCount: findMesh[0].points.actions.hitCount, points: [ { x: findMesh[0].position[0], @@ -62,14 +63,14 @@ const Agv = ({ z: findMesh[0].position[2], }, { - x: findMesh[0].point.actions.start.x, + x: findMesh[0].points.actions.start.x, y: 0, - z: findMesh[0].point.actions.start.y, + z: findMesh[0].points.actions.start.y, }, { - x: findMesh[0].point.actions.end.x, + x: findMesh[0].points.actions.end.x, y: 0, - z: findMesh[0].point.actions.end.y, + z: findMesh[0].points.actions.end.y, }, ], }, @@ -106,12 +107,11 @@ const Agv = ({ return ( <> - <PolygonGenerator groupRef={groupRef} lines={lines} plane={plane} /> + <PolygonGenerator groupRef={groupRef} lines={lines} /> <NavMeshDetails lines={lines} setNavMesh={setNavMesh} groupRef={groupRef} - plane={plane} /> {pathPoints.map((pair, i) => ( <> @@ -122,6 +122,7 @@ const Agv = ({ key={i} speed={pair.modelSpeed} bufferTime={pair.bufferTime} + hitCount={pair.hitCount} /> {/* {pair.points.length > 2 && ( <> @@ -149,7 +150,12 @@ const Agv = ({ )} */} </> ))} - <group ref={groupRef} visible={false} name="Meshes"></group> + <group ref={groupRef} visible={false} name="Meshes"> + <mesh rotation-x={CONSTANTS.planeConfig.rotation} position={CONSTANTS.planeConfig.position3D} name="Plane" receiveShadow> + <planeGeometry args={[300, 300]} /> + <meshBasicMaterial color={CONSTANTS.planeConfig.color} /> + </mesh> + </group> </> ); }; diff --git a/app/src/modules/builder/agv/navMeshDetails.tsx b/app/src/modules/builder/agv/navMeshDetails.tsx index ecb539b..ae95942 100644 --- a/app/src/modules/builder/agv/navMeshDetails.tsx +++ b/app/src/modules/builder/agv/navMeshDetails.tsx @@ -10,14 +10,12 @@ interface NavMeshDetailsProps { setNavMesh: (navMesh: any) => void; groupRef: React.MutableRefObject<THREE.Group | null>; lines: Types.RefLines; - plane: Types.RefMesh; } export default function NavMeshDetails({ lines, setNavMesh, groupRef, - plane, }: NavMeshDetailsProps) { const { scene } = useThree(); @@ -34,14 +32,13 @@ export default function NavMeshDetails({ const [positions, indices] = getPositionsAndIndices(meshes); - const cs = 0.25; - const ch = 0.69; + const cellSize = 0.35; + const cellHeight = 0.7; const walkableRadius = 0.5; - const { success, navMesh } = generateSoloNavMesh(positions, indices, { - cs, - ch, - walkableRadius: Math.round(walkableRadius / ch), + cs: cellSize, + ch: cellHeight, + walkableRadius: Math.round(walkableRadius / cellHeight), }); if (!success || !navMesh) { @@ -50,10 +47,14 @@ export default function NavMeshDetails({ setNavMesh(navMesh); + scene.children + .filter((child) => child instanceof DebugDrawer) + .forEach((child) => scene.remove(child)); + const debugDrawer = new DebugDrawer(); debugDrawer.drawNavMesh(navMesh); // scene.add(debugDrawer); - } catch (error) {} + } catch (error) { } }; initializeNavigation(); diff --git a/app/src/modules/builder/agv/pathNavigator.tsx b/app/src/modules/builder/agv/pathNavigator.tsx index a83b690..65a299b 100644 --- a/app/src/modules/builder/agv/pathNavigator.tsx +++ b/app/src/modules/builder/agv/pathNavigator.tsx @@ -12,6 +12,7 @@ interface PathNavigatorProps { id: string; speed: number; bufferTime: number; + hitCount: number; } export default function PathNavigator({ @@ -20,6 +21,7 @@ export default function PathNavigator({ id, speed, bufferTime, + hitCount, }: PathNavigatorProps) { const [path, setPath] = useState<[number, number, number][]>([]); const progressRef = useRef(0); @@ -32,7 +34,7 @@ export default function PathNavigator({ const [startPoint, setStartPoint] = useState(new THREE.Vector3()); const isWaiting = useRef<boolean>(false); // Flag to track waiting state const delayTime = bufferTime; - + const movingForward = useRef<boolean>(true); // Tracks whether the object is moving forward // Compute distances and total distance when the path changes useEffect(() => { @@ -52,6 +54,7 @@ export default function PathNavigator({ progressRef.current = 0; }, [path]); + // Compute the path using NavMeshQuery useEffect(() => { if (!navMesh || selectedPoints.length === 0) return; @@ -119,17 +122,22 @@ export default function PathNavigator({ if (!isWaiting.current) { isWaiting.current = true; // Set waiting flag - setTimeout(() => { - progressRef.current = 0; // Reset progress - movingForward.current = !movingForward.current; // Toggle direction - - // Reverse the path and distances arrays - path.reverse(); - distancesRef.current.reverse(); - - // Reset the waiting flag - isWaiting.current = false; - }, delayTime * 1000); // Convert seconds to milliseconds + if (movingForward.current) { + // Moving forward: reached the end, wait for `delay` + // console.log( + // "Reached end position. Waiting for delay:", + // delayTime, + // "seconds" + // ); + setTimeout(() => { + // After delay, reverse direction + movingForward.current = false; + progressRef.current = 0; // Reset progress + path.reverse(); // Reverse the path + distancesRef.current.reverse(); + isWaiting.current = false; // Reset waiting flag + }, delayTime * 1000); // Wait for `delay` seconds + } } return; } @@ -154,7 +162,7 @@ export default function PathNavigator({ findObject.position.copy(startPoint); } }); - + return ( <> {path.length > 0 && ( diff --git a/app/src/modules/builder/agv/polygonGenerator.tsx b/app/src/modules/builder/agv/polygonGenerator.tsx index 8682371..2462018 100644 --- a/app/src/modules/builder/agv/polygonGenerator.tsx +++ b/app/src/modules/builder/agv/polygonGenerator.tsx @@ -6,21 +6,12 @@ import arrayLinesToObject from "../geomentries/lines/lineConvertions/arrayLinesT interface PolygonGeneratorProps { groupRef: React.MutableRefObject<THREE.Group | null>; lines: Types.RefLines; - plane: Types.RefMesh; } export default function PolygonGenerator({ groupRef, lines, - plane, }: PolygonGeneratorProps) { - // const [rooms, setRooms] = useState<THREE.Vector3[][]>([]); - - useEffect(() => { - if (groupRef.current && plane.current) { - groupRef.current.add(plane.current.clone()); - } - }, [groupRef, plane]); useEffect(() => { let allLines = arrayLinesToObject(lines.current); @@ -37,13 +28,14 @@ export default function PolygonGenerator({ uuid: point.uuid, })) ); + if (!result || result.some((line) => !line)) { return; } const lineFeatures = result?.map((line: any) => turf.lineString(line.map((p: any) => p?.position)) - ); + ); const polygons = turf.polygonize(turf.featureCollection(lineFeatures)); renderWallGeometry(wallPoints); @@ -79,8 +71,8 @@ export default function PolygonGenerator({ groupRef.current?.add(mesh); } }); - } + }, [lines.current]); const renderWallGeometry = (walls: THREE.Vector3[][]) => { diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index a1ed87b..91c1f12 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -136,7 +136,7 @@ async function handleModelLoad( tempLoader.current = undefined; } - const newFloorItem: Types.FloorItemType = { + const newFloorItem: Types.EventData = { modeluuid: model.uuid, modelname: selectedItem.name, modelfileID: selectedItem.id, @@ -154,7 +154,7 @@ async function handleModelLoad( if (res.type === "Conveyor") { const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID()); - const backendEventData: Extract<Types.FloorItemType['eventData'], { type: 'Conveyor' }> = { + const backendEventData: Extract<Types.EventData['eventData'], { type: 'Conveyor' }> = { type: 'Conveyor', points: res.points.map((point: any, index: number) => ({ uuid: pointUUIDs[index], @@ -167,7 +167,7 @@ async function handleModelLoad( material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', - isUsed: false + isUsed: true }], triggers: [], connections: { @@ -189,7 +189,7 @@ async function handleModelLoad( // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, // false, // true, - // newFloorItem.eventData + // { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed } // ); // SOCKET @@ -206,8 +206,7 @@ async function handleModelLoad( eventData: backendEventData, socketId: socket.id }; - - console.log('data: ', data); + setFloorItems((prevItems) => { const updatedItems = [...(prevItems || []), newFloorItem]; localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); @@ -222,12 +221,25 @@ async function handleModelLoad( setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ ...(prevEvents || []), - eventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + eventData as Types.ConveyorEventsSchema ]); socket.emit("v2:model-asset:add", data); - } else { + } else if (res.type === "Vehicle") { + + const pointUUID = THREE.MathUtils.generateUUID(); + + const backendEventData: Extract<Types.EventData['eventData'], { type: 'Vehicle' }> = { + type: "Vehicle", + points: { + uuid: pointUUID, + position: res.points.position as [number, number, number], + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, + connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] }, + speed: 2, + } + } // API @@ -239,7 +251,8 @@ async function handleModelLoad( // newFloorItem.position, // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, // false, - // true + // true, + // { type: backendEventData.type, points: backendEventData.points } // ); // SOCKET @@ -253,15 +266,26 @@ async function handleModelLoad( rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, isLocked: false, isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, socketId: socket.id }; - + + const eventData: any = backendEventData; + eventData.modeluuid = newFloorItem.modeluuid; + eventData.modelName = newFloorItem.modelname; + eventData.position = newFloorItem.position; + setFloorItems((prevItems) => { const updatedItems = [...(prevItems || []), newFloorItem]; localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); return updatedItems; }); + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + ...(prevEvents || []), + eventData as Types.VehicleEventsSchema + ]); + socket.emit("v2:model-asset:add", data); } diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index 2b77fb2..cef7d74 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -110,7 +110,6 @@ const FloorItemsGroup = ({ } gltfLoaderWorker.postMessage({ floorItems: data }); } else { - console.log('data: ', data); gltfLoaderWorker.postMessage({ floorItems: [] }); loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths); updateLoadingProgress(100); diff --git a/app/src/modules/collaboration/collabCams.tsx b/app/src/modules/collaboration/collabCams.tsx index b15d17d..20d983e 100644 --- a/app/src/modules/collaboration/collabCams.tsx +++ b/app/src/modules/collaboration/collabCams.tsx @@ -111,7 +111,7 @@ const CamModelsGroup = () => { socket.off("userDisConnectRespones"); socket.off("cameraUpdateResponse"); }; - }, [socket, activeUsers]); + }, [socket]); // useEffect(() => { diff --git a/app/src/modules/collaboration/socketResponses.dev.tsx b/app/src/modules/collaboration/socketResponses.dev.tsx index b8cec55..8771303 100644 --- a/app/src/modules/collaboration/socketResponses.dev.tsx +++ b/app/src/modules/collaboration/socketResponses.dev.tsx @@ -143,10 +143,6 @@ export default function SocketResponses({ isVisible: data.data.isVisible, }; - if (data.data.eventData) { - newFloorItem.eventData = data.data.eventData; - } - setFloorItems((prevItems: any) => { const updatedItems = [...(prevItems || []), newFloorItem]; localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); @@ -221,10 +217,6 @@ export default function SocketResponses({ isVisible: data.data.isVisible, }; - if (data.data.eventData) { - newFloorItem.eventData = data.data.eventData; - } - setFloorItems((prevItems: any) => { const updatedItems = [...(prevItems || []), newFloorItem]; localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); diff --git a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts index 3630378..5dd41de 100644 --- a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts @@ -23,10 +23,10 @@ async function loadInitialFloorItems( localStorage.setItem("FloorItems", JSON.stringify(items)); await initializeDB(); - if (items.message === "floorItems not found") return; + if (items.message === "floorItems not found") return; if (items) { - const storedFloorItems: Types.FloorItems = items; + const storedFloorItems: Types.EventData[] = items; const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); @@ -53,7 +53,6 @@ async function loadInitialFloorItems( }); for (const item of storedFloorItems) { - console.log('item: ', item); if (!item.modelfileID) return; const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]); let storedPosition; @@ -155,7 +154,7 @@ async function loadInitialFloorItems( function processLoadedModel( gltf: any, - item: Types.FloorItemType, + item: Types.EventData, itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void @@ -193,7 +192,7 @@ function processLoadedModel( }, ]); - if (item.eventData || item.modelfileID === '67e3da19c2e8f37134526e6a') { + if (item.eventData) { processEventData(item, setSimulationPaths); } @@ -201,7 +200,7 @@ function processLoadedModel( gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' }); } -function processEventData(item: Types.FloorItemType, setSimulationPaths: any) { +function processEventData(item: Types.EventData, setSimulationPaths: any) { if (item.eventData?.type === 'Conveyor') { @@ -215,29 +214,19 @@ function processEventData(item: Types.FloorItemType, setSimulationPaths: any) { ...(prevEvents || []), data as Types.ConveyorEventsSchema ]); + } else { - const pointUUID = THREE.MathUtils.generateUUID(); - const pointPosition = new THREE.Vector3(0, 1.3, 0); + const data: any = item.eventData; + data.modeluuid = item.modeluuid; + data.modelName = item.modelname; + data.position = item.position; - const newVehiclePath: Types.VehicleEventsSchema = { - modeluuid: item.modeluuid, - modelName: item.modelname, - type: 'Vehicle', - point: { - uuid: pointUUID, - position: [pointPosition.x, pointPosition.y, pointPosition.z], - actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, - connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, - speed: 2, - }, - position: [...item.position], - }; - - setSimulationPaths((prevEvents: (Types.VehicleEventsSchema)[]) => [ + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ ...(prevEvents || []), - newVehiclePath as Types.VehicleEventsSchema + data as Types.VehicleEventsSchema ]); + } } diff --git a/app/src/modules/scene/controls/selection/copyPasteControls.tsx b/app/src/modules/scene/controls/selection/copyPasteControls.tsx index dea1da6..abcc1b0 100644 --- a/app/src/modules/scene/controls/selection/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selection/copyPasteControls.tsx @@ -236,10 +236,91 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ ...(prevEvents || []), - newEventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + newEventData as Types.ConveyorEventsSchema ]); socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'Vehicle' && eventData) { + const createVehiclePoint = () => { + const pointUUID = THREE.MathUtils.generateUUID(); + const vehiclePoint = (eventData as Types.VehicleEventsSchema)?.points; + const hasActions = vehiclePoint?.actions !== undefined; + + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + type: 'Inherit', + start: {}, + hitCount: 0, + end: {}, + buffer: 0 + }; + + return { + uuid: pointUUID, + position: vehiclePoint?.position, + actions: hasActions + ? { + ...vehiclePoint.actions, + uuid: THREE.MathUtils.generateUUID() + } + : defaultAction, + connections: { + source: { modelUUID: obj.uuid, pointUUID }, + targets: [] + }, + speed: vehiclePoint?.speed || 1 + }; + }; + + const backendEventData = { + type: 'Vehicle', + points: createVehiclePoint(), + speed: (eventData as Types.VehicleEventsSchema)?.points.speed + }; + + // API + + // setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id, + }; + + const newEventData: any = backendEventData; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.VehicleEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/controls/selection/duplicationControls.tsx b/app/src/modules/scene/controls/selection/duplicationControls.tsx index 185ca68..c28c401 100644 --- a/app/src/modules/scene/controls/selection/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selection/duplicationControls.tsx @@ -5,6 +5,7 @@ import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, u import { toast } from "react-toastify"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import * as Types from "../../../../types/world/worldTypes"; +import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi"; const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedObjects, setpastedObjects, selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); @@ -182,16 +183,16 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb //REST - // await setFloorItemApi( + // setFloorItemApi( // organization, // obj.uuid, // obj.userData.name, + // obj.userData.modelId, // [worldPosition.x, worldPosition.y, worldPosition.z], // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, // false, // true, - // backendEventData + // { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed } // ); //SOCKET @@ -217,10 +218,90 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ ...(prevEvents || []), - newEventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + newEventData as Types.ConveyorEventsSchema ]); socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'Vehicle' && eventData) { + const createVehiclePoint = () => { + const pointUUID = THREE.MathUtils.generateUUID(); + const vehiclePoint = (eventData as Types.VehicleEventsSchema)?.points; + const hasActions = vehiclePoint?.actions !== undefined; + + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + type: 'Inherit', + start: {}, + hitCount: 0, + end: {}, + buffer: 0 + }; + + return { + uuid: pointUUID, + position: vehiclePoint?.position, + actions: hasActions + ? { + ...vehiclePoint.actions, + uuid: THREE.MathUtils.generateUUID() + } + : defaultAction, + connections: { + source: { modelUUID: obj.uuid, pointUUID }, + targets: [] + }, + speed: vehiclePoint?.speed || 2 + }; + }; + + const backendEventData = { + type: 'Vehicle', + points: createVehiclePoint() + }; + + // API + + // setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id, + }; + + const newEventData: any = backendEventData; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.VehicleEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/controls/selection/moveControls.tsx b/app/src/modules/scene/controls/selection/moveControls.tsx index 350b487..a4340b5 100644 --- a/app/src/modules/scene/controls/selection/moveControls.tsx +++ b/app/src/modules/scene/controls/selection/moveControls.tsx @@ -238,7 +238,59 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje return updatedEvents; }); - // socket.emit("v2:model-asset:add", data); + socket.emit("v2:model-asset:add", data); + } else if (eventData.type === 'Vehicle' && eventData) { + + const backendEventData = { + type: 'Vehicle', + points: eventData.points + }; + + // REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // backendEventData + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id, + }; + + const newEventData: any = backendEventData; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/controls/selection/rotateControls.tsx b/app/src/modules/scene/controls/selection/rotateControls.tsx index 6ad8309..708a00a 100644 --- a/app/src/modules/scene/controls/selection/rotateControls.tsx +++ b/app/src/modules/scene/controls/selection/rotateControls.tsx @@ -5,6 +5,7 @@ import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, u // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; +import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi"; function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); @@ -197,15 +198,15 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo speed: (eventData as Types.ConveyorEventsSchema)?.speed }; - //REST + // REST // await setFloorItemApi( // organization, // obj.uuid, // obj.userData.name, + // obj.userData.modelId, // [worldPosition.x, worldPosition.y, worldPosition.z], // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, // false, // true, // backendEventData @@ -241,7 +242,60 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo return updatedEvents; }); - // socket.emit("v2:model-asset:add", data); + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'Vehicle' && eventData) { + + const backendEventData = { + type: 'Vehicle', + points: eventData.points + }; + + // REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // backendEventData + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id, + }; + + const newEventData: any = backendEventData; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index ffa020e..30d8a8f 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -91,7 +91,7 @@ export default function PostProcessing() { )} {selectedActionSphere && ( <Outline - selection={[selectedActionSphere.point]} + selection={[selectedActionSphere.points]} selectionLayer={10} width={1000} blendFunction={BlendFunction.ALPHA} diff --git a/app/src/modules/scene/world/world.tsx b/app/src/modules/scene/world/world.tsx index 1f95499..1b4f71f 100644 --- a/app/src/modules/scene/world/world.tsx +++ b/app/src/modules/scene/world/world.tsx @@ -367,7 +367,9 @@ export default function World() { /> {/* <DrieHtmlTemp itemsGroup={itemsGroup} /> */} - {activeModule === "simulation" && <Agv lines={lines} plane={plane} />} + + {activeModule === "simulation" && <Agv lines={lines} />} + </> ); } diff --git a/app/src/modules/simulation/path/pathConnector.tsx b/app/src/modules/simulation/path/pathConnector.tsx index 7352932..3866a10 100644 --- a/app/src/modules/simulation/path/pathConnector.tsx +++ b/app/src/modules/simulation/path/pathConnector.tsx @@ -5,12 +5,14 @@ import * as Types from '../../../types/world/worldTypes'; import { QuadraticBezierLine } from '@react-three/drei'; import { useIsConnecting, useSimulationPaths } from '../../../store/store'; import useModuleStore from '../../../store/useModuleStore'; +import { usePlayButtonStore } from '../../../store/usePlayButtonStore'; function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject<THREE.Group> }) { const { activeModule } = useModuleStore(); const { gl, raycaster, scene, pointer, camera } = useThree(); const { setIsConnecting } = useIsConnecting(); const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { isPlaying } = usePlayButtonStore(); const [firstSelected, setFirstSelected] = useState<{ pathUUID: string; @@ -89,12 +91,12 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // In the updatePathConnections function, modify the Vehicle handling section: else if (path.type === 'Vehicle') { // Handle outgoing connections from Vehicle - if (path.modeluuid === fromPathUUID && path.point.uuid === fromPointUUID) { + if (path.modeluuid === fromPathUUID && path.points.uuid === fromPointUUID) { const newTarget = { pathUUID: toPathUUID, pointUUID: toPointUUID }; - const existingTargets = path.point.connections.targets || []; + const existingTargets = path.points.connections.targets || []; // Check if target is a Conveyor const toPath = simulationPaths.find(p => p.modeluuid === toPathUUID); @@ -115,10 +117,10 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec )) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, connections: { - ...path.point.connections, + ...path.points.connections, targets: [...existingTargets, newTarget] } } @@ -126,12 +128,12 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } } // Handle incoming connections to Vehicle - else if (path.modeluuid === toPathUUID && path.point.uuid === toPointUUID) { + else if (path.modeluuid === toPathUUID && path.points.uuid === toPointUUID) { const reverseTarget = { pathUUID: fromPathUUID, pointUUID: fromPointUUID }; - const existingTargets = path.point.connections.targets || []; + const existingTargets = path.points.connections.targets || []; // Check if source is a Conveyor const fromPath = simulationPaths.find(p => p.modeluuid === fromPathUUID); @@ -152,10 +154,10 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec )) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, connections: { - ...path.point.connections, + ...path.points.connections, targets: [...existingTargets, reverseTarget] } } @@ -215,13 +217,13 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec let isStartOrEnd = false; - if (intersected.userData.path.points) { + if (intersected.userData.path.points && intersected.userData.path.points.length > 1) { isStartOrEnd = intersected.userData.path.points.length > 0 && ( sphereUUID === intersected.userData.path.points[0].uuid || sphereUUID === intersected.userData.path.points[intersected.userData.path.points.length - 1].uuid ); - } else if (intersected.userData.path.point) { - isStartOrEnd = sphereUUID === intersected.userData.path.point.uuid; + } else if (intersected.userData.path.points) { + isStartOrEnd = sphereUUID === intersected.userData.path.points.uuid; } if (pathUUID) { @@ -253,7 +255,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec t.pathUUID === pathUUID && t.pointUUID === sphereUUID ); } else if (path.type === 'Vehicle') { - return path.point.connections.targets.some(t => + return path.points.connections.targets.some(t => t.pathUUID === pathUUID && t.pointUUID === sphereUUID ); } @@ -269,7 +271,8 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // For Vehicles, check if they're already connected to anything if (intersected.userData.path.type === 'Vehicle') { - const vehicleConnections = intersected.userData.path.point.connections.targets.length; + console.log('intersected: ', intersected); + const vehicleConnections = intersected.userData.path.points.connections.targets.length; if (vehicleConnections >= 1) { console.log("Vehicle can only have one connection"); return; @@ -418,7 +421,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec t.pathUUID === pathUUID && t.pointUUID === sphereUUID ); } else if (path.type === 'Vehicle') { - return path.point.connections.targets.some(t => + return path.points.connections.targets.some(t => t.pathUUID === pathUUID && t.pointUUID === sphereUUID ); } @@ -440,7 +443,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // Check vehicle connection rules const isVehicleAtMaxConnections = pathData.type === 'Vehicle' && - pathData.point.connections.targets.length >= 1; + pathData.points.connections.targets.length >= 1; const isVehicleConnectingToNonConveyor = (firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') || (secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor'); @@ -501,7 +504,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }); return ( - <> + <group name='simulationConnectionGroup' visible={!isPlaying} > {simulationPaths.flatMap(path => { if (path.type === 'Conveyor') { return path.points.flatMap(point => @@ -545,8 +548,8 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }) ); } else if (path.type === 'Vehicle') { - return path.point.connections.targets.map((target, index) => { - const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.point.uuid); + return path.points.connections.targets.map((target, index) => { + const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.points.uuid); const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID); if (fromSphere && toSphere) { @@ -566,7 +569,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return ( <QuadraticBezierLine - key={`${path.point.uuid}-${target.pointUUID}-${index}`} + key={`${path.points.uuid}-${target.pointUUID}-${index}`} start={fromWorldPosition.toArray()} end={toWorldPosition.toArray()} mid={midPoint.toArray()} @@ -596,7 +599,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec dashScale={20} /> )} - </> + </group> ); } diff --git a/app/src/modules/simulation/path/pathCreation.tsx b/app/src/modules/simulation/path/pathCreation.tsx index dd531a9..619a011 100644 --- a/app/src/modules/simulation/path/pathCreation.tsx +++ b/app/src/modules/simulation/path/pathCreation.tsx @@ -15,6 +15,7 @@ import { import { useFrame, useThree } from "@react-three/fiber"; import { useSubModuleStore } from "../../../store/useModuleStore"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { setEventApi } from "../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; function PathCreation({ pathsGroupRef, @@ -24,16 +25,12 @@ function PathCreation({ const { isPlaying } = usePlayButtonStore(); const { renderDistance } = useRenderDistance(); const { setSubModule } = useSubModuleStore(); - const { setSelectedActionSphere, selectedActionSphere } = - useSelectedActionSphere(); + const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere(); const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); const { editingPoint, setEditingPoint } = useEditingPoint(); const { previewPosition, setPreviewPosition } = usePreviewPosition(); const { raycaster, camera, pointer, gl } = useThree(); - const plane = useMemo( - () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), - [] - ); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { setSelectedPath } = useSelectedPath(); const { simulationPaths, setSimulationPaths } = useSimulationPaths(); const { isConnecting } = useIsConnecting(); @@ -42,9 +39,7 @@ function PathCreation({ const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); const isMovingRef = useRef(false); const transformRef = useRef<any>(null); - const [transformMode, setTransformMode] = useState< - "translate" | "rotate" | null - >(null); + const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); useEffect(() => { setTransformMode(null); @@ -81,20 +76,20 @@ function PathCreation({ return { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { - ...point, - position: [ - selectedActionSphere.point.position.x, - selectedActionSphere.point.position.y, - selectedActionSphere.point.position.z, - ], - rotation: [ - selectedActionSphere.point.rotation.x, - selectedActionSphere.point.rotation.y, - selectedActionSphere.point.rotation.z, - ], - } + ...point, + position: [ + selectedActionSphere.points.position.x, + selectedActionSphere.points.position.y, + selectedActionSphere.points.position.z, + ], + rotation: [ + selectedActionSphere.points.rotation.x, + selectedActionSphere.points.rotation.y, + selectedActionSphere.points.rotation.z, + ], + } : point ), }; @@ -161,26 +156,37 @@ function PathCreation({ }; }, [eyeDropMode, editingPoint, previewPosition]); + const updateBackend = async (updatedPath: Types.VehicleEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + await setEventApi( + organization, + updatedPath.modeluuid, + { type: "Vehicle", points: updatedPath.points } + ); + } + const handlePointUpdate = ( pointType: "start" | "end", x: number, z: number ) => { - if (!selectedActionSphere?.point?.uuid) return; - + if (!selectedActionSphere?.points?.uuid) return; const updatedPaths = simulationPaths.map((path) => { + if ( path.type === "Vehicle" && - path.point.uuid === selectedActionSphere.point.uuid + path.points.uuid === selectedActionSphere.points.uuid ) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, actions: { - ...path.point.actions, + ...path.points.actions, [pointType]: { - ...path.point.actions[pointType], + ...path.points.actions[pointType], x: x, y: z, }, @@ -191,6 +197,13 @@ function PathCreation({ return path; }); + const updatedPath = updatedPaths.find( + (path): path is Types.VehicleEventsSchema => + path.type === "Vehicle" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); }; @@ -239,12 +252,12 @@ function PathCreation({ e.stopPropagation(); setSelectedActionSphere({ path, - point: sphereRefs.current[point.uuid], + points: sphereRefs.current[point.uuid], }); setSubModule("mechanics"); setSelectedPath(null); }} - userData={{ point, path }} + userData={{ points, path }} onPointerMissed={() => { if (eyeDropMode) return; setSubModule("properties"); @@ -256,8 +269,8 @@ function PathCreation({ index === 0 ? "orange" : index === path.points.length - 1 - ? "blue" - : "green" + ? "blue" + : "green" } /> </Sphere> @@ -318,23 +331,23 @@ function PathCreation({ }} > <Sphere - key={path.point.uuid} - uuid={path.point.uuid} - position={path.point.position} + key={path.points.uuid} + uuid={path.points.uuid} + position={path.points.position} args={[0.15, 32, 32]} name="events-sphere" - ref={(el) => (sphereRefs.current[path.point.uuid] = el!)} + ref={(el) => (sphereRefs.current[path.points.uuid] = el!)} onClick={(e) => { if (isConnecting || eyeDropMode) return; e.stopPropagation(); setSelectedActionSphere({ path, - point: sphereRefs.current[path.point.uuid], + points: sphereRefs.current[path.points.uuid], }); setSubModule("mechanics"); setSelectedPath(null); }} - userData={{ point: path.point, path }} + userData={{ points: path.points, path }} onPointerMissed={() => { if (eyeDropMode) return; setSubModule("properties"); @@ -352,7 +365,7 @@ function PathCreation({ {selectedActionSphere && transformMode && ( <TransformControls ref={transformRef} - object={selectedActionSphere.point} + object={selectedActionSphere.points} mode={transformMode} onMouseUp={updateSimulationPaths} /> diff --git a/app/src/modules/simulation/process/animation.Worker.js b/app/src/modules/simulation/process/animation.Worker.js deleted file mode 100644 index faa6587..0000000 --- a/app/src/modules/simulation/process/animation.Worker.js +++ /dev/null @@ -1,916 +0,0 @@ -// // animation-worker.js -// // This web worker handles animation calculations off the main thread - -// /* eslint-disable no-restricted-globals */ -// // The above disables the ESLint rule for this file since 'self' is valid in web workers - -// // Store process data, animation states, and objects -// let processes = []; -// let animationStates = {}; -// let lastTimestamp = 0; - -// // Message handler for communication with main thread -// self.onmessage = function (event) { -// const { type, data } = event.data; - -// switch (type) { -// case "initialize": -// processes = data.processes; -// initializeAnimationStates(); -// break; - -// case "update": -// const { timestamp, isPlaying } = data; -// if (isPlaying) { -// const delta = (timestamp - lastTimestamp) / 1000; // Convert to seconds -// updateAnimations(delta, timestamp); -// } -// lastTimestamp = timestamp; -// break; - -// case "reset": -// resetAnimations(); -// break; - -// case "togglePlay": -// // If resuming from pause, recalculate the time delta -// lastTimestamp = data.timestamp; -// break; -// } -// }; - -// // Initialize animation states for all processes -// function initializeAnimationStates() { -// animationStates = {}; - -// processes.forEach((process) => { -// animationStates[process.id] = { -// spawnedObjects: {}, -// nextSpawnTime: 0, -// objectIdCounter: 0, -// }; -// }); - -// // Send initial states back to main thread -// self.postMessage({ -// type: "statesInitialized", -// data: { animationStates }, -// }); -// } - -// // Reset all animations -// function resetAnimations() { -// initializeAnimationStates(); -// } - -// // Find spawn point in a process -// function findSpawnPoint(process) { -// for (const path of process.paths || []) { -// for (const point of path.points || []) { -// const spawnAction = point.actions?.find( -// (a) => a.isUsed && a.type === "Spawn" -// ); -// if (spawnAction) { -// return { point, path }; -// } -// } -// } -// return null; -// } - -// // Create a new spawned object with proper initial position -// function createSpawnedObject(process, spawnPoint, currentTime, materialType) { -// // Extract spawn position from the actual spawn point -// const position = spawnPoint.point.position -// ? [...spawnPoint.point.position] -// : [0, 0, 0]; - -// // Get the path position and add it to the spawn point position -// const pathPosition = spawnPoint.path.pathPosition || [0, 0, 0]; -// const absolutePosition = [ -// position[0] + pathPosition[0], -// position[1] + pathPosition[1], -// position[2] + pathPosition[2], -// ]; - -// return { -// id: `obj-${process.id}-${animationStates[process.id].objectIdCounter}`, -// position: absolutePosition, -// state: { -// currentIndex: 0, -// progress: 0, -// isAnimating: true, -// speed: process.speed || 1, -// isDelaying: false, -// delayStartTime: 0, -// currentDelayDuration: 0, -// delayComplete: false, -// currentPathIndex: 0, -// // Store the spawn point index to start animation from correct path point -// spawnPointIndex: getPointIndexInProcess(process, spawnPoint.point), -// }, -// visible: true, -// materialType: materialType || "Default", -// spawnTime: currentTime, -// }; -// } - -// // Get the index of a point within the process animation path -// function getPointIndexInProcess(process, point) { -// if (!process.paths) return 0; - -// let cumulativePoints = 0; -// for (const path of process.paths) { -// for (let i = 0; i < (path.points?.length || 0); i++) { -// if (path.points[i].uuid === point.uuid) { -// return cumulativePoints + i; -// } -// } -// cumulativePoints += path.points?.length || 0; -// } - -// return 0; -// } - -// // Get point data for current animation index -// function getPointDataForAnimationIndex(process, index) { -// if (!process.paths) return null; - -// let cumulativePoints = 0; -// for (const path of process.paths) { -// const pointCount = path.points?.length || 0; - -// if (index < cumulativePoints + pointCount) { -// const pointIndex = index - cumulativePoints; -// return path.points?.[pointIndex] || null; -// } - -// cumulativePoints += pointCount; -// } - -// return null; -// } - -// // Convert process paths to Vector3 format -// function getProcessPath(process) { -// return process.animationPath?.map((p) => ({ x: p.x, y: p.y, z: p.z })) || []; -// } - -// // Handle material swap for an object -// function handleMaterialSwap(processId, objectId, materialType) { -// const processState = animationStates[processId]; -// if (!processState || !processState.spawnedObjects[objectId]) return; - -// processState.spawnedObjects[objectId].materialType = materialType; - -// // Notify main thread about material change -// self.postMessage({ -// type: "materialChanged", -// data: { -// processId, -// objectId, -// materialType, -// }, -// }); -// } - -// // Handle point actions for an object -// function handlePointActions(processId, objectId, actions = [], currentTime) { -// let shouldStopAnimation = false; -// const processState = animationStates[processId]; - -// if (!processState || !processState.spawnedObjects[objectId]) return false; - -// const objectState = processState.spawnedObjects[objectId]; - -// actions.forEach((action) => { -// if (!action.isUsed) return; - -// switch (action.type) { -// case "Delay": -// if (objectState.state.isDelaying) return; - -// const delayDuration = -// typeof action.delay === "number" -// ? action.delay -// : parseFloat(action.delay || "0"); - -// if (delayDuration > 0) { -// objectState.state.isDelaying = true; -// objectState.state.delayStartTime = currentTime; -// objectState.state.currentDelayDuration = delayDuration; -// objectState.state.delayComplete = false; -// shouldStopAnimation = true; -// } -// break; - -// case "Despawn": -// delete processState.spawnedObjects[objectId]; -// shouldStopAnimation = true; - -// // Notify main thread about despawn -// self.postMessage({ -// type: "objectDespawned", -// data: { -// processId, -// objectId, -// }, -// }); -// break; - -// case "Swap": -// if (action.material) { -// handleMaterialSwap(processId, objectId, action.material); -// } -// break; - -// default: -// break; -// } -// }); - -// return shouldStopAnimation; -// } - -// // Check if point has non-inherit actions -// function hasNonInheritActions(actions = []) { -// return actions.some((action) => action.isUsed && action.type !== "Inherit"); -// } - -// // Calculate vector lerp (linear interpolation) -// function lerpVectors(v1, v2, alpha) { -// return { -// x: v1.x + (v2.x - v1.x) * alpha, -// y: v1.y + (v2.y - v1.y) * alpha, -// z: v1.z + (v2.z - v1.z) * alpha, -// }; -// } - -// // Calculate vector distance -// function distanceBetweenVectors(v1, v2) { -// const dx = v2.x - v1.x; -// const dy = v2.y - v1.y; -// const dz = v2.z - v1.z; -// return Math.sqrt(dx * dx + dy * dy + dz * dz); -// } - -// // Process spawn logic -// function processSpawns(currentTime) { -// processes.forEach((process) => { -// const processState = animationStates[process.id]; -// if (!processState) return; - -// const spawnPointData = findSpawnPoint(process); -// if (!spawnPointData || !spawnPointData.point.actions) return; - -// const spawnAction = spawnPointData.point.actions.find( -// (a) => a.isUsed && a.type === "Spawn" -// ); -// if (!spawnAction) return; - -// const spawnInterval = -// typeof spawnAction.spawnInterval === "number" -// ? spawnAction.spawnInterval -// : parseFloat(spawnAction.spawnInterval || "0"); - -// if (currentTime >= processState.nextSpawnTime) { -// const newObject = createSpawnedObject( -// process, -// spawnPointData, -// currentTime, -// spawnAction.material || "Default" -// ); - -// processState.spawnedObjects[newObject.id] = newObject; -// processState.objectIdCounter++; -// processState.nextSpawnTime = currentTime + spawnInterval; - -// // Notify main thread about new object -// self.postMessage({ -// type: "objectSpawned", -// data: { -// processId: process.id, -// object: newObject, -// }, -// }); -// } -// }); -// } - -// // Update all animations -// function updateAnimations(delta, currentTime) { -// // First handle spawning of new objects -// processSpawns(currentTime); - -// // Then animate existing objects -// processes.forEach((process) => { -// const processState = animationStates[process.id]; -// if (!processState) return; - -// const path = getProcessPath(process); -// if (path.length < 2) return; - -// const updatedObjects = {}; -// let hasChanges = false; - -// Object.entries(processState.spawnedObjects).forEach(([objectId, obj]) => { -// if (!obj.visible || !obj.state.isAnimating) return; - -// const stateRef = obj.state; - -// // Use the spawnPointIndex as starting point if it's the initial movement -// if (stateRef.currentIndex === 0 && stateRef.progress === 0) { -// stateRef.currentIndex = stateRef.spawnPointIndex || 0; -// } - -// // Get current point data -// const currentPointData = getPointDataForAnimationIndex( -// process, -// stateRef.currentIndex -// ); - -// // Execute actions when arriving at a new point -// if (stateRef.progress === 0 && currentPointData?.actions) { -// const shouldStop = handlePointActions( -// process.id, -// objectId, -// currentPointData.actions, -// currentTime -// ); -// if (shouldStop) return; -// } - -// // Handle delays -// if (stateRef.isDelaying) { -// if ( -// currentTime - stateRef.delayStartTime >= -// stateRef.currentDelayDuration -// ) { -// stateRef.isDelaying = false; -// stateRef.delayComplete = true; -// } else { -// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; -// return; // Keep waiting -// } -// } - -// const nextPointIdx = stateRef.currentIndex + 1; -// const isLastPoint = nextPointIdx >= path.length; - -// if (isLastPoint) { -// if (currentPointData?.actions) { -// const shouldStop = !hasNonInheritActions(currentPointData.actions); -// if (shouldStop) { -// // Reached the end of path with no more actions -// delete processState.spawnedObjects[objectId]; - -// // Notify main thread to remove the object -// self.postMessage({ -// type: "objectCompleted", -// data: { -// processId: process.id, -// objectId, -// }, -// }); -// return; -// } -// } -// } - -// if (!isLastPoint) { -// const currentPos = path[stateRef.currentIndex]; -// const nextPos = path[nextPointIdx]; -// const distance = distanceBetweenVectors(currentPos, nextPos); -// const movement = stateRef.speed * delta; - -// // Update progress based on distance and speed -// const oldProgress = stateRef.progress; -// stateRef.progress += movement / distance; - -// if (stateRef.progress >= 1) { -// // Reached next point -// stateRef.currentIndex = nextPointIdx; -// stateRef.progress = 0; -// stateRef.delayComplete = false; -// obj.position = [nextPos.x, nextPos.y, nextPos.z]; -// } else { -// // Interpolate position -// const lerpedPos = lerpVectors(currentPos, nextPos, stateRef.progress); -// obj.position = [lerpedPos.x, lerpedPos.y, lerpedPos.z]; -// } - -// // Only send updates when there's meaningful movement -// if (Math.abs(oldProgress - stateRef.progress) > 0.01) { -// hasChanges = true; -// } -// } - -// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; -// }); - -// // Update animation state with modified objects -// if (Object.keys(updatedObjects).length > 0) { -// processState.spawnedObjects = { -// ...processState.spawnedObjects, -// ...updatedObjects, -// }; - -// // Only send position updates when there are meaningful changes -// if (hasChanges) { -// self.postMessage({ -// type: "positionsUpdated", -// data: { -// processId: process.id, -// objects: updatedObjects, -// }, -// }); -// } -// } -// }); -// } - -// animation-worker.js -// This web worker handles animation calculations off the main thread - -/* eslint-disable no-restricted-globals */ -// The above disables the ESLint rule for this file since 'self' is valid in web workers - -// Store process data, animation states, and objects -let processes = []; -let animationStates = {}; -let lastTimestamp = 0; -let debugMode = true; - -// Logger function for debugging -function log(...args) { - if (debugMode) { - self.postMessage({ - type: "debug", - data: { message: args.join(' ') } - }); - } -} - -// Message handler for communication with main thread -self.onmessage = function (event) { - const { type, data } = event.data; - log(`Worker received message: ${type}`); - - switch (type) { - case "initialize": - processes = data.processes; - log(`Initialized with ${processes.length} processes`); - initializeAnimationStates(); - break; - - case "update": - const { timestamp, isPlaying } = data; - if (isPlaying) { - const delta = lastTimestamp === 0 ? 0.016 : (timestamp - lastTimestamp) / 1000; // Convert to seconds - updateAnimations(delta, timestamp); - } - lastTimestamp = timestamp; - break; - - case "reset": - log("Resetting animations"); - resetAnimations(); - break; - - case "togglePlay": - // If resuming from pause, recalculate the time delta - log(`Toggle play: ${data.isPlaying}`); - lastTimestamp = data.timestamp; - break; - - case "setDebug": - debugMode = data.enabled; - log(`Debug mode: ${debugMode}`); - break; - } -}; - -// Initialize animation states for all processes -function initializeAnimationStates() { - animationStates = {}; - - processes.forEach((process) => { - if (!process || !process.id) { - log("Invalid process found:", process); - return; - } - - animationStates[process.id] = { - spawnedObjects: {}, - nextSpawnTime: 0, - objectIdCounter: 0, - }; - }); - - // Send initial states back to main thread - self.postMessage({ - type: "statesInitialized", - data: { animationStates }, - }); -} - -// Reset all animations -function resetAnimations() { - initializeAnimationStates(); -} - -// Find spawn point in a process -function findSpawnPoint(process) { - if (!process || !process.paths) { - log(`No paths found for process ${process?.id}`); - return null; - } - - for (const path of process.paths) { - if (!path || !path.points) continue; - - for (const point of path.points) { - if (!point || !point.actions) continue; - - const spawnAction = point.actions.find( - (a) => a && a.isUsed && a.type === "Spawn" - ); - if (spawnAction) { - return { point, path }; - } - } - } - log(`No spawn points found for process ${process.id}`); - return null; -} - -// Create a new spawned object with proper initial position -function createSpawnedObject(process, spawnPoint, currentTime, materialType) { - // Extract spawn position from the actual spawn point - const position = spawnPoint.point.position - ? [...spawnPoint.point.position] - : [0, 0, 0]; - - // Get the path position and add it to the spawn point position - const pathPosition = spawnPoint.path.pathPosition || [0, 0, 0]; - const absolutePosition = [ - position[0] + pathPosition[0], - position[1] + pathPosition[1], - position[2] + pathPosition[2], - ]; - - return { - id: `obj-${process.id}-${animationStates[process.id].objectIdCounter}`, - position: absolutePosition, - state: { - currentIndex: 0, - progress: 0, - isAnimating: true, - speed: process.speed || 1, - isDelaying: false, - delayStartTime: 0, - currentDelayDuration: 0, - delayComplete: false, - currentPathIndex: 0, - // Store the spawn point index to start animation from correct path point - spawnPointIndex: getPointIndexInProcess(process, spawnPoint.point), - }, - visible: true, - materialType: materialType || "Default", - spawnTime: currentTime, - }; -} - -// Get the index of a point within the process animation path -function getPointIndexInProcess(process, point) { - if (!process.paths) return 0; - - let cumulativePoints = 0; - for (const path of process.paths) { - for (let i = 0; i < (path.points?.length || 0); i++) { - if (path.points[i].uuid === point.uuid) { - return cumulativePoints + i; - } - } - cumulativePoints += path.points?.length || 0; - } - - return 0; -} - -// Get point data for current animation index -function getPointDataForAnimationIndex(process, index) { - if (!process.paths) return null; - - let cumulativePoints = 0; - for (const path of process.paths) { - const pointCount = path.points?.length || 0; - - if (index < cumulativePoints + pointCount) { - const pointIndex = index - cumulativePoints; - return path.points?.[pointIndex] || null; - } - - cumulativePoints += pointCount; - } - - return null; -} - -// Convert process paths to Vector3 format -function getProcessPath(process) { - if (!process.animationPath) { - log(`No animation path for process ${process.id}`); - return []; - } - return process.animationPath.map((p) => ({ x: p.x, y: p.y, z: p.z })) || []; -} - -// Handle material swap for an object -function handleMaterialSwap(processId, objectId, materialType) { - const processState = animationStates[processId]; - if (!processState || !processState.spawnedObjects[objectId]) return; - - processState.spawnedObjects[objectId].materialType = materialType; - - // Notify main thread about material change - self.postMessage({ - type: "materialChanged", - data: { - processId, - objectId, - materialType, - }, - }); -} - -// Handle point actions for an object -function handlePointActions(processId, objectId, actions = [], currentTime) { - let shouldStopAnimation = false; - const processState = animationStates[processId]; - - if (!processState || !processState.spawnedObjects[objectId]) return false; - - const objectState = processState.spawnedObjects[objectId]; - - actions.forEach((action) => { - if (!action || !action.isUsed) return; - - switch (action.type) { - case "Delay": - if (objectState.state.isDelaying) return; - - const delayDuration = - typeof action.delay === "number" - ? action.delay - : parseFloat(action.delay || "0"); - - if (delayDuration > 0) { - objectState.state.isDelaying = true; - objectState.state.delayStartTime = currentTime; - objectState.state.currentDelayDuration = delayDuration; - objectState.state.delayComplete = false; - shouldStopAnimation = true; - } - break; - - case "Despawn": - delete processState.spawnedObjects[objectId]; - shouldStopAnimation = true; - - // Notify main thread about despawn - self.postMessage({ - type: "objectDespawned", - data: { - processId, - objectId, - }, - }); - break; - - case "Swap": - if (action.material) { - handleMaterialSwap(processId, objectId, action.material); - } - break; - - default: - break; - } - }); - - return shouldStopAnimation; -} - -// Check if point has non-inherit actions -function hasNonInheritActions(actions = []) { - return actions.some((action) => action && action.isUsed && action.type !== "Inherit"); -} - -// Calculate vector lerp (linear interpolation) -function lerpVectors(v1, v2, alpha) { - return { - x: v1.x + (v2.x - v1.x) * alpha, - y: v1.y + (v2.y - v1.y) * alpha, - z: v1.z + (v2.z - v1.z) * alpha, - }; -} - -// Calculate vector distance -function distanceBetweenVectors(v1, v2) { - const dx = v2.x - v1.x; - const dy = v2.y - v1.y; - const dz = v2.z - v1.z; - return Math.sqrt(dx * dx + dy * dy + dz * dz); -} - -// Process spawn logic -function processSpawns(currentTime) { - processes.forEach((process) => { - const processState = animationStates[process.id]; - if (!processState) return; - - const spawnPointData = findSpawnPoint(process); - if (!spawnPointData || !spawnPointData.point.actions) return; - - const spawnAction = spawnPointData.point.actions.find( - (a) => a.isUsed && a.type === "Spawn" - ); - if (!spawnAction) return; - - const spawnInterval = - typeof spawnAction.spawnInterval === "number" - ? spawnAction.spawnInterval - : parseFloat(spawnAction.spawnInterval || "2"); // Default to 2 seconds if not specified - - if (currentTime >= processState.nextSpawnTime) { - const newObject = createSpawnedObject( - process, - spawnPointData, - currentTime, - spawnAction.material || "Default" - ); - - processState.spawnedObjects[newObject.id] = newObject; - processState.objectIdCounter++; - processState.nextSpawnTime = currentTime + spawnInterval; - - log(`Spawned object ${newObject.id} for process ${process.id}`); - - // Notify main thread about new object - self.postMessage({ - type: "objectSpawned", - data: { - processId: process.id, - object: newObject, - }, - }); - } - }); -} - -// Update all animations -function updateAnimations(delta, currentTime) { - // First handle spawning of new objects - processSpawns(currentTime); - - // Then animate existing objects - processes.forEach((process) => { - const processState = animationStates[process.id]; - if (!processState) return; - - const path = getProcessPath(process); - if (path.length < 2) { - log(`Path too short for process ${process.id}, length: ${path.length}`); - return; - } - - const updatedObjects = {}; - let hasChanges = false; - - Object.entries(processState.spawnedObjects).forEach(([objectId, obj]) => { - if (!obj.visible || !obj.state.isAnimating) return; - - const stateRef = obj.state; - - // Use the spawnPointIndex as starting point if it's the initial movement - if (stateRef.currentIndex === 0 && stateRef.progress === 0) { - stateRef.currentIndex = stateRef.spawnPointIndex || 0; - } - - // Get current point data - const currentPointData = getPointDataForAnimationIndex( - process, - stateRef.currentIndex - ); - - // Execute actions when arriving at a new point - if (stateRef.progress === 0 && currentPointData?.actions) { - const shouldStop = handlePointActions( - process.id, - objectId, - currentPointData.actions, - currentTime - ); - if (shouldStop) return; - } - - // Handle delays - if (stateRef.isDelaying) { - if ( - currentTime - stateRef.delayStartTime >= - stateRef.currentDelayDuration - ) { - stateRef.isDelaying = false; - stateRef.delayComplete = true; - } else { - updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; - return; // Keep waiting - } - } - - const nextPointIdx = stateRef.currentIndex + 1; - const isLastPoint = nextPointIdx >= path.length; - - if (isLastPoint) { - if (currentPointData?.actions) { - const shouldStop = !hasNonInheritActions(currentPointData.actions); - if (shouldStop) { - // Reached the end of path with no more actions - delete processState.spawnedObjects[objectId]; - log(`Object ${objectId} completed path`); - - // Notify main thread to remove the object - self.postMessage({ - type: "objectCompleted", - data: { - processId: process.id, - objectId, - }, - }); - return; - } - } - } - - if (!isLastPoint) { - const currentPos = path[stateRef.currentIndex]; - const nextPos = path[nextPointIdx]; - const distance = distanceBetweenVectors(currentPos, nextPos); - - // Ensure we don't divide by zero - if (distance > 0) { - const movement = stateRef.speed * delta; - - // Update progress based on distance and speed - const oldProgress = stateRef.progress; - stateRef.progress += movement / distance; - - if (stateRef.progress >= 1) { - // Reached next point - stateRef.currentIndex = nextPointIdx; - stateRef.progress = 0; - stateRef.delayComplete = false; - obj.position = [nextPos.x, nextPos.y, nextPos.z]; - } else { - // Interpolate position - const lerpedPos = lerpVectors(currentPos, nextPos, stateRef.progress); - obj.position = [lerpedPos.x, lerpedPos.y, lerpedPos.z]; - } - - // Only send updates when there's meaningful movement - if (Math.abs(oldProgress - stateRef.progress) > 0.01) { - hasChanges = true; - } - } else { - // Skip to next point if distance is zero - stateRef.currentIndex = nextPointIdx; - stateRef.progress = 0; - obj.position = [nextPos.x, nextPos.y, nextPos.z]; - hasChanges = true; - } - } - - updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; - }); - - // Update animation state with modified objects - if (Object.keys(updatedObjects).length > 0) { - processState.spawnedObjects = { - ...processState.spawnedObjects, - ...updatedObjects, - }; - - // Only send position updates when there are meaningful changes - if (hasChanges) { - self.postMessage({ - type: "positionsUpdated", - data: { - processId: process.id, - objects: updatedObjects, - }, - }); - } - } - }); -} diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx index 512b165..d82b800 100644 --- a/app/src/modules/simulation/process/processAnimator.tsx +++ b/app/src/modules/simulation/process/processAnimator.tsx @@ -1,590 +1,16 @@ -// import React, { useRef, useState, useEffect, useMemo } from "react"; -// import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -// import { GLTFLoader } from "three-stdlib"; -// import { useLoader, useFrame } from "@react-three/fiber"; -// import * as THREE from "three"; -// import { GLTF } from "three-stdlib"; -// import boxGltb from "../../../assets/gltf-glb/crate_box.glb"; - -// interface PointAction { -// uuid: string; -// name: string; -// type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap"; -// objectType: string; -// material: string; -// delay: string | number; -// spawnInterval: string | number; -// isUsed: boolean; -// } - -// interface ProcessPoint { -// uuid: string; -// position: number[]; -// rotation: number[]; -// actions: PointAction[]; -// connections: { -// source: { pathUUID: string; pointUUID: string }; -// targets: { pathUUID: string; pointUUID: string }[]; -// }; -// } - -// interface ProcessPath { -// modeluuid: string; -// modelName: string; -// points: ProcessPoint[]; -// pathPosition: number[]; -// pathRotation: number[]; -// speed: number; -// } - -// interface ProcessData { -// id: string; -// paths: ProcessPath[]; -// animationPath: { x: number; y: number; z: number }[]; -// pointActions: PointAction[][]; -// speed: number; -// customMaterials?: Record<string, THREE.Material>; -// renderAs?: "box" | "custom"; -// } - -// interface AnimationState { -// currentIndex: number; -// progress: number; -// isAnimating: boolean; -// speed: number; -// isDelaying: boolean; -// delayStartTime: number; -// currentDelayDuration: number; -// delayComplete: boolean; -// currentPathIndex: number; -// } - -// interface SpawnedObject { -// ref: React.RefObject<THREE.Group | THREE.Mesh>; -// state: AnimationState; -// visible: boolean; -// material: THREE.Material; -// spawnTime: number; -// currentMaterialType: string; -// position: THREE.Vector3; // The position of the object -// } - -// interface ProcessAnimationState { -// spawnedObjects: { [objectId: string]: SpawnedObject }; -// nextSpawnTime: number; -// objectIdCounter: number; -// } - -// const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ -// processes, -// }) => { -// -// const gltf = useLoader(GLTFLoader, boxGltb) as GLTF; -// const { isPlaying } = usePlayButtonStore(); -// const groupRef = useRef<THREE.Group>(null); - -// const [animationStates, setAnimationStates] = useState< -// Record<string, ProcessAnimationState> -// >({}); - -// // Base materials -// const baseMaterials = useMemo( -// () => ({ -// Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), -// Box: new THREE.MeshStandardMaterial({ -// color: 0xcccccc, -// metalness: 0.8, -// roughness: 0.2, -// }), -// Crate: new THREE.MeshStandardMaterial({ -// color: 0x00aaff, -// metalness: 0.1, -// roughness: 0.5, -// }), -// Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), -// }), -// [] -// ); - -// // Initialize animation states when processes or play state changes -// useEffect(() => { -// if (!isPlaying) { -// setAnimationStates({}); -// return; -// } - -// const newStates: Record<string, ProcessAnimationState> = {}; -// processes.forEach((process) => { -// newStates[process.id] = { -// spawnedObjects: {}, -// nextSpawnTime: 0, -// objectIdCounter: 0, -// }; -// }); -// setAnimationStates(newStates); -// }, [isPlaying, processes]); - -// // Find spawn point in a process -// const findSpawnPoint = (process: ProcessData): ProcessPoint | null => { -// for (const path of process.paths || []) { -// for (const point of path.points || []) { -// const spawnAction = point.actions?.find( -// (a) => a.isUsed && a.type === "Spawn" -// ); -// if (spawnAction) { -// return point; -// } -// } -// } -// return null; -// }; - -// // Find the corresponding animation path point for a spawn point -// const findAnimationPathPoint = (process: ProcessData, spawnPoint: ProcessPoint): THREE.Vector3 => { -// // If we have an animation path, use the first point -// if (process.animationPath && process.animationPath.length > 0) { -// // Find the index of this point in the path -// let pointIndex = 0; - -// // Try to find the corresponding point in the animation path -// for (const path of process.paths || []) { -// for (let i = 0; i < (path.points?.length || 0); i++) { -// const point = path.points?.[i]; -// if (point && point.uuid === spawnPoint.uuid) { -// // Found the matching point -// if (process.animationPath[pointIndex]) { -// const p = process.animationPath[pointIndex]; -// return new THREE.Vector3(p.x, p.y, p.z); -// } -// } -// pointIndex++; -// } -// } - -// // Fallback to the spawn point's position -// return new THREE.Vector3( -// spawnPoint.position[0], -// spawnPoint.position[1], -// spawnPoint.position[2] -// ); -// } - -// // If no animation path, use the spawn point's position -// return new THREE.Vector3( -// spawnPoint.position[0], -// spawnPoint.position[1], -// spawnPoint.position[2] -// ); -// }; - -// // Create a new spawned object -// const createSpawnedObject = ( -// process: ProcessData, -// currentTime: number, -// materialType: string, -// spawnPoint: ProcessPoint -// ): SpawnedObject => { -// const processMaterials = { -// ...baseMaterials, -// ...(process.customMaterials || {}), -// }; - -// // Get the position where we should spawn -// const spawnPosition = findAnimationPathPoint(process, spawnPoint); - -// return { -// ref: React.createRef(), -// state: { -// currentIndex: 0, -// progress: 0, -// isAnimating: true, -// speed: process.speed || 1, -// isDelaying: false, -// delayStartTime: 0, -// currentDelayDuration: 0, -// delayComplete: false, -// currentPathIndex: 0, -// }, -// visible: true, -// material: -// processMaterials[materialType as keyof typeof processMaterials] || -// baseMaterials.Default, -// currentMaterialType: materialType, -// spawnTime: currentTime, -// position: spawnPosition, // Store the position directly -// }; -// }; - -// // Handle material swap for an object -// const handleMaterialSwap = ( -// processId: string, -// objectId: string, -// materialType: string -// ) => { -// setAnimationStates((prev) => { -// const processState = prev[processId]; -// if (!processState || !processState.spawnedObjects[objectId]) return prev; - -// const process = processes.find((p) => p.id === processId); -// const processMaterials = { -// ...baseMaterials, -// ...(process?.customMaterials || {}), -// }; - -// const newMaterial = -// processMaterials[materialType as keyof typeof processMaterials] || -// baseMaterials.Default; - -// return { -// ...prev, -// [processId]: { -// ...processState, -// spawnedObjects: { -// ...processState.spawnedObjects, -// [objectId]: { -// ...processState.spawnedObjects[objectId], -// material: newMaterial, -// currentMaterialType: materialType, -// }, -// }, -// }, -// }; -// }); -// }; - -// // Handle point actions for an object -// const handlePointActions = ( -// processId: string, -// objectId: string, -// actions: PointAction[] = [], -// currentTime: number -// ): boolean => { -// let shouldStopAnimation = false; - -// actions.forEach((action) => { -// if (!action.isUsed) return; - -// switch (action.type) { -// case "Delay": -// setAnimationStates((prev) => { -// const processState = prev[processId]; -// if ( -// !processState || -// !processState.spawnedObjects[objectId] || -// processState.spawnedObjects[objectId].state.isDelaying -// ) { -// return prev; -// } - -// const delayDuration = -// typeof action.delay === "number" -// ? action.delay -// : parseFloat(action.delay as string) || 0; - -// if (delayDuration > 0) { -// return { -// ...prev, -// [processId]: { -// ...processState, -// spawnedObjects: { -// ...processState.spawnedObjects, -// [objectId]: { -// ...processState.spawnedObjects[objectId], -// state: { -// ...processState.spawnedObjects[objectId].state, -// isDelaying: true, -// delayStartTime: currentTime, -// currentDelayDuration: delayDuration, -// delayComplete: false, -// }, -// }, -// }, -// }, -// }; -// } -// return prev; -// }); -// shouldStopAnimation = true; -// break; - -// case "Despawn": -// setAnimationStates((prev) => { -// const processState = prev[processId]; -// if (!processState) return prev; - -// const newSpawnedObjects = { ...processState.spawnedObjects }; -// delete newSpawnedObjects[objectId]; - -// return { -// ...prev, -// [processId]: { -// ...processState, -// spawnedObjects: newSpawnedObjects, -// }, -// }; -// }); -// shouldStopAnimation = true; -// break; - -// case "Swap": -// if (action.material) { -// handleMaterialSwap(processId, objectId, action.material); -// } -// break; - -// default: -// break; -// } -// }); - -// return shouldStopAnimation; -// }; - -// // Check if point has non-inherit actions -// const hasNonInheritActions = (actions: PointAction[] = []): boolean => { -// return actions.some((action) => action.isUsed && action.type !== "Inherit"); -// }; - -// // Get point data for current animation index -// const getPointDataForAnimationIndex = ( -// process: ProcessData, -// index: number -// ): ProcessPoint | null => { -// if (!process.paths) return null; - -// let cumulativePoints = 0; -// for (const path of process.paths) { -// const pointCount = path.points?.length || 0; - -// if (index < cumulativePoints + pointCount) { -// const pointIndex = index - cumulativePoints; -// return path.points?.[pointIndex] || null; -// } - -// cumulativePoints += pointCount; -// } - -// return null; -// }; - -// // Spawn objects for all processes -// useFrame((state) => { -// if (!isPlaying) return; - -// const currentTime = state.clock.getElapsedTime(); -// setAnimationStates((prev) => { -// const newStates = { ...prev }; - -// processes.forEach((process) => { -// const processState = newStates[process.id]; -// if (!processState) return; - -// const spawnPoint = findSpawnPoint(process); -// if (!spawnPoint || !spawnPoint.actions) return; - -// const spawnAction = spawnPoint.actions.find( -// (a) => a.isUsed && a.type === "Spawn" -// ); -// if (!spawnAction) return; - -// const spawnInterval = -// typeof spawnAction.spawnInterval === "number" -// ? spawnAction.spawnInterval -// : parseFloat(spawnAction.spawnInterval as string) || 0; - -// if (currentTime >= processState.nextSpawnTime) { -// const objectId = `obj-${process.id}-${processState.objectIdCounter}`; - -// // Create the new object with the spawn point -// const newObject = createSpawnedObject( -// process, -// currentTime, -// spawnAction.material || "Default", -// spawnPoint -// ); - -// newStates[process.id] = { -// ...processState, -// spawnedObjects: { -// ...processState.spawnedObjects, -// [objectId]: newObject, -// }, -// objectIdCounter: processState.objectIdCounter + 1, -// nextSpawnTime: currentTime + spawnInterval, -// }; -// } -// }); - -// return newStates; -// }); -// }); - -// // Animate objects for all processes -// useFrame((state, delta) => { -// if (!isPlaying) return; - -// const currentTime = state.clock.getElapsedTime(); -// setAnimationStates((prev) => { -// const newStates = { ...prev }; - -// processes.forEach((process) => { -// const processState = newStates[process.id]; -// if (!processState) return; - -// const path = -// process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || -// []; -// if (path.length < 2) return; - -// const updatedObjects = { ...processState.spawnedObjects }; - -// Object.entries(processState.spawnedObjects).forEach( -// ([objectId, obj]) => { -// if (!obj.visible || !obj.state.isAnimating) return; - -// const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current; -// if (!currentRef) return; - -// // Set the position when the reference is first available -// if (obj.position && obj.state.currentIndex === 0 && obj.state.progress === 0) { -// currentRef.position.copy(obj.position); -// } - -// const stateRef = obj.state; - -// // Get current point data -// const currentPointData = getPointDataForAnimationIndex( -// process, -// stateRef.currentIndex -// ); - -// // Execute actions when arriving at a new point -// if (stateRef.progress === 0 && currentPointData?.actions) { -// const shouldStop = handlePointActions( -// process.id, -// objectId, -// currentPointData.actions, -// currentTime -// ); -// if (shouldStop) return; -// } - -// // Handle delays -// if (stateRef.isDelaying) { -// if ( -// currentTime - stateRef.delayStartTime >= -// stateRef.currentDelayDuration -// ) { -// stateRef.isDelaying = false; -// stateRef.delayComplete = true; -// } else { -// return; // Keep waiting -// } -// } - -// const nextPointIdx = stateRef.currentIndex + 1; -// const isLastPoint = nextPointIdx >= path.length; - -// if (isLastPoint) { -// if (currentPointData?.actions) { -// const shouldStop = !hasNonInheritActions( -// currentPointData.actions -// ); -// if (shouldStop) { -// currentRef.position.copy(path[stateRef.currentIndex]); -// delete updatedObjects[objectId]; -// return; -// } -// } -// } - -// if (!isLastPoint) { -// const nextPoint = path[nextPointIdx]; -// const distance = -// path[stateRef.currentIndex].distanceTo(nextPoint); -// const movement = stateRef.speed * delta; -// stateRef.progress += movement / distance; - -// if (stateRef.progress >= 1) { -// stateRef.currentIndex = nextPointIdx; -// stateRef.progress = 0; -// stateRef.delayComplete = false; -// currentRef.position.copy(nextPoint); -// } else { -// currentRef.position.lerpVectors( -// path[stateRef.currentIndex], -// nextPoint, -// stateRef.progress -// ); -// } -// } - -// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; -// } -// ); - -// newStates[process.id] = { -// ...processState, -// spawnedObjects: updatedObjects, -// }; -// }); - -// return newStates; -// }); -// }); - -// if (!processes || processes.length === 0) { -// return null; -// } - -// return ( -// <> -// {Object.entries(animationStates).flatMap(([processId, processState]) => -// Object.entries(processState.spawnedObjects) -// .filter(([_, obj]) => obj.visible) -// .map(([objectId, obj]) => { -// const process = processes.find((p) => p.id === processId); -// const renderAs = process?.renderAs || "custom"; - -// return renderAs === "box" ? ( -// <mesh -// key={objectId} -// ref={obj.ref as React.RefObject<THREE.Mesh>} -// material={obj.material} -// position={obj.position} // Set position directly in the JSX -// > -// <boxGeometry args={[1, 1, 1]} /> -// </mesh> -// ) : ( -// gltf?.scene && ( -// <group -// key={objectId} -// ref={obj.ref as React.RefObject<THREE.Group>} -// position={obj.position} // Set position directly in the JSX -// > -// <primitive -// object={gltf.scene.clone()} -// material={obj.material} -// /> -// </group> -// ) -// ); -// }) -// )} -// </> -// ); -// }; - -// export default ProcessAnimator; - import React, { useRef, useState, useEffect, useMemo } from "react"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { + useAnimationPlaySpeed, + usePauseButtonStore, + usePlayButtonStore, + useResetButtonStore, +} from "../../../store/usePlayButtonStore"; import { GLTFLoader } from "three-stdlib"; import { useLoader, useFrame } from "@react-three/fiber"; import * as THREE from "three"; import { GLTF } from "three-stdlib"; -import boxGltb from "../../../assets/gltf-glb/crate_box.glb"; -import camera from "../../../assets/gltf-glb/camera face 2.gltf"; +import crate from "../../../assets/gltf-glb/crate_box.glb"; +import box from "../../../assets/gltf-glb/box.glb"; interface PointAction { uuid: string; @@ -646,14 +72,13 @@ interface SpawnedObject { material: THREE.Material; spawnTime: number; currentMaterialType: string; - position: THREE.Vector3; // The position of the object + position: THREE.Vector3; } interface ProcessAnimationState { spawnedObjects: { [objectId: string]: SpawnedObject }; nextSpawnTime: number; objectIdCounter: number; - // New fields for process-wide delay isProcessDelaying: boolean; processDelayStartTime: number; processDelayDuration: number; @@ -662,55 +87,118 @@ interface ProcessAnimationState { const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ processes, }) => { - - const gltf = useLoader(GLTFLoader, boxGltb) as GLTF; - const { isPlaying } = usePlayButtonStore(); + const gltf = useLoader(GLTFLoader, crate) as GLTF; + const { isPlaying, setIsPlaying } = usePlayButtonStore(); + const { isPaused, setIsPaused } = usePauseButtonStore(); + const { isReset, setReset } = useResetButtonStore(); const groupRef = useRef<THREE.Group>(null); - + const debugRef = useRef<boolean>(false); + const clockRef = useRef<THREE.Clock>(new THREE.Clock()); + const pauseTimeRef = useRef<number>(0); + const elapsedBeforePauseRef = useRef<number>(0); + const animationStatesRef = useRef<Record<string, ProcessAnimationState>>({}); + const { speed, setSpeed } = useAnimationPlaySpeed(); + const prevIsPlaying = useRef<boolean | null>(null); + const [internalResetFlag, setInternalResetFlag] = useState(false); const [animationStates, setAnimationStates] = useState< Record<string, ProcessAnimationState> >({}); + // Store the speed in a ref to access the latest value in animation frames + const speedRef = useRef<number>(speed); + + // Update the ref when speed changes + useEffect(() => { + speedRef.current = speed; + }, [speed]); + + useEffect(() => { + if (prevIsPlaying.current !== null) { + setAnimationStates({}); + } + + // Update ref to current isPlaying after effect + prevIsPlaying.current = isPlaying; + + // setAnimationStates({}); + }, [isPlaying]); + + // Sync ref with state + useEffect(() => { + animationStatesRef.current = animationStates; + }, [animationStates]); + // Base materials const baseMaterials = useMemo( () => ({ - Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), - Box: new THREE.MeshPhongMaterial({ - color: 0xcccccc, - }), - Crate: new THREE.MeshStandardMaterial({ - color: 0x00aaff, - metalness: 0.1, - roughness: 0.5, - }), + Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), + Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), }), [] ); + // Replace your reset effect with this: + useEffect(() => { + if (isReset) { + // 1. Mark that we're doing an internal reset + setInternalResetFlag(true); + + // 2. Pause the animation first + setIsPlaying(false); + setIsPaused(false); + + // 3. Reset all animation states + setAnimationStates({}); + animationStatesRef.current = {}; + + // 4. Reset timing references + clockRef.current = new THREE.Clock(); + elapsedBeforePauseRef.current = 0; + pauseTimeRef.current = 0; + + // 5. Clear the external reset flag + setReset(false); + + // 6. After state updates are complete, restart + setTimeout(() => { + setInternalResetFlag(false); + setIsPlaying(true); + }, 0); + } + }, [isReset, setReset, setIsPlaying, setIsPaused]); + + // Handle pause state changes + useEffect(() => { + if (isPaused) { + pauseTimeRef.current = clockRef.current.getElapsedTime(); + } else if (pauseTimeRef.current > 0) { + const pausedDuration = + clockRef.current.getElapsedTime() - pauseTimeRef.current; + elapsedBeforePauseRef.current += pausedDuration; + } + }, [isPaused]); + // Initialize animation states when processes or play state changes useEffect(() => { - if (!isPlaying) { - setAnimationStates({}); - return; + if (isPlaying && !internalResetFlag) { + const newStates: Record<string, ProcessAnimationState> = {}; + processes.forEach((process) => { + newStates[process.id] = { + spawnedObjects: {}, + nextSpawnTime: 0, + objectIdCounter: 0, + isProcessDelaying: false, + processDelayStartTime: 0, + processDelayDuration: 0, + }; + }); + setAnimationStates(newStates); + animationStatesRef.current = newStates; + clockRef.current.start(); } + }, [isPlaying, processes, internalResetFlag]); - const newStates: Record<string, ProcessAnimationState> = {}; - processes.forEach((process) => { - newStates[process.id] = { - spawnedObjects: {}, - nextSpawnTime: 0, - objectIdCounter: 0, - // Initialize process-wide delay state - isProcessDelaying: false, - processDelayStartTime: 0, - processDelayDuration: 0, - }; - }); - setAnimationStates(newStates); - }, [isPlaying, processes]); - - // Find spawn point in a process const findSpawnPoint = (process: ProcessData): ProcessPoint | null => { for (const path of process.paths || []) { for (const point of path.points || []) { @@ -725,22 +213,16 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ return null; }; - // Find the corresponding animation path point for a spawn point const findAnimationPathPoint = ( process: ProcessData, spawnPoint: ProcessPoint ): THREE.Vector3 => { - // If we have an animation path, use the first point if (process.animationPath && process.animationPath.length > 0) { - // Find the index of this point in the path let pointIndex = 0; - - // Try to find the corresponding point in the animation path for (const path of process.paths || []) { for (let i = 0; i < (path.points?.length || 0); i++) { const point = path.points?.[i]; if (point && point.uuid === spawnPoint.uuid) { - // Found the matching point if (process.animationPath[pointIndex]) { const p = process.animationPath[pointIndex]; return new THREE.Vector3(p.x, p.y, p.z); @@ -749,16 +231,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ pointIndex++; } } - - // Fallback to the spawn point's position - return new THREE.Vector3( - spawnPoint.position[0], - spawnPoint.position[1], - spawnPoint.position[2] - ); } - - // If no animation path, use the spawn point's position return new THREE.Vector3( spawnPoint.position[0], spawnPoint.position[1], @@ -766,7 +239,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ); }; - // Create a new spawned object const createSpawnedObject = ( process: ProcessData, currentTime: number, @@ -778,8 +250,14 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ...(process.customMaterials || {}), }; - // Get the position where we should spawn const spawnPosition = findAnimationPathPoint(process, spawnPoint); + const material = + processMaterials[materialType as keyof typeof processMaterials] || + baseMaterials.Default; + + if (debugRef.current) { + console.log(`Creating object with material: ${materialType}`, material); + } return { ref: React.createRef(), @@ -787,7 +265,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ currentIndex: 0, progress: 0, isAnimating: true, - speed: process.speed || 1, + speed: process.speed || 1, // Process base speed (will be multiplied by global speed) isDelaying: false, delayStartTime: 0, currentDelayDuration: 0, @@ -795,34 +273,50 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ currentPathIndex: 0, }, visible: true, - material: - processMaterials[materialType as keyof typeof processMaterials] || - baseMaterials.Default, + material: material, currentMaterialType: materialType, spawnTime: currentTime, - position: spawnPosition, // Store the position directly + position: spawnPosition, }; }; - // Handle material swap for an object const handleMaterialSwap = ( processId: string, objectId: string, materialType: string ) => { + if (debugRef.current) { + console.log(`Attempting material swap to: ${materialType}`); + } + setAnimationStates((prev) => { const processState = prev[processId]; - if (!processState || !processState.spawnedObjects[objectId]) return prev; + if (!processState || !processState.spawnedObjects[objectId]) { + if (debugRef.current) console.log("Object not found for swap"); + return prev; + } const process = processes.find((p) => p.id === processId); + if (!process) { + if (debugRef.current) console.log("Process not found"); + return prev; + } + const processMaterials = { ...baseMaterials, - ...(process?.customMaterials || {}), + ...(process.customMaterials || {}), }; const newMaterial = - processMaterials[materialType as keyof typeof processMaterials] || - baseMaterials.Default; + processMaterials[materialType as keyof typeof processMaterials]; + if (!newMaterial) { + if (debugRef.current) console.log(`Material ${materialType} not found`); + return prev; + } + + if (debugRef.current) { + console.log(`Swapping material for ${objectId} to ${materialType}`); + } return { ...prev, @@ -841,7 +335,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ }); }; - // Handle point actions for an object const handlePointActions = ( processId: string, objectId: string, @@ -853,6 +346,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ actions.forEach((action) => { if (!action.isUsed) return; + if (debugRef.current) { + console.log(`Processing action: ${action.type} for ${objectId}`); + } + switch (action.type) { case "Delay": setAnimationStates((prev) => { @@ -871,18 +368,16 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ...prev, [processId]: { ...processState, - // Set process-wide delay instead of object-specific delay isProcessDelaying: true, processDelayStartTime: currentTime, processDelayDuration: delayDuration, - // Update the specific object's state as well spawnedObjects: { ...processState.spawnedObjects, [objectId]: { ...processState.spawnedObjects[objectId], state: { ...processState.spawnedObjects[objectId].state, - isAnimating: false, // Explicitly pause animation during delay + isAnimating: false, isDelaying: true, delayStartTime: currentTime, currentDelayDuration: delayDuration, @@ -931,12 +426,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ return shouldStopAnimation; }; - // Check if point has non-inherit actions const hasNonInheritActions = (actions: PointAction[] = []): boolean => { return actions.some((action) => action.isUsed && action.type !== "Inherit"); }; - // Get point data for current animation index const getPointDataForAnimationIndex = ( process: ProcessData, index: number @@ -958,11 +451,12 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ return null; }; - // Spawn objects for all processes - useFrame((state) => { - if (!isPlaying) return; + useFrame(() => { + if (!isPlaying || isPaused) return; + + const currentTime = + clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; - const currentTime = state.clock.getElapsedTime(); setAnimationStates((prev) => { const newStates = { ...prev }; @@ -970,18 +464,18 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const processState = newStates[process.id]; if (!processState) return; - // Skip spawning if the process is currently in a delay if (processState.isProcessDelaying) { - // Check if delay is over + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + processState.processDelayDuration / speedRef.current; + if ( currentTime - processState.processDelayStartTime >= - processState.processDelayDuration + effectiveDelayTime ) { - // Reset process delay state newStates[process.id] = { ...processState, isProcessDelaying: false, - // Reset delay state on all objects in this process spawnedObjects: Object.entries( processState.spawnedObjects ).reduce( @@ -993,8 +487,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ...obj.state, isDelaying: false, delayComplete: true, - isAnimating: true, // Ensure animation resumes - // Force a small progress to ensure movement starts + isAnimating: true, progress: obj.state.progress === 0 ? 0.001 : obj.state.progress, }, @@ -1004,7 +497,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ), }; } - return; // Skip spawning while delaying + return; } const spawnPoint = findSpawnPoint(process); @@ -1020,10 +513,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ? spawnAction.spawnInterval : parseFloat(spawnAction.spawnInterval as string) || 0; + // Apply global speed to spawn intervals (faster speed = more frequent spawns) + const effectiveSpawnInterval = spawnInterval / speedRef.current; + if (currentTime >= processState.nextSpawnTime) { const objectId = `obj-${process.id}-${processState.objectIdCounter}`; - - // Create the new object with the spawn point const newObject = createSpawnedObject( process, currentTime, @@ -1038,7 +532,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ [objectId]: newObject, }, objectIdCounter: processState.objectIdCounter + 1, - nextSpawnTime: currentTime + spawnInterval, + nextSpawnTime: currentTime + effectiveSpawnInterval, }; } }); @@ -1047,11 +541,12 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ }); }); - // Animate objects for all processes - useFrame((state, delta) => { - if (!isPlaying) return; + useFrame((_, delta) => { + if (!isPlaying || isPaused) return; + + const currentTime = + clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; - const currentTime = state.clock.getElapsedTime(); setAnimationStates((prev) => { const newStates = { ...prev }; @@ -1059,18 +554,18 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const processState = newStates[process.id]; if (!processState) return; - // Check if the process-wide delay is active if (processState.isProcessDelaying) { - // Check if the delay has completed + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + processState.processDelayDuration / speedRef.current; + if ( currentTime - processState.processDelayStartTime >= - processState.processDelayDuration + effectiveDelayTime ) { - // Reset process delay state AND resume animation newStates[process.id] = { ...processState, isProcessDelaying: false, - // Reset delay state on all objects in this process AND ensure isAnimating is true spawnedObjects: Object.entries( processState.spawnedObjects ).reduce( @@ -1082,8 +577,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ...obj.state, isDelaying: false, delayComplete: true, - isAnimating: true, // Ensure animation resumes - // Important: Force progress to a small positive value to ensure movement + isAnimating: true, progress: obj.state.progress === 0 ? 0.005 : obj.state.progress, }, @@ -1092,10 +586,8 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ {} ), }; - // Skip the rest of the processing for this frame to allow the state update to take effect return newStates; } else { - // If we're still in a process-wide delay, don't animate anything return newStates; } } @@ -1109,13 +601,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ Object.entries(processState.spawnedObjects).forEach( ([objectId, obj]) => { - // Skip objects that are explicitly not visible if (!obj.visible) return; const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current; if (!currentRef) return; - // Set the position when the reference is first available if ( obj.position && obj.state.currentIndex === 0 && @@ -1126,27 +616,22 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const stateRef = obj.state; - // Check if we're delaying at the object level and update accordingly if (stateRef.isDelaying) { - if ( - currentTime - stateRef.delayStartTime >= - stateRef.currentDelayDuration - ) { - // Delay is complete, resume animation + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + stateRef.currentDelayDuration / speedRef.current; + + if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) { stateRef.isDelaying = false; stateRef.delayComplete = true; - stateRef.isAnimating = true; // Explicitly resume animation + stateRef.isAnimating = true; - // Force movement from the current point by setting progress to a small value - // if we're at the start of a segment if (stateRef.progress === 0) { stateRef.progress = 0.005; } - // Force an immediate position update to ensure visually accurate position const nextPointIdx = stateRef.currentIndex + 1; if (nextPointIdx < path.length) { - // Calculate the position slightly ahead of the current point const slightProgress = Math.max(stateRef.progress, 0.005); currentRef.position.lerpVectors( path[stateRef.currentIndex], @@ -1157,23 +642,25 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ); } } else { - // Still delaying, don't animate this object updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; return; } } - // Skip animation if the object shouldn't be animating if (!stateRef.isAnimating) return; - // Get current point data const currentPointData = getPointDataForAnimationIndex( process, stateRef.currentIndex ); - // Execute actions when arriving at a new point if (stateRef.progress === 0 && currentPointData?.actions) { + if (debugRef.current) { + console.log( + `At point ${stateRef.currentIndex} with actions:`, + currentPointData.actions + ); + } const shouldStop = handlePointActions( process.id, objectId, @@ -1195,10 +682,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ currentPointData.actions ); if (shouldStop) { - // uncomment this or write own logic to handle the object when reaching the last point of the process - - // currentRef.position.copy(path[stateRef.currentIndex]); - // delete updatedObjects[objectId]; return; } } @@ -1208,37 +691,35 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const nextPoint = path[nextPointIdx]; const distance = path[stateRef.currentIndex].distanceTo(nextPoint); - const movement = stateRef.speed * delta; - // If we just resumed from a delay, ensure we make actual progress + // Apply both process-specific speed and global speed multiplier + const effectiveSpeed = stateRef.speed * speedRef.current; + const movement = effectiveSpeed * delta; + if (stateRef.delayComplete && stateRef.progress < 0.01) { - // Boost initial movement after delay to ensure visible progress - stateRef.progress = 0.05; // Small but visible initial progress - stateRef.delayComplete = false; // Reset flag so we don't do this again + stateRef.progress = 0.05; + stateRef.delayComplete = false; } else { - // Normal progress calculation stateRef.progress += movement / distance; } if (stateRef.progress >= 1) { - // We've reached the next point stateRef.currentIndex = nextPointIdx; stateRef.progress = 0; currentRef.position.copy(nextPoint); - // Check if we need to execute actions at this new point const newPointData = getPointDataForAnimationIndex( process, stateRef.currentIndex ); - if (newPointData?.actions) { - // We've arrived at a new point with actions, handle them in the next frame - // We don't call handlePointActions directly here to avoid state update issues - // The actions will be handled in the next frame when progress is 0 + if (newPointData?.actions && debugRef.current) { + console.log( + `Reached new point with actions:`, + newPointData.actions + ); } } else { - // Normal path interpolation currentRef.position.lerpVectors( path[stateRef.currentIndex], nextPoint, @@ -1274,29 +755,40 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const process = processes.find((p) => p.id === processId); const renderAs = process?.renderAs || "custom"; - return renderAs === "box" ? ( - <mesh - key={objectId} - ref={obj.ref as React.RefObject<THREE.Mesh>} - material={obj.material} - position={obj.position} // Set position directly in the JSX - > - <boxGeometry args={[1, 1, 1]} /> - </mesh> - ) : ( - gltf?.scene && ( + if (renderAs === "box") { + return ( + <mesh + key={objectId} + ref={obj.ref as React.RefObject<THREE.Mesh>} + material={obj.material} + position={obj.position} + > + <boxGeometry args={[1, 1, 1]} /> + </mesh> + ); + } + + if (gltf?.scene) { + // Clone the scene and apply the material to all meshes + const clonedScene = gltf.scene.clone(); + clonedScene.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.material = obj.material; + } + }); + + return ( <group key={objectId} ref={obj.ref as React.RefObject<THREE.Group>} - position={obj.position} // Set position directly in the JSX + position={obj.position} > - <primitive - object={gltf.scene.clone()} - material={obj.material} - /> + <primitive object={clonedScene} /> </group> - ) - ); + ); + } + + return null; }) )} </> diff --git a/app/src/modules/simulation/process/processCreator.tsx b/app/src/modules/simulation/process/processCreator.tsx index 91bc4c1..f089285 100644 --- a/app/src/modules/simulation/process/processCreator.tsx +++ b/app/src/modules/simulation/process/processCreator.tsx @@ -486,20 +486,20 @@ function convertToSimulationPath( 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)], + uuid: path.points.uuid, + position: path.points.position, + actions: Array.isArray(path.points.actions) + ? path.points.actions.map(normalizeAction) + : [normalizeAction(path.points.actions)], connections: { - targets: path.point.connections.targets.map((target) => ({ + targets: path.points.connections.targets.map((target) => ({ pathUUID: target.pathUUID, })), }, }, ], pathPosition: path.position, - speed: path.point.speed || 1, + speed: path.points.speed || 1, }; } } diff --git a/app/src/modules/simulation/process/processObjectRender.tsx b/app/src/modules/simulation/process/processObjectRender.tsx new file mode 100644 index 0000000..7f5a0cc --- /dev/null +++ b/app/src/modules/simulation/process/processObjectRender.tsx @@ -0,0 +1,114 @@ +import React, { useRef, useEffect } from "react"; +import { useFrame } from "@react-three/fiber"; +import * as THREE from "three"; +import { GLTF } from "three-stdlib"; +import { Box3Helper } from "three"; +import { SpawnedObject, ProcessData } from "./types"; + +interface ProcessObjectRendererProps { + objectId: string; + object: SpawnedObject; + process: ProcessData; + gltf: GLTF; + showBoundingBox?: boolean; +} + +export const ProcessObjectRenderer: React.FC<ProcessObjectRendererProps> = ({ + objectId, + object, + process, + gltf, + showBoundingBox = false, +}) => { + const meshRef = useRef<THREE.Mesh>(null); + const boxHelperRef = useRef<THREE.Box3Helper | null>(null); + const boundingBoxRef = useRef<THREE.Box3>(new THREE.Box3()); + + // Issue 1: Can't assign to ref.current as it's read-only + useEffect(() => { + if (object.ref && meshRef.current) { + // Instead of direct assignment, we need to store the mesh reference another way + // Option 1: If you can modify the SpawnedObject interface, add a setMesh method + if (typeof (object as any).setMesh === 'function') { + (object as any).setMesh(meshRef.current); + } + + // Option 2: Store the mesh in a property that isn't ref.current + // This requires modifying your SpawnedObject interface to include this property + (object as any).meshInstance = meshRef.current; + + // Option 3: If you need to maintain compatibility, you could use Object.defineProperty + // But this is a hack and not recommended + // Object.defineProperty(object.ref, 'current', { value: meshRef.current, writable: true }); + } + }, [object.ref]); + + // Create a bounding box helper for visualization + useFrame(() => { + if (meshRef.current && showBoundingBox) { + // Update the bounding box to match the mesh position + if (!boxHelperRef.current) { + // Get the size of the mesh + const size = new THREE.Vector3(1, 1, 1); + + // If the mesh has geometry, use its dimensions + if (meshRef.current.geometry) { + const box = new THREE.Box3().setFromObject(meshRef.current); + box.getSize(size); + } + + // Create a new bounding box centered on the mesh + boundingBoxRef.current = new THREE.Box3().setFromCenterAndSize( + meshRef.current.position, + size + ); + + // Create a helper to visualize the box + boxHelperRef.current = new Box3Helper( + boundingBoxRef.current, + new THREE.Color(0xff0000) + ); + + // Add the helper to the scene + meshRef.current.parent?.add(boxHelperRef.current); + } else { + // Update the box position to match the mesh + boundingBoxRef.current.setFromCenterAndSize( + meshRef.current.position, + boundingBoxRef.current.getSize(new THREE.Vector3()) + ); + + // Force the helper to update + boxHelperRef.current.updateMatrixWorld(true); + } + } + }); + + if (gltf?.scene) { + return ( + <primitive + ref={meshRef} + object={gltf.scene.clone()} + position={[0, 0, 0]} + material={object.material} + /> + ); + } + + // Issue 2: Material color type problem + return ( + <mesh ref={meshRef}> + <boxGeometry args={[1, 1, 1]} /> + <meshStandardMaterial + // Fix the color property access + color={ + object.material && 'color' in object.material + ? (object.material as THREE.MeshStandardMaterial).color + : "#00ff00" + } + metalness={0.5} + roughness={0.3} + /> + </mesh> + ); +}; \ No newline at end of file diff --git a/app/src/modules/simulation/process/types.ts b/app/src/modules/simulation/process/types.ts new file mode 100644 index 0000000..64a3e6a --- /dev/null +++ b/app/src/modules/simulation/process/types.ts @@ -0,0 +1,79 @@ +import * as THREE from "three"; +import React from "react"; + +export interface ProcessPoint { + uuid: string; + position: number[]; + actions?: PointAction[]; +} + +export interface PointAction { + type: string; + isUsed: boolean; + spawnInterval?: number | string; + material?: string; + delay?: number | string; +} + +export interface ProcessData { + id: string; + name: string; + paths?: { + points?: ProcessPoint[]; + }[]; + animationPath?: { x: number; y: number; z: number }[]; + speed?: number; + customMaterials?: Record<string, THREE.Material>; +} + +export interface ProcessAnimationState { + spawnedObjects: Record<string, SpawnedObject | SpawnedObjectWithCollision>; + nextSpawnTime: number; + objectIdCounter: number; + isProcessDelaying: boolean; + processDelayStartTime: number; + processDelayDuration: number; + isCollisionPaused?: boolean; +} + +export interface SpawnedObject { + ref: React.RefObject<THREE.Object3D>; + state: { + currentIndex: number; + progress: number; + isAnimating: boolean; + speed: number; + isDelaying: boolean; + delayStartTime: number; + currentDelayDuration: number; + delayComplete: boolean; + currentPathIndex: number; + }; + visible: boolean; + material: THREE.Material; + currentMaterialType: string; + spawnTime: number; + position: THREE.Vector3; + collision?: { + boundingBox: THREE.Box3; + isColliding: boolean; + colliding: boolean; // Added this property + }; +} + +// For use in your processAnimator.tsx +// Update the CollisionState interface to include all required properties +interface CollisionState { + boundingBox: THREE.Box3; + isColliding: boolean; + colliding: boolean; // This was missing + collidingWith: string[]; + } +export interface SpawnedObjectWithCollision extends SpawnedObject { + collision: { + boundingBox: THREE.Box3; + isColliding: boolean; + colliding: boolean; + collidingWith: string[]; + }; + } diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 830a0b1..fd8b520 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -19,7 +19,7 @@ function Simulation() { const [processes, setProcesses] = useState([]); useEffect(() => { - console.log('simulationPaths: ', simulationPaths); + // console.log('simulationPaths: ', simulationPaths); }, [simulationPaths]); // useEffect(() => { diff --git a/app/src/modules/simulation/simulationUI.tsx b/app/src/modules/simulation/simulationUI.tsx index 0ce9fc2..bff2b20 100644 --- a/app/src/modules/simulation/simulationUI.tsx +++ b/app/src/modules/simulation/simulationUI.tsx @@ -19,7 +19,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => { -// if (point.uuid === selectedActionSphere.point.uuid) { +// if (point.uuid === selectedActionSphere.points.uuid) { // const actionIndex = point.actions.length; // const newAction = { // uuid: THREE.MathUtils.generateUUID(), @@ -46,7 +46,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { ...point, actions: point.actions.filter(action => action.uuid !== uuid) } // : point // ), @@ -61,7 +61,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => @@ -81,7 +81,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => @@ -101,7 +101,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => @@ -121,7 +121,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => @@ -152,7 +152,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => { -// if (point.uuid === selectedActionSphere.point.uuid) { +// if (point.uuid === selectedActionSphere.points.uuid) { // const triggerIndex = point.triggers.length; // const newTrigger = { // uuid: THREE.MathUtils.generateUUID(), @@ -176,7 +176,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { ...point, triggers: point.triggers.filter(trigger => trigger.uuid !== uuid) } // : point // ), @@ -191,7 +191,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // triggers: point.triggers.map((trigger) => @@ -217,7 +217,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => ({ @@ -238,7 +238,7 @@ // const updatedPaths = simulationPaths.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // triggers: point.triggers.map((trigger) => ({ @@ -255,7 +255,7 @@ // const selectedPoint = useMemo(() => { // if (!selectedActionSphere) return null; -// return simulationPaths.flatMap((path) => path.points).find((point) => point.uuid === selectedActionSphere.point.uuid); +// return simulationPaths.flatMap((path) => path.points).find((point) => point.uuid === selectedActionSphere.points.uuid); // }, [selectedActionSphere, simulationPaths]); // const createPath = () => { diff --git a/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts b/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts index a1ac727..2685ca4 100644 --- a/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts +++ b/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts @@ -1,8 +1,8 @@ -let BackEnd_url = `http://${process.env.REACT_APP_SERVER_ASSET_LIBRARY_URL}`; +let BackEnd_url = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; export const getCategoryAsset = async (categoryName: any) => { try { const response = await fetch( - `${BackEnd_url}/api/v2/getCatagoryAssets/${categoryName}`, + `${BackEnd_url}/api/v2/getCategoryAssets/${categoryName}`, { method: "GET", headers: { diff --git a/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts b/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts index 86c8f71..b419963 100644 --- a/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts +++ b/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts @@ -17,7 +17,7 @@ export const setEventApi = async ( }); if (!response.ok) { - throw new Error("Failed to set or update Floor Item"); + throw new Error("Failed to set or Update Event Data"); } const result = await response.json(); diff --git a/app/src/store/usePlayButtonStore.ts b/app/src/store/usePlayButtonStore.ts index e975a7c..bed2bd7 100644 --- a/app/src/store/usePlayButtonStore.ts +++ b/app/src/store/usePlayButtonStore.ts @@ -4,8 +4,32 @@ type PlayButtonStore = { isPlaying: boolean; // Updated state name to reflect the play/pause status more clearly setIsPlaying: (value: boolean) => void; // Updated setter function name for clarity }; +type PauseButtonStore = { + isPaused: boolean; // Updated state name to reflect the play/pause status more clearly + setIsPaused: (value: boolean) => void; // Updated setter function name for clarity +}; +type ResetButtonStore = { + isReset: boolean; // Updated state name to reflect the play/pause status more clearly + setReset: (value: boolean) => void; // Updated setter function name for clarity +}; +interface AnimationSpeedState { + speed: number; + setSpeed: (value: number) => void; +} export const usePlayButtonStore = create<PlayButtonStore>((set) => ({ isPlaying: false, // Default state for play/pause setIsPlaying: (value) => set({ isPlaying: value }), // Update isPlaying state })); +export const useResetButtonStore = create<ResetButtonStore>((set) => ({ + isReset: false, // Default state for play/pause + setReset: (value) => set({ isReset: value }), // Update isPlaying state +})); +export const usePauseButtonStore = create<PauseButtonStore>((set) => ({ + isPaused: false, // Default state for play/pause + setIsPaused: (value) => set({ isPaused: value }), // Update isPlaying state +})); +export const useAnimationPlaySpeed = create<AnimationSpeedState>((set) => ({ + speed: 1, + setSpeed: (value) => set({ speed: value }), +})); diff --git a/app/src/types/world/worldTypes.d.ts b/app/src/types/world/worldTypes.d.ts index d1ce13f..3ec0682 100644 --- a/app/src/types/world/worldTypes.d.ts +++ b/app/src/types/world/worldTypes.d.ts @@ -201,27 +201,6 @@ export type FloorItemType = { modelfileID: string; isLocked: boolean; isVisible: boolean; - eventData?: { - type: 'Conveyor'; - points: { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | []; - triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | []; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; - }[]; - speed: number | string; - } | { - type: 'Vehicle'; - point: { - uuid: string; - position: [number, number, number]; - actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; - speed: number; - }; - }; }; // Array of floor items for managing multiple objects on the floor @@ -328,12 +307,43 @@ interface VehicleEventsSchema { modeluuid: string; modelName: string; type: 'Vehicle'; - point: { + points: { uuid: string; position: [number, number, number]; actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; speed: number; }; position: [number, number, number]; -} \ No newline at end of file +} + +export type EventData = { + modeluuid: string; + modelname: string; + position: [number, number, number]; + rotation: { x: number; y: number; z: number }; + modelfileID: string; + isLocked: boolean; + isVisible: boolean; + eventData?: { + type: 'Conveyor'; + points: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | []; + triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | []; + connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + }[]; + speed: number | string; + } | { + type: 'Vehicle'; + points: { + uuid: string; + position: [number, number, number]; + actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + speed: number; + }; + }; +}