import React, { 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 { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useMaterialStore } from '../../../../../store/simulation/useMaterialStore'; import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; import { useProductStore } from '../../../../../store/simulation/useProductStore'; import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; import { useStorageUnitStore } from '../../../../../store/simulation/useStorageUnitStore'; import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { const [currentPhase, setCurrentPhase] = useState<(string)>("init"); const [path, setPath] = useState<[number, number, number][]>([]); const [ikSolver, setIkSolver] = useState(null); const { scene } = useThree(); const restPosition = new THREE.Vector3(0, 1.75, -1.6); const targetBone = "Target"; const groupRef = useRef(null); const pauseTimeRef = useRef(null); const isPausedRef = useRef(false); let startTime: number; const { setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); const { decrementVehicleLoad, removeLastMaterial } = useVehicleStore(); const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = useStorageUnitStore(); const { setIsVisible, getMaterialById } = useMaterialStore(); const { selectedProduct } = useSelectedProduct(); const { getActionByUuid, getEventByActionUuid, getEventByModelUuid } = useProductStore(); const { triggerPointActions } = useTriggerHandler(); const { isPlaying } = usePlayButtonStore(); const { isReset } = useResetButtonStore(); const { isPaused } = usePauseButtonStore(); function firstFrame() { startTime = performance.now(); step(); } const action = getActionByUuid(selectedProduct.productId, armBot.currentAction?.actionUuid || ''); const handlePickUpTrigger = () => { if (armBot.currentAction && armBot.currentAction.materialId) { const material = getMaterialById(armBot.currentAction.materialId); if (material && material.previous && material.previous.modelUuid) { const previousModel = getEventByActionUuid(selectedProduct.productId, material.previous.actionUuid); if (previousModel) { if (previousModel.type === 'transfer') { setIsVisible(armBot.currentAction.materialId, false); } else if (previousModel.type === 'machine') { // machine specific logic } else if (previousModel.type === 'vehicle') { decrementVehicleLoad(previousModel.modelUuid, 1); removeLastMaterial(previousModel.modelUuid); } else if (previousModel.type === 'storageUnit') { // storage unit logic removeLastStorageMaterial(previousModel.modelUuid); updateCurrentLoad(previousModel.modelUuid, -1) } } else { setIsVisible(armBot.currentAction.materialId, false); } } else { setIsVisible(armBot.currentAction.materialId, false); } } } const handleDropTrigger = () => { if (armBot.currentAction) { const action = getActionByUuid(selectedProduct.productId, armBot.currentAction.actionUuid); if (action && action.triggers[0].triggeredAsset?.triggeredModel.modelUuid) { const model = getEventByModelUuid(selectedProduct.productId, action?.triggers[0].triggeredAsset?.triggeredModel.modelUuid); if (!model) return; if (model.type === 'transfer') { setIsVisible(armBot.currentAction.materialId || '', true); } else if (model.type === 'machine') { // } else if (model.type === 'vehicle') { // } else if (model.type === 'storageUnit') { // } } if (action && armBot.currentAction.materialId) { triggerPointActions(action, armBot.currentAction.materialId) removeCurrentAction(armBot.modelUuid) } } } function step() { if (isPausedRef.current) { if (!pauseTimeRef.current) { pauseTimeRef.current = performance.now(); } requestAnimationFrame(() => step()); return; } if (pauseTimeRef.current) { const pauseDuration = performance.now() - pauseTimeRef.current; startTime += pauseDuration; pauseTimeRef.current = null; } const elapsedTime = performance.now() - startTime; if (elapsedTime < 1000) { // Wait until 1500ms has passed requestAnimationFrame(step); return; } if (currentPhase === "picking") { setArmBotActive(armBot.modelUuid, true); setArmBotState(armBot.modelUuid, "running"); setCurrentPhase("start-to-end"); startTime = 0 if (!action) return; const startPoint = (action as RoboticArmAction).process.startPoint; const endPoint = (action as RoboticArmAction).process.endPoint; if (startPoint && endPoint) { let curve = createCurveBetweenTwoPoints( new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2])); if (curve) { logStatus(armBot.modelUuid, "picking the object"); setPath(curve.points.map(point => [point.x, point.y, point.z])) handlePickUpTrigger(); } } logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") } else if (currentPhase === "dropping") { setArmBotActive(armBot.modelUuid, true); setArmBotState(armBot.modelUuid, "running"); setCurrentPhase("end-to-rest"); startTime = 0; if (!action) return; const endPoint = (action as RoboticArmAction).process.endPoint; if (endPoint) { let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition); if (curve) { logStatus(armBot.modelUuid, "dropping the object"); setPath(curve.points.map(point => [point.x, point.y, point.z])); handleDropTrigger(); } } logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") } } useEffect(() => { isPausedRef.current = isPaused; }, [isPaused]); useEffect(() => { if (isReset || !isPlaying) { logStatus(armBot.modelUuid, "Simulation Play Reset Successfully") setArmBotActive(armBot.modelUuid, false) setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("init"); setPath([]) setIkSolver(null); removeCurrentAction(armBot.modelUuid) isPausedRef.current = false pauseTimeRef.current = null startTime = 0 const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone ); if (targetBones && isPlaying) { let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition) if (curve) { setPath(curve.points.map(point => [point.x, point.y, point.z])); logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") } } } }, [isReset, 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. if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") { if (targetBones) { setArmBotActive(armBot.modelUuid, true) setArmBotState(armBot.modelUuid, "running") setCurrentPhase("init-to-rest"); let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition) if (curve) { setPath(curve.points.map(point => [point.x, point.y, point.z])); } } logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") } //Waiting for trigger. else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) { logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction") } //Moving to pickup point else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) { if (armBot.currentAction) { setArmBotActive(armBot.modelUuid, true); setArmBotState(armBot.modelUuid, "running"); setCurrentPhase("rest-to-start"); if (!action) return; const startPoint = (action as RoboticArmAction).process.startPoint; if (startPoint) { let curve = createCurveBetweenTwoPoints(targetBones.position, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2])); if (curve) { setPath(curve.points.map(point => [point.x, point.y, point.z])); } } } logStatus(armBot.modelUuid, "Moving armBot from rest point to start position.") } // Moving to Pick to Drop position else if (armBot && !armBot.isActive && armBot.state === "running" && currentPhase === "picking" && armBot.currentAction) { requestAnimationFrame(firstFrame); } //Moving to drop point to restPosition else if (armBot && !armBot.isActive && armBot.state === "running" && currentPhase === "dropping" && armBot.currentAction) { requestAnimationFrame(firstFrame); } } else { logStatus(armBot.modelUuid, "Simulation Play Exited") setArmBotActive(armBot.modelUuid, false) setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("init"); setIkSolver(null); setPath([]) isPausedRef.current = false pauseTimeRef.current = null isPausedRef.current = false startTime = 0 removeCurrentAction(armBot.modelUuid) } }, [currentPhase, armBot, isPlaying, isReset, ikSolver]) function createCurveBetweenTwoPoints(p1: any, p2: any) { const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5); const points = [p1, mid, p2]; return new THREE.CatmullRomCurve3(points); } const HandleCallback = () => { if (armBot.isActive && armBot.state == "running" && currentPhase == "init-to-rest") { logStatus(armBot.modelUuid, "Callback triggered: rest"); setArmBotActive(armBot.modelUuid, false) setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("rest"); setPath([]) } else if (armBot.isActive && armBot.state == "running" && currentPhase == "rest-to-start") { logStatus(armBot.modelUuid, "Callback triggered: pick."); setArmBotActive(armBot.modelUuid, false) setArmBotState(armBot.modelUuid, "running") setCurrentPhase("picking"); setPath([]) } else if (armBot.isActive && armBot.state == "running" && currentPhase == "start-to-end") { logStatus(armBot.modelUuid, "Callback triggered: drop."); setArmBotActive(armBot.modelUuid, false) setArmBotState(armBot.modelUuid, "running") setCurrentPhase("dropping"); setPath([]) } else if (armBot.isActive && armBot.state == "running" && currentPhase == "end-to-rest") { logStatus(armBot.modelUuid, "Callback triggered: rest, cycle completed."); setArmBotActive(armBot.modelUuid, false) setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("rest"); setPath([]) } } const logStatus = (id: string, status: string) => { // console.log('status: ', status); } return ( <> {!isReset && isPlaying && ( <> )} ) } export default RoboticArmInstance;