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 { useProductStore } from '../../../../../store/simulation/useProductStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; function RoboticArmInstance({ armBot }: { readonly 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); const isSpeedRef = useRef(null); let startTime: number; const { selectedProductStore } = useProductContext(); const { materialStore, armBotStore, vehicleStore, storageUnitStore } = useSceneContext(); const { setArmBotActive, setArmBotState, removeCurrentAction, incrementActiveTime, incrementIdleTime } = armBotStore(); const { decrementVehicleLoad, removeLastMaterial } = vehicleStore(); const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = storageUnitStore(); const { getMaterialById, setIsVisible, setIsPaused } = materialStore(); const { selectedProduct } = selectedProductStore(); const { getActionByUuid, getEventByActionUuid, getEventByModelUuid } = useProductStore(); const { triggerPointActions } = useTriggerHandler(); const { isPlaying } = usePlayButtonStore(); const { isReset } = useResetButtonStore(); const { isPaused } = usePauseButtonStore(); const { speed } = useAnimationPlaySpeed(); const activeSecondsElapsed = useRef(0); const idleSecondsElapsed = useRef(0); const animationFrameIdRef = useRef(null); const previousTimeRef = useRef(null); const lastRemoved = useRef<{ type: string, materialId: string } | null>(null); function firstFrame() { startTime = performance.now(); step(); } const action = getActionByUuid(selectedProduct.productUuid, 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.productUuid, 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) } lastRemoved.current = { type: previousModel.type, materialId: armBot.currentAction.materialId }; } else { setIsVisible(armBot.currentAction.materialId, false); } } else { setIsVisible(armBot.currentAction.materialId, false); } } } const handleDropTrigger = () => { if (armBot.currentAction) { const action = getActionByUuid(selectedProduct.productUuid, armBot.currentAction.actionUuid); const model = getEventByModelUuid(selectedProduct.productUuid, action?.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); if (action && 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) } if (lastRemoved.current) { if (lastRemoved.current.type === 'transfer') { setIsPaused(lastRemoved.current.materialId, true) } else { setIsPaused(lastRemoved.current.materialId, false) } } } } 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(() => { isSpeedRef.current = speed; }, [speed]); 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 activeSecondsElapsed.current = 0; idleSecondsElapsed.current = 0; previousTimeRef.current = null; if (animationFrameIdRef.current !== null) { cancelAnimationFrame(animationFrameIdRef.current); animationFrameIdRef.current = null; } 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]) function animate(currentTime: number) { if (previousTimeRef.current === null) { previousTimeRef.current = currentTime; } const deltaTime = (currentTime - previousTimeRef.current) / 1000; previousTimeRef.current = currentTime; if (armBot.isActive) { if (!isPausedRef.current) { activeSecondsElapsed.current += deltaTime * isSpeedRef.current; // console.log(' activeSecondsElapsed.current: ', activeSecondsElapsed.current); } } else { if (!isPausedRef.current) { idleSecondsElapsed.current += deltaTime * isSpeedRef.current; // console.log('idleSecondsElapsed.current: ', idleSecondsElapsed.current); } } animationFrameIdRef.current = requestAnimationFrame(animate); } useEffect(() => { if (!isPlaying) return if (!armBot.isActive && armBot.state === "idle" && (currentPhase === "rest" || currentPhase === "init")) { cancelAnimationFrame(animationFrameIdRef.current!); animationFrameIdRef.current = null; const roundedActiveTime = Math.round(activeSecondsElapsed.current); // Get the final rounded active time // console.log('🚨Final Active Time:',armBot.modelUuid, roundedActiveTime, 'seconds'); incrementActiveTime(armBot.modelUuid, roundedActiveTime); activeSecondsElapsed.current = 0; } else if (armBot.isActive && armBot.state !== "idle" && currentPhase !== "rest" && armBot.currentAction) { cancelAnimationFrame(animationFrameIdRef.current!); animationFrameIdRef.current = null; const roundedIdleTime = Math.round(idleSecondsElapsed.current); // Get the final rounded idle time // console.log('🕒 Final Idle Time:', armBot.modelUuid,roundedIdleTime, 'seconds'); incrementIdleTime(armBot.modelUuid, roundedIdleTime); idleSecondsElapsed.current = 0; } if (animationFrameIdRef.current === null) { animationFrameIdRef.current = requestAnimationFrame(animate); } return () => { if (animationFrameIdRef.current !== null) { cancelAnimationFrame(animationFrameIdRef.current); animationFrameIdRef.current = null; // Reset the animation frame ID } }; }, [armBot.isActive, armBot.state, currentPhase]) 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 activeSecondsElapsed.current = 0; idleSecondsElapsed.current = 0; previousTimeRef.current = null; if (animationFrameIdRef.current !== null) { cancelAnimationFrame(animationFrameIdRef.current); animationFrameIdRef.current = null; } 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;