diff --git a/app/src/components/ui/componets/Dropped3dWidget.tsx b/app/src/components/ui/componets/Dropped3dWidget.tsx
index f0d5e66..f43cce5 100644
--- a/app/src/components/ui/componets/Dropped3dWidget.tsx
+++ b/app/src/components/ui/componets/Dropped3dWidget.tsx
@@ -1,5 +1,5 @@
import { useThree } from "@react-three/fiber";
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useRef } from "react";
import { useAsset3dWidget, useSocketStore, useWidgetSubOption } from "../../../store/store";
import useModuleStore from "../../../store/useModuleStore";
import { ThreeState } from "../../../types/world/worldTypes";
@@ -13,30 +13,43 @@ import { generateUniqueId } from "../../../functions/generateUniqueId";
import { adding3dWidgets } from "../../../services/realTimeVisulization/zoneData/add3dWidget";
import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zoneData/get3dWidgetData";
import { use3DWidget } from "../../../store/useDroppedObjectsStore";
-import { useZoneWidgetStore } from "../../../store/useZone3DWidgetStore";
+import { useLeftData, useRightClickSelected, useRightSelected, useTopData, useZoneWidgetStore } from "../../../store/useZone3DWidgetStore";
+import { useWidgetStore } from "../../../store/useWidgetStore";
+import EditWidgetOption from "../menu/EditWidgetOption";
export default function Dropped3dWidgets() {
const { widgetSelect } = useAsset3dWidget();
const { activeModule } = useModuleStore();
- const { raycaster, gl, scene }: ThreeState = useThree();
+ const { raycaster, gl, scene, mouse, camera }: ThreeState = useThree();
const { widgetSubOption } = useWidgetSubOption();
const { selectedZone } = useSelectedZoneStore();
+ const { top, setTop } = useTopData()
+ const { left, setLeft } = useLeftData()
+ const { rightSelect, setRightSelect } = useRightSelected()
// ✅ Use Zustand Store instead of useState
- const { zoneWidgetData, setZoneWidgetData, addWidget } = useZoneWidgetStore();
+ const { zoneWidgetData, setZoneWidgetData, addWidget, updateWidgetPosition } = useZoneWidgetStore();
const { setWidgets3D } = use3DWidget();
const { visualizationSocket } = useSocketStore();
+ const { rightClickSelected, setRightClickSelected } = useRightClickSelected()
+
+ const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)); // Floor plane for horizontal move
+ const verticalPlane = useRef(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0)); // Vertical plane for vertical move
+ const planeIntersect = useRef(new THREE.Vector3());
+ // let [verticalPlane, setFloorPlanesVertical] = useState(
+ // new THREE.Plane(new THREE.Vector3(0, 1, 0))
+ // );
useEffect(() => {
if (activeModule !== "visualization") return;
if (!selectedZone.zoneId) return;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
async function get3dWidgetData() {
let result = await get3dWidgetZoneData(selectedZone.zoneId, organization);
- console.log('result: ', result);
+
setWidgets3D(result);
const formattedWidgets = result.map((widget: any) => ({
@@ -89,13 +102,13 @@ export default function Dropped3dWidgets() {
widget: newWidget,
zoneId: selectedZone.zoneId
}
- console.log('add3dWidget: ', add3dWidget);
+
if (visualizationSocket) {
visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget)
}
// let response = await adding3dWidgets(selectedZone.zoneId, organization, newWidget);
- // console.log('response: ', response);
+ //
// if (response.message === "Widget created successfully") {
addWidget(selectedZone.zoneId, newWidget);
@@ -111,23 +124,142 @@ export default function Dropped3dWidgets() {
const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || [];
+ useEffect(() => {
+ if (!rightClickSelected) return;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ if (rightSelect === "Duplicate") {
+ const widgetToDuplicate = activeZoneWidgets.find(w => w.id === rightClickSelected);
+ if (!widgetToDuplicate) return;
+ const newWidget = {
+ id: generateUniqueId(),
+ type: widgetToDuplicate.type,
+ position: [
+ widgetToDuplicate.position[0] + 0.5, // Slightly shift position
+ widgetToDuplicate.position[1],
+ widgetToDuplicate.position[2] + 0.5,
+ ] as [number, number, number],
+ };
+ let add3dWidget = {
+ organization,
+ widget: newWidget,
+ zoneId: selectedZone.zoneId
+ };
+ // if (visualizationSocket) {
+ // visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget);
+ // }
+ addWidget(selectedZone.zoneId, newWidget);
+ setRightSelect(null);
+ setRightClickSelected(null);
+ }
+ if (rightSelect === "Delete") {
+ let deleteWidget = {
+ organization,
+ widgetId: rightClickSelected,
+ zoneId: selectedZone.zoneId
+ };
+ // if (visualizationSocket) {
+ // visualizationSocket.emit("v2:viz-3D-widget:delete", deleteWidget);
+ // }
+ setZoneWidgetData(selectedZone.zoneId, activeZoneWidgets.filter(w => w.id !== rightClickSelected));
+ setRightClickSelected(null);
+ setRightSelect(null);
+ }
+ if (rightSelect === "Horizontal Move") {
+
+ }
+ if (rightSelect === "Vertical Move") {
+
+ }
+
+ }, [rightSelect, rightClickSelected]);
+
+ useEffect(() => {
+ const handleMouseMove = (event: MouseEvent) => {
+ if (!rightClickSelected || !rightSelect) return;
+
+
+ const selectedZone = Object.keys(zoneWidgetData).find(zoneId =>
+ zoneWidgetData[zoneId].some(widget => widget.id === rightClickSelected)
+ );
+ if (!selectedZone) return;
+
+ const selectedWidget = zoneWidgetData[selectedZone].find(widget => widget.id === rightClickSelected);
+ if (!selectedWidget) return;
+
+ const rect = gl.domElement.getBoundingClientRect();
+ mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+
+ raycaster.setFromCamera(mouse, camera);
+
+ if (rightSelect === "Horizontal Move" && raycaster.ray.intersectPlane(plane.current, planeIntersect.current)) {
+
+ updateWidgetPosition(selectedZone, rightClickSelected, [
+ planeIntersect.current.x,
+ selectedWidget.position[1],
+ planeIntersect.current.z
+ ]);
+ }
+
+ if (rightSelect === "Vertical Move") {
+ if (raycaster.ray.intersectPlane(verticalPlane.current, planeIntersect.current)) {
+ updateWidgetPosition(selectedZone, rightClickSelected, [
+ selectedWidget.position[0],
+ planeIntersect.current.y, // Ensure Y value updates correctly
+ selectedWidget.position[2]
+ ]);
+ } else {
+ console.log("No Intersection with Vertical Plane");
+ }
+ }
+ };
+
+ const handleMouseUp = () => {
+
+ if (rightClickSelected && (rightSelect === "Horizontal Move" || rightSelect === "Vertical Move")) {
+
+ setTimeout(() => {
+ setRightClickSelected(null);
+ setRightSelect(null);
+ }, 50);
+ }
+ };
+
+ // Attach events to window instead of gl.domElement
+ window.addEventListener("mousemove", handleMouseMove);
+ window.addEventListener("mouseup", handleMouseUp);
+
+ return () => {
+ window.removeEventListener("mousemove", handleMouseMove);
+ window.removeEventListener("mouseup", handleMouseUp);
+ };
+ }, [rightClickSelected, rightSelect, zoneWidgetData, gl]);
+
+
+
+
return (
<>
{activeZoneWidgets.map(({ id, type, position }) => {
- console.log('Typeeeeeeeeeeee',type);
+ const handleRightClick = (event: React.MouseEvent) => {
+ event.preventDefault();
+ setRightClickSelected(id)
+ };
switch (type) {
case "ui-Widget 1":
- return
;
+ return
;
case "ui-Widget 2":
- return
;
+ return
;
case "ui-Widget 3":
- return
;
+ return
;
case "ui-Widget 4":
- return
;
+ return
;
default:
return null;
}
})}
+
>
);
}
diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx
index 619cbb0..915e3b3 100644
--- a/app/src/components/ui/componets/RealTimeVisulization.tsx
+++ b/app/src/components/ui/componets/RealTimeVisulization.tsx
@@ -23,6 +23,8 @@ import SocketRealTimeViz from "../../../modules/visualization/realTimeVizSocket.
import RenderOverlay from "../../templates/Overlay";
import ConfirmationPopup from "../../layout/confirmationPopup/ConfirmationPopup";
import DroppedObjects from "./DroppedFloatingWidgets";
+import EditWidgetOption from "../menu/EditWidgetOption";
+import { useRightClickSelected } from "../../../store/useZone3DWidgetStore";
type Side = "top" | "bottom" | "left" | "right";
@@ -55,8 +57,9 @@ const RealTimeVisulization: React.FC = () => {
const [droppedObjects, setDroppedObjects] = useState
([]);
const [zonesData, setZonesData] = useState({});
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
- const { zones } = useZones();
+
+ const { rightClickSelected, setRightClickSelected } = useRightClickSelected()
const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false);
const [floatingWidgets, setFloatingWidgets] = useState<
@@ -93,7 +96,7 @@ const RealTimeVisulization: React.FC = () => {
{}
);
setZonesData(formattedData);
- } catch (error) {}
+ } catch (error) { }
}
GetZoneData();
@@ -178,7 +181,6 @@ const RealTimeVisulization: React.FC = () => {
.getState()
.addObject(selectedZone.zoneName, newObject);
// }
-
// Update floating widgets state
setFloatingWidgets((prevWidgets) => ({
...prevWidgets,
@@ -192,9 +194,10 @@ const RealTimeVisulization: React.FC = () => {
],
},
}));
- } catch (error) {}
+ } catch (error) { }
};
+ function handleRightClickSel(){}
return (
{
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
}}
>
- {/*
-
- */}
{openConfirmationPopup && (
{
{activeModule === "visualization" && selectedZone.zoneName !== "" && }
{activeModule === "visualization" && }
- {/* */}
- {/*
-
- */}
+
+ {activeModule === "visualization" && widgetSubOption === "3D" && rightClickSelected && }
+
{activeModule === "visualization" && (
<>
{
-// const { getTemplate } = useTemplateStore.getState();
-// const { setSelectedZone } = useSelectedZoneStore.getState();
-
-// // Find the template by ID
-// const template: Template | undefined = getTemplate(templateId);
-
-// if (!template) {
-// console.error("Template not found!");
-// return;
-// }
-
-// // Update the selected zone with the template data
-// setSelectedZone((prev) => ({
-// ...prev,
-// panelOrder: template.panelOrder,
-// activeSides: Array.from(new Set([...prev.activeSides, ...template.panelOrder])),
-// widgets: template.widgets, // Keep widget structure the same
-// }));
-
-// console.log("Dropped template applied:", template);
-// };
diff --git a/app/src/components/ui/menu/EditWidgetOption.tsx b/app/src/components/ui/menu/EditWidgetOption.tsx
index 9ef67af..ce5261a 100644
--- a/app/src/components/ui/menu/EditWidgetOption.tsx
+++ b/app/src/components/ui/menu/EditWidgetOption.tsx
@@ -1,15 +1,26 @@
-import React from "react";
+import React, { useEffect } from "react";
+import { useLeftData, useRightSelected, useTopData } from "../../../store/useZone3DWidgetStore";
interface EditWidgetOptionProps {
options: string[];
}
const EditWidgetOption: React.FC = ({ options }) => {
+ const { top, setTop } = useTopData()
+ const { left, setLeft } = useLeftData()
+ const { rightSelect, setRightSelect } = useRightSelected()
+
+ useEffect(() => {
+
+ console.log('left: ', left);
+ console.log('top: ', top);
+ }, [top, left])
+
return (
-
+
{options.map((option, index) => (
-
+
setRightSelect(option)}>
{option}
))}
diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts
index a4255bd..a1ed87b 100644
--- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts
+++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts
@@ -24,6 +24,7 @@ async function addAssetModel(
socket: Socket
,
selectedItem: any,
setSelectedItem: any,
+ setSimulationPaths: any,
plane: Types.RefMesh,
): Promise {
@@ -64,7 +65,7 @@ async function addAssetModel(
const cachedModel = THREE.Cache.get(selectedItem.id);
if (cachedModel) {
// console.log(`[Cache] Fetching ${selectedItem.name}`);
- handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
+ handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket);
return;
} else {
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
@@ -77,7 +78,7 @@ async function addAssetModel(
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(selectedItem.id, gltf);
- handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
+ handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket);
},
() => {
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
@@ -89,7 +90,7 @@ async function addAssetModel(
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`).then((res) => res.blob());
await storeGLTF(selectedItem.id, modelBlob);
THREE.Cache.add(selectedItem.id, gltf);
- await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
+ await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket);
},
() => {
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
@@ -112,10 +113,11 @@ async function handleModelLoad(
tempLoader: Types.RefMesh,
isTempLoader: Types.RefBoolean,
setFloorItems: Types.setFloorItemSetState,
+ setSimulationPaths: any,
socket: Socket
) {
const model = gltf.scene.clone();
- model.userData = { name: selectedItem.name, modelId: selectedItem.id };
+ model.userData = { name: selectedItem.name, modelId: selectedItem.id, modeluuid: model.uuid };
model.position.set(intersectPoint!.x, 3 + intersectPoint!.y, intersectPoint!.z);
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
@@ -152,7 +154,7 @@ async function handleModelLoad(
if (res.type === "Conveyor") {
const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID());
- const eventData: Extract = {
+ const backendEventData: Extract = {
type: 'Conveyor',
points: res.points.map((point: any, index: number) => ({
uuid: pointUUIDs[index],
@@ -176,49 +178,94 @@ async function handleModelLoad(
speed: 'Inherit'
};
- // console.log('eventData: ', eventData);
- newFloorItem.eventData = eventData;
+ // API
+
+ // await setFloorItemApi(
+ // organization,
+ // newFloorItem.modeluuid,
+ // newFloorItem.modelname,
+ // newFloorItem.modelfileID,
+ // newFloorItem.position,
+ // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
+ // false,
+ // true,
+ // newFloorItem.eventData
+ // );
+
+ // SOCKET
+
+ const data = {
+ organization,
+ modeluuid: newFloorItem.modeluuid,
+ modelname: newFloorItem.modelname,
+ modelfileID: newFloorItem.modelfileID,
+ position: newFloorItem.position,
+ rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
+ isLocked: false,
+ isVisible: true,
+ eventData: backendEventData,
+ socketId: socket.id
+ };
+
+ console.log('data: ', data);
+ setFloorItems((prevItems) => {
+ const updatedItems = [...(prevItems || []), newFloorItem];
+ localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
+ return updatedItems;
+ });
+
+ const eventData: any = backendEventData;
+ eventData.modeluuid = newFloorItem.modeluuid;
+ eventData.modelName = newFloorItem.modelname;
+ eventData.position = newFloorItem.position;
+ eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z];
+
+ setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [
+ ...(prevEvents || []),
+ eventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema
+ ]);
+
+ socket.emit("v2:model-asset:add", data);
+
+ } else {
+
+ // API
+
+ // await setFloorItemApi(
+ // organization,
+ // newFloorItem.modeluuid,
+ // newFloorItem.modelname,
+ // newFloorItem.modelfileID,
+ // newFloorItem.position,
+ // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
+ // false,
+ // true
+ // );
+
+ // SOCKET
+
+ const data = {
+ organization,
+ modeluuid: newFloorItem.modeluuid,
+ modelname: newFloorItem.modelname,
+ modelfileID: newFloorItem.modelfileID,
+ position: newFloorItem.position,
+ rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
+ isLocked: false,
+ isVisible: true,
+ socketId: socket.id
+ };
+
+ setFloorItems((prevItems) => {
+ const updatedItems = [...(prevItems || []), newFloorItem];
+ localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
+ return updatedItems;
+ });
+
+ socket.emit("v2:model-asset:add", data);
+
}
- setFloorItems((prevItems) => {
- const updatedItems = [...(prevItems || []), newFloorItem];
- localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
- return updatedItems;
- });
-
- // API
-
- // await setFloorItemApi(
- // organization,
- // newFloorItem.modeluuid,
- // newFloorItem.modelname,
- // newFloorItem.modelfileID,
- // newFloorItem.position,
- // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
- // false,
- // true,
- // newFloorItem.eventData
- // );
-
- // SOCKET
-
- const data = {
- organization,
- modeluuid: newFloorItem.modeluuid,
- modelname: newFloorItem.modelname,
- modelfileID: newFloorItem.modelfileID,
- position: newFloorItem.position,
- rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
- isLocked: false,
- isVisible: true,
- eventData: newFloorItem.eventData,
- socketId: socket.id
- };
- console.log('data: ', data);
-
-
- socket.emit("v2:model-asset:add", data);
-
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
});
diff --git a/app/src/modules/builder/geomentries/assets/assetManager.ts b/app/src/modules/builder/geomentries/assets/assetManager.ts
index 38d0721..240e7a1 100644
--- a/app/src/modules/builder/geomentries/assets/assetManager.ts
+++ b/app/src/modules/builder/geomentries/assets/assetManager.ts
@@ -121,7 +121,7 @@ export default async function assetManager(
const model = gltf;
model.uuid = item.modeluuid;
- model.userData = { name: item.modelname, modelId: item.modelfileID };
+ model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid };
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
model.position.set(...item.position);
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
diff --git a/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts b/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts
index 3bbe2cc..a71aaea 100644
--- a/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts
+++ b/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts
@@ -10,6 +10,7 @@ async function DeleteFloorItems(
itemsGroup: Types.RefGroup,
hoveredDeletableFloorItem: Types.RefMesh,
setFloorItems: Types.setFloorItemSetState,
+ setSimulationPaths: any,
socket: Socket
): Promise {
@@ -74,6 +75,12 @@ async function DeleteFloorItems(
itemsGroup.current.remove(hoveredDeletableFloorItem.current);
}
setFloorItems(updatedItems);
+
+ setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => {
+ const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== removedItem.modeluuid);
+ return updatedEvents;
+ });
+
toast.success("Model Removed!");
}
}
diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx
index bee47b5..2b77fb2 100644
--- a/app/src/modules/builder/groups/floorItemsGroup.tsx
+++ b/app/src/modules/builder/groups/floorItemsGroup.tsx
@@ -10,6 +10,7 @@ import {
useRenderDistance,
useselectedFloorItem,
useSelectedItem,
+ useSimulationPaths,
useSocketStore,
useToggleView,
useTransformMode,
@@ -64,6 +65,7 @@ const FloorItemsGroup = ({
const { setselectedFloorItem } = useselectedFloorItem();
const { activeTool } = useActiveTool();
const { selectedItem, setSelectedItem } = useSelectedItem();
+ const { simulationPaths, setSimulationPaths } = useSimulationPaths();
const { setLoadingProgress } = useLoadingProgress();
const { activeModule } = useModuleStore();
const { socket } = useSocketStore();
@@ -96,16 +98,23 @@ const FloorItemsGroup = ({
};
getFloorAssets(organization).then((data) => {
- const uniqueItems = (data as Types.FloorItems).filter(
- (item, index, self) =>
- index === self.findIndex((t) => t.modelfileID === item.modelfileID)
- );
- totalAssets = uniqueItems.length;
- if (totalAssets === 0) {
+ if (data.length > 0) {
+ const uniqueItems = (data as Types.FloorItems).filter(
+ (item, index, self) =>
+ index === self.findIndex((t) => t.modelfileID === item.modelfileID)
+ );
+ totalAssets = uniqueItems.length;
+ if (totalAssets === 0) {
+ updateLoadingProgress(100);
+ return;
+ }
+ gltfLoaderWorker.postMessage({ floorItems: data });
+ } else {
+ console.log('data: ', data);
+ gltfLoaderWorker.postMessage({ floorItems: [] });
+ loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths);
updateLoadingProgress(100);
- return;
}
- gltfLoaderWorker.postMessage({ floorItems: data });
});
gltfLoaderWorker.onmessage = async (event) => {
@@ -122,7 +131,7 @@ const FloorItemsGroup = ({
updateLoadingProgress(progress);
if (loadedAssets === totalAssets) {
- loadInitialFloorItems(itemsGroup, setFloorItems);
+ loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths);
updateLoadingProgress(100);
}
});
@@ -240,6 +249,7 @@ const FloorItemsGroup = ({
itemsGroup,
hoveredDeletableFloorItem,
setFloorItems,
+ setSimulationPaths,
socket
);
}
@@ -365,6 +375,7 @@ const FloorItemsGroup = ({
socket,
selectedItem,
setSelectedItem,
+ setSimulationPaths,
plane
);
}
@@ -408,7 +419,6 @@ const FloorItemsGroup = ({
activeModule,
]);
- useEffect(() => {}, [floorItems]);
useFrame(() => {
if (controls)
diff --git a/app/src/modules/collaboration/socketResponses.dev.tsx b/app/src/modules/collaboration/socketResponses.dev.tsx
index d17557d..b8cec55 100644
--- a/app/src/modules/collaboration/socketResponses.dev.tsx
+++ b/app/src/modules/collaboration/socketResponses.dev.tsx
@@ -105,7 +105,7 @@ export default function SocketResponses({
// console.log(`Getting ${data.data.modelname} from cache`);
const model = cachedModel.scene.clone();
model.uuid = data.data.modeluuid;
- model.userData = { name: data.data.modelname, modelId: data.data.modelFileID };
+ model.userData = { name: data.data.modelname, modelId: data.data.modelFileID, modeluuid: data.data.modeluuid };
model.position.set(...data.data.position as [number, number, number]);
model.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z);
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
@@ -183,7 +183,7 @@ export default function SocketResponses({
THREE.Cache.remove(url);
const model = gltf.scene;
model.uuid = data.data.modeluuid;
- model.userData = { name: data.data.modelname, modelId: data.data.modelFileID };
+ model.userData = { name: data.data.modelname, modelId: data.data.modelFileID, modeluuid: data.data.modeluuid };
model.position.set(...data.data.position as [number, number, number]);
model.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z);
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
diff --git a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts
index dfff78d..3630378 100644
--- a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts
+++ b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts
@@ -11,7 +11,8 @@ import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAss
async function loadInitialFloorItems(
itemsGroup: Types.RefGroup,
- setFloorItems: Types.setFloorItemSetState
+ setFloorItems: Types.setFloorItemSetState,
+ setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void
): Promise {
if (!itemsGroup.current) return;
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
@@ -22,6 +23,8 @@ async function loadInitialFloorItems(
localStorage.setItem("FloorItems", JSON.stringify(items));
await initializeDB();
+ if (items.message === "floorItems not found") return;
+
if (items) {
const storedFloorItems: Types.FloorItems = items;
const loader = new GLTFLoader();
@@ -50,6 +53,7 @@ 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;
@@ -68,7 +72,7 @@ async function loadInitialFloorItems(
const cachedModel = THREE.Cache.get(item.modelfileID!);
if (cachedModel) {
// console.log(`[Cache] Fetching ${item.modelname}`);
- processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems);
+ processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
return;
@@ -85,7 +89,7 @@ async function loadInitialFloorItems(
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(item.modelfileID!, gltf);
- processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
+ processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
},
@@ -108,7 +112,7 @@ async function loadInitialFloorItems(
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
await storeGLTF(item.modelfileID!, modelBlob);
THREE.Cache.add(item.modelfileID!, gltf);
- processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
+ processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
},
@@ -121,34 +125,23 @@ async function loadInitialFloorItems(
});
} else {
// console.log(`Item ${item.modelname} is not near`);
+ setFloorItems((prevItems) => [
+ ...(prevItems || []),
+ {
+ modeluuid: item.modeluuid,
+ modelname: item.modelname,
+ position: item.position,
+ rotation: item.rotation,
+ modelfileID: item.modelfileID,
+ isLocked: item.isLocked,
+ isVisible: item.isVisible,
+ },
+ ]);
+
if (item.eventData) {
- setFloorItems((prevItems) => [
- ...(prevItems || []),
- {
- modeluuid: item.modeluuid,
- modelname: item.modelname,
- position: item.position,
- rotation: item.rotation,
- modelfileID: item.modelfileID,
- isLocked: item.isLocked,
- isVisible: item.isVisible,
- // eventData: item.eventData,
- },
- ]);
- } else {
- setFloorItems((prevItems) => [
- ...(prevItems || []),
- {
- modeluuid: item.modeluuid,
- modelname: item.modelname,
- position: item.position,
- rotation: item.rotation,
- modelfileID: item.modelfileID,
- isLocked: item.isLocked,
- isVisible: item.isVisible,
- },
- ]);
+ processEventData(item, setSimulationPaths);
}
+
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
}
@@ -164,12 +157,13 @@ function processLoadedModel(
gltf: any,
item: Types.FloorItemType,
itemsGroup: Types.RefGroup,
- setFloorItems: Types.setFloorItemSetState
+ setFloorItems: Types.setFloorItemSetState,
+ setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void
) {
const model = gltf;
model.uuid = item.modeluuid;
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
- model.userData = { name: item.modelname, modelId: item.modelfileID };
+ model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid };
model.position.set(...item.position);
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
@@ -186,39 +180,67 @@ function processLoadedModel(
itemsGroup?.current?.add(model);
- if (item.eventData) {
- setFloorItems((prevItems) => [
- ...(prevItems || []),
- {
- modeluuid: item.modeluuid,
- modelname: item.modelname,
- position: item.position,
- rotation: item.rotation,
- modelfileID: item.modelfileID,
- isLocked: item.isLocked,
- isVisible: item.isVisible,
- // eventData: item.eventData,
- },
- ]);
- } else {
- setFloorItems((prevItems) => [
- ...(prevItems || []),
- {
- modeluuid: item.modeluuid,
- modelname: item.modelname,
- position: item.position,
- rotation: item.rotation,
- modelfileID: item.modelfileID,
- isLocked: item.isLocked,
- isVisible: item.isVisible,
- },
- ]);
+ setFloorItems((prevItems) => [
+ ...(prevItems || []),
+ {
+ modeluuid: item.modeluuid,
+ modelname: item.modelname,
+ position: item.position,
+ rotation: item.rotation,
+ modelfileID: item.modelfileID,
+ isLocked: item.isLocked,
+ isVisible: item.isVisible,
+ },
+ ]);
+
+ if (item.eventData || item.modelfileID === '67e3da19c2e8f37134526e6a') {
+ processEventData(item, setSimulationPaths);
}
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' });
}
+function processEventData(item: Types.FloorItemType, setSimulationPaths: any) {
+
+ if (item.eventData?.type === 'Conveyor') {
+
+ const data: any = item.eventData;
+ data.modeluuid = item.modeluuid;
+ data.modelName = item.modelname;
+ data.position = item.position;
+ data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
+
+ setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [
+ ...(prevEvents || []),
+ data as Types.ConveyorEventsSchema
+ ]);
+ } else {
+
+ const pointUUID = THREE.MathUtils.generateUUID();
+ const pointPosition = new THREE.Vector3(0, 1.3, 0);
+
+ 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)[]) => [
+ ...(prevEvents || []),
+ newVehiclePath as Types.VehicleEventsSchema
+ ]);
+ }
+}
+
function checkLoadingCompletion(
modelsLoaded: number,
modelsToLoad: number,
diff --git a/app/src/modules/scene/controls/selection/copyPasteControls.tsx b/app/src/modules/scene/controls/selection/copyPasteControls.tsx
index 4628311..dea1da6 100644
--- a/app/src/modules/scene/controls/selection/copyPasteControls.tsx
+++ b/app/src/modules/scene/controls/selection/copyPasteControls.tsx
@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
-import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
+import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store";
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
@@ -10,6 +10,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
+ const { simulationPaths, setSimulationPaths } = useSimulationPaths();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore()
@@ -150,37 +151,128 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
return updatedItems;
});
+ let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid);
+
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
+ if (eventData) {
+ if (eventData.type === 'Conveyor' && eventData) {
+ const createConveyorPoint = (index: number) => {
+ const pointUUID = THREE.MathUtils.generateUUID();
+ const hasActions = (eventData as Types.ConveyorEventsSchema)?.points[index].actions.length > 0;
- //REST
+ const defaultAction = {
+ uuid: THREE.MathUtils.generateUUID(),
+ name: 'Action 1',
+ type: 'Inherit',
+ material: 'Inherit',
+ delay: 'Inherit',
+ spawnInterval: 'Inherit',
+ isUsed: true
+ };
- // await setFloorItemApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
+ return {
+ uuid: pointUUID,
+ position: (eventData as Types.ConveyorEventsSchema)?.points[index].position,
+ rotation: (eventData as Types.ConveyorEventsSchema)?.points[index].rotation,
+ actions: hasActions
+ ? (eventData as Types.ConveyorEventsSchema)?.points[index].actions.map(action => ({
+ ...action,
+ uuid: THREE.MathUtils.generateUUID()
+ }))
+ : [defaultAction],
+ triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers,
+ connections: {
+ source: { pathUUID: obj.uuid, pointUUID },
+ targets: []
+ }
+ };
+ };
- //SOCKET
+ const backendEventData = {
+ type: 'Conveyor',
+ points: [
+ createConveyorPoint(0), // point1
+ createConveyorPoint(1), // middlePoint
+ createConveyorPoint(2) // point2
+ ],
+ speed: (eventData as Types.ConveyorEventsSchema)?.speed
+ };
- 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,
- socketId: socket.id,
- };
+ //REST
- socket.emit("v2:model-asset:add", data);
+ // await setFloorItemApi(
+ // organization,
+ // obj.uuid,
+ // obj.userData.name,
+ // [worldPosition.x, worldPosition.y, worldPosition.z],
+ // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
+ // obj.userData.modelId,
+ // 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;
+ newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
+
+ setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [
+ ...(prevEvents || []),
+ newEventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema
+ ]);
+
+ socket.emit("v2:model-asset:add", data);
+ }
+ } else {
+
+ //REST
+
+ // await setFloorItemApi(
+ // organization,
+ // obj.uuid,
+ // obj.userData.name,
+ // [worldPosition.x, worldPosition.y, worldPosition.z],
+ // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
+ // obj.userData.modelId,
+ // false,
+ // true,
+ // );
+
+ //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,
+ socketId: socket.id,
+ };
+
+ socket.emit("v2:model-asset:add", data);
+
+ }
itemsGroupRef.current.add(obj);
}
diff --git a/app/src/modules/scene/controls/selection/duplicationControls.tsx b/app/src/modules/scene/controls/selection/duplicationControls.tsx
index 6b32195..185ca68 100644
--- a/app/src/modules/scene/controls/selection/duplicationControls.tsx
+++ b/app/src/modules/scene/controls/selection/duplicationControls.tsx
@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
-import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
+import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store";
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
@@ -10,11 +10,11 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
+ const { simulationPaths, setSimulationPaths } = useSimulationPaths();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
-
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
@@ -131,37 +131,129 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
return updatedItems;
});
+ let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid);
+
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
- //REST
+ if (eventData) {
+ if (eventData.type === 'Conveyor' && eventData) {
+ const createConveyorPoint = (index: number) => {
+ const pointUUID = THREE.MathUtils.generateUUID();
+ const hasActions = (eventData as Types.ConveyorEventsSchema)?.points[index].actions.length > 0;
- // await setFloorItemApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
+ const defaultAction = {
+ uuid: THREE.MathUtils.generateUUID(),
+ name: 'Action 1',
+ type: 'Inherit',
+ material: 'Inherit',
+ delay: 'Inherit',
+ spawnInterval: 'Inherit',
+ isUsed: true
+ };
- //SOCKET
+ return {
+ uuid: pointUUID,
+ position: (eventData as Types.ConveyorEventsSchema)?.points[index].position,
+ rotation: (eventData as Types.ConveyorEventsSchema)?.points[index].rotation,
+ actions: hasActions
+ ? (eventData as Types.ConveyorEventsSchema)?.points[index].actions.map(action => ({
+ ...action,
+ uuid: THREE.MathUtils.generateUUID()
+ }))
+ : [defaultAction],
+ triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers,
+ connections: {
+ source: { pathUUID: obj.uuid, pointUUID },
+ targets: []
+ }
+ };
+ };
- 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,
- socketId: socket.id,
- };
+ const backendEventData = {
+ type: 'Conveyor',
+ points: [
+ createConveyorPoint(0), // point1
+ createConveyorPoint(1), // middlePoint
+ createConveyorPoint(2) // point2
+ ],
+ speed: (eventData as Types.ConveyorEventsSchema)?.speed
+ };
- socket.emit("v2:model-asset:add", data);
+ //REST
+
+ // await setFloorItemApi(
+ // organization,
+ // obj.uuid,
+ // obj.userData.name,
+ // [worldPosition.x, worldPosition.y, worldPosition.z],
+ // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
+ // obj.userData.modelId,
+ // 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;
+ newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
+
+ setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [
+ ...(prevEvents || []),
+ newEventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema
+ ]);
+
+ socket.emit("v2:model-asset:add", data);
+ }
+ } else {
+
+ //REST
+
+ // await setFloorItemApi(
+ // organization,
+ // obj.uuid,
+ // obj.userData.name,
+ // [worldPosition.x, worldPosition.y, worldPosition.z],
+ // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
+ // obj.userData.modelId,
+ // false,
+ // true,
+ // );
+
+ //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,
+ socketId: socket.id,
+ };
+
+ socket.emit("v2:model-asset:add", data);
+
+ }
itemsGroupRef.current.add(obj);
}
diff --git a/app/src/modules/scene/controls/selection/moveControls.tsx b/app/src/modules/scene/controls/selection/moveControls.tsx
index 15973d8..350b487 100644
--- a/app/src/modules/scene/controls/selection/moveControls.tsx
+++ b/app/src/modules/scene/controls/selection/moveControls.tsx
@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
-import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
+import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
@@ -12,6 +12,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
+ const { simulationPaths, setSimulationPaths } = useSimulationPaths();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef([]);
@@ -179,37 +180,99 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
return updatedItems;
});
+ let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid);
+
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
- //REST
+ if (eventData) {
+ if (eventData.type === 'Conveyor' && eventData) {
- // await setFloorItemApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
+ const backendEventData = {
+ type: 'Conveyor',
+ points: eventData.points,
+ speed: (eventData as Types.ConveyorEventsSchema)?.speed
+ };
- //SOCKET
+ //REST
- 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,
- socketId: socket.id,
- };
+ // await setFloorItemApi(
+ // organization,
+ // obj.uuid,
+ // obj.userData.name,
+ // [worldPosition.x, worldPosition.y, worldPosition.z],
+ // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
+ // obj.userData.modelId,
+ // false,
+ // true,
+ // backendEventData
+ // );
- socket.emit("v2:model-asset:add", data);
+ //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;
+ newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
+
+ 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 {
+
+
+ //REST
+
+ // await setFloorItemApi(
+ // organization,
+ // obj.uuid,
+ // obj.userData.name,
+ // [worldPosition.x, worldPosition.y, worldPosition.z],
+ // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
+ // obj.userData.modelId,
+ // false,
+ // true,
+ // );
+
+ //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,
+ socketId: socket.id,
+ };
+
+ socket.emit("v2:model-asset:add", data);
+
+ }
itemsGroupRef.current.add(obj);
}
diff --git a/app/src/modules/scene/controls/selection/rotateControls.tsx b/app/src/modules/scene/controls/selection/rotateControls.tsx
index f8771be..6ad8309 100644
--- a/app/src/modules/scene/controls/selection/rotateControls.tsx
+++ b/app/src/modules/scene/controls/selection/rotateControls.tsx
@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
-import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
+import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
@@ -12,6 +12,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
+ const { simulationPaths, setSimulationPaths } = useSimulationPaths();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef([]);
@@ -182,37 +183,99 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
return updatedItems;
});
+ let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid);
+
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
- //REST
+ if (eventData) {
+ if (eventData.type === 'Conveyor' && eventData) {
- // await setFloorItemApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
+ const backendEventData = {
+ type: 'Conveyor',
+ points: eventData.points,
+ speed: (eventData as Types.ConveyorEventsSchema)?.speed
+ };
- //SOCKET
+ //REST
- 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,
- socketId: socket.id,
- };
+ // await setFloorItemApi(
+ // organization,
+ // obj.uuid,
+ // obj.userData.name,
+ // [worldPosition.x, worldPosition.y, worldPosition.z],
+ // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
+ // obj.userData.modelId,
+ // false,
+ // true,
+ // backendEventData
+ // );
- socket.emit("v2:model-asset:add", data);
+ //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;
+ newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
+
+ 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 {
+
+
+ //REST
+
+ // await setFloorItemApi(
+ // organization,
+ // obj.uuid,
+ // obj.userData.name,
+ // [worldPosition.x, worldPosition.y, worldPosition.z],
+ // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
+ // obj.userData.modelId,
+ // false,
+ // true,
+ // );
+
+ //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,
+ socketId: socket.id,
+ };
+
+ socket.emit("v2:model-asset:add", data);
+
+ }
itemsGroupRef.current.add(obj);
}
diff --git a/app/src/modules/scene/controls/selection/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx
index 8e9ac96..fd7e19e 100644
--- a/app/src/modules/scene/controls/selection/selectionControls.tsx
+++ b/app/src/modules/scene/controls/selection/selectionControls.tsx
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
import { SelectionHelper } from "./selectionHelper";
import { useFrame, useThree } from "@react-three/fiber";
-import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
+import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store";
import BoundingBox from "./boundingBoxHelper";
import { toast } from "react-toastify";
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
@@ -20,6 +20,7 @@ const SelectionControls: React.FC = () => {
const itemsGroupRef = useRef(undefined);
const selectionGroup = useRef() as Types.RefGroup;
const { toggleView } = useToggleView();
+ const { setSimulationPaths } = useSimulationPaths();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const [movedObjects, setMovedObjects] = useState([]);
const [rotatedObjects, setRotatedObjects] = useState([]);
@@ -239,6 +240,11 @@ const SelectionControls: React.FC = () => {
}
});
+ setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => {
+ const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== selectedMesh.uuid);
+ return updatedEvents;
+ });
+
itemsGroupRef.current?.remove(selectedMesh);
});
diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx
index 15597b8..ffa020e 100644
--- a/app/src/modules/scene/postProcessing/postProcessing.tsx
+++ b/app/src/modules/scene/postProcessing/postProcessing.tsx
@@ -93,13 +93,13 @@ export default function PostProcessing() {
@@ -108,9 +108,9 @@ export default function PostProcessing() {
{
const newPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] = [];
- floorItems.forEach((item: Types.FloorItemType) => {
- if (item.modelfileID === "672a090f80d91ac979f4d0bd") {
- console.log('item: ', item);
- const point1Position = new THREE.Vector3(0, 0.85, 2.2);
- const middlePointPosition = new THREE.Vector3(0, 0.85, 0);
- const point2Position = new THREE.Vector3(0, 0.85, -2.2);
+ // floorItems.forEach((item: Types.FloorItemType) => {
+ // if (item.modelfileID === "672a090f80d91ac979f4d0bd") {
+ // const point1Position = new THREE.Vector3(0, 0.85, 2.2);
+ // const middlePointPosition = new THREE.Vector3(0, 0.85, 0);
+ // const point2Position = new THREE.Vector3(0, 0.85, -2.2);
- const point1UUID = THREE.MathUtils.generateUUID();
- const middlePointUUID = THREE.MathUtils.generateUUID();
- const point2UUID = THREE.MathUtils.generateUUID();
+ // const point1UUID = THREE.MathUtils.generateUUID();
+ // const middlePointUUID = THREE.MathUtils.generateUUID();
+ // const point2UUID = THREE.MathUtils.generateUUID();
- const newPath: Types.ConveyorEventsSchema = {
- modeluuid: item.modeluuid,
- modelName: item.modelname,
- type: 'Conveyor',
- points: [
- {
- uuid: point1UUID,
- position: [point1Position.x, point1Position.y, point1Position.z],
- rotation: [0, 0, 0],
- actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }],
- triggers: [],
- connections: { source: { pathUUID: item.modeluuid, pointUUID: point1UUID }, targets: [] },
- },
- {
- uuid: middlePointUUID,
- position: [middlePointPosition.x, middlePointPosition.y, middlePointPosition.z],
- rotation: [0, 0, 0],
- actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }],
- triggers: [],
- connections: { source: { pathUUID: item.modeluuid, pointUUID: middlePointUUID }, targets: [] },
- },
- {
- uuid: point2UUID,
- position: [point2Position.x, point2Position.y, point2Position.z],
- rotation: [0, 0, 0],
- actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }],
- triggers: [],
- connections: { source: { pathUUID: item.modeluuid, pointUUID: point2UUID }, targets: [] },
- },
- ],
- position: [...item.position],
- rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
- speed: 'Inherit',
- };
+ // const newPath: Types.ConveyorEventsSchema = {
+ // modeluuid: item.modeluuid,
+ // modelName: item.modelname,
+ // type: 'Conveyor',
+ // points: [
+ // {
+ // uuid: point1UUID,
+ // position: [point1Position.x, point1Position.y, point1Position.z],
+ // rotation: [0, 0, 0],
+ // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }],
+ // triggers: [],
+ // connections: { source: { pathUUID: item.modeluuid, pointUUID: point1UUID }, targets: [] },
+ // },
+ // {
+ // uuid: middlePointUUID,
+ // position: [middlePointPosition.x, middlePointPosition.y, middlePointPosition.z],
+ // rotation: [0, 0, 0],
+ // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }],
+ // triggers: [],
+ // connections: { source: { pathUUID: item.modeluuid, pointUUID: middlePointUUID }, targets: [] },
+ // },
+ // {
+ // uuid: point2UUID,
+ // position: [point2Position.x, point2Position.y, point2Position.z],
+ // rotation: [0, 0, 0],
+ // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }],
+ // triggers: [],
+ // connections: { source: { pathUUID: item.modeluuid, pointUUID: point2UUID }, targets: [] },
+ // },
+ // ],
+ // position: [...item.position],
+ // rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
+ // speed: 'Inherit',
+ // };
- newPaths.push(newPath);
- } else if (item.modelfileID === "67e3da19c2e8f37134526e6a") {
- const pointUUID = THREE.MathUtils.generateUUID();
- const pointPosition = new THREE.Vector3(0, 1.3, 0);
+ // newPaths.push(newPath);
+ // } else if (item.modelfileID === "67e3da19c2e8f37134526e6a") {
+ // const pointUUID = THREE.MathUtils.generateUUID();
+ // const pointPosition = new THREE.Vector3(0, 1.3, 0);
- 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],
- };
+ // 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],
+ // };
- newPaths.push(newVehiclePath);
- }
- });
+ // newPaths.push(newVehiclePath);
+ // }
+ // });
- setSimulationPaths(newPaths);
+ // setSimulationPaths(newPaths);
+ // console.log('floorItems: ', floorItems);
}, [floorItems]);
return null;
diff --git a/app/src/modules/simulation/path/pathCreation.tsx b/app/src/modules/simulation/path/pathCreation.tsx
index 7af4afd..dd531a9 100644
--- a/app/src/modules/simulation/path/pathCreation.tsx
+++ b/app/src/modules/simulation/path/pathCreation.tsx
@@ -14,12 +14,14 @@ import {
} from "../../../store/store";
import { useFrame, useThree } from "@react-three/fiber";
import { useSubModuleStore } from "../../../store/useModuleStore";
+import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
function PathCreation({
pathsGroupRef,
}: {
pathsGroupRef: React.MutableRefObject;
}) {
+ const { isPlaying } = usePlayButtonStore();
const { renderDistance } = useRenderDistance();
const { setSubModule } = useSubModuleStore();
const { setSelectedActionSphere, selectedActionSphere } =
@@ -66,7 +68,7 @@ function PathCreation({
const distance = new THREE.Vector3(
...group.position.toArray()
).distanceTo(camera.position);
- group.visible = distance <= renderDistance;
+ group.visible = ((distance <= renderDistance) && !isPlaying);
}
});
});
@@ -193,7 +195,7 @@ function PathCreation({
};
return (
-
+
{simulationPaths.map((path) => {
if (path.type === "Conveyor") {
const points = path.points.map(
diff --git a/app/src/modules/simulation/process/animation.Worker.js b/app/src/modules/simulation/process/animation.Worker.js
new file mode 100644
index 0000000..faa6587
--- /dev/null
+++ b/app/src/modules/simulation/process/animation.Worker.js
@@ -0,0 +1,916 @@
+// // 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/mesh.tsx b/app/src/modules/simulation/process/mesh.tsx
new file mode 100644
index 0000000..f6b94bb
--- /dev/null
+++ b/app/src/modules/simulation/process/mesh.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+const Mesh: React.FC = () => {
+ return ;
+};
+
+export default Mesh;
diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx
index 123ec80..512b165 100644
--- a/app/src/modules/simulation/process/processAnimator.tsx
+++ b/app/src/modules/simulation/process/processAnimator.tsx
@@ -1,3 +1,582 @@
+// 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;
+// 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;
+// 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(null);
+
+// const [animationStates, setAnimationStates] = useState<
+// Record
+// >({});
+
+// // 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 = {};
+// 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" ? (
+// }
+// material={obj.material}
+// position={obj.position} // Set position directly in the JSX
+// >
+//
+//
+// ) : (
+// gltf?.scene && (
+// }
+// position={obj.position} // Set position directly in the JSX
+// >
+//
+//
+// )
+// );
+// })
+// )}
+// >
+// );
+// };
+
+// export default ProcessAnimator;
+
import React, { useRef, useState, useEffect, useMemo } from "react";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
import { GLTFLoader } from "three-stdlib";
@@ -5,11 +584,13 @@ 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";
interface PointAction {
uuid: string;
name: string;
type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
+ objectType: string;
material: string;
delay: string | number;
spawnInterval: string | number;
@@ -42,6 +623,8 @@ interface ProcessData {
animationPath: { x: number; y: number; z: number }[];
pointActions: PointAction[][];
speed: number;
+ customMaterials?: Record;
+ renderAs?: "box" | "custom";
}
interface AnimationState {
@@ -54,37 +637,46 @@ interface AnimationState {
currentDelayDuration: number;
delayComplete: boolean;
currentPathIndex: number;
- spawnPoints: Record<
- string,
- {
- position: THREE.Vector3;
- interval: number;
- lastSpawnTime: number;
- }
- >;
}
-const MAX_SPAWNED_OBJECTS = 20;
+interface SpawnedObject {
+ ref: React.RefObject;
+ 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;
+ // New fields for process-wide delay
+ isProcessDelaying: boolean;
+ processDelayStartTime: number;
+ processDelayDuration: number;
+}
const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
processes,
}) => {
- console.log("processes: ", processes);
+
const gltf = useLoader(GLTFLoader, boxGltb) as GLTF;
- const { isPlaying, setIsPlaying } = usePlayButtonStore();
-
+ const { isPlaying } = usePlayButtonStore();
const groupRef = useRef(null);
- const meshRef = useRef(null);
- const [visible, setVisible] = useState(false);
- const spawnedObjectsRef = useRef([]);
- const materials = useMemo(
+ const [animationStates, setAnimationStates] = useState<
+ Record
+ >({});
+
+ // Base materials
+ const baseMaterials = useMemo(
() => ({
Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
- Box: new THREE.MeshStandardMaterial({
+ Box: new THREE.MeshPhongMaterial({
color: 0xcccccc,
- metalness: 0.8,
- roughness: 0.2,
}),
Crate: new THREE.MeshStandardMaterial({
color: 0x00aaff,
@@ -96,108 +688,166 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
[]
);
- const [currentMaterial, setCurrentMaterial] = useState(
- materials.Default
- );
-
- const { animationPath, currentProcess } = useMemo(() => {
- const defaultProcess = {
- animationPath: [],
- pointActions: [],
- speed: 1,
- paths: [],
- };
- const cp = processes?.[0] || defaultProcess;
- return {
- animationPath:
- cp.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || [],
- currentProcess: cp,
- };
- }, [processes]);
-
- const animationStateRef = useRef({
- currentIndex: 0,
- progress: 0,
- isAnimating: false,
- speed: currentProcess.speed,
- isDelaying: false,
- delayStartTime: 0,
- currentDelayDuration: 0,
- delayComplete: false,
- currentPathIndex: 0,
- spawnPoints: {},
- });
-
- const getPointDataForAnimationIndex = (index: number) => {
- if (!processes[0]?.paths) return null;
-
- if (index < 3) {
- return processes[0].paths[0]?.points[index];
- } else {
- const path2Index = index - 3;
- return processes[0].paths[1]?.points[path2Index];
+ // Initialize animation states when processes or play state changes
+ useEffect(() => {
+ if (!isPlaying) {
+ setAnimationStates({});
+ return;
}
+
+ const newStates: Record = {};
+ 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 || []) {
+ const spawnAction = point.actions?.find(
+ (a) => a.isUsed && a.type === "Spawn"
+ );
+ if (spawnAction) {
+ return point;
+ }
+ }
+ }
+ return null;
};
- useEffect(() => {
- if (isPlaying) {
- setVisible(true);
- animationStateRef.current = {
+ // 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: currentProcess.speed,
+ speed: process.speed || 1,
isDelaying: false,
delayStartTime: 0,
currentDelayDuration: 0,
delayComplete: false,
currentPathIndex: 0,
- spawnPoints: {},
+ },
+ 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 || {}),
};
- // Clear spawned objects
- if (groupRef.current) {
- spawnedObjectsRef.current.forEach((obj) => {
- if (groupRef.current?.children.includes(obj)) {
- groupRef.current.remove(obj);
- }
- if (obj instanceof THREE.Mesh) {
- obj.material.dispose();
- }
- });
- spawnedObjectsRef.current = [];
- }
+ const newMaterial =
+ processMaterials[materialType as keyof typeof processMaterials] ||
+ baseMaterials.Default;
- const currentRef = gltf?.scene ? groupRef.current : meshRef.current;
- if (currentRef && animationPath.length > 0) {
- currentRef.position.copy(animationPath[0]);
- }
- } else {
- animationStateRef.current.isAnimating = false;
- }
- }, [isPlaying, currentProcess, animationPath]);
-
- const handleMaterialSwap = (materialType: string) => {
- const newMaterial =
- materials[materialType as keyof typeof materials] || materials.Default;
- setCurrentMaterial(newMaterial);
-
- spawnedObjectsRef.current.forEach((obj) => {
- if (obj instanceof THREE.Mesh) {
- obj.material = newMaterial.clone();
- }
+ return {
+ ...prev,
+ [processId]: {
+ ...processState,
+ spawnedObjects: {
+ ...processState.spawnedObjects,
+ [objectId]: {
+ ...processState.spawnedObjects[objectId],
+ material: newMaterial,
+ currentMaterialType: materialType,
+ },
+ },
+ },
+ };
});
};
- const hasNonInheritActions = (actions: PointAction[] = []) => {
- return actions.some((action) => action.isUsed && action.type !== "Inherit");
- };
-
+ // Handle point actions for an object
const handlePointActions = (
+ processId: string,
+ objectId: string,
actions: PointAction[] = [],
- currentTime: number,
- currentPosition: THREE.Vector3
- ) => {
+ currentTime: number
+ ): boolean => {
let shouldStopAnimation = false;
actions.forEach((action) => {
@@ -205,53 +855,75 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
switch (action.type) {
case "Delay":
- if (
- !animationStateRef.current.isDelaying &&
- !animationStateRef.current.delayComplete
- ) {
+ setAnimationStates((prev) => {
+ const processState = prev[processId];
+ if (!processState || processState.isProcessDelaying) {
+ return prev;
+ }
+
const delayDuration =
typeof action.delay === "number"
? action.delay
: parseFloat(action.delay as string) || 0;
if (delayDuration > 0) {
- animationStateRef.current.isDelaying = true;
- animationStateRef.current.delayStartTime = currentTime;
- animationStateRef.current.currentDelayDuration = delayDuration;
- shouldStopAnimation = true;
+ return {
+ ...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
+ isDelaying: true,
+ delayStartTime: currentTime,
+ currentDelayDuration: delayDuration,
+ delayComplete: false,
+ },
+ },
+ },
+ },
+ };
}
- }
- break;
-
- case "Despawn":
- setVisible(false);
- setIsPlaying(false);
- animationStateRef.current.isAnimating = false;
+ return prev;
+ });
shouldStopAnimation = true;
break;
- case "Spawn":
- const spawnInterval =
- typeof action.spawnInterval === "number"
- ? action.spawnInterval
- : parseFloat(action.spawnInterval as string) || 1;
+ case "Despawn":
+ setAnimationStates((prev) => {
+ const processState = prev[processId];
+ if (!processState) return prev;
- const positionKey = currentPosition.toArray().join(",");
+ const newSpawnedObjects = { ...processState.spawnedObjects };
+ delete newSpawnedObjects[objectId];
- animationStateRef.current.spawnPoints[positionKey] = {
- position: currentPosition.clone(),
- interval: spawnInterval,
- lastSpawnTime: currentTime - spawnInterval, // Force immediate spawn
- };
+ return {
+ ...prev,
+ [processId]: {
+ ...processState,
+ spawnedObjects: newSpawnedObjects,
+ },
+ };
+ });
+ shouldStopAnimation = true;
break;
case "Swap":
if (action.material) {
- handleMaterialSwap(action.material);
+ handleMaterialSwap(processId, objectId, action.material);
}
break;
- case "Inherit":
+ default:
break;
}
});
@@ -259,158 +931,376 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
return shouldStopAnimation;
};
- useFrame((state, delta) => {
- const currentRef = gltf?.scene ? groupRef.current : meshRef.current;
- if (
- !currentRef ||
- !animationStateRef.current.isAnimating ||
- animationPath.length < 2
- ) {
- return;
+ // 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();
- const path = animationPath;
- const stateRef = animationStateRef.current;
+ setAnimationStates((prev) => {
+ const newStates = { ...prev };
- if (stateRef.currentIndex === 3 && stateRef.currentPathIndex === 0) {
- stateRef.currentPathIndex = 1;
- }
+ processes.forEach((process) => {
+ const processState = newStates[process.id];
+ if (!processState) return;
- const currentPointData = getPointDataForAnimationIndex(
- stateRef.currentIndex
- );
-
- if (stateRef.progress === 0 && currentPointData?.actions) {
- const shouldStop = handlePointActions(
- currentPointData.actions,
- currentTime,
- currentRef.position
- );
- if (shouldStop) return;
- }
-
- if (stateRef.isDelaying) {
- if (
- currentTime - stateRef.delayStartTime >=
- stateRef.currentDelayDuration
- ) {
- stateRef.isDelaying = false;
- stateRef.delayComplete = true;
- } else {
- return;
- }
- }
-
- // Handle spawning - this is the key updated part
- Object.entries(stateRef.spawnPoints).forEach(([key, spawnPoint]) => {
- if (currentTime - spawnPoint.lastSpawnTime >= spawnPoint.interval) {
- spawnPoint.lastSpawnTime = currentTime;
-
- if (gltf?.scene && groupRef?.current) {
- const newObject = gltf.scene.clone();
- newObject.position.copy(spawnPoint.position);
-
- newObject.traverse((child) => {
- if (child instanceof THREE.Mesh) {
- child.material = currentMaterial.clone();
- }
- });
-
- groupRef.current.add(newObject);
- spawnedObjectsRef.current.push(newObject);
-
- // Clean up old objects if needed
- console.log(
- "spawnedObjectsRef.current.length: ",
- spawnedObjectsRef.current.length
- );
- if (spawnedObjectsRef.current.length > MAX_SPAWNED_OBJECTS) {
- const oldest = spawnedObjectsRef.current.shift();
- if (oldest && groupRef.current.children.includes(oldest)) {
- groupRef.current.remove(oldest);
- if (oldest instanceof THREE.Mesh) {
- oldest.material.dispose();
- }
- }
+ // Skip spawning if the process is currently in a delay
+ if (processState.isProcessDelaying) {
+ // Check if delay is over
+ if (
+ currentTime - processState.processDelayStartTime >=
+ processState.processDelayDuration
+ ) {
+ // 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(
+ (acc, [id, obj]) => ({
+ ...acc,
+ [id]: {
+ ...obj,
+ state: {
+ ...obj.state,
+ isDelaying: false,
+ delayComplete: true,
+ isAnimating: true, // Ensure animation resumes
+ // Force a small progress to ensure movement starts
+ progress:
+ obj.state.progress === 0 ? 0.001 : obj.state.progress,
+ },
+ },
+ }),
+ {}
+ ),
+ };
}
+ return; // Skip spawning while delaying
}
- }
- });
- const nextPointIdx = stateRef.currentIndex + 1;
- const isLastPoint = nextPointIdx >= path.length;
+ const spawnPoint = findSpawnPoint(process);
+ if (!spawnPoint || !spawnPoint.actions) return;
- if (isLastPoint) {
- if (currentPointData?.actions) {
- const shouldStop = !hasNonInheritActions(currentPointData.actions);
- if (shouldStop) {
- currentRef.position.copy(path[stateRef.currentIndex]);
- setIsPlaying(false);
- stateRef.isAnimating = false;
- 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
+ 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;
+ });
});
- useEffect(() => {
- return () => {
- if (groupRef.current) {
- spawnedObjectsRef.current.forEach((obj) => {
- if (groupRef.current?.children.includes(obj)) {
- groupRef.current.remove(obj);
+ // 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;
+
+ // Check if the process-wide delay is active
+ if (processState.isProcessDelaying) {
+ // Check if the delay has completed
+ if (
+ currentTime - processState.processDelayStartTime >=
+ processState.processDelayDuration
+ ) {
+ // 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(
+ (acc, [id, obj]) => ({
+ ...acc,
+ [id]: {
+ ...obj,
+ state: {
+ ...obj.state,
+ isDelaying: false,
+ delayComplete: true,
+ isAnimating: true, // Ensure animation resumes
+ // Important: Force progress to a small positive value to ensure movement
+ progress:
+ obj.state.progress === 0 ? 0.005 : obj.state.progress,
+ },
+ },
+ }),
+ {}
+ ),
+ };
+ // 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;
}
- if (obj instanceof THREE.Mesh) {
- obj.material.dispose();
+ }
+
+ 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]) => {
+ // 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 &&
+ obj.state.progress === 0
+ ) {
+ currentRef.position.copy(obj.position);
+ }
+
+ 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
+ stateRef.isDelaying = false;
+ stateRef.delayComplete = true;
+ stateRef.isAnimating = true; // Explicitly resume animation
+
+ // 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],
+ nextPointIdx < path.length
+ ? path[nextPointIdx]
+ : path[stateRef.currentIndex],
+ slightProgress
+ );
+ }
+ } 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) {
+ const shouldStop = handlePointActions(
+ process.id,
+ objectId,
+ currentPointData.actions,
+ currentTime
+ );
+ if (shouldStop) {
+ updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
+ return;
+ }
+ }
+
+ const nextPointIdx = stateRef.currentIndex + 1;
+ const isLastPoint = nextPointIdx >= path.length;
+
+ if (isLastPoint) {
+ if (currentPointData?.actions) {
+ const shouldStop = !hasNonInheritActions(
+ 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;
+ }
+ }
+ }
+
+ if (!isLastPoint) {
+ 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
+ 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
+ } 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
+ }
+ } else {
+ // Normal path interpolation
+ currentRef.position.lerpVectors(
+ path[stateRef.currentIndex],
+ nextPoint,
+ stateRef.progress
+ );
+ }
+ }
+
+ updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
}
- });
- spawnedObjectsRef.current = [];
- }
- };
- }, []);
+ );
+
+ newStates[process.id] = {
+ ...processState,
+ spawnedObjects: updatedObjects,
+ };
+ });
+
+ return newStates;
+ });
+ });
if (!processes || processes.length === 0) {
return null;
}
- if (!gltf?.scene) {
- return visible ? (
-
-
-
- ) : 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 visible ? (
-
-
-
- ) : null;
+ return renderAs === "box" ? (
+ }
+ material={obj.material}
+ position={obj.position} // Set position directly in the JSX
+ >
+
+
+ ) : (
+ gltf?.scene && (
+ }
+ position={obj.position} // Set position directly in the JSX
+ >
+
+
+ )
+ );
+ })
+ )}
+ >
+ );
};
export default ProcessAnimator;
diff --git a/app/src/modules/simulation/process/processContainer.tsx b/app/src/modules/simulation/process/processContainer.tsx
index 967376c..bf8fa48 100644
--- a/app/src/modules/simulation/process/processContainer.tsx
+++ b/app/src/modules/simulation/process/processContainer.tsx
@@ -10,6 +10,7 @@ const ProcessContainer: React.FC = () => {
<>
{processes.length > 0 && }
+
>
);
};
diff --git a/app/src/modules/simulation/process/processCreator.tsx b/app/src/modules/simulation/process/processCreator.tsx
index 8e1ed1d..91bc4c1 100644
--- a/app/src/modules/simulation/process/processCreator.tsx
+++ b/app/src/modules/simulation/process/processCreator.tsx
@@ -1,3 +1,402 @@
+// import React, {
+// useEffect,
+// useMemo,
+// useState,
+// useCallback,
+// useRef,
+// } from "react";
+// import { useSimulationPaths } from "../../../store/store";
+// import * as THREE from "three";
+// import { useThree } from "@react-three/fiber";
+// import {
+// ConveyorEventsSchema,
+// VehicleEventsSchema,
+// } from "../../../types/world/worldTypes";
+
+// // Type definitions
+// export interface PointAction {
+// uuid: string;
+// name: string;
+// type: string;
+// material: string;
+// delay: number | string;
+// spawnInterval: string | number;
+// isUsed: boolean;
+// }
+
+// export interface PathPoint {
+// uuid: string;
+// position: [number, number, number];
+// actions: PointAction[];
+// connections: {
+// targets: Array<{ pathUUID: string }>;
+// };
+// }
+
+// export interface SimulationPath {
+// modeluuid: string;
+// points: PathPoint[];
+// pathPosition: [number, number, number];
+// speed?: number;
+// }
+
+// export interface Process {
+// id: string;
+// paths: SimulationPath[];
+// animationPath: THREE.Vector3[];
+// pointActions: PointAction[][];
+// speed: number;
+// }
+
+// interface ProcessCreatorProps {
+// onProcessesCreated: (processes: Process[]) => void;
+// }
+
+// // Convert event schemas to SimulationPath
+// function convertToSimulationPath(
+// path: ConveyorEventsSchema | VehicleEventsSchema
+// ): SimulationPath {
+// const { modeluuid } = path;
+
+// // Simplified normalizeAction function that preserves exact original properties
+// const normalizeAction = (action: any): PointAction => {
+// return { ...action }; // Return exact copy with no modifications
+// };
+
+// if (path.type === "Conveyor") {
+// return {
+// modeluuid,
+// points: path.points.map((point) => ({
+// uuid: point.uuid,
+// position: point.position,
+// actions: point.actions.map(normalizeAction), // Preserve exact actions
+// connections: {
+// targets: point.connections.targets.map((target) => ({
+// pathUUID: target.pathUUID,
+// })),
+// },
+// })),
+// pathPosition: path.position,
+// speed:
+// typeof path.speed === "string"
+// ? parseFloat(path.speed) || 1
+// : path.speed || 1,
+// };
+// } else {
+// return {
+// modeluuid,
+// points: [
+// {
+// uuid: path.point.uuid,
+// position: path.point.position,
+// actions: Array.isArray(path.point.actions)
+// ? path.point.actions.map(normalizeAction)
+// : [normalizeAction(path.point.actions)],
+// connections: {
+// targets: path.point.connections.targets.map((target) => ({
+// pathUUID: target.pathUUID,
+// })),
+// },
+// },
+// ],
+// pathPosition: path.position,
+// speed: path.point.speed || 1,
+// };
+// }
+// }
+
+// // Custom shallow comparison for arrays
+// const areArraysEqual = (a: any[], b: any[]) => {
+// if (a.length !== b.length) return false;
+// for (let i = 0; i < a.length; i++) {
+// if (a[i] !== b[i]) return false;
+// }
+// return true;
+// };
+
+// // Helper function to create an empty process
+// const createEmptyProcess = (): Process => ({
+// id: `process-${Math.random().toString(36).substring(2, 11)}`,
+// paths: [],
+// animationPath: [],
+// pointActions: [],
+// speed: 1,
+// });
+
+// // Enhanced connection checking function
+// function shouldReverseNextPath(
+// currentPath: SimulationPath,
+// nextPath: SimulationPath
+// ): boolean {
+// if (nextPath.points.length !== 3) return false;
+
+// const currentLastPoint = currentPath.points[currentPath.points.length - 1];
+// const nextFirstPoint = nextPath.points[0];
+// const nextLastPoint = nextPath.points[nextPath.points.length - 1];
+
+// // Check if current last connects to next last (requires reversal)
+// const connectsToLast = currentLastPoint.connections.targets.some(
+// (target) =>
+// target.pathUUID === nextPath.modeluuid &&
+// nextLastPoint.connections.targets.some(
+// (t) => t.pathUUID === currentPath.modeluuid
+// )
+// );
+
+// // Check if current last connects to next first (no reversal needed)
+// const connectsToFirst = currentLastPoint.connections.targets.some(
+// (target) =>
+// target.pathUUID === nextPath.modeluuid &&
+// nextFirstPoint.connections.targets.some(
+// (t) => t.pathUUID === currentPath.modeluuid
+// )
+// );
+
+// // Only reverse if connected to last point and not to first point
+// return connectsToLast && !connectsToFirst;
+// }
+
+// // Updated path adjustment function
+// function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] {
+// if (paths.length < 2) return paths;
+
+// const adjustedPaths = [...paths];
+
+// for (let i = 0; i < adjustedPaths.length - 1; i++) {
+// const currentPath = adjustedPaths[i];
+// const nextPath = adjustedPaths[i + 1];
+
+// if (shouldReverseNextPath(currentPath, nextPath)) {
+// const reversedPoints = [
+// nextPath.points[2],
+// nextPath.points[1],
+// nextPath.points[0],
+// ];
+
+// adjustedPaths[i + 1] = {
+// ...nextPath,
+// points: reversedPoints,
+// };
+// }
+// }
+
+// return adjustedPaths;
+// }
+
+// // Main hook for process creation
+// export function useProcessCreation() {
+// const { scene } = useThree();
+// const [processes, setProcesses] = useState([]);
+
+// const hasSpawnAction = useCallback((path: SimulationPath): boolean => {
+// return path.points.some((point) =>
+// point.actions.some((action) => action.type.toLowerCase() === "spawn")
+// );
+// }, []);
+
+// const createProcess = useCallback(
+// (paths: SimulationPath[]): Process => {
+// if (!paths || paths.length === 0) {
+// return createEmptyProcess();
+// }
+
+// const animationPath: THREE.Vector3[] = [];
+// const pointActions: PointAction[][] = [];
+// const processSpeed = paths[0]?.speed || 1;
+
+// for (const path of paths) {
+// for (const point of path.points) {
+// const obj = scene.getObjectByProperty("uuid", point.uuid);
+// if (!obj) {
+// console.warn(`Object with UUID ${point.uuid} not found in scene`);
+// continue;
+// }
+
+// const position = obj.getWorldPosition(new THREE.Vector3());
+// animationPath.push(position.clone());
+// pointActions.push(point.actions);
+// }
+// }
+
+// return {
+// id: `process-${Math.random().toString(36).substring(2, 11)}`,
+// paths,
+// animationPath,
+// pointActions,
+// speed: processSpeed,
+// };
+// },
+// [scene]
+// );
+
+// const getAllConnectedPaths = useCallback(
+// (
+// initialPath: SimulationPath,
+// allPaths: SimulationPath[],
+// visited: Set = new Set()
+// ): SimulationPath[] => {
+// const connectedPaths: SimulationPath[] = [];
+// const queue: SimulationPath[] = [initialPath];
+// visited.add(initialPath.modeluuid);
+
+// const pathMap = new Map();
+// allPaths.forEach((path) => pathMap.set(path.modeluuid, path));
+
+// while (queue.length > 0) {
+// const currentPath = queue.shift()!;
+// connectedPaths.push(currentPath);
+
+// // Process outgoing connections
+// for (const point of currentPath.points) {
+// for (const target of point.connections.targets) {
+// if (!visited.has(target.pathUUID)) {
+// const targetPath = pathMap.get(target.pathUUID);
+// if (targetPath) {
+// visited.add(target.pathUUID);
+// queue.push(targetPath);
+// }
+// }
+// }
+// }
+
+// // Process incoming connections
+// for (const [uuid, path] of pathMap) {
+// if (!visited.has(uuid)) {
+// const hasConnectionToCurrent = path.points.some((point) =>
+// point.connections.targets.some(
+// (t) => t.pathUUID === currentPath.modeluuid
+// )
+// );
+// if (hasConnectionToCurrent) {
+// visited.add(uuid);
+// queue.push(path);
+// }
+// }
+// }
+// }
+
+// return connectedPaths;
+// },
+// []
+// );
+
+// const createProcessesFromPaths = useCallback(
+// (paths: SimulationPath[]): Process[] => {
+// if (!paths || paths.length === 0) return [];
+
+// const visited = new Set();
+// const processes: Process[] = [];
+// const pathMap = new Map();
+// paths.forEach((path) => pathMap.set(path.modeluuid, path));
+
+// for (const path of paths) {
+// if (!visited.has(path.modeluuid) && hasSpawnAction(path)) {
+// const connectedPaths = getAllConnectedPaths(path, paths, visited);
+// const adjustedPaths = adjustPathPointsOrder(connectedPaths);
+// const process = createProcess(adjustedPaths);
+// processes.push(process);
+// }
+// }
+
+// return processes;
+// },
+// [createProcess, getAllConnectedPaths, hasSpawnAction]
+// );
+
+// return {
+// processes,
+// createProcessesFromPaths,
+// setProcesses,
+// };
+// }
+
+// const ProcessCreator: React.FC = React.memo(
+// ({ onProcessesCreated }) => {
+// const { simulationPaths } = useSimulationPaths();
+// const { createProcessesFromPaths } = useProcessCreation();
+// const prevPathsRef = useRef([]);
+// const prevProcessesRef = useRef([]);
+
+// const convertedPaths = useMemo((): SimulationPath[] => {
+// if (!simulationPaths) return [];
+// return simulationPaths.map((path) =>
+// convertToSimulationPath(
+// path as ConveyorEventsSchema | VehicleEventsSchema
+// )
+// );
+// }, [simulationPaths]);
+
+// const pathsDependency = useMemo(() => {
+// if (!convertedPaths) return null;
+// return convertedPaths.map((path) => ({
+// id: path.modeluuid,
+// hasSpawn: path.points.some((p: PathPoint) =>
+// p.actions.some((a: PointAction) => a.type.toLowerCase() === "spawn")
+// ),
+// connections: path.points
+// .flatMap((p: PathPoint) =>
+// p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID)
+// )
+// .join(","),
+// }));
+// }, [convertedPaths]);
+
+// useEffect(() => {
+// if (!convertedPaths || convertedPaths.length === 0) {
+// if (prevProcessesRef.current.length > 0) {
+// onProcessesCreated([]);
+// prevProcessesRef.current = [];
+// }
+// return;
+// }
+
+// if (areArraysEqual(prevPathsRef.current, convertedPaths)) {
+// return;
+// }
+
+// prevPathsRef.current = convertedPaths;
+// const newProcesses = createProcessesFromPaths(convertedPaths);
+
+// // console.log("--- Action Types in Paths ---");
+// // convertedPaths.forEach((path) => {
+// // path.points.forEach((point) => {
+// // point.actions.forEach((action) => {
+// // console.log(
+// // `Path ${path.modeluuid}, Point ${point.uuid}: ${action.type}`
+// // );
+// // });
+// // });
+// // });
+// // console.log("New processes:", newProcesses);
+
+// if (
+// newProcesses.length !== prevProcessesRef.current.length ||
+// !newProcesses.every(
+// (proc, i) =>
+// proc.paths.length === prevProcessesRef.current[i]?.paths.length &&
+// proc.paths.every(
+// (path, j) =>
+// path.modeluuid ===
+// prevProcessesRef.current[i]?.paths[j]?.modeluuid
+// )
+// )
+// ) {
+// onProcessesCreated(newProcesses);
+// // prevProcessesRef.current = newProcesses;
+// }
+// }, [
+// pathsDependency,
+// onProcessesCreated,
+// convertedPaths,
+// createProcessesFromPaths,
+// ]);
+
+// return null;
+// }
+// );
+
+// export default ProcessCreator;
+
import React, {
useEffect,
useMemo,
@@ -156,12 +555,38 @@ function shouldReverseNextPath(
return connectsToLast && !connectsToFirst;
}
+// Check if a point has a spawn action
+function hasSpawnAction(point: PathPoint): boolean {
+ return point.actions.some((action) => action.type.toLowerCase() === "spawn");
+}
+
+// Ensure spawn point is always at the beginning of the path
+function ensureSpawnPointIsFirst(path: SimulationPath): SimulationPath {
+ if (path.points.length !== 3) return path;
+
+ // If the third point has spawn action and first doesn't, reverse the array
+ if (hasSpawnAction(path.points[2]) && !hasSpawnAction(path.points[0])) {
+ return {
+ ...path,
+ points: [...path.points].reverse(),
+ };
+ }
+
+ return path;
+}
+
// Updated path adjustment function
function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] {
- if (paths.length < 2) return paths;
+ if (paths.length < 1) return paths;
const adjustedPaths = [...paths];
+ // First ensure all paths have spawn points at the beginning
+ for (let i = 0; i < adjustedPaths.length; i++) {
+ adjustedPaths[i] = ensureSpawnPointIsFirst(adjustedPaths[i]);
+ }
+
+ // Then handle connections between paths
for (let i = 0; i < adjustedPaths.length - 1; i++) {
const currentPath = adjustedPaths[i];
const nextPath = adjustedPaths[i + 1];
@@ -326,13 +751,17 @@ const ProcessCreator: React.FC = React.memo(
);
}, [simulationPaths]);
+ // Enhanced dependency tracking that includes action types
const pathsDependency = useMemo(() => {
if (!convertedPaths) return null;
return convertedPaths.map((path) => ({
id: path.modeluuid,
- hasSpawn: path.points.some((p: PathPoint) =>
- p.actions.some((a: PointAction) => a.type.toLowerCase() === "spawn")
- ),
+ // Track all action types for each point
+ actionSignature: path.points
+ .map((point, index) =>
+ point.actions.map((action) => `${index}-${action.type}`).join("|")
+ )
+ .join(","),
connections: path.points
.flatMap((p: PathPoint) =>
p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID)
@@ -341,6 +770,7 @@ const ProcessCreator: React.FC = React.memo(
}));
}, [convertedPaths]);
+ // Force process recreation when paths change
useEffect(() => {
if (!convertedPaths || convertedPaths.length === 0) {
if (prevProcessesRef.current.length > 0) {
@@ -350,42 +780,16 @@ const ProcessCreator: React.FC = React.memo(
return;
}
- if (areArraysEqual(prevPathsRef.current, convertedPaths)) {
- return;
- }
-
- prevPathsRef.current = convertedPaths;
+ // Always regenerate processes if the pathsDependency has changed
+ // This ensures action type changes will be detected
const newProcesses = createProcessesFromPaths(convertedPaths);
+ prevPathsRef.current = convertedPaths;
- // console.log("--- Action Types in Paths ---");
- // convertedPaths.forEach((path) => {
- // path.points.forEach((point) => {
- // point.actions.forEach((action) => {
- // console.log(
- // `Path ${path.modeluuid}, Point ${point.uuid}: ${action.type}`
- // );
- // });
- // });
- // });
- // console.log("New processes:", newProcesses);
-
- if (
- newProcesses.length !== prevProcessesRef.current.length ||
- !newProcesses.every(
- (proc, i) =>
- proc.paths.length === prevProcessesRef.current[i]?.paths.length &&
- proc.paths.every(
- (path, j) =>
- path.modeluuid ===
- prevProcessesRef.current[i]?.paths[j]?.modeluuid
- )
- )
- ) {
- onProcessesCreated(newProcesses);
- // prevProcessesRef.current = newProcesses;
- }
+ // Always update processes when action types change
+ onProcessesCreated(newProcesses);
+ prevProcessesRef.current = newProcesses;
}, [
- pathsDependency,
+ pathsDependency, // This now includes action types
onProcessesCreated,
convertedPaths,
createProcessesFromPaths,
diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx
index fd8b520..830a0b1 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/services/factoryBuilder/assest/floorAsset/setEventsApt.ts b/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts
new file mode 100644
index 0000000..86c8f71
--- /dev/null
+++ b/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts
@@ -0,0 +1,32 @@
+let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
+
+export const setEventApi = async (
+ organization: string,
+ modeluuid: string,
+ eventData: any
+) => {
+ try {
+ const body: any = { organization, modeluuid, eventData };
+
+ const response = await fetch(`${url_Backend_dwinzo}/api/v2/eventDataUpdate`, {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to set or update Floor Item");
+ }
+
+ const result = await response.json();
+ return result;
+ } catch (error) {
+ if (error instanceof Error) {
+ throw new Error(error.message);
+ } else {
+ throw new Error("An unknown error occurred");
+ }
+ }
+};
\ No newline at end of file
diff --git a/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts b/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts
index 75583cc..e25e05e 100644
--- a/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts
+++ b/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts
@@ -30,7 +30,6 @@ export const setFloorItemApi = async (
}
const result = await response.json();
- console.log('result: ', result);
return result;
} catch (error) {
if (error instanceof Error) {
diff --git a/app/src/store/store.ts b/app/src/store/store.ts
index 87f937c..365eb97 100644
--- a/app/src/store/store.ts
+++ b/app/src/store/store.ts
@@ -12,7 +12,7 @@ export const useSocketStore = create((set: any, get: any) => ({
}
const socket = io(
- `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}`,
+ `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder`,
{
reconnection: false,
auth: { email, organization },
@@ -343,14 +343,21 @@ export const useSelectedPath = create((set: any) => ({
interface SimulationPathsStore {
simulationPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[];
setSimulationPaths: (
- paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]
+ paths:
+ | (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]
+ | ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]
+ ) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[])
) => void;
}
export const useSimulationPaths = create((set) => ({
simulationPaths: [],
- setSimulationPaths: (paths) => set({ simulationPaths: paths }),
-}));
+ setSimulationPaths: (paths) =>
+ set((state) => ({
+ simulationPaths:
+ typeof paths === "function" ? paths(state.simulationPaths) : paths,
+ })),
+}))
export const useIsConnecting = create((set: any) => ({
isConnecting: false,
diff --git a/app/src/store/useZone3DWidgetStore.ts b/app/src/store/useZone3DWidgetStore.ts
index fbb8e74..850623f 100644
--- a/app/src/store/useZone3DWidgetStore.ts
+++ b/app/src/store/useZone3DWidgetStore.ts
@@ -1,33 +1,77 @@
+
import { create } from "zustand";
type WidgetData = {
- id: string;
- type: string;
- position: [number, number, number];
+ id: string;
+ type: string;
+ position: [number, number, number];
+ tempPosition?: [number, number, number];
};
type ZoneWidgetStore = {
- zoneWidgetData: Record;
- setZoneWidgetData: (zoneId: string, widgets: WidgetData[]) => void;
- addWidget: (zoneId: string, widget: WidgetData) => void;
+ zoneWidgetData: Record;
+ setZoneWidgetData: (zoneId: string, widgets: WidgetData[]) => void;
+ addWidget: (zoneId: string, widget: WidgetData) => void;
+ updateWidgetPosition: (zoneId: string, widgetId: string, newPosition: [number, number, number]) => void;
};
export const useZoneWidgetStore = create((set) => ({
- zoneWidgetData: {},
-
- setZoneWidgetData: (zoneId, widgets) =>
- set((state) => ({
- zoneWidgetData: {
- ...state.zoneWidgetData,
- [zoneId]: widgets,
- },
- })),
-
- addWidget: (zoneId, widget) =>
- set((state) => ({
- zoneWidgetData: {
- ...state.zoneWidgetData,
- [zoneId]: [...(state.zoneWidgetData[zoneId] || []), widget],
- },
- })),
+ zoneWidgetData: {},
+
+ setZoneWidgetData: (zoneId, widgets) =>
+ set((state) => ({
+ zoneWidgetData: { ...state.zoneWidgetData, [zoneId]: widgets },
+ })),
+
+ addWidget: (zoneId, widget) =>
+ set((state) => ({
+ zoneWidgetData: {
+ ...state.zoneWidgetData,
+ [zoneId]: [...(state.zoneWidgetData[zoneId] || []), widget],
+ },
+ })),
+
+ updateWidgetPosition: (zoneId, widgetId, newPosition) =>
+ set((state) => {
+ const widgets = state.zoneWidgetData[zoneId] || [];
+ return {
+ zoneWidgetData: {
+ ...state.zoneWidgetData,
+ [zoneId]: widgets.map((widget) =>
+ widget.id === widgetId ? { ...widget, position: newPosition } : widget
+ ),
+ },
+ };
+ }),
+}));
+
+
+interface RightClickStore {
+ rightClickSelected: string | null;
+ setRightClickSelected: (x: string | null) => void;
+}
+
+export const useRightClickSelected = create((set) => ({
+ rightClickSelected: null, // Default to null
+ setRightClickSelected: (x) => set({ rightClickSelected: x }),
+}));
+
+export const useTopData = create((set: any) => ({
+ top: 0,
+ setTop: (x: any) => set({ top: x }),
+}));
+
+export const useLeftData = create((set: any) => ({
+ left: 0,
+ setLeft: (x: any) => set({ left: x }),
+}));
+
+interface RightSelectStore {
+ rightSelect: string | null;
+ setRightSelect: (x: string | null) => void;
+}
+
+export const useRightSelected = create((set) => ({
+ rightSelect: null, // Default state is null
+ setRightSelect: (x) => set({ rightSelect: x }),
}));
diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss
index e64ec08..a79823c 100644
--- a/app/src/styles/pages/realTimeViz.scss
+++ b/app/src/styles/pages/realTimeViz.scss
@@ -716,11 +716,6 @@
}
-.editWidgetOptions-wrapper {
-
- height: 100vh;
- width: 100vw;
-}
.editWidgetOptions {
position: absolute;