Merge branch 'dev-simulation/human' into main-dev

This commit is contained in:
2025-07-07 16:21:40 +05:30
36 changed files with 901 additions and 974 deletions

View File

@@ -4,7 +4,7 @@ import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsse
import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store';
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { FloorItems, RefGroup, RefMesh } from "../../../types/world/worldTypes";
import { FloorItems, RefMesh } from "../../../types/world/worldTypes";
import Models from "./models/models";
import useModuleStore from "../../../store/useModuleStore";
import { useThree } from "@react-three/fiber";

View File

@@ -17,6 +17,7 @@ import { useSceneContext } from '../../../../scene/sceneContext';
import { useVersionContext } from '../../../version/versionContext';
import { SkeletonUtils } from 'three-stdlib';
import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore';
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
function Model({ asset }: { readonly asset: Asset }) {
const { camera, controls, gl } = useThree();
@@ -55,9 +56,24 @@ function Model({ asset }: { readonly asset: Asset }) {
const blendFactor = useRef(0);
const blendDuration = 0.5;
const updateBackend = (
productName: string,
productUuid: string,
projectId: string,
eventData: EventsSchema
) => {
upsertProductOrEventApi({
productName: productName,
productUuid: productUuid,
projectId: projectId,
eventDatas: eventData,
versionId: selectedVersion?.versionId || '',
});
};
useEffect(() => {
setDeletableFloorItem(null);
if (selectedFloorItem === null) {
if (selectedFloorItem === null || selectedFloorItem.modelUuid !== asset.modelUuid) {
resetAnimation(asset.modelUuid);
}
}, [activeModule, toolMode, selectedFloorItem])
@@ -217,7 +233,16 @@ function Model({ asset }: { readonly asset: Asset }) {
const response = socket.emit('v1:model-asset:delete', data)
eventStore.getState().removeEvent(asset.modelUuid);
productStore.getState().deleteEvent(asset.modelUuid);
const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid);
updatedEvents.forEach((event) => {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
})
if (response) {
@@ -291,7 +316,7 @@ function Model({ asset }: { readonly asset: Asset }) {
useFrame((_, delta) => {
if (mixerRef.current) {
mixerRef.current.update(delta * speed);
mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1));
}
});

View File

@@ -1,62 +0,0 @@
import * as THREE from 'three';
import * as Types from "../../../../types/world/worldTypes";
import * as CONSTANTS from "../../../../types/world/worldConstants";
import texturePath from "../../../../assets/textures/floor/white1.png";
import texturePathDark from "../../../../assets/textures/floor/black.png";
// Cache for materials
const materialCache = new Map<string, THREE.Material>();
export default function addFloorToScene(
shape: THREE.Shape,
layer: number,
floorGroup: Types.RefGroup,
userData: any,
) {
const savedTheme: string | null = localStorage.getItem('theme');
const textureLoader = new THREE.TextureLoader();
const textureScale = CONSTANTS.floorConfig.textureScale;
const materialKey = `floorMaterial_${textureScale}`;
let material: THREE.Material;
if (materialCache.has(materialKey)) {
material = materialCache.get(materialKey) as THREE.Material;
} else {
const floorTexture = textureLoader.load(savedTheme === "dark" ? texturePathDark : texturePath);
// const floorTexture = textureLoader.load(texturePath);
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(textureScale, textureScale);
floorTexture.colorSpace = THREE.SRGBColorSpace;
material = new THREE.MeshStandardMaterial({
map: floorTexture,
side: THREE.DoubleSide,
});
materialCache.set(materialKey, material);
}
const extrudeSettings = {
depth: CONSTANTS.floorConfig.height,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const mesh = new THREE.Mesh(geometry, material);
mesh.receiveShadow = true;
mesh.position.y = (layer) * CONSTANTS.wallConfig.height;
mesh.rotateX(Math.PI / 2);
mesh.name = `Floor_Layer_${layer}`;
// Store UUIDs for debugging or future processing
mesh.userData.uuids = userData;
floorGroup.current.add(mesh);
}

View File

@@ -1,190 +0,0 @@
import * as THREE from 'three';
import * as turf from '@turf/turf';
import * as CONSTANTS from '../../../../types/world/worldConstants';
import * as Types from "../../../../types/world/worldTypes";
// temp
import blueFloorImage from "../../../../assets/textures/floor/blue.png"
function loadOnlyFloors(
floorGroup: Types.RefGroup,
linesByLayer: any,
layer: any,
): void {
////////// Creating polygon floor based on the onlyFloorlines.current which does not add roof to it, The lines are still stored in Lines.current as well //////////
let floorsInLayer = linesByLayer[layer];
floorsInLayer = floorsInLayer.filter((line: any) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName);
const floorResult = floorsInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
pair.map((point) => ({
position: [point[0].x, point[0].z],
uuid: point[1]
}))
);
const FloorLineFeatures = floorResult.map((line: any) => turf.lineString(line.map((p: any) => p.position)));
function identifyPolygonsAndConnectedLines(FloorLineFeatures: any) {
const floorpolygons = [];
const connectedLines = [];
const unprocessedLines = [...FloorLineFeatures]; // Copy the features
while (unprocessedLines.length > 0) {
const currentLine = unprocessedLines.pop();
const coordinates = currentLine.geometry.coordinates;
// Check if the line is closed (forms a polygon)
if (
coordinates[0][0] === coordinates[coordinates.length - 1][0] &&
coordinates[0][1] === coordinates[coordinates.length - 1][1]
) {
floorpolygons.push(turf.polygon([coordinates])); // Add as a polygon
continue;
}
// Check if the line connects to another line
let connected = false;
for (let i = unprocessedLines.length - 1; i >= 0; i--) {
const otherCoordinates = unprocessedLines[i].geometry.coordinates;
// Check if lines share a start or end point
if (
coordinates[0][0] === otherCoordinates[otherCoordinates.length - 1][0] &&
coordinates[0][1] === otherCoordinates[otherCoordinates.length - 1][1]
) {
// Merge lines
const mergedCoordinates = [...otherCoordinates, ...coordinates.slice(1)];
unprocessedLines[i] = turf.lineString(mergedCoordinates);
connected = true;
break;
} else if (
coordinates[coordinates.length - 1][0] === otherCoordinates[0][0] &&
coordinates[coordinates.length - 1][1] === otherCoordinates[0][1]
) {
// Merge lines
const mergedCoordinates = [...coordinates, ...otherCoordinates.slice(1)];
unprocessedLines[i] = turf.lineString(mergedCoordinates);
connected = true;
break;
}
}
if (!connected) {
connectedLines.push(currentLine); // Add unconnected line as-is
}
}
return { floorpolygons, connectedLines };
}
const { floorpolygons, connectedLines } = identifyPolygonsAndConnectedLines(FloorLineFeatures);
function convertConnectedLinesToPolygons(connectedLines: any) {
return connectedLines.map((line: any) => {
const coordinates = line.geometry.coordinates;
// If the line has more than two points, close the polygon
if (coordinates.length > 2) {
const firstPoint = coordinates[0];
const lastPoint = coordinates[coordinates.length - 1];
// Check if already closed; if not, close it
if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
coordinates.push(firstPoint);
}
// Convert the closed line into a polygon
return turf.polygon([coordinates]);
}
// If not enough points for a polygon, return the line unchanged
return line;
});
}
const convertedConnectedPolygons = convertConnectedLinesToPolygons(connectedLines);
if (convertedConnectedPolygons.length > 0) {
const validPolygons = convertedConnectedPolygons.filter(
(polygon: any) => polygon.geometry?.type === "Polygon"
);
if (validPolygons.length > 0) {
floorpolygons.push(...validPolygons);
}
}
function convertPolygonsToOriginalFormat(floorpolygons: any, originalLines: [THREE.Vector3, string, number, string][][]) {
return floorpolygons.map((polygon: any) => {
const coordinates = polygon.geometry.coordinates[0]; // Extract the coordinates array (assume it's a single polygon)
// Map each coordinate back to its original structure
const mappedPoints = coordinates.map((coord: [number, number]) => {
const [x, z] = coord;
// Find the original point matching this coordinate
const originalPoint = originalLines.flat().find(([point]) => point.x === x && point.z === z);
if (!originalPoint) {
throw new Error(`Original point for coordinate [${x}, ${z}] not found.`);
}
return originalPoint;
});
// Create pairs of consecutive points
const pairs: typeof originalLines = [];
for (let i = 0; i < mappedPoints.length - 1; i++) {
pairs.push([mappedPoints[i], mappedPoints[i + 1]]);
}
return pairs;
});
}
const convertedFloorPolygons: Types.OnlyFloorLines = convertPolygonsToOriginalFormat(floorpolygons, floorsInLayer);
convertedFloorPolygons.forEach((floor) => {
const points: THREE.Vector3[] = [];
floor.forEach((lineSegment) => {
const startPoint = lineSegment[0][0];
points.push(new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z));
});
const lastLine = floor[floor.length - 1];
const endPoint = lastLine[1][0];
points.push(new THREE.Vector3(endPoint.x, endPoint.y, endPoint.z));
const shape = new THREE.Shape();
shape.moveTo(points[0].x, points[0].z);
points.forEach(point => shape.lineTo(point.x, point.z));
shape.closePath();
const extrudeSettings = {
depth: 0.3,
bevelEnabled: false
};
const texture = new THREE.TextureLoader().load(blueFloorImage);
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.colorSpace = THREE.SRGBColorSpace;
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.floorConfig.defaultColor, side: THREE.DoubleSide, map: texture });
const mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.position.y = (floor[0][0][2] - 0.99) * CONSTANTS.wallConfig.height;
mesh.rotateX(Math.PI / 2);
mesh.name = `Only_Floor_Line_${floor[0][0][2]}`;
mesh.userData = floor;
floorGroup?.current?.add(mesh);
});
}
export default loadOnlyFloors;

View File

@@ -1,291 +0,0 @@
// import { useEffect } from "react";
// import {
// useObjectPosition,
// useObjectRotation,
// useSelectedWallItem,
// useSocketStore,
// useWallItems,
// useSelectedItem,
// useToolMode,
// } from "../../../store/builder/store";
// import { Csg } from "../csg/csg";
// import * as Types from "../../../types/world/worldTypes";
// import * as CONSTANTS from "../../../types/world/worldConstants";
// import * as THREE from "three";
// import { useThree } from "@react-three/fiber";
// import handleMeshMissed from "../eventFunctions/handleMeshMissed";
// import DeleteWallItems from "../geomentries/walls/deleteWallItems";
// import loadInitialWallItems from "../IntialLoad/loadInitialWallItems";
// import AddWallItems from "../geomentries/walls/addWallItems";
// import useModuleStore from "../../../store/useModuleStore";
// import { useParams } from "react-router-dom";
// import { getUserData } from "../../../functions/getUserData";
// import { useVersionContext } from "../version/versionContext";
// const WallItemsGroup = ({
// currentWallItem,
// hoveredDeletableWallItem,
// selectedItemsIndex,
// setSelectedItemsIndex,
// CSGGroup,
// }: any) => {
// const state = useThree();
// const { socket } = useSocketStore();
// const { pointer, camera, raycaster } = state;
// const { toolMode } = useToolMode();
// const { wallItems, setWallItems } = useWallItems();
// const { setObjectPosition } = useObjectPosition();
// const { setObjectRotation } = useObjectRotation();
// const { setSelectedWallItem } = useSelectedWallItem();
// const { activeModule } = useModuleStore();
// const { selectedItem } = useSelectedItem();
// const { selectedVersionStore } = useVersionContext();
// const { selectedVersion } = selectedVersionStore();
// const { projectId } = useParams();
// const { userId, organization } = getUserData();
// useEffect(() => {
// // Load Wall Items from the backend
// if (!projectId || !selectedVersion) return;
// loadInitialWallItems(setWallItems, projectId, selectedVersion?.versionId);
// }, [selectedVersion?.versionId]);
// ////////// Update the Position value changes in the selected item //////////
// useEffect(() => {
// const canvasElement = state.gl.domElement;
// function handlePointerMove(e: any) {
// if (selectedItemsIndex !== null && toolMode === 'cursor' && e.buttons === 1) {
// const Raycaster = state.raycaster;
// const intersects = Raycaster.intersectObjects(CSGGroup.current?.children[0].children!, true);
// const Object = intersects.find((child) => child.object.name.includes("WallRaycastReference"));
// if (Object) {
// (state.controls as any)!.enabled = false;
// setWallItems((prevItems: any) => {
// const updatedItems = [...prevItems];
// let position: [number, number, number] = [0, 0, 0];
// if (updatedItems[selectedItemsIndex].type === "fixed-move") {
// position = [
// Object!.point.x,
// Math.floor(Object!.point.y / CONSTANTS.wallConfig.height) *
// CONSTANTS.wallConfig.height,
// Object!.point.z,
// ];
// } else if (updatedItems[selectedItemsIndex].type === "free-move") {
// position = [Object!.point.x, Object!.point.y, Object!.point.z];
// }
// requestAnimationFrame(() => {
// setObjectPosition(new THREE.Vector3(...position));
// setObjectRotation({
// x: THREE.MathUtils.radToDeg(Object!.object.rotation.x),
// y: THREE.MathUtils.radToDeg(Object!.object.rotation.y),
// z: THREE.MathUtils.radToDeg(Object!.object.rotation.z),
// });
// });
// updatedItems[selectedItemsIndex] = {
// ...updatedItems[selectedItemsIndex],
// position: position,
// quaternion: Object!.object.quaternion.clone() as Types.QuaternionType,
// };
// return updatedItems;
// });
// }
// }
// }
// async function handlePointerUp() {
// const Raycaster = state.raycaster;
// const intersects = Raycaster.intersectObjects(
// CSGGroup.current?.children[0].children!,
// true
// );
// const Object = intersects.find((child) =>
// child.object.name.includes("WallRaycastReference")
// );
// if (Object) {
// if (selectedItemsIndex !== null) {
// let currentItem: any = null;
// setWallItems((prevItems: any) => {
// const updatedItems = [...prevItems];
// const WallItemsForStorage = updatedItems.map((item) => {
// const { model, ...rest } = item;
// return {
// ...rest,
// modelUuid: model?.uuid,
// };
// });
// currentItem = updatedItems[selectedItemsIndex];
// localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
// return updatedItems;
// });
// setTimeout(async () => {
// //REST
// // await setWallItem(
// // organization,
// // currentItem?.model?.uuid,
// // currentItem.modelName,
// // currentItem.assetId,
// // currentItem.type!,
// // currentItem.csgposition!,
// // currentItem.csgscale!,
// // currentItem.position,
// // currentItem.quaternion,
// // currentItem.scale!,
// // )
// //SOCKET
// const data = {
// organization,
// modelUuid: currentItem.model?.uuid!,
// assetId: currentItem.assetId,
// modelName: currentItem.modelName!,
// type: currentItem.type!,
// csgposition: currentItem.csgposition!,
// csgscale: currentItem.csgscale!,
// position: currentItem.position!,
// quaternion: currentItem.quaternion,
// scale: currentItem.scale!,
// socketId: socket.id,
// versionId: selectedVersion?.versionId || '',
// projectId,
// userId
// };
// // console.log('data: ', data);
// socket.emit("v1:wallItems:set", data);
// }, 0);
// (state.controls as any)!.enabled = true;
// }
// }
// }
// canvasElement.addEventListener("pointermove", handlePointerMove);
// canvasElement.addEventListener("pointerup", handlePointerUp);
// return () => {
// canvasElement.removeEventListener("pointermove", handlePointerMove);
// canvasElement.removeEventListener("pointerup", handlePointerUp);
// };
// }, [selectedItemsIndex, selectedVersion?.versionId]);
// useEffect(() => {
// const canvasElement = state.gl.domElement;
// let drag = false;
// let isLeftMouseDown = false;
// const onMouseDown = (evt: any) => {
// if (evt.button === 0) {
// isLeftMouseDown = true;
// drag = false;
// }
// };
// const onMouseUp = (evt: any) => {
// if (evt.button === 0) {
// isLeftMouseDown = false;
// if (!drag && toolMode === '3D-Delete' && activeModule === "builder") {
// DeleteWallItems(
// hoveredDeletableWallItem,
// setWallItems,
// wallItems,
// socket,
// projectId,
// selectedVersion?.versionId || '',
// );
// }
// }
// };
// const onMouseMove = () => {
// if (isLeftMouseDown) {
// drag = true;
// }
// };
// const onDrop = (event: any) => {
// if (selectedItem.category !== 'Fenestration') return;
// pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
// pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
// raycaster.setFromCamera(pointer, camera);
// if (selectedItem.id && selectedVersion && projectId) {
// if (selectedItem.subCategory) {
// AddWallItems(
// selectedItem,
// raycaster,
// CSGGroup,
// setWallItems,
// socket,
// projectId,
// selectedVersion?.versionId || '',
// );
// }
// event.preventDefault();
// }
// };
// const onDragOver = (event: any) => {
// event.preventDefault();
// };
// canvasElement.addEventListener("mousedown", onMouseDown);
// canvasElement.addEventListener("mouseup", onMouseUp);
// canvasElement.addEventListener("mousemove", onMouseMove);
// canvasElement.addEventListener("drop", onDrop);
// canvasElement.addEventListener("dragover", onDragOver);
// return () => {
// canvasElement.removeEventListener("mousedown", onMouseDown);
// canvasElement.removeEventListener("mouseup", onMouseUp);
// canvasElement.removeEventListener("mousemove", onMouseMove);
// canvasElement.removeEventListener("drop", onDrop);
// canvasElement.removeEventListener("dragover", onDragOver);
// };
// }, [toolMode, wallItems, selectedItem, camera, selectedVersion?.versionId]);
// useEffect(() => {
// if (toolMode && activeModule === "builder") {
// handleMeshMissed(
// currentWallItem,
// setSelectedWallItem,
// setSelectedItemsIndex
// );
// setSelectedWallItem(null);
// setSelectedItemsIndex(null);
// }
// }, [toolMode]);
// return (
// <>
// {wallItems.map((item: Types.WallItem, index: number) => (
// <group
// key={index}
// position={item.position}
// quaternion={item.quaternion}
// scale={item.scale}
// >
// <Csg
// position={item.csgposition!}
// scale={item.csgscale!}
// model={item.model!}
// hoveredDeletableWallItem={hoveredDeletableWallItem}
// />
// </group>
// ))}
// </>
// );
// };
// export default WallItemsGroup;

View File

@@ -1,74 +0,0 @@
// import { Geometry } from "@react-three/csg";
// import {
// useSelectedWallItem,
// useToggleView,
// useToolMode,
// useWallItems,
// useWalls,
// } from "../../../store/builder/store";
// import handleMeshDown from "../eventFunctions/handleMeshDown";
// import handleMeshMissed from "../eventFunctions/handleMeshMissed";
// import WallsMesh from "./wallsMesh";
// import WallItemsGroup from "./wallItemsGroup";
// const WallsAndWallItems = ({
// CSGGroup,
// setSelectedItemsIndex,
// selectedItemsIndex,
// currentWallItem,
// csg,
// lines,
// hoveredDeletableWallItem,
// }: any) => {
// const { walls } = useWalls();
// const { wallItems } = useWallItems();
// const { toggleView } = useToggleView();
// const { toolMode } = useToolMode();
// const { setSelectedWallItem } = useSelectedWallItem();
// return (
// <mesh
// ref={CSGGroup as any}
// name="Walls"
// key={walls.length}
// receiveShadow
// visible={!toggleView}
// onClick={(event) => {
// if (toolMode === "cursor") {
// handleMeshDown(
// event,
// currentWallItem,
// setSelectedWallItem,
// setSelectedItemsIndex,
// wallItems,
// toggleView
// );
// }
// }}
// onPointerMissed={() => {
// if (toolMode === "cursor") {
// handleMeshMissed(
// currentWallItem,
// setSelectedWallItem,
// setSelectedItemsIndex
// );
// setSelectedWallItem(null);
// setSelectedItemsIndex(null);
// }
// }}
// >
// <Geometry ref={csg as any} computeVertexNormals useGroups>
// <WallsMesh lines={lines} />
// <WallItemsGroup
// currentWallItem={currentWallItem}
// hoveredDeletableWallItem={hoveredDeletableWallItem}
// selectedItemsIndex={selectedItemsIndex}
// setSelectedItemsIndex={setSelectedItemsIndex}
// CSGGroup={CSGGroup}
// />
// </Geometry>
// </mesh>
// );
// };
// export default WallsAndWallItems;

View File

@@ -1,82 +0,0 @@
// import * as THREE from "three";
// import * as Types from "../../../types/world/worldTypes";
// import * as CONSTANTS from "../../../types/world/worldConstants";
// import { Base } from "@react-three/csg";
// import { MeshDiscardMaterial } from "@react-three/drei";
// import { useUpdateScene, useWalls } from "../../../store/builder/store";
// import React, { useEffect } from "react";
// import { getLines } from "../../../services/factoryBuilder/lines/getLinesApi";
// import objectLinesToArray from "../geomentries/lines/lineConvertions/objectLinesToArray";
// import loadWalls from "../geomentries/walls/loadWalls";
// import texturePath from "../../../assets/textures/floor/wall-tex.png";
// import { useParams } from "react-router-dom";
// import { getUserData } from "../../../functions/getUserData";
// import { useVersionContext } from "../version/versionContext";
// const WallsMeshComponent = ({ lines }: any) => {
// const { walls, setWalls } = useWalls();
// const { updateScene, setUpdateScene } = useUpdateScene();
// const { projectId } = useParams();
// const { selectedVersionStore } = useVersionContext();
// const { selectedVersion } = selectedVersionStore();
// const { organization } = getUserData();
// useEffect(() => {
// if (updateScene) {
// if (!selectedVersion) {
// setUpdateScene(false);
// return;
// };
// getLines(organization, projectId, selectedVersion?.versionId || '').then((data) => {
// const Lines: Types.Lines = objectLinesToArray(data);
// localStorage.setItem("Lines", JSON.stringify(Lines));
// if (Lines) {
// loadWalls(lines, setWalls);
// }
// });
// setUpdateScene(false);
// }
// }, [updateScene, selectedVersion?.versionId]);
// const textureLoader = new THREE.TextureLoader();
// const wallTexture = textureLoader.load(texturePath);
// wallTexture.wrapS = wallTexture.wrapT = THREE.RepeatWrapping;
// wallTexture.repeat.set(0.1, 0.1);
// wallTexture.colorSpace = THREE.SRGBColorSpace;
// return (
// <>
// {walls.map((wall: Types.Wall, index: number) => (
// <mesh key={index} renderOrder={1}>
// <Base
// name={`Wall${index + 1}`}
// geometry={wall[0]}
// rotation={wall[1]}
// position={wall[2]}
// userData={{ WallType: wall[3], Layer: wall[4] }}
// >
// <meshStandardMaterial
// side={THREE.DoubleSide}
// color={CONSTANTS.wallConfig.defaultColor}
// map={wallTexture}
// />
// </Base>
// <mesh
// castShadow
// geometry={wall[0]}
// rotation={wall[1]}
// position={wall[2]}
// name={`WallRaycastReference_${index + 1}`}
// >
// <MeshDiscardMaterial />
// </mesh>
// </mesh>
// ))}
// </>
// );
// };
// const WallsMesh = React.memo(WallsMeshComponent);
// export default WallsMesh;

View File

@@ -1,8 +1,8 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/builder/store";
// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
import * as Types from "../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
import { useParams } from "react-router-dom";
@@ -10,6 +10,8 @@ import { getUserData } from "../../../../functions/getUserData";
import { useSceneContext } from "../../sceneContext";
import { useVersionContext } from "../../../builder/version/versionContext";
// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
const CopyPasteControls = ({
copiedObjects,
setCopiedObjects,
@@ -109,7 +111,7 @@ const CopyPasteControls = ({
const copySelection = () => {
if (selectedAssets.length > 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = asset.clone();
const clone = SkeletonUtils.clone(asset);
clone.position.copy(asset.position);
return clone;
});

View File

@@ -1,8 +1,8 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/builder/store";
// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
import * as Types from "../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
import { useParams } from "react-router-dom";
@@ -10,6 +10,8 @@ import { getUserData } from "../../../../functions/getUserData";
import { useSceneContext } from "../../sceneContext";
import { useVersionContext } from "../../../builder/version/versionContext";
// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
const DuplicationControls = ({
duplicatedObjects,
setDuplicatedObjects,
@@ -104,7 +106,7 @@ const DuplicationControls = ({
const duplicateSelection = () => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = asset.clone();
const clone = SkeletonUtils.clone(asset);
clone.position.copy(asset.position);
return clone;
});

View File

@@ -17,6 +17,8 @@ import { useParams } from "react-router-dom";
import { getUserData } from "../../../../functions/getUserData";
import { useSceneContext } from "../../sceneContext";
import { useVersionContext } from "../../../builder/version/versionContext";
import { useProductContext } from "../../../simulation/products/productContext";
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
const SelectionControls: React.FC = () => {
const { camera, controls, gl, scene, raycaster, pointer } = useThree();
@@ -37,6 +39,8 @@ const SelectionControls: React.FC = () => {
const { toolMode } = useToolMode();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { projectId } = useParams();
const isDragging = useRef(false);
@@ -48,6 +52,21 @@ const SelectionControls: React.FC = () => {
const isShiftSelecting = useRef(false);
const { userId, organization } = getUserData();
const updateBackend = (
productName: string,
productUuid: string,
projectId: string,
eventData: EventsSchema
) => {
upsertProductOrEventApi({
productName: productName,
productUuid: productUuid,
projectId: projectId,
eventDatas: eventData,
versionId: selectedVersion?.versionId || '',
});
};
useEffect(() => {
if (!camera || !scene || toggleView) return;
@@ -284,7 +303,16 @@ const SelectionControls: React.FC = () => {
const response = socket.emit("v1:model-asset:delete", data);
eventStore.getState().removeEvent(selectedMesh.uuid);
productStore.getState().deleteEvent(selectedMesh.uuid);
const updatedEvents = productStore.getState().deleteEvent(selectedMesh.uuid);
updatedEvents.forEach((event) => {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
})
if (response) {

View File

@@ -0,0 +1,36 @@
import { useCallback } from "react";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function useAssemblyHandler() {
const { materialStore, humanStore, productStore } = useSceneContext();
const { getMaterialById } = materialStore();
const { getModelUuidByActionUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { incrementHumanLoad, addCurrentMaterial } = humanStore();
const assemblyLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleAssembly = useCallback((action: HumanAction, materialId?: string) => {
if (!action || action.actionType !== 'assembly' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return;
incrementHumanLoad(modelUuid, 1);
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
assemblyLogStatus(material.materialName, `performing assembly action`);
}, [getMaterialById]);
return {
handleAssembly,
};
}

View File

@@ -1,13 +1,19 @@
import { useEffect, useCallback } from 'react';
import { useWorkerHandler } from './actionHandler/useWorkerHandler';
import { useAssemblyHandler } from './actionHandler/useAssemblyHandler';
export function useHumanActions() {
const { handleWorker } = useWorkerHandler();
const { handleAssembly } = useAssemblyHandler();
const handleWorkerAction = useCallback((action: HumanAction, materialId: string) => {
handleWorker(action, materialId);
}, [handleWorker]);
const handleAssemblyAction = useCallback((action: HumanAction, materialId: string) => {
handleAssembly(action, materialId);
}, [handleAssembly]);
const handleHumanAction = useCallback((action: HumanAction, materialId: string) => {
if (!action) return;
@@ -15,10 +21,13 @@ export function useHumanActions() {
case 'worker':
handleWorkerAction(action, materialId);
break;
case 'assembly':
handleAssemblyAction(action, materialId);
break;
default:
console.warn(`Unknown Human action type: ${action.actionType}`);
}
}, [handleWorkerAction]);
}, [handleWorkerAction, handleAssemblyAction]);
const cleanup = useCallback(() => {
}, []);

View File

@@ -39,7 +39,7 @@ export function useActionHandler() {
case 'store': case 'retrieve':
handleStorageAction(action as StorageAction, materialId as string);
break;
case 'worker':
case 'worker': case 'assembly':
handleHumanAction(action as HumanAction, materialId as string);
break;
default:

View File

@@ -80,7 +80,7 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start
const distances = [];
let accumulatedDistance = 0;
let index = 0;
const rotationSpeed = 1;
const rotationSpeed = 1.5;
for (let i = 0; i < currentPath.length - 1; i++) {
const start = new THREE.Vector3(...currentPath[i]);
@@ -108,7 +108,14 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start
if (angle < 0.01) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.slerp(targetQuaternion, delta * rotationSpeed * speed * human.speed * 5);
const step = rotationSpeed * delta * speed * human.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
const isAligned = angle < 0.01;
@@ -145,7 +152,14 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start
object.quaternion.copy(targetQuaternion);
setRestingRotation(false);
} else {
object.quaternion.slerp(targetQuaternion, delta * rotationSpeed * speed * human.speed * 4);
const step = rotationSpeed * delta * speed * human.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
if (human.currentMaterials.length > 0) {

View File

@@ -6,6 +6,7 @@ import { MaterialModel } from '../../../materials/instances/material/materialMod
const MaterialAnimator = ({ human }: { human: HumanStatus }) => {
const meshRef = useRef<any>(null!);
const [hasLoad, setHasLoad] = useState(false);
const [isAttached, setIsAttached] = useState(false);
const { scene } = useThree();
useEffect(() => {
@@ -13,32 +14,45 @@ const MaterialAnimator = ({ human }: { human: HumanStatus }) => {
}, [human.currentLoad]);
useEffect(() => {
if (!hasLoad || !meshRef.current) return;
if (!hasLoad || !meshRef.current) {
setIsAttached(false);
return;
}
const humanModel = scene.getObjectByProperty("uuid", human.modelUuid) as THREE.Object3D;
if (!humanModel) return;
meshRef.current.visible = false;
const bone = humanModel.getObjectByName('PlaceObjectRefBone') as THREE.Bone;
if (bone) {
if (meshRef.current.parent) {
meshRef.current.parent.remove(meshRef.current);
}
bone.add(meshRef.current);
meshRef.current.position.set(0, 0, 0);
meshRef.current.rotation.set(0, 0, 0);
meshRef.current.scale.set(1, 1, 1);
meshRef.current.visible = true;
setIsAttached(true);
}
}, [hasLoad, human.modelUuid]);
}, [hasLoad, human.modelUuid, scene]);
return (
<>
{hasLoad && human.currentMaterials.length > 0 && (
{hasLoad && human.point.action.actionType === 'worker' && human.currentMaterials.length > 0 && (
<MaterialModel
matRef={meshRef}
materialId={human.currentMaterials[0].materialId || ''}
materialType={human.currentMaterials[0].materialType || 'Default material'}
visible={isAttached}
/>
)}
</>
);
};
export default MaterialAnimator;
export default MaterialAnimator;

View File

@@ -16,7 +16,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
const { isPlaying } = usePlayButtonStore();
const { scene } = useThree();
const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
const { removeMaterial, setEndTime } = materialStore();
const { removeMaterial, setEndTime, setMaterial } = materialStore();
const { getStorageUnitById } = storageUnitStore();
const { getArmBotById } = armBotStore();
const { getConveyorById } = conveyorStore();
@@ -41,6 +41,13 @@ function HumanInstance({ human }: { human: HumanStatus }) {
const previousTimeRef = useRef<number | null>(null);
const animationFrameIdRef = useRef<number | null>(null);
const humanAsset = getAssetById(human.modelUuid);
const processStartTimeRef = useRef<number | null>(null);
const processTimeRef = useRef<number>(0);
const processAnimationIdRef = useRef<number | null>(null);
const accumulatedPausedTimeRef = useRef<number>(0);
const lastPauseTimeRef = useRef<number | null>(null);
const hasLoggedHalfway = useRef(false);
const hasLoggedCompleted = useRef(false);
useEffect(() => {
isPausedRef.current = isPaused;
@@ -94,6 +101,16 @@ function HumanInstance({ human }: { human: HumanStatus }) {
cancelAnimationFrame(animationFrameIdRef.current)
animationFrameIdRef.current = null
}
if (processAnimationIdRef.current) {
cancelAnimationFrame(processAnimationIdRef.current);
processAnimationIdRef.current = null;
}
processStartTimeRef.current = null;
processTimeRef.current = 0;
accumulatedPausedTimeRef.current = 0;
lastPauseTimeRef.current = null;
hasLoggedHalfway.current = false;
hasLoggedCompleted.current = false;
const object = scene.getObjectByProperty('uuid', human.modelUuid);
if (object && human) {
object.position.set(human.position[0], human.position[1], human.position[2]);
@@ -103,7 +120,103 @@ function HumanInstance({ human }: { human: HumanStatus }) {
useEffect(() => {
if (isPlaying) {
if (!human.point.action.pickUpPoint || !human.point.action.dropPoint) return;
if (!human.point.action.assemblyPoint || human.point.action.actionType === 'worker') return;
if (!human.isActive && human.state === 'idle' && currentPhase === 'init') {
setHumanState(human.modelUuid, 'idle');
setCurrentPhase('waiting');
setHumanPicking(human.modelUuid, false);
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Human is waiting for material in assembly');
} else if (!human.isActive && human.state === 'idle' && currentPhase === 'waiting') {
if (human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current !== 'working_standing') {
setCurrentAnimation(human.modelUuid, 'working_standing', true, true, false);
setHumanState(human.modelUuid, 'running');
setCurrentPhase('assembling');
setHumanPicking(human.modelUuid, true);
setHumanActive(human.modelUuid, true);
processStartTimeRef.current = performance.now();
processTimeRef.current = human.point.action.processTime || 0;
accumulatedPausedTimeRef.current = 0;
lastPauseTimeRef.current = null;
hasLoggedHalfway.current = false;
hasLoggedCompleted.current = false;
if (!processAnimationIdRef.current) {
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
}
}
} else if (human.isActive && human.state === 'running' && human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current === 'working_standing' && humanAsset.animationState?.isCompleted) {
if (human.point.action.assemblyPoint && currentPhase === 'assembling') {
setHumanState(human.modelUuid, 'idle');
setCurrentPhase('waiting');
setHumanPicking(human.modelUuid, false);
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Human is waiting for material in assembly');
decrementHumanLoad(human.modelUuid, 1);
const material = removeLastMaterial(human.modelUuid);
if (material) {
triggerPointActions(human.point.action, material.materialId);
}
}
}
} else {
reset()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]);
const trackAssemblyProcess = useCallback(() => {
const now = performance.now();
if (!processStartTimeRef.current || !human.point.action.processTime) {
return;
}
if (isPausedRef.current) {
if (!lastPauseTimeRef.current) {
lastPauseTimeRef.current = now;
}
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
return;
} else if (lastPauseTimeRef.current) {
accumulatedPausedTimeRef.current += now - lastPauseTimeRef.current;
lastPauseTimeRef.current = null;
}
const elapsed = (now - processStartTimeRef.current - accumulatedPausedTimeRef.current) * isSpeedRef.current;
const totalProcessTimeMs = human.point.action.processTime * 1000;
if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) {
hasLoggedHalfway.current = true;
if (human.currentMaterials.length > 0) {
setMaterial(human.currentMaterials[0].materialId, human.point.action.swapMaterial || 'Default Material');
}
humanStatus(human.modelUuid, `🟡 Human ${human.modelUuid} reached halfway in assembly.`);
}
if (elapsed >= totalProcessTimeMs && !hasLoggedCompleted.current) {
hasLoggedCompleted.current = true;
setCurrentAnimation(human.modelUuid, 'working_standing', true, true, true);
if (processAnimationIdRef.current) {
cancelAnimationFrame(processAnimationIdRef.current);
processAnimationIdRef.current = null;
}
humanStatus(human.modelUuid, `✅ Human ${human.modelUuid} completed assembly process.`);
return;
}
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
}, [human.modelUuid, human.point.action.processTime, human.currentMaterials]);
useEffect(() => {
if (isPlaying) {
if (!human.point.action.pickUpPoint || !human.point.action.dropPoint || human.point.action.actionType === 'assembly') return;
if (!human.isActive && human.state === 'idle' && currentPhase === 'init') {
const toPickupPath = computePath(
@@ -145,7 +258,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true);
humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point');
}
} else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0) {
} else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0 && humanAsset?.animationState?.current !== 'pickup') {
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
}
} else if (!human.isActive && human.state === 'idle' && currentPhase === 'dropping' && human.currentLoad === 0) {
@@ -253,6 +366,9 @@ function HumanInstance({ human }: { human: HumanStatus }) {
function startUnloadingProcess() {
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
if (human.point.action.triggers.length > 0) {
const trigger = getTriggerByUuid(selectedProduct.productUuid, human.point.action.triggers[0]?.triggerUuid);

View File

@@ -4,21 +4,24 @@ import { useFrame, useThree } from '@react-three/fiber';
import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore';
import { useProductContext } from '../../../products/productContext';
import { useSceneContext } from '../../../../scene/sceneContext';
import { Group, Plane, Vector3 } from 'three';
import { Group, Plane, Vector2, Vector3 } from 'three';
import { useVersionContext } from '../../../../builder/version/versionContext';
import { useParams } from 'react-router-dom';
import startPoint from "../../../../../assets/gltf-glb/ui/arrow_green.glb";
import startEnd from "../../../../../assets/gltf-glb/ui/arrow_red.glb";
import startPoint from "../../../../../assets/gltf-glb/ui/human-ui-green.glb";
import startEnd from "../../../../../assets/gltf-glb/ui/human-ui-orange.glb";
import assembly from "../../../../../assets/gltf-glb/ui/human-ui-assembly.glb";
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
function HumanUi() {
const { scene: startScene } = useGLTF(startPoint) as any;
const { scene: endScene } = useGLTF(startEnd) as any;
const { scene: assemblyScene } = useGLTF(assembly) as any;
const startMarker = useRef<Group>(null);
const endMarker = useRef<Group>(null);
const assemblyMarker = useRef<Group>(null);
const outerGroup = useRef<Group>(null);
const prevMousePos = useRef({ x: 0, y: 0 });
const { controls, raycaster } = useThree();
const { controls, raycaster, camera } = useThree();
const { selectedEventSphere } = useSelectedEventSphere();
const { selectedProductStore } = useProductContext();
const { humanStore, productStore } = useSceneContext();
@@ -27,11 +30,13 @@ function HumanUi() {
const { updateEvent } = productStore();
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]);
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]);
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]);
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, Math.PI, 0]);
const [assemblyRotation, setAssemblyRotation] = useState<[number, number, number]>([0, 0, 0]);
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
const { isDragging, setIsDragging } = useIsDragging();
const { isRotating, setIsRotating } = useIsRotating();
const plane = useRef(new Plane(new Vector3(0, 1, 0), 0));
const dragOffset = useRef(new Vector3());
const [selectedHumanData, setSelectedHumanData] = useState<{
position: [number, number, number];
@@ -43,6 +48,10 @@ function HumanUi() {
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const selectedHuman = selectedEventSphere ? getHumanById(selectedEventSphere.userData.modelUuid) : null;
const actionType = selectedHuman?.point?.action?.actionType || null;
const isAssembly = actionType === 'assembly';
const updateBackend = (
productName: string,
productUuid: string,
@@ -69,21 +78,42 @@ function HumanUi() {
rotation: selectedHuman.rotation,
});
if (outerGroup.current) {
outerGroup.current.position.set(
selectedHuman.position[0],
selectedHuman.position[1],
selectedHuman.position[2]
);
}
const action = selectedHuman.point.action;
if (action.pickUpPoint?.position && outerGroup.current) {
const worldPos = new Vector3(...action.pickUpPoint.position);
const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
setStartPosition([localPosition.x, 0.5, localPosition.z]);
setStartPosition([localPosition.x, 1, localPosition.z]);
setStartRotation(action.pickUpPoint.rotation || [0, 0, 0]);
} else {
setStartPosition([0, 1, 0]);
setStartRotation([0, Math.PI, 0]);
}
if (action.dropPoint?.position && outerGroup.current) {
const worldPos = new Vector3(...action.dropPoint.position);
const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
setEndPosition([localPosition.x, 0.5, localPosition.z]);
setEndRotation(action.dropPoint.rotation || [0, 0, 0]);
setEndPosition([localPosition.x, 1, localPosition.z]);
setEndRotation(action.dropPoint.rotation || [0, Math.PI, 0]);
} else {
setEndPosition([0, 1, 0]);
setEndRotation([0, 0, 0]);
}
if (action.assemblyPoint?.rotation) {
setAssemblyRotation(action.assemblyPoint.rotation);
} else {
setAssemblyRotation([0, 0, 0]);
}
}, [selectedEventSphere, outerGroup.current, selectedAction, humans]);
const handlePointerDown = (
@@ -91,84 +121,117 @@ function HumanUi() {
state: "start" | "end",
rotation: "start" | "end"
) => {
if (e.object.name === "handle") {
if (isAssembly) return;
e.stopPropagation();
const intersection = new Vector3();
const pointer = new Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.ray.intersectPlane(plane.current, intersection);
if (e.object.parent.name === "handle") {
const normalizedX = (e.clientX / window.innerWidth) * 2 - 1;
const normalizedY = -(e.clientY / window.innerHeight) * 2 + 1;
prevMousePos.current = { x: normalizedX, y: normalizedY };
setIsRotating(rotation);
if (controls) (controls as any).enabled = false;
setIsDragging(null);
} else {
setIsDragging(state);
setIsRotating(null);
if (controls) (controls as any).enabled = false;
}
if (intersects) {
let localPoint: Vector3 | null = null;
if (outerGroup.current) {
localPoint = outerGroup.current.worldToLocal(intersection.clone());
}
const marker = state === "start" ? startMarker.current : endMarker.current;
if (marker && localPoint) {
const markerPos = new Vector3().copy(marker.position);
dragOffset.current.copy(markerPos.sub(localPoint));
}
}
if (controls) (controls as any).enabled = false;
};
const handlePointerUp = () => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
if (selectedEventSphere?.userData.modelUuid && selectedAction.actionId) {
const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
if (!selectedEventSphere?.userData.modelUuid || !selectedAction?.actionId) return;
if (selectedHuman && outerGroup.current && startMarker.current && endMarker.current) {
const worldPosStart = new Vector3(...startPosition);
const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone());
const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
if (!selectedHuman || !outerGroup.current) return;
const worldPosEnd = new Vector3(...endPosition);
const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone());
const isAssembly = selectedHuman.point?.action?.actionType === 'assembly';
const updatedAction = {
...selectedHuman.point.action,
pickUpPoint: {
position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number],
rotation: startRotation
},
dropPoint: {
position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number],
rotation: endRotation
}
};
let updatedAction;
const event = updateEvent(
selectedProduct.productUuid,
selectedEventSphere.userData.modelUuid,
{
...selectedHuman,
point: {
...selectedHuman.point,
action: updatedAction
}
}
);
if (isAssembly) {
updatedAction = {
...selectedHuman.point.action,
assemblyPoint: {
rotation: assemblyRotation
},
};
} else {
if (!startMarker.current || !endMarker.current) return;
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
const worldPosStart = new Vector3(...startPosition);
const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone());
const worldPosEnd = new Vector3(...endPosition);
const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone());
updatedAction = {
...selectedHuman.point.action,
pickUpPoint: {
position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number],
rotation: startRotation,
},
dropPoint: {
position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number],
rotation: endRotation,
},
};
}
const event = updateEvent(
selectedProduct.productUuid,
selectedEventSphere.userData.modelUuid,
{
...selectedHuman,
point: {
...selectedHuman.point,
action: updatedAction,
},
}
);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
};
useFrame(() => {
if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return;
if (isAssembly || !isDragging || !plane.current || !raycaster || !outerGroup.current) return;
const intersectPoint = new Vector3();
const intersects = raycaster.ray.intersectPlane(
plane.current,
intersectPoint
);
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
if (!intersects) return;
const localPoint = outerGroup?.current.worldToLocal(intersectPoint.clone());
const localPoint = outerGroup.current.worldToLocal(intersectPoint.clone()).add(dragOffset.current);
if (isDragging === "start") {
setStartPosition([localPoint.x, 0.5, localPoint.z]);
setStartPosition([localPoint.x, 1, localPoint.z]);
} else if (isDragging === "end") {
setEndPosition([localPoint.x, 0.5, localPoint.z]);
setEndPosition([localPoint.x, 1, localPoint.z]);
}
});
@@ -177,11 +240,17 @@ function HumanUi() {
const currentPointerX = state.pointer.x;
const deltaX = currentPointerX - prevMousePos.current.x;
prevMousePos.current.x = currentPointerX;
const marker =isRotating === "start" ? startMarker.current : endMarker.current;
const marker = isRotating === "start" ? isAssembly ? assemblyMarker.current : startMarker.current : isAssembly ? assemblyMarker.current : endMarker.current;
if (marker) {
const rotationSpeed = 10;
marker.rotation.y += deltaX * rotationSpeed;
if (isRotating === "start") {
if (isAssembly && isRotating === "start") {
setAssemblyRotation([
marker.rotation.x,
marker.rotation.y,
marker.rotation.z,
]);
} else if (isRotating === "start") {
setStartRotation([
marker.rotation.x,
marker.rotation.y,
@@ -212,7 +281,7 @@ function HumanUi() {
return () => {
window.removeEventListener("pointerup", handleGlobalPointerUp);
};
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation]);
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, assemblyRotation]);
return (
<>
@@ -220,44 +289,71 @@ function HumanUi() {
<group
position={selectedHumanData.position}
ref={outerGroup}
rotation={[0, Math.PI, 0]}
>
<primitive
name="startMarker"
object={startScene}
ref={startMarker}
position={startPosition}
rotation={startRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
}}
onPointerMissed={() => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
{isAssembly ? (
<primitive
ref={assemblyMarker}
object={assemblyScene}
position={[0, 1, 0]}
rotation={assemblyRotation}
onPointerDown={(e: any) => {
if (e.object.parent.name === "handle") {
e.stopPropagation();
const normalizedX = (e.clientX / window.innerWidth) * 2 - 1;
prevMousePos.current.x = normalizedX;
setIsRotating("start");
setIsDragging(null);
if (controls) (controls as any).enabled = false;
}
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
) : (
<>
<primitive
name="startMarker"
object={startScene}
ref={startMarker}
position={startPosition}
rotation={startRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
<primitive
name="endMarker"
object={endScene}
ref={endMarker}
position={endPosition}
rotation={endRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
}}
onPointerMissed={() => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
<primitive
name="endMarker"
object={endScene}
ref={endMarker}
position={endPosition}
rotation={endRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
</>
)}
</group>
)}
</>
)
);
}
export default HumanUi

View File

@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import { Line, Text } from '@react-three/drei';
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
@@ -10,7 +10,6 @@ type PointWithDegree = {
};
function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone, armBot, path }: any) {
const { scene } = useThree();
const progressRef = useRef(0);
const curveRef = useRef<THREE.Vector3[] | null>(null);
const totalDistanceRef = useRef(0);
@@ -202,11 +201,7 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone
}, [circlePoints, currentPath]);
// Frame update for animation
useFrame((state, delta) => {
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
if (targetMesh) {
targetMesh.visible = (!isPlaying)
}
useFrame((_, delta) => {
if (!ikSolver) return;
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);

View File

@@ -1,10 +1,8 @@
import { useEffect, useRef, useState } from 'react'
import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import IKInstance from '../ikInstance/ikInstance';
import RoboticArmAnimator from '../animator/roboticArmAnimator';
import MaterialAnimator from '../animator/materialAnimator';
import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_1.glb";
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
import { useSceneContext } from '../../../../scene/sceneContext';
@@ -14,17 +12,16 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
const [currentPhase, setCurrentPhase] = useState<(string)>("init");
const [path, setPath] = useState<[number, number, number][]>([]);
const [ikSolver, setIkSolver] = useState<any>(null);
const { scene } = useThree();
const restPosition = new THREE.Vector3(0, 1.75, -1.6);
const targetBone = "Target";
const groupRef = useRef<any>(null);
const pauseTimeRef = useRef<number | null>(null);
const isPausedRef = useRef<boolean>(false);
const isSpeedRef = useRef<any>(null);
let startTime: number;
const { selectedProductStore } = useProductContext();
const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore } = useSceneContext();
const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, assetStore } = useSceneContext();
const { resetAsset } = assetStore();
const { setArmBotActive, setArmBotState, removeCurrentAction, incrementActiveTime, incrementIdleTime } = armBotStore();
const { decrementVehicleLoad, removeLastMaterial } = vehicleStore();
const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = storageUnitStore();
@@ -190,7 +187,8 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
setCurrentPhase("init");
setPath([])
setIkSolver(null);
removeCurrentAction(armBot.modelUuid)
removeCurrentAction(armBot.modelUuid);
resetAsset(armBot.modelUuid);
isPausedRef.current = false
pauseTimeRef.current = null
startTime = 0
@@ -266,14 +264,9 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
}
};
}, [armBot.isActive, armBot.state, currentPhase])
}, [armBot, currentPhase, isPlaying])
useEffect(() => {
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
if (targetMesh) {
targetMesh.visible = (!isPlaying)
}
const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
if (!isReset && isPlaying) {
//Moving armBot from initial point to rest position.
@@ -390,7 +383,7 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
<>
{!isReset && isPlaying && (
<>
<IKInstance modelUrl={armModel} setIkSolver={setIkSolver} armBot={armBot} groupRef={groupRef} />
<IKInstance setIkSolver={setIkSolver} armBot={armBot} />
<RoboticArmAnimator
HandleCallback={HandleCallback}
restPosition={restPosition}

View File

@@ -1,83 +1,84 @@
import { useEffect, useMemo } from 'react'
import { useEffect } from 'react'
import * as THREE from "three";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
import { useLoader } from "@react-three/fiber";
import { useThree } from "@react-three/fiber";
import { CCDIKSolver, CCDIKHelper } from "three/examples/jsm/animation/CCDIKSolver";
import { usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
type IKInstanceProps = {
modelUrl: string;
setIkSolver: any
armBot: ArmBotStatus;
groupRef: any;
};
function IKInstance({ modelUrl, setIkSolver, armBot, groupRef }: IKInstanceProps) {
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
const draco = new DRACOLoader();
draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/");
loader.setDRACOLoader(draco);
});
const cloned = useMemo(() => clone(gltf?.scene), [gltf]);
function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
const { scene } = useThree();
const { isPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
const targetBoneName = "Target";
const skinnedMeshName = "link_0";
useEffect(() => {
if (!gltf) return;
const OOI: any = {};
cloned.traverse((n: any) => {
if (n.name === targetBoneName) OOI.Target_Bone = n;
if (n.name === skinnedMeshName) OOI.Skinned_Mesh = n;
});
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
const iks = [
{
target: 7,
effector: 6,
links: [
{
index: 5,
enabled: true,
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
rotationMax: new THREE.Vector3(Math.PI / 2, 0, 0),
},
{
index: 4,
enabled: true,
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
rotationMax: new THREE.Vector3(0, 0, 0),
},
{
index: 3,
enabled: true,
rotationMin: new THREE.Vector3(0, 0, 0),
rotationMax: new THREE.Vector3(2, 0, 0),
},
{ index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) },
{ index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) },
],
},
];
let retryId: NodeJS.Timeout | null = null;
const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks);
setIkSolver(solver);
const trySetup = () => {
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
// const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
if (!targetMesh) {
retryId = setTimeout(trySetup, 100);
return;
}
// scene.add(helper);
const OOI: any = {};
targetMesh.traverse((n: any) => {
if (n.name === targetBoneName) OOI.Target_Bone = n;
if (n.name === skinnedMeshName) OOI.Skinned_Mesh = n;
});
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
const iks = [
{
target: 7,
effector: 6,
links: [
{
index: 5,
enabled: true,
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
rotationMax: new THREE.Vector3(Math.PI / 2, 0, 0),
},
{
index: 4,
enabled: true,
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
rotationMax: new THREE.Vector3(0, 0, 0),
},
{
index: 3,
enabled: true,
rotationMin: new THREE.Vector3(0, 0, 0),
rotationMax: new THREE.Vector3(2, 0, 0),
},
{ index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) },
{ index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) },
],
},
];
}, [cloned, gltf]);
const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks);
setIkSolver(solver);
// const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
// scene.add(helper);
};
trySetup();
return () => {
if (retryId) clearTimeout(retryId);
};
}, [isPlaying, isReset]);
return (
<group ref={groupRef} position={armBot.position} rotation={armBot.rotation}>
<primitive
uuid={`${armBot.modelUuid}_IK`}
object={cloned}
scale={[1, 1, 1]}
name={armBot.modelName}
/>
</group>
<>
</>
)
}

View File

@@ -301,7 +301,6 @@ const VehicleUI = () => {
return selectedVehicleData ? (
<group
position={selectedVehicleData.position}
rotation={selectedVehicleData.rotation}
ref={outerGroup}
>
<group

View File

@@ -299,6 +299,10 @@ export function useTriggerHandler() {
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid);
if (human && human.modelUuid === "cc62adae-7000-447b-b845-6d4910de503a") {
console.log(human);
}
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
@@ -330,7 +334,9 @@ export function useTriggerHandler() {
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -341,7 +347,9 @@ export function useTriggerHandler() {
addVehicleToMonitor(vehicle.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -354,7 +362,9 @@ export function useTriggerHandler() {
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -365,7 +375,9 @@ export function useTriggerHandler() {
addVehicleToMonitor(vehicle.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -382,7 +394,9 @@ export function useTriggerHandler() {
if (conveyor) {
if (!conveyor.isPaused) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -393,7 +407,9 @@ export function useTriggerHandler() {
addConveyorToMonitor(conveyor.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -406,7 +422,9 @@ export function useTriggerHandler() {
if (conveyor) {
if (!conveyor.isPaused) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -417,7 +435,9 @@ export function useTriggerHandler() {
addConveyorToMonitor(conveyor.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -434,7 +454,9 @@ export function useTriggerHandler() {
if (machine) {
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) {
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -443,7 +465,9 @@ export function useTriggerHandler() {
addMachineToMonitor(machine.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -456,7 +480,9 @@ export function useTriggerHandler() {
if (machine) {
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) {
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -465,7 +491,9 @@ export function useTriggerHandler() {
addMachineToMonitor(machine.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -480,7 +508,9 @@ export function useTriggerHandler() {
// Handle current action from arm bot
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -489,7 +519,9 @@ export function useTriggerHandler() {
setIsPaused(materialId, true);
addHumanToMonitor(human.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId)
}
);
@@ -501,7 +533,9 @@ export function useTriggerHandler() {
// Handle current action from arm bot
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -510,7 +544,9 @@ export function useTriggerHandler() {
setIsPaused(materialId, true);
addHumanToMonitor(human.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId)
}
);
@@ -1280,7 +1316,7 @@ export function useTriggerHandler() {
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
const material = getMaterialById(materialId);
if (material) {
if (material && action.actionType === 'worker') {
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
@@ -1410,6 +1446,28 @@ export function useTriggerHandler() {
handleAction(action, material.materialId);
}
} else if (material && action.actionType === 'assembly') {
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
})
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid,
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid,
})
setIsPaused(material.materialId, false);
setIsVisible(material.materialId, true);
}
}

View File

@@ -6,40 +6,53 @@ import { MaterialModel } from '../../../materials/instances/material/materialMod
const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => {
const meshRef = useRef<any>(null!);
const [hasLoad, setHasLoad] = useState(false);
const [isAttached, setIsAttached] = useState(false);
const { scene } = useThree();
const offset = new THREE.Vector3(0, 0.85, 0);
useEffect(() => {
setHasLoad(agvDetail.currentLoad > 0);
const loadState = agvDetail.currentLoad > 0;
setHasLoad(loadState);
if (!loadState) {
setIsAttached(false);
if (meshRef.current?.parent) {
meshRef.current.parent.remove(meshRef.current);
}
}
}, [agvDetail.currentLoad]);
useFrame(() => {
if (!hasLoad || !meshRef.current) return;
if (!hasLoad || !meshRef.current || isAttached) return;
const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D;
if (agvModel) {
const worldPosition = offset.clone().applyMatrix4(agvModel.matrixWorld);
meshRef.current.position.copy(worldPosition);
meshRef.current.rotation.copy(agvModel.rotation);
if (agvModel && !isAttached) {
if (meshRef.current.parent) {
meshRef.current.parent.remove(meshRef.current);
}
agvModel.add(meshRef.current);
meshRef.current.position.copy(offset);
meshRef.current.rotation.set(0, 0, 0);
meshRef.current.scale.set(1, 1, 1);
setIsAttached(true);
}
});
return (
<>
{hasLoad && (
<>
{agvDetail.currentMaterials.length > 0 &&
<MaterialModel
matRef={meshRef}
materialId={agvDetail.currentMaterials[0].materialId || ''}
materialType={agvDetail.currentMaterials[0].materialType || 'Default material'}
/>
}
</>
{hasLoad && agvDetail.currentMaterials.length > 0 && (
<MaterialModel
matRef={meshRef}
materialId={agvDetail.currentMaterials[0].materialId || ''}
materialType={agvDetail.currentMaterials[0].materialType || 'Default material'}
visible={isAttached}
/>
)}
</>
);
};
export default MaterialAnimator;
export default MaterialAnimator;

View File

@@ -80,7 +80,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const distances = [];
let accumulatedDistance = 0;
let index = 0;
const rotationSpeed = 1;
const rotationSpeed = 0.75;
for (let i = 0; i < currentPath.length - 1; i++) {
const start = new THREE.Vector3(...currentPath[i]);
@@ -100,17 +100,25 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const end = new THREE.Vector3(...currentPath[index + 1]);
const segmentDistance = distances[index];
const currentDirection = new THREE.Vector3().subVectors(end, start).normalize();
const targetAngle = Math.atan2(currentDirection.x, currentDirection.z);
const currentAngle = object.rotation.y;
const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)));
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
targetQuaternion.multiply(y180);
let angleDifference = targetAngle - currentAngle;
if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI;
if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < 0.01) {
object.quaternion.copy(targetQuaternion);
} else {
const step = rotationSpeed * delta * speed * agvDetail.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
const maxRotationStep = (rotationSpeed * speed * agvDetail.speed) * delta;
object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep);
const isAligned = Math.abs(angleDifference) < 0.01;
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
const isAligned = angle < 0.01;
if (isAligned) {
progressRef.current += delta * (speed * agvDetail.speed);
@@ -122,17 +130,25 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
if (progressRef.current >= totalDistance) {
if (restRotation && objectRotation) {
const targetEuler = new THREE.Euler(
objectRotation.x,
objectRotation.y - agvDetail.point.action.steeringAngle,
objectRotation.z
);
const targetQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
object.quaternion.slerp(targetQuaternion, delta * (rotationSpeed * speed * agvDetail.speed));
if (object.quaternion.angleTo(targetQuaternion) < 0.01) {
const targetEuler = new THREE.Euler(0, objectRotation.y - agvDetail.point.action.steeringAngle, 0);
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
const targetQuaternion = baseQuaternion.multiply(y180);
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < 0.01) {
object.quaternion.copy(targetQuaternion);
object.rotation.copy(targetEuler);
setRestingRotation(false);
} else {
const step = rotationSpeed * delta * speed * agvDetail.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
return;
}