diff --git a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts index 6ab7230..eab6e3e 100644 --- a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts @@ -71,7 +71,7 @@ async function loadInitialFloorItems( // Check Three.js Cache const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { - // console.log(`[Cache] Fetching ${item.modelName}`); + // processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); @@ -81,7 +81,7 @@ async function loadInitialFloorItems( // Check IndexedDB const indexedDBModel = await retrieveGLTF(item.modelfileID!); if (indexedDBModel) { - // console.log(`[IndexedDB] Fetching ${item.modelName}`); + // const blobUrl = URL.createObjectURL(indexedDBModel); loader.load(blobUrl, (gltf) => { URL.revokeObjectURL(blobUrl); @@ -102,7 +102,7 @@ async function loadInitialFloorItems( } // Fetch from Backend - // console.log(`[Backend] Fetching ${item.modelName}`); + // const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`; loader.load(modelUrl, async (gltf) => { const modelBlob = await fetch(modelUrl).then((res) => res.blob()); @@ -120,7 +120,7 @@ async function loadInitialFloorItems( ); }); } else { - // console.log(`Item ${item.modelName} is not near`); + // setFloorItems((prevItems) => [ ...(prevItems || []), { @@ -281,8 +281,8 @@ function processLoadedModel( actionName: "Action 1", actionType: "pickAndPlace", process: { - startPoint: [0, 0, 0], - endPoint: [0, 0, 0] + startPoint: null, + endPoint: null }, triggers: [] } diff --git a/app/src/modules/builder/geomentries/assets/assetManager.ts b/app/src/modules/builder/geomentries/assets/assetManager.ts index 5f7798e..889d905 100644 --- a/app/src/modules/builder/geomentries/assets/assetManager.ts +++ b/app/src/modules/builder/geomentries/assets/assetManager.ts @@ -18,7 +18,7 @@ export default async function assetManager( const taskId = ++currentTaskId; // Increment taskId for each call activePromises.set(taskId, true); // Mark task as active - // console.log("Received message from worker:", data); + // if (data.toRemove.length > 0) { data.toRemove.forEach((uuid: string) => { @@ -58,7 +58,7 @@ export default async function assetManager( // Check Three.js Cache const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { - // console.log(`[Cache] Fetching ${item.modelName}`); + // processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve); return; } @@ -66,7 +66,7 @@ export default async function assetManager( // Check IndexedDB const indexedDBModel = await retrieveGLTF(item.modelfileID!); if (indexedDBModel) { - // console.log(`[IndexedDB] Fetching ${item.modelName}`); + // const blobUrl = URL.createObjectURL(indexedDBModel); loader.load( blobUrl, @@ -86,7 +86,7 @@ export default async function assetManager( } // Fetch from Backend - // console.log(`[Backend] Fetching ${item.modelName}`); + // loader.load( modelUrl, async (gltf) => { @@ -114,14 +114,14 @@ export default async function assetManager( const existingModel = itemsGroup?.current?.getObjectByProperty("uuid", item.modelUuid); if (existingModel) { - // console.log(`Model ${item.modelName} already exists in the scene.`); + // resolve(); return; } const model = gltf; model.uuid = item.modelUuid; - model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid }; + model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid, eventData: item.eventData }; model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); model.position.set(...item.position); model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z); diff --git a/app/src/modules/scene/controls/selectionControls/moveControls.tsx b/app/src/modules/scene/controls/selectionControls/moveControls.tsx index cc9ce50..3084952 100644 --- a/app/src/modules/scene/controls/selectionControls/moveControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/moveControls.tsx @@ -222,6 +222,8 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje event ); } + + newFloorItem.eventData = eventData; } } diff --git a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx b/app/src/modules/scene/controls/selectionControls/rotateControls.tsx index 7ea7045..58dab0c 100644 --- a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/rotateControls.tsx @@ -222,6 +222,8 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo event ); } + + newFloorItem.eventData = eventData; } } diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index e178a81..0ed7e13 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -21,7 +21,7 @@ function PointsCreator() { const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere(); - const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData(); + const { selectedEventData,setSelectedEventData, clearSelectedEventData } = useSelectedEventData(); const { isDragging } = useIsDragging(); const { isRotating } = useIsRotating(); @@ -30,6 +30,7 @@ function PointsCreator() { const eventData = getEventByModelUuid( selectedEventSphere.userData.modelUuid ); + if (eventData) { setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid); } else { @@ -134,7 +135,10 @@ function PointsCreator() { {events.map((event, i) => { if (event.type === "transfer") { return ( - + {event.points.map((point, j) => ( { + if (selectedEventData?.data.type !== 'vehicle') { + // clearSelectedEventSphere(); + } + setTransformMode(null); + }} key={`${i}-${j}`} position={new THREE.Vector3(...point.position)} - // rotation={new THREE.Euler(...point.rotation)} - userData={{ modelUuid: event.modelUuid, pointUuid: point.uuid }} + userData={{ + modelUuid: event.modelUuid, + pointUuid: point.uuid, + }} > @@ -159,7 +171,10 @@ function PointsCreator() { ); } else if (event.type === "vehicle") { return ( - + @@ -181,7 +198,10 @@ function PointsCreator() { ); } else if (event.type === "roboticArm") { return ( - + { + setTransformMode(null); + }} position={new THREE.Vector3(...event.point.position)} - // rotation={new THREE.Euler(...event.point.rotation)} - userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} > @@ -203,7 +228,10 @@ function PointsCreator() { ); } else if (event.type === "machine") { return ( - + { + // clearSelectedEventSphere(); + setTransformMode(null); + }} position={new THREE.Vector3(...event.point.position)} - // rotation={new THREE.Euler(...event.point.rotation)} - userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} > @@ -244,4 +278,4 @@ function PointsCreator() { ); } -export default PointsCreator; +export default PointsCreator; \ No newline at end of file diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index c82b73d..10f90bf 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -1,68 +1,220 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' -import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; -import { useFrame, useThree } from '@react-three/fiber'; -import * as THREE from "three" -import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { useFrame } from '@react-three/fiber'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { + useAnimationPlaySpeed, + usePauseButtonStore, + usePlayButtonStore, + useResetButtonStore +} from '../../../../../store/usePlayButtonStore'; -function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase, ikSolver, targetBone, robot, logStatus, groupRef, processes, armBotCurveRef, path }: any) { - const { armBots } = useArmBotStore(); - const { scene } = useThree(); - const restSpeed = 0.1; - const restPosition = new THREE.Vector3(0, 1, -1.6); - const initialCurveRef = useRef(null); - const initialStartPositionRef = useRef(null); - const [initialProgress, setInitialProgress] = useState(0); - const [progress, setProgress] = useState(0); - const [needsInitialMovement, setNeedsInitialMovement] = useState(true); - const [isInitializing, setIsInitializing] = useState(true); - const { isPlaying } = usePlayButtonStore(); - const statusRef = useRef("idle"); - // Create a ref for initialProgress - const initialProgressRef = useRef(0); +function RoboticArmAnimator({ + HandleCallback, + restPosition, + ikSolver, + targetBone, + armBot, + logStatus, + path +}: any) { + const progressRef = useRef(0); + const curveRef = useRef(null); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]); + const [customCurvePoints, setCustomCurvePoints] = useState(null); + // Zustand stores + const { isPlaying } = usePlayButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { isReset } = useResetButtonStore(); + const { speed } = useAnimationPlaySpeed(); + + // Update path state whenever `path` prop changes useEffect(() => { - - setCurrentPath(path) - }, [path]) + setCurrentPath(path); + }, [path]); + // Reset logic when `isPlaying` changes useEffect(() => { + if (!isPlaying) { + setCurrentPath([]); + curveRef.current = null; + } + }, [isPlaying]); - }, [currentPath]) + // Handle circle points based on armBot position + useEffect(() => { + const points = generateRingPoints(1.6, 64) + setCirclePoints(points); + }, [armBot.position]); + + function generateRingPoints(radius: any, segments: any) { + const points: [number, number, number][] = []; + for (let i = 0; i < segments; i++) { + // Calculate angle for current segment + const angle = (i / segments) * Math.PI * 2; + // Calculate x and z coordinates (y remains the same for a flat ring) + const x = Math.cos(angle) * radius; + const z = Math.sin(angle) * radius; + points.push([x, 1.5, z]); + } + return points; + } + + const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => { + for (let i = 0; i < points.length; i++) { + const [x, y, z] = points[i]; + if ( + Math.abs(x - nearestPoint[0]) < epsilon && + Math.abs(y - nearestPoint[1]) < epsilon && + Math.abs(z - nearestPoint[2]) < epsilon + ) { + return i; // Found the matching index + } + } + return -1; // Not found + }; + + + // Handle nearest points and final path (including arc points) + useEffect(() => { + if (circlePoints.length > 0 && currentPath.length > 0) { + const start = currentPath[0]; + const end = currentPath[currentPath.length - 1]; + + const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number]; + const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number]; + + const findNearest = (target: [number, number, number]) => { + return circlePoints.reduce((nearest, point) => { + const distance = Math.hypot( + target[0] - point[0], + target[1] - point[1], + target[2] - point[2] + ); + const nearestDistance = Math.hypot( + target[0] - nearest[0], + target[1] - nearest[1], + target[2] - nearest[2] + ); + return distance < nearestDistance ? point : nearest; + }, circlePoints[0]); + }; + + const nearestToStart = findNearest(raisedStart); + + const nearestToEnd = findNearest(raisedEnd); + + const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints); + + const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints); + + // Find clockwise and counter-clockwise distances + const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + 64) % 64; + + const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + 64) % 64; + + const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance; + + // Collect arc points between start and end + let arcPoints: [number, number, number][] = []; + + if (clockwiseIsShorter) { + if (indexOfNearestStart <= indexOfNearestEnd) { + arcPoints = circlePoints.slice(indexOfNearestStart, indexOfNearestEnd + 1); + } else { + // Wrap around + arcPoints = [ + ...circlePoints.slice(indexOfNearestStart, 64), + ...circlePoints.slice(0, indexOfNearestEnd + 1) + ]; + } + } else { + if (indexOfNearestStart >= indexOfNearestEnd) { + for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) { + arcPoints.push(circlePoints[i]); + } + } else { + for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) { + arcPoints.push(circlePoints[i]); + } + } + } + + // Continue your custom path logic + const pathVectors = [ + new THREE.Vector3(start[0], start[1], start[2]), // start + new THREE.Vector3(raisedStart[0], raisedStart[1], raisedStart[2]), // lift up + new THREE.Vector3(nearestToStart[0], raisedStart[1], nearestToStart[2]), // move to arc start + ...arcPoints.map(point => new THREE.Vector3(point[0], raisedStart[1], point[2])), + new THREE.Vector3(nearestToEnd[0], raisedEnd[1], nearestToEnd[2]), // move from arc end + new THREE.Vector3(raisedEnd[0], raisedEnd[1], raisedEnd[2]), // lowered end + new THREE.Vector3(end[0], end[1], end[2]) // end + ]; + + const customCurve = new THREE.CatmullRomCurve3(pathVectors, false, 'centripetal', 1); + const generatedPoints = customCurve.getPoints(100); + setCustomCurvePoints(generatedPoints); + } + }, [circlePoints, currentPath]); + + // Frame update for animation useFrame((_, delta) => { - if (!ikSolver || !currentPath || currentPath.length === 0) return; + if (!ikSolver) return; - const bone = ikSolver.mesh.skeleton.bones.find( - (b: any) => b.name === targetBone - ); + const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone); if (!bone) return; - // Ensure currentPath is a valid array of 3D points, create a CatmullRomCurve3 from it - const curve = new THREE.CatmullRomCurve3( - currentPath.map(point => new THREE.Vector3(point[0], point[1], point[2])) - ); + if (isPlaying) { + if (!isPaused && customCurvePoints && currentPath.length > 0) { + const curvePoints = customCurvePoints; + const speedAdjustedProgress = progressRef.current + (speed * armBot.speed); + const index = Math.floor(speedAdjustedProgress); + if (index >= curvePoints.length) { + // Reached the end of the curve + HandleCallback(); + setCurrentPath([]); + curveRef.current = null; + progressRef.current = 0; + } else { + const point = curvePoints[index]; + bone.position.copy(point); + progressRef.current = speedAdjustedProgress; + } + } else if (isPaused) { + logStatus(armBot.modelUuid, 'Simulation Paused'); + } - const next = initialProgressRef.current + delta * 0.5; - if (next >= 1) { - // bone.position.copy(restPosition); - HandleCallback(); // Call the callback when the path is completed - initialProgressRef.current = 0; // Set ref to 1 when done - } else { - const point = curve.getPoint(next); // Get the interpolated point from the curve - bone.position.copy(point); // Update the bone position along the curve - initialProgressRef.current = next; // Update progress + ikSolver.update(); + } else if (!isPlaying && currentPath.length === 0) { + // Not playing anymore, reset to rest + bone.position.copy(restPosition); + ikSolver.update(); } - - ikSolver.update(); }); - return ( - <> - ) + <> + {customCurvePoints && currentPath && isPlaying && ( + + [p.x, p.y, p.z] as [number, number, number])} + color="green" + lineWidth={5} + dashed={false} + /> + + )} + + + + + + ); } -export default RoboticArmAnimator; \ No newline at end of file +export default RoboticArmAnimator; diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index af3ffe3..9199ede 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -1,61 +1,141 @@ import React, { useEffect, useRef, useState } from 'react' import IKInstance from '../ikInstance/ikInstance'; import RoboticArmAnimator from '../animator/roboticArmAnimator'; -import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb"; import { useThree } from "@react-three/fiber"; import { useFloorItems } from '../../../../../store/store'; import useModuleStore from '../../../../../store/useModuleStore'; -import { Vector3 } from "three"; import * as THREE from "three"; +import { useSelectedAction, useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; +import { useProductStore } from '../../../../../store/simulation/useProductStore'; -interface Process { - triggerId: string; - startPoint?: Vector3; - endPoint?: Vector3; - speed: number; -} -function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { +function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { - const { isPlaying } = usePlayButtonStore(); const [currentPhase, setCurrentPhase] = useState<(string)>("init"); - const { scene } = useThree(); - const targetBone = "Target"; - const { activeModule } = useModuleStore(); - const [ikSolver, setIkSolver] = useState(null); - const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); - const { floorItems } = useFloorItems(); - const groupRef = useRef(null); - const [processes, setProcesses] = useState([]); - const [armBotCurvePoints, setArmBotCurvePoints] = useState({ start: [], end: [] }) - const restPosition = new THREE.Vector3(0, 1, -1.6); - let armBotCurveRef = useRef(null) 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; + //zustand + const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); + const { products, getActionByUuid } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { floorItems } = useFloorItems(); + const { activeModule } = useModuleStore(); + const { isPlaying } = usePlayButtonStore(); + const { isReset, setReset } = useResetButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { selectedAction } = useSelectedAction(); + + function firstFrame() { + startTime = performance.now(); + step(); + } + 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 < 1500) { + // 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 + const startPoint = armBot.point.actions[0].process.startPoint; + const endPoint = armBot.point.actions[0].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])) + } + } + 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; + const endPoint = armBot.point.actions[0].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])); + } + } + logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") + } + + } + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); useEffect(() => { - let armItems = floorItems?.filter((val: any) => - val.modelUuid === "3abf5d46-b59e-4e6b-9c02-a4634b64b82d" - ); - // Get the first matching item - let armItem = armItems?.[0]; - if (armItem) { - const targetMesh = scene?.getObjectByProperty("uuid", armItem.modelUuid); - if (targetMesh) { - targetMesh.visible = activeModule !== "simulation" - } + const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid); + + if (targetMesh) { + targetMesh.visible = activeModule !== "simulation" } + const targetBones = ikSolver?.mesh.skeleton.bones.find( (b: any) => b.name === targetBone ); + if (isReset) { + logStatus(armBot.modelUuid, "Simulation Play Reset Successfully") + removeCurrentAction(armBot.modelUuid) + setArmBotActive(armBot.modelUuid, true) + setArmBotState(armBot.modelUuid, "running") + setCurrentPhase("init-to-rest"); + isPausedRef.current = false + pauseTimeRef.current = null + isPausedRef.current = false + startTime = 0 + if (targetBones) { + let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position) + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + setReset(false); + logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") + } if (isPlaying) { - //Moving armBot from initial point to rest position. - if (!robot?.isActive && robot?.state == "idle" && currentPhase == "init") { - setArmBotActive(robot.modelUuid, true) - setArmBotState(robot.modelUuid, "running") + //Moving armBot from initial point to rest position. + if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") { + + setArmBotActive(armBot.modelUuid, true) + setArmBotState(armBot.modelUuid, "running") setCurrentPhase("init-to-rest"); if (targetBones) { let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition) @@ -63,21 +143,26 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { setPath(curve.points.map(point => [point.x, point.y, point.z])); } } - logStatus(robot.modelUuid, "Moving armBot from initial point to rest position.") + logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") } //Waiting for trigger. - else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && !robot.currentAction) { - logStatus(robot.modelUuid, "Waiting to trigger CurrentAction") - setTimeout(() => { - addCurrentAction(robot.modelUuid, 'action-003'); + else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) { + logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction") + const timeoutId = setTimeout(() => { + addCurrentAction(armBot.modelUuid, selectedAction?.actionId); + console.log('selectedAction?.actionId: ', selectedAction?.actionId); }, 3000); + return () => clearTimeout(timeoutId); } - else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && robot.currentAction) { - if (robot.currentAction) { - setArmBotActive(robot.modelUuid, true); - setArmBotState(robot.modelUuid, "running"); + 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"); - const startPoint = robot.point.actions[0].process.startPoint; + let actiondata = getActionByUuid(selectedProduct.productId, selectedAction.actionId) + console.log('actiondata: ', actiondata); + const startPoint = armBot.point.actions[0].process.startPoint; if (startPoint) { let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2])); if (curve) { @@ -85,105 +170,109 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { } } } - logStatus(robot.modelUuid, "Moving armBot from rest point to start position.") + logStatus(armBot.modelUuid, "Moving armBot from rest point to start position.") } - else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "picking" && robot.currentAction) { - setArmBotActive(robot.modelUuid, true); - setArmBotState(robot.modelUuid, "running"); - setCurrentPhase("start-to-end"); - const startPoint = robot.point.actions[0].process.startPoint; - const endPoint = robot.point.actions[0].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) { - setTimeout(() => { - logStatus(robot.modelUuid, "picking the object"); - setPath(curve.points.map(point => [point.x, point.y, point.z])); - }, 1500) - } - } - logStatus(robot.modelUuid, "Moving armBot from start point to end position.") + else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "picking" && armBot.currentAction) { + requestAnimationFrame(firstFrame); + // setArmBotActive(armBot.modelUuid, true); + // setArmBotState(armBot.modelUuid, "running"); + // setCurrentPhase("start-to-end"); + // const startPoint = armBot.point.actions[0].process.startPoint; + // const endPoint = armBot.point.actions[0].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) { + // setTimeout(() => { + // logStatus(armBot.modelUuid, "picking the object"); + // setPath(curve.points.map(point => [point.x, point.y, point.z])); + // }, 1500) + // } + // } + // logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") } - else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "dropping" && robot.currentAction) { - setArmBotActive(robot.modelUuid, true); - setArmBotState(robot.modelUuid, "running"); - setCurrentPhase("end-to-rest"); - const endPoint = robot.point.actions[0].process.endPoint; - if (endPoint) { - let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition - ); - if (curve) { - setTimeout(() => { - logStatus(robot.modelUuid, "dropping the object"); - setPath(curve.points.map(point => [point.x, point.y, point.z])); - }, 1500) - } - } - logStatus(robot.modelUuid, "Moving armBot from end point to rest position.") + else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "dropping" && armBot.currentAction) { + requestAnimationFrame(firstFrame); + // setArmBotActive(armBot.modelUuid, true); + // setArmBotState(armBot.modelUuid, "running"); + // setCurrentPhase("end-to-rest"); + // const endPoint = armBot.point.actions[0].process.endPoint; + // if (endPoint) { + // let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition); + // if (curve) { + // setTimeout(() => { + // logStatus(armBot.modelUuid, "dropping the object"); + // setPath(curve.points.map(point => [point.x, point.y, point.z])); + // }, 1500) + // } + // } + // logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") } + } else { + logStatus(armBot.modelUuid, "Simulation Play Stopped") + setArmBotActive(armBot.modelUuid, false) + setArmBotState(armBot.modelUuid, "idle") + setCurrentPhase("init"); + setPath([]) + removeCurrentAction(armBot.modelUuid) + } - }, [currentPhase, robot, isPlaying, ikSolver]) + }, [currentPhase, armBot, isPlaying, ikSolver, isReset]) function createCurveBetweenTwoPoints(p1: any, p2: any) { const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5); - mid.y += 0.5; - + // mid.y += 0.5; const points = [p1, mid, p2]; return new THREE.CatmullRomCurve3(points); } const HandleCallback = () => { - if (robot.isActive && robot.state == "running" && currentPhase == "init-to-rest") { - logStatus(robot.modelUuid, "Callback triggered: rest"); - setArmBotActive(robot.modelUuid, false) - setArmBotState(robot.modelUuid, "idle") + 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 (robot.isActive && robot.state == "running" && currentPhase == "rest-to-start") { - logStatus(robot.modelUuid, "Callback triggered: pick."); - setArmBotActive(robot.modelUuid, false) - setArmBotState(robot.modelUuid, "idle") + else if (armBot.isActive && armBot.state == "running" && currentPhase == "rest-to-start") { + logStatus(armBot.modelUuid, "Callback triggered: pick."); + setArmBotActive(armBot.modelUuid, false) + setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("picking"); setPath([]) } - else if (robot.isActive && robot.state == "running" && currentPhase == "start-to-end") { - logStatus(robot.modelUuid, "Callback triggered: drop."); - setArmBotActive(robot.modelUuid, false) - setArmBotState(robot.modelUuid, "idle") + else if (armBot.isActive && armBot.state == "running" && currentPhase == "start-to-end") { + logStatus(armBot.modelUuid, "Callback triggered: drop."); + setArmBotActive(armBot.modelUuid, false) + setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("dropping"); setPath([]) } - else if (robot.isActive && robot.state == "running" && currentPhase == "end-to-rest") { - logStatus(robot.modelUuid, "Callback triggered: rest, cycle completed."); - setArmBotActive(robot.modelUuid, false) - setArmBotState(robot.modelUuid, "idle") + 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([]) - removeCurrentAction(robot.modelUuid) + removeCurrentAction(armBot.modelUuid) } } const logStatus = (id: string, status: string) => { // - + } return ( <> - - - + + ) } -export default RoboticArmInstance; \ No newline at end of file +export default RoboticArmInstance; diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index 645cbb5..e692ca5 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -3,20 +3,19 @@ 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 { useFrame, useLoader, useThree } from "@react-three/fiber"; +import { useLoader, useThree } from "@react-three/fiber"; import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver"; +import { TransformControls } from '@react-three/drei'; + type IKInstanceProps = { modelUrl: string; ikSolver: any; setIkSolver: any - robot: any; - groupRef: React.RefObject; - processes: any; - setArmBotCurvePoints: any + armBot: any; + groupRef: any; }; -function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processes, setArmBotCurvePoints }: IKInstanceProps) { - - const { scene } = useThree(); +function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKInstanceProps) { + const { scene } = useThree() 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/"); @@ -25,6 +24,8 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe const cloned = useMemo(() => clone(gltf?.scene), [gltf]); const targetBoneName = "Target"; const skinnedMeshName = "link_0"; + const [selectedArm, setSelectedArm] = useState(); + useEffect(() => { if (!gltf) return; const OOI: any = {}; @@ -66,15 +67,19 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe setIkSolver(solver); const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05) + // groupRef.current.add(helper); - // scene.add(helper) + setSelectedArm(OOI.Target_Bone); + scene.add(helper) }, [gltf]); return ( <> - + { + setSelectedArm(groupRef.current?.getObjectByName(targetBoneName)) + }}> + {/* {selectedArm && } */} ) } diff --git a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx index 1089fa5..0acc1d9 100644 --- a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx +++ b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx @@ -1,4 +1,3 @@ -import React from 'react' import RoboticArmInstance from './armInstance/roboticArmInstance'; import { useArmBotStore } from '../../../../store/simulation/useArmBotStore'; @@ -8,9 +7,8 @@ function RoboticArmInstances() { return ( <> {armBots?.map((robot: ArmBotStatus) => ( - + ))} - ) } diff --git a/app/src/modules/simulation/roboticArm/roboticArm.tsx b/app/src/modules/simulation/roboticArm/roboticArm.tsx index 69e6da0..2cb9ea6 100644 --- a/app/src/modules/simulation/roboticArm/roboticArm.tsx +++ b/app/src/modules/simulation/roboticArm/roboticArm.tsx @@ -2,18 +2,26 @@ import { useEffect } from "react"; import RoboticArmInstances from "./instances/roboticArmInstances"; import { useArmBotStore } from "../../../store/simulation/useArmBotStore"; import { useFloorItems } from "../../../store/store"; +import { useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from "../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../store/simulation/useProductStore"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import ArmBotUI from "../ui/arm/armBotUI"; function RoboticArm() { - const { armBots, addArmBot, removeArmBot } = useArmBotStore(); + const { armBots, addArmBot, clearArmBots } = useArmBotStore(); + const { products, getProductById } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { selectedEventData } = useSelectedEventData(); + const { isPlaying } = usePlayButtonStore(); const { floorItems } = useFloorItems(); - const armBotStatusSample: RoboticArmEventSchema[] = [ { state: "idle", - modelUuid: "3abf5d46-b59e-4e6b-9c02-a4634b64b82d", + modelUuid: "8790b4d5-fb6e-49e0-8161-04945fe3fdc4", modelName: "ArmBot-X200", - position: [0.20849215906958463, 0, 0.32079278127773675], + position: [4.317833205016363, 0, -3.2829924989068715], rotation: [-1.3768690876192207e-15, 1.4883085074751308, 1.5407776675834467e-15], type: "roboticArm", speed: 1.5, @@ -29,19 +37,9 @@ function RoboticArm() { process: { startPoint: [-1, 2, 1], endPoint: [-2, 1, -1], + // startPoint: [-2, 1, -1], + // endPoint: [-1, 2, 1], }, - // process: { - // "startPoint": [ - // 0.37114476008711866, - // 1.9999999999999998, - // 1.8418816116721384 - // ], - // "endPoint": [ - // -0.42197069459490777, - // 1, - // -3.159515927851809 - // ] - // }, triggers: [ { triggerUuid: "trigger-001", @@ -95,7 +93,7 @@ function RoboticArm() { position: [95.94347308985614, 0, 6.742905194869091], rotation: [0, 0, 0], type: "roboticArm", - speed: 1.5, + speed: 0.1, point: { uuid: "point-123", position: [0, 1.5, 0], @@ -106,8 +104,10 @@ function RoboticArm() { actionName: "Pick Component", actionType: "pickAndPlace", process: { - startPoint: [2.52543010919071, 0, 8.433681161200905], - endPoint: [95.3438373267953, 0, 9.0279187421610025], + // startPoint: [2.52543010919071, 0, 8.433681161200905], + // endPoint: [95.3438373267953, 0, 9.0279187421610025], + startPoint: null, + endPoint: null, }, triggers: [ { @@ -158,20 +158,32 @@ function RoboticArm() { ]; useEffect(() => { - - removeArmBot(armBotStatusSample[0].modelUuid); - addArmBot('123', armBotStatusSample[0]); - // addArmBot('123', armBotStatusSample[1]); - // addCurrentAction('armbot-xyz-001', 'action-001'); - }, []); + if (selectedProduct.productId) { + const product = getProductById(selectedProduct.productId); + if (product) { + clearArmBots(); + product.eventDatas.forEach(events => { + if (events.type === 'roboticArm') { + addArmBot(selectedProduct.productId, events); + } + }); + } + } + }, [selectedProduct, products]); useEffect(() => { - - }, [armBots]); + }, [armBots]) + + useEffect(() => { + + }, [selectedEventData, selectedEventSphere, isPlaying]); return ( <> + {selectedEventSphere && selectedEventData?.data.type === "roboticArm" && + < ArmBotUI /> + } ); } diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 757a9ef..efacd53 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -29,14 +29,14 @@ function Simulation() { return ( <> + + {activeModule === 'simulation' && <> - - diff --git a/app/src/modules/simulation/ui/arm/PickDropPoints.tsx b/app/src/modules/simulation/ui/arm/PickDropPoints.tsx index 4544a46..c44eee8 100644 --- a/app/src/modules/simulation/ui/arm/PickDropPoints.tsx +++ b/app/src/modules/simulation/ui/arm/PickDropPoints.tsx @@ -9,7 +9,7 @@ interface PickDropProps { actionType: "pick" | "drop"; actionUuid: string; gltfScene: THREE.Group; - selectedPoint: THREE.Mesh | null; + handlePointerDown: (e: ThreeEvent) => void; isSelected: boolean; } @@ -21,12 +21,10 @@ const PickDropPoints: React.FC = ({ actionType, actionUuid, gltfScene, - selectedPoint, handlePointerDown, isSelected, }) => { const groupRef = useRef(null); - return ( = ({ : new THREE.Vector3(0, 0, 0) } onPointerDown={(e) => { - e.stopPropagation(); // Important to prevent event bubbling + + e.stopPropagation(); // Prevent event bubbling if (!isSelected) return; handlePointerDown(e); }} userData={{ modelUuid, pointUuid, actionType, actionUuid }} > { + const cloned = gltfScene.clone(); + cloned.traverse((child: any) => { + if (child.isMesh) { + child.userData = { modelUuid, pointUuid, actionType, actionUuid }; + } + }); + return cloned; + })()} + position={[0, 0, 0]} scale={[0.5, 0.5, 0.5]} /> diff --git a/app/src/modules/simulation/ui/arm/armBotUI.tsx b/app/src/modules/simulation/ui/arm/armBotUI.tsx new file mode 100644 index 0000000..8a2d18f --- /dev/null +++ b/app/src/modules/simulation/ui/arm/armBotUI.tsx @@ -0,0 +1,190 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { useSelectedAction, useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { useArmBotStore } from '../../../../store/simulation/useArmBotStore'; +import { useGLTF } from '@react-three/drei'; +import { useThree } from '@react-three/fiber'; +import { useProductStore } from '../../../../store/simulation/useProductStore'; +import PickDropPoints from './PickDropPoints'; +import useDraggableGLTF from './useDraggableGLTF'; +import * as THREE from 'three'; + +import armPick from "../../../../assets/gltf-glb/arm_ui_pick.glb"; +import armDrop from "../../../../assets/gltf-glb/arm_ui_drop.glb"; +import useModuleStore from '../../../../store/useModuleStore'; + +type Positions = { + pick: [number, number, number]; + drop: [number, number, number]; + default: [number, number, number]; +}; + +const ArmBotUI = () => { + const { getEventByModelUuid } = useProductStore(); + const { selectedEventData } = useSelectedEventData(); + const { selectedProduct } = useSelectedProduct(); + const { armBots, updateStartPoint, updateEndPoint } = useArmBotStore(); + const { scene } = useThree(); + const { selectedAction } = useSelectedAction(); + + const armUiPick = useGLTF(armPick) as any; + const armUiDrop = useGLTF(armDrop) as any; + + const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 0, 0]); + const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 0, 0]); + const [selectedArmBotData, setSelectedArmBotData] = useState(null); + + // Fetch and setup selected ArmBot data + useEffect(() => { + if (selectedEventData?.data.type === "roboticArm") { + const selectedArmBot = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid); + + if (selectedArmBot?.type === "roboticArm") { + setSelectedArmBotData(selectedArmBot); + const defaultPositions = getDefaultPositions(selectedArmBot.modelUuid); + const matchingAction = armBots?.flatMap((robot: ArmBotStatus) => robot.point.actions) + .find((action) => action.actionUuid === selectedAction.actionId); + if (matchingAction) { + const startPoint = matchingAction.process.startPoint; + const pickPosition = (!startPoint || (Array.isArray(startPoint) && startPoint.every(v => v === 0))) + ? defaultPositions.pick + : startPoint; + + const endPoint = matchingAction.process.endPoint; + const dropPosition = (!endPoint || (Array.isArray(endPoint) && endPoint.every(v => v === 0))) + ? defaultPositions.drop + : endPoint; + + setStartPosition(pickPosition); + setEndPosition(dropPosition); + } + } + } + }, [selectedEventData, selectedProduct, getEventByModelUuid, selectedAction]); + + + function getDefaultPositions(modelUuid: string): Positions { + const modelData = getEventByModelUuid(selectedProduct.productId, modelUuid); + + if (modelData?.type === "roboticArm") { + const baseX = modelData.point.position?.[0] || 0; + const baseY = modelData.point.position?.[1] || 0;; + const baseZ = modelData.point.position?.[2] || 0; + return { + pick: [baseX, baseY, baseZ + 0.5], + drop: [baseX, baseY, baseZ - 0.5], + default: [baseX, baseY, baseZ], + }; + } + + return { + pick: [0.5, 1.5, 0], + drop: [-0.5, 1.5, 0], + default: [0, 1.5, 0], + }; + } + + function getLocalPosition(parentUuid: string, worldPosArray: [number, number, number] | null): [number, number, number] | null { + if (worldPosArray) { + const worldPos = new THREE.Vector3(...worldPosArray); + const parentObject = scene.getObjectByProperty('uuid', parentUuid); + + if (parentObject) { + const localPos = worldPos.clone(); + parentObject.worldToLocal(localPos); + return [localPos.x, localPos.y, localPos.z]; + } + } + return null; + } + + const updatePointToState = (obj: THREE.Object3D) => { + const { modelUuid, actionType, actionUuid } = obj.userData; + const newPosition = new THREE.Vector3(); + obj.getWorldPosition(newPosition); + const worldPositionArray = newPosition.toArray() as [number, number, number]; + + if (selectedEventData?.data.type === "roboticArm") { + const selectedArmBot = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid); + + const armBot = selectedArmBot?.modelUuid === modelUuid ? selectedArmBot : null; + if (!armBot) return; + + if (armBot.type === "roboticArm") { + armBot?.point?.actions?.map((action: any) => { + if (action.actionUuid === actionUuid) { + const updatedProcess = { ...action.process }; + + if (actionType === "pick") { + updatedProcess.startPoint = getLocalPosition(modelUuid, worldPositionArray); + setStartPosition(updatedProcess.startPoint) + updateStartPoint(modelUuid, actionUuid, updatedProcess.startPoint); + } else if (actionType === "drop") { + updatedProcess.endPoint = getLocalPosition(modelUuid, worldPositionArray); + setEndPosition(updatedProcess.endPoint) + updateEndPoint(modelUuid, actionUuid, updatedProcess.endPoint); + } + return { + ...action, + process: updatedProcess, + }; + } + return action; + }); + + } + } + } + + useEffect(() => { + + + }, [armBots]) + + const { handlePointerDown } = useDraggableGLTF(updatePointToState); + + if (!selectedArmBotData || !Array.isArray(selectedArmBotData.point?.actions)) { + return null; // avoid rendering if no data yet + } + return ( + <> + {selectedArmBotData.point.actions.map((action: any) => { + if (action.actionUuid === selectedAction.actionId) { + return ( + + + + + + + ); + } else { + return null; // important! must return something + } + })} + + ); + +}; + +export default ArmBotUI; diff --git a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts index b7e9272..e450bfd 100644 --- a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts +++ b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts @@ -1,11 +1,11 @@ -import { useRef } from "react"; +import { useRef, useState } from "react"; import * as THREE from "three"; import { ThreeEvent, useThree } from "@react-three/fiber"; type OnUpdateCallback = (object: THREE.Object3D) => void; export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { - const { camera, gl, controls, scene } = useThree(); + const { camera, gl, controls } = useThree(); const activeObjRef = useRef(null); const planeRef = useRef( new THREE.Plane(new THREE.Vector3(0, 1, 0), 0) @@ -15,6 +15,7 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { const raycaster = new THREE.Raycaster(); const pointer = new THREE.Vector2(); + const [objectWorldPos, setObjectWorldPos] = useState(new THREE.Vector3()); const handlePointerDown = (e: ThreeEvent) => { e.stopPropagation(); @@ -33,10 +34,10 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { activeObjRef.current = obj; initialPositionRef.current.copy(obj.position); + // Get world position - const objectWorldPos = new THREE.Vector3(); - obj.getWorldPosition(objectWorldPos); + setObjectWorldPos(obj.getWorldPosition(objectWorldPos)); // Set plane at the object's Y level planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y); @@ -61,52 +62,56 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { const handlePointerMove = (e: PointerEvent) => { if (!activeObjRef.current) return; - + // Check if Shift key is pressed const isShiftKeyPressed = e.shiftKey; - + // Get the mouse position relative to the canvas const rect = gl.domElement.getBoundingClientRect(); pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; - + // Update raycaster to point to the mouse position raycaster.setFromCamera(pointer, camera); - + // Create a vector to store intersection point const intersection = new THREE.Vector3(); - const intersects = raycaster.ray.intersectPlane(planeRef.current, intersection); + const intersects = raycaster.ray.intersectPlane( + planeRef.current, + intersection + ); if (!intersects) return; - + // Add offset for dragging intersection.add(offsetRef.current); - console.log('intersection: ', intersection); - + // Get the parent's world matrix if exists const parent = activeObjRef.current.parent; const targetPosition = new THREE.Vector3(); - + + // OnPointerDown + initialPositionRef.current.copy(objectWorldPos); + + // OnPointerMove if (isShiftKeyPressed) { - console.log('isShiftKeyPressed: ', isShiftKeyPressed); - // For Y-axis only movement, maintain original X and Z - console.log('initialPositionRef: ', initialPositionRef); - console.log('intersection.y: ', intersection); - targetPosition.set( - initialPositionRef.current.x, - intersection.y, - initialPositionRef.current.z - ); + const { x: initialX, y: initialY } = initialPositionRef.current; + const { x: objectX, z: objectZ } = objectWorldPos; + + const deltaX = intersection.x - initialX; + + targetPosition.set(objectX, initialY + deltaX, objectZ); } else { // For free movement targetPosition.copy(intersection); } - + // Convert world position to local if object is nested inside a parent if (parent) { parent.worldToLocal(targetPosition); } - + // Update object position + activeObjRef.current.position.copy(targetPosition); }; @@ -126,6 +131,3 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { return { handlePointerDown }; } - - - diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index 19f049c..3fbc8ef 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -29,7 +29,7 @@ function Vehicles() { }, [selectedProduct, products]); useEffect(() => { - // console.log('vehicles: ', vehicles); + // }, [vehicles]) return (