import { useEffect, useRef, useState } from "react"; import * as THREE from "three"; import IKInstance from "../ikInstance/ikInstance"; import RoboticArmAnimator from "../animator/roboticArmAnimator"; import MaterialAnimator from "../animator/materialAnimator"; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from "../../../../../store/ui/usePlayButtonStore"; import { useTriggerHandler } from "../../../triggers/triggerHandler/useTriggerHandler"; import { useSceneContext } from "../../../../scene/sceneContext"; function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { const [currentPhase, setCurrentPhase] = useState("init"); const [path, setPath] = useState<[number, number, number][]>([]); const [ikSolver, setIkSolver] = useState(null); const restPosition = new THREE.Vector3(0, 1.75, -1.6); const targetBone = "Target"; const pauseTimeRef = useRef(null); const isPausedRef = useRef(false); const isSpeedRef = useRef(null); const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, assetStore } = useSceneContext(); const { resetAsset } = assetStore(); const { setArmBotActive, setArmBotState, removeCurrentAction, incrementActiveTime, incrementIdleTime, armBots } = armBotStore(); const { decrementVehicleLoad, removeLastMaterial } = vehicleStore(); const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = storageUnitStore(); const { getMaterialById, setIsVisible } = materialStore(); const { getActionByUuid, getEventByActionUuid, getEventByModelUuid, selectedProduct } = productStore(); 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; modelId: string } | null>(null); const action = getActionByUuid(selectedProduct.productUuid, armBot.currentAction?.actionUuid || ""); const handlePickUpTrigger = () => { if (armBot?.currentAction?.materialId) { const material = getMaterialById(armBot.currentAction.materialId); if (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, modelId: previousModel.modelUuid }; } 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?.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) // } // } } }; 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); resetAsset(armBot.modelUuid); isPausedRef.current = false; pauseTimeRef.current = null; 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, armBots, currentPhase, isPlaying]); useEffect(() => { const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone); if (!isReset && isPlaying) { 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."); } else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) { logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction"); } 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."); } else if (armBot && !armBot.isActive && armBot.state === "running" && currentPhase === "picking" && armBot.currentAction) { setTimeout(() => { setArmBotActive(armBot.modelUuid, true); setArmBotState(armBot.modelUuid, "running"); setCurrentPhase("start-to-end"); 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."); }, 100); } else if (armBot && !armBot.isActive && armBot.state === "running" && currentPhase === "dropping" && armBot.currentAction) { setTimeout(() => { setArmBotActive(armBot.modelUuid, true); setArmBotState(armBot.modelUuid, "running"); setCurrentPhase("end-to-rest"); 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."); }, 100); } } 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; 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.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;