diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx
index c7bd8bc..997d078 100644
--- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx
+++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx
@@ -28,22 +28,11 @@ function RoboticArmMechanics() {
selectedEventData.data.modelUuid,
selectedEventData.selectedPoint
) as RoboticArmPointSchema | undefined;
- const action = getActionByUuid(selectedProduct.productId, selectedAction.actionId) as RoboticArmPointSchema["actions"][0] | undefined;
- if (action) {
- if (point?.actions) {
- setSelectedPointData(point);
- if (point.actions.length > 0 && action) {
- setActiveOption(action.actionType as "default" | "pickAndPlace");
- setSelectedAction(selectedAction.actionId, selectedAction.actionName);
- }
- }
- } else {
- if (point?.actions) {
- setSelectedPointData(point);
- if (point.actions.length > 0) {
- setActiveOption(point.actions[0].actionType as "default" | "pickAndPlace");
- setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName);
- }
+ if (point?.actions) {
+ setSelectedPointData(point);
+ if (point.actions.length > 0) {
+ setActiveOption(point.actions[0].actionType as "default" | "pickAndPlace");
+ setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName);
}
}
} else {
diff --git a/app/src/modules/simulation/materials/instances/material/materialModel.tsx b/app/src/modules/simulation/materials/instances/material/materialModel.tsx
index f27541b..2093584 100644
--- a/app/src/modules/simulation/materials/instances/material/materialModel.tsx
+++ b/app/src/modules/simulation/materials/instances/material/materialModel.tsx
@@ -32,7 +32,7 @@ export function MaterialModel({ materialType, matRef, ...props }: ModelProps) {
);
diff --git a/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx
index 893f759..b458d32 100644
--- a/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx
@@ -1,17 +1,16 @@
import { useFrame } from '@react-three/fiber';
import React, { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
-import { useLogger } from '../../../../../components/ui/log/LoggerContext';
+import { MaterialModel } from '../../../materials/instances/material/materialModel';
type MaterialAnimatorProps = {
ikSolver: any;
- armBot: any;
+ armBot: ArmBotStatus;
currentPhase: string;
};
export default function MaterialAnimator({ ikSolver, armBot, currentPhase }: MaterialAnimatorProps) {
- const sphereRef = useRef(null);
- const boneWorldPosition = new THREE.Vector3();
+ const sphereRef = useRef(null);
const [isRendered, setIsRendered] = useState(false);
useEffect(() => {
@@ -41,7 +40,8 @@ export default function MaterialAnimator({ ikSolver, armBot, currentPhase }: Mat
const direction = new THREE.Vector3();
direction.subVectors(boneWorldPos, boneTargetWorldPos).normalize();
const downwardDirection = direction.clone().negate();
- const adjustedPosition = boneWorldPos.clone().addScaledVector(downwardDirection, -0.25);
+
+ const adjustedPosition = boneWorldPos.clone().addScaledVector(downwardDirection, -0.01);
//set position
sphereRef.current.position.copy(adjustedPosition);
@@ -56,10 +56,10 @@ export default function MaterialAnimator({ ikSolver, armBot, currentPhase }: Mat
return (
<>
{isRendered && (
-
-
-
-
+
)}
>
);
diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
index a1e88cc..3ca7665 100644
--- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
@@ -2,38 +2,33 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
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';
+import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
+
+type PointWithDegree = {
+ position: [number, number, number];
+ degree: number;
+};
+
+function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone, armBot, path }: any) {
-function RoboticArmAnimator({
- HandleCallback,
- restPosition,
- ikSolver,
- targetBone,
- armBot,
- 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);
- let curveHeight = 1.75
const totalDistanceRef = useRef(0);
const startTimeRef = useRef(null);
const segmentDistancesRef = useRef([]);
+ const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
+ const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
+ const [circlePointsWithDegrees, setCirclePointsWithDegrees] = useState([]);
+ const [customCurvePoints, setCustomCurvePoints] = useState(null);
+ let curveHeight = 1.75
+ const CIRCLE_RADIUS = 1.6
+
// Zustand stores
const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore();
const { speed } = useAnimationPlaySpeed();
- const CIRCLE_RADIUS = 1.6
-
// Update path state whenever `path` prop changes
useEffect(() => {
setCurrentPath(path);
@@ -47,7 +42,7 @@ function RoboticArmAnimator({
//Handle Reset Animation
useEffect(() => {
- if (isReset) {
+ if (isReset || !isPlaying) {
progressRef.current = 0;
curveRef.current = null;
setCurrentPath([]);
@@ -77,8 +72,8 @@ function RoboticArmAnimator({
return points;
}
-
- function generateRingPointsWithDegrees(radius: number, segments: number) {
+ //Generate CirclePoints with Angle
+ function generateRingPointsWithDegrees(radius: number, segments: number, initialRotation: [number, number, number]) {
const points: { position: [number, number, number]; degree: number }[] = [];
for (let i = 0; i < segments; i++) {
const angleRadians = (i / segments) * Math.PI * 2;
@@ -92,10 +87,12 @@ function RoboticArmAnimator({
}
return points;
}
- useEffect(() => {
- const points = generateRingPointsWithDegrees(CIRCLE_RADIUS, 64);
- }, [armBot.position]);
+ // Handle circle points based on armBot position
+ useEffect(() => {
+ const points = generateRingPointsWithDegrees(CIRCLE_RADIUS, 64, armBot.rotation);
+ setCirclePointsWithDegrees(points)
+ }, [armBot.rotation]);
// Function for find nearest Circlepoints Index
const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => {
@@ -121,9 +118,34 @@ function RoboticArmAnimator({
}, circlePoints[0]);
};
+ // Helper function to collect points and check forbidden degrees
+ const collectArcPoints = (startIdx: number, endIdx: number, clockwise: boolean) => {
+ const totalSegments = 64;
+ const arcPoints: [number, number, number][] = [];
+ let i = startIdx;
+
+ while (i !== (endIdx + (clockwise ? 1 : -1) + totalSegments) % totalSegments) {
+ const { degree, position } = circlePointsWithDegrees[i];
+ // Skip over
+ arcPoints.push(position);
+ i = (i + (clockwise ? 1 : -1) + totalSegments) % totalSegments;
+ }
+ return arcPoints;
+ };
+
+ //Range to restrict angle
+ const hasForbiddenDegrees = (arc: [number, number, number][]) => {
+ return arc.some(p => {
+ const idx = findNearestIndex(p, circlePoints);
+ const degree = circlePointsWithDegrees[idx]?.degree || 0;
+ return degree >= 271 && degree <= 300; // Forbidden range: 271° to 300°
+ });
+ };
+
// 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];
@@ -140,33 +162,6 @@ function RoboticArmAnimator({
const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + totalSegments) % totalSegments;
const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + totalSegments) % totalSegments;
- // Prepare degrees
- const degreesList = generateRingPointsWithDegrees(CIRCLE_RADIUS, totalSegments);
-
- // Helper function to collect points and check forbidden degrees
- const collectArcPoints = (startIdx: number, endIdx: number, clockwise: boolean) => {
- const arcPoints: [number, number, number][] = [];
- let i = startIdx;
-
- while (i !== (endIdx + (clockwise ? 1 : -1) + totalSegments) % totalSegments) {
- const { degree, position } = degreesList[i];
-
- // Skip over
- arcPoints.push(position);
- i = (i + (clockwise ? 1 : -1) + totalSegments) % totalSegments;
- }
- return arcPoints;
- };
-
- const hasForbiddenDegrees = (arc: [number, number, number][]) => {
- return arc.some(p => {
- const idx = findNearestIndex(p, circlePoints);
- const degree = degreesList[idx]?.degree || 0;
- return degree >= 271 && degree <= 300; // Forbidden range: 271° to 300°
- });
- };
-
-
// Try both directions
const arcClockwise = collectArcPoints(indexOfNearestStart, indexOfNearestEnd, true);
const arcCounterClockwise = collectArcPoints(indexOfNearestStart, indexOfNearestEnd, false);
@@ -206,8 +201,6 @@ function RoboticArmAnimator({
}
}, [circlePoints, currentPath]);
-
-
// Frame update for animation
useFrame((state, delta) => {
if (!ikSolver) return;
@@ -215,7 +208,6 @@ function RoboticArmAnimator({
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
if (!bone) return;
-
if (isPlaying) {
if (isReset) {
bone.position.copy(restPosition);
@@ -260,12 +252,17 @@ function RoboticArmAnimator({
ikSolver.update();
}
} else if (!isPlaying && currentPath.length === 0) {
+ progressRef.current = 0;
+ startTimeRef.current = null;
+ setCurrentPath([]);
+ setCustomCurvePoints([]);
bone.position.copy(restPosition);
+
}
ikSolver.update();
});
-
+ //Helper to Visible the Circle and Curve
return (
<>
{customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && (
@@ -278,50 +275,16 @@ function RoboticArmAnimator({
/>
)}
-
+
{/* Green ring */}
- {/* Markers at 90°, 180°, 270°, 360° */}
- {[90, 180, 270, 360].map((degree, index) => {
- const rad = (degree * Math.PI) / 180;
- const x = CIRCLE_RADIUS * Math.cos(rad);
- const z = CIRCLE_RADIUS * Math.sin(rad);
- const y = 0; // same plane as the ring (Y axis)
-
- return (
-
-
-
-
- );
- })}
-
- {/* Optional: Text Labels */}
-
- {[90, 180, 270, 360].map((degree, index) => {
- const rad = (degree * Math.PI) / 180;
- const x = CIRCLE_RADIUS * Math.cos(rad);
- const z = CIRCLE_RADIUS * Math.sin(rad);
- const y = 0.15; // lift the text slightly above the ring
-
- return (
-
- {degree}°
-
- );
- })}
-
>
diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
index ea6521b..035f25e 100644
--- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
@@ -5,7 +5,6 @@ import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '..
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_1.glb";
import { useThree } from "@react-three/fiber";
-import useModuleStore from '../../../../../store/useModuleStore';
import * as THREE from "three";
import MaterialAnimator from '../animator/materialAnimator';
@@ -24,16 +23,15 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
let startTime: number;
//zustand
const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
- const { activeModule } = useModuleStore();
const { isPlaying } = usePlayButtonStore();
- const { isReset, setReset } = useResetButtonStore();
+ const { isReset } = useResetButtonStore();
const { isPaused } = usePauseButtonStore();
-
function firstFrame() {
startTime = performance.now();
step();
}
+
function step() {
if (isPausedRef.current) {
if (!pauseTimeRef.current) {
@@ -87,6 +85,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
}
}
+
useEffect(() => {
isPausedRef.current = isPaused;
}, [isPaused]);
@@ -98,6 +97,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("init");
setPath([])
+ setIkSolver(null);
removeCurrentAction(armBot.modelUuid)
isPausedRef.current = false
pauseTimeRef.current = null
@@ -117,17 +117,17 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
useEffect(() => {
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
if (targetMesh) {
- targetMesh.visible = activeModule !== "simulation"
+ targetMesh.visible = (!isPlaying)
}
const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
- if (isPlaying) {
+ if (!isReset && isPlaying) {
//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, targetBones.position)
+ 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]));
}
@@ -142,9 +142,9 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
}, 3000);
return () => clearTimeout(timeoutId);
}
+ //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");
@@ -158,17 +158,20 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
}
logStatus(armBot.modelUuid, "Moving armBot from rest point to start position.")
}
+ // Moving to Pick to Drop position
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "picking" && armBot.currentAction) {
requestAnimationFrame(firstFrame);
}
+ //Moving to drop point to restPosition
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "dropping" && armBot.currentAction) {
requestAnimationFrame(firstFrame);
}
- }else{
+ } 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
@@ -177,7 +180,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
removeCurrentAction(armBot.modelUuid)
}
- }, [currentPhase, armBot, isPlaying, ikSolver])
+ }, [currentPhase, armBot, isPlaying, isReset, ikSolver])
function createCurveBetweenTwoPoints(p1: any, p2: any) {
@@ -224,9 +227,13 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
return (
<>
-
-
+ {!isReset && isPlaying && (
+ <>
+
+
+ >
+ )}
>
)
diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
index be41a95..4bd05a6 100644
--- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
@@ -11,7 +11,7 @@ type IKInstanceProps = {
modelUrl: string;
ikSolver: any;
setIkSolver: any
- armBot: any;
+ armBot: ArmBotStatus;
groupRef: any;
};
function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKInstanceProps) {
@@ -57,12 +57,6 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKIns
rotationMin: new THREE.Vector3(0, 0, 0),
rotationMax: new THREE.Vector3(2, 0, 0),
},
- // {
- // index: 1,
- // enabled: true,
- // rotationMin: new THREE.Vector3(0, -Math.PI, 0),
- // rotationMax: new THREE.Vector3(0, Math.PI, 0),
- // },
{ index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) },
{ index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) },
],
@@ -76,7 +70,7 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKIns
setSelectedArm(OOI.Target_Bone);
- scene.add(helper);
+ // scene.add(helper);
}, [cloned, gltf, setIkSolver]);
@@ -86,10 +80,10 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKIns
setSelectedArm(groupRef.current?.getObjectByName(targetBoneName))
}}>
{/* {selectedArm && } */}
diff --git a/app/src/modules/simulation/roboticArm/roboticArm.tsx b/app/src/modules/simulation/roboticArm/roboticArm.tsx
index e112884..91ccb55 100644
--- a/app/src/modules/simulation/roboticArm/roboticArm.tsx
+++ b/app/src/modules/simulation/roboticArm/roboticArm.tsx
@@ -1,24 +1,37 @@
-import { useEffect } from "react";
-import RoboticArmInstances from "./instances/roboticArmInstances";
+import { useEffect, useState } from "react";
import { useArmBotStore } from "../../../store/simulation/useArmBotStore";
-import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
-import ArmBotUI from "../ui/arm/armBotUI";
+import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
+import ArmBotUI from "../ui/arm/armBotUI";
+import RoboticArmInstances from "./instances/roboticArmInstances";
function RoboticArm() {
- const { armBots } = useArmBotStore();
+ const { armBots, getArmBotById } = useArmBotStore();
const { selectedEventSphere } = useSelectedEventSphere();
- const { selectedEventData } = useSelectedEventData();
const { isPlaying } = usePlayButtonStore();
+ const [isArmBotSelected, setIsArmBotSelected] = useState(false);
useEffect(() => {
// console.log('armBots: ', armBots);
}, [armBots])
+ useEffect(() => {
+ if (selectedEventSphere) {
+ const selectedArmBot = getArmBotById(selectedEventSphere.userData.modelUuid);
+ if (selectedArmBot) {
+ setIsArmBotSelected(true);
+ } else {
+ setIsArmBotSelected(false);
+ }
+ }
+ }, [selectedEventSphere])
+
return (
<>
+
- {selectedEventSphere && selectedEventData?.data.type === "roboticArm" && !isPlaying &&
+
+ {isArmBotSelected && !isPlaying &&
< ArmBotUI />
}
@@ -26,4 +39,4 @@ function RoboticArm() {
);
}
-export default RoboticArm;
+export default RoboticArm;
\ No newline at end of file
diff --git a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
index fdb271c..e1f7fbf 100644
--- a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
+++ b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
@@ -3,195 +3,200 @@ import * as THREE from "three";
import { ThreeEvent, useThree } from "@react-three/fiber";
import { useProductStore } from "../../../../store/simulation/useProductStore";
import {
- useSelectedEventData,
- useSelectedProduct,
+ useSelectedEventData,
+ useSelectedProduct,
} from "../../../../store/simulation/useSimulationStore";
type OnUpdateCallback = (object: THREE.Object3D) => void;
export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
- const { getEventByModelUuid, updateAction, getActionByUuid } =
- useProductStore();
- const { selectedEventData } = useSelectedEventData();
- const { selectedProduct } = useSelectedProduct();
- const { camera, gl, controls } = useThree();
- const activeObjRef = useRef(null);
- const planeRef = useRef(
- new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
- );
- const offsetRef = useRef(new THREE.Vector3());
- const initialPositionRef = useRef(new THREE.Vector3());
+ const { getEventByModelUuid, updateAction, getActionByUuid } =
+ useProductStore();
+ const { selectedEventData } = useSelectedEventData();
+ const { selectedProduct } = useSelectedProduct();
+ const { camera, gl, controls } = useThree();
+ const activeObjRef = useRef(null);
+ const planeRef = useRef(
+ new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
+ );
+ const offsetRef = useRef(new THREE.Vector3());
+ const initialPositionRef = useRef(new THREE.Vector3());
- const raycaster = new THREE.Raycaster();
- const pointer = new THREE.Vector2();
- const [objectWorldPos, setObjectWorldPos] = useState(new THREE.Vector3());
+ const raycaster = new THREE.Raycaster();
+ const pointer = new THREE.Vector2();
+ const [objectWorldPos, setObjectWorldPos] = useState(new THREE.Vector3());
- const handlePointerDown = (e: ThreeEvent) => {
- e.stopPropagation();
+ const handlePointerDown = (e: ThreeEvent) => {
+ e.stopPropagation();
- let obj: THREE.Object3D | null = e.object;
+ let obj: THREE.Object3D | null = e.object;
- // Traverse up until we find modelUuid in userData
- while (obj && !obj.userData?.modelUuid) {
- obj = obj.parent;
- }
-
- if (!obj) return;
-
- // Disable orbit controls while dragging
- if (controls) (controls as any).enabled = false;
-
- activeObjRef.current = obj;
- initialPositionRef.current.copy(obj.position);
-
- // Get world position
- setObjectWorldPos(obj.getWorldPosition(objectWorldPos));
-
- // Set plane at the object's Y level
- planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y);
-
- // Convert pointer to NDC
- 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;
-
- // Raycast to intersection
- raycaster.setFromCamera(pointer, camera);
- const intersection = new THREE.Vector3();
- raycaster.ray.intersectPlane(planeRef.current, intersection);
-
- // Calculate offset
- offsetRef.current.copy(objectWorldPos).sub(intersection);
-
- // Start listening for drag
- gl.domElement.addEventListener("pointermove", handlePointerMove);
- gl.domElement.addEventListener("pointerup", handlePointerUp);
- };
-
- const handlePointerMove = (e: PointerEvent) => {
- if (!activeObjRef.current) return;
- if (selectedEventData?.data.type === "roboticArm") {
- const selectedArmBot = getEventByModelUuid(
- selectedProduct.productId,
- selectedEventData.data.modelUuid
- );
- if (!selectedArmBot) 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
- );
- if (!intersects) return;
-
- // Add offset for dragging
- intersection.add(offsetRef.current);
-
- // 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) {
- 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);
- }
-
- // CONSTRAIN MOVEMENT HERE:
- const centerX = selectedArmBot.position[0];
- const centerZ = selectedArmBot.position[2];
- const minDistance = 1.2;
- const maxDistance = 2;
-
- let deltaX = targetPosition.x - centerX;
- let deltaZ = targetPosition.z - centerZ;
-
- // Calculate angle in radians
- let angle = Math.atan2(deltaZ, deltaX); // [-PI, PI]
-
- // Convert angle to degrees
- let angleDeg = (angle * 180) / Math.PI; // [-180, 180]
-
- // Normalize to [0, 360]
- if (angleDeg < 0) {
- angleDeg += 360;
- }
-
- // Allow only angles between 90° and 270°
- if (angleDeg < 0 || angleDeg > 270) {
- // Clamp to nearest boundary (90° or 270°)
- const distanceTo90 = Math.abs(angleDeg - 0);
- const distanceTo270 = Math.abs(angleDeg - 270);
-
- if (distanceTo90 < distanceTo270) {
- angleDeg = 0;
- } else {
- return
- // angleDeg = 270;
+ // Traverse up until we find modelUuid in userData
+ while (obj && !obj.userData?.modelUuid) {
+ obj = obj.parent;
}
- // Update angle in radians
- angle = (angleDeg * Math.PI) / 180;
- }
+ if (!obj) return;
- // Clamp distance
- let distance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ);
- const clampedDistance = Math.min(
- Math.max(distance, minDistance),
- maxDistance
- );
+ // Disable orbit controls while dragging
+ if (controls) (controls as any).enabled = false;
- // Set final target position
- targetPosition.x = centerX + Math.cos(angle) * clampedDistance;
- targetPosition.z = centerZ + Math.sin(angle) * clampedDistance;
+ activeObjRef.current = obj;
+ initialPositionRef.current.copy(obj.position);
- // Clamp Y axis if needed
- targetPosition.y = Math.min(Math.max(targetPosition.y, 0.6), 1.9);
+ // Get world position
+ setObjectWorldPos(obj.getWorldPosition(objectWorldPos));
- // Convert to local if parent exists
- if (parent) {
- parent.worldToLocal(targetPosition);
- }
+ // Set plane at the object's Y level
+ planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y);
- // Update the object position
- activeObjRef.current.position.copy(targetPosition);
- }
- };
+ // Convert pointer to NDC
+ 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;
- const handlePointerUp = () => {
- if (controls) (controls as any).enabled = true;
+ // Raycast to intersection
+ raycaster.setFromCamera(pointer, camera);
+ const intersection = new THREE.Vector3();
+ raycaster.ray.intersectPlane(planeRef.current, intersection);
- if (activeObjRef.current) {
- // Pass the updated position to the onUpdate callback to persist it
- onUpdate(activeObjRef.current);
- }
+ // Calculate offset
+ offsetRef.current.copy(objectWorldPos).sub(intersection);
- gl.domElement.removeEventListener("pointermove", handlePointerMove);
- gl.domElement.removeEventListener("pointerup", handlePointerUp);
+ // Start listening for drag
+ gl.domElement.addEventListener("pointermove", handlePointerMove);
+ gl.domElement.addEventListener("pointerup", handlePointerUp);
+ };
- activeObjRef.current = null;
- };
+ const handlePointerMove = (e: PointerEvent) => {
+ if (!activeObjRef.current) return;
+ if (selectedEventData?.data.type === "roboticArm") {
+ const selectedArmBot = getEventByModelUuid(
+ selectedProduct.productId,
+ selectedEventData.data.modelUuid
+ );
+ if (!selectedArmBot) return;
+ // Check if Shift key is pressed
+ const isShiftKeyPressed = e.shiftKey;
- return { handlePointerDown };
+ // 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
+ );
+ if (!intersects) return;
+
+ // Add offset for dragging
+ intersection.add(offsetRef.current);
+
+ // 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) {
+ 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);
+ }
+
+ // CONSTRAIN MOVEMENT HERE:
+ const centerX = selectedArmBot.position[0];
+ const centerZ = selectedArmBot.position[2];
+ const minDistance = 1.2;
+ const maxDistance = 2;
+
+ const delta = new THREE.Vector3(targetPosition.x - centerX, 0, targetPosition.z - centerZ);
+
+ // Create quaternion from rotation
+ const robotEuler = new THREE.Euler(selectedArmBot.rotation[0], selectedArmBot.rotation[1], selectedArmBot.rotation[2]);
+ const robotQuaternion = new THREE.Quaternion().setFromEuler(robotEuler);
+
+ // Inverse rotate
+ const inverseQuaternion = robotQuaternion.clone().invert();
+ delta.applyQuaternion(inverseQuaternion);
+
+ // Angle in robot local space
+ let relativeAngle = Math.atan2(delta.z, delta.x);
+ let angleDeg = (relativeAngle * 180) / Math.PI;
+ if (angleDeg < 0) {
+ angleDeg += 360;
+ }
+
+ // Clamp angle
+ if (angleDeg < 0 || angleDeg > 270) {
+ const distanceTo90 = Math.abs(angleDeg - 0);
+ const distanceTo270 = Math.abs(angleDeg - 270);
+ if (distanceTo90 < distanceTo270) {
+ angleDeg = 0;
+ } else {
+ return;
+ }
+ relativeAngle = (angleDeg * Math.PI) / 180;
+ }
+
+ // Distance clamp
+ const distance = delta.length();
+ const clampedDistance = Math.min(Math.max(distance, minDistance), maxDistance);
+
+ // Calculate local target
+ const finalLocal = new THREE.Vector3(
+ Math.cos(relativeAngle) * clampedDistance,
+ 0,
+ Math.sin(relativeAngle) * clampedDistance
+ );
+
+ // Rotate back to world space
+ finalLocal.applyQuaternion(robotQuaternion);
+
+ targetPosition.x = centerX + finalLocal.x;
+ targetPosition.z = centerZ + finalLocal.z;
+
+
+ // Clamp Y axis if needed
+ targetPosition.y = Math.min(Math.max(targetPosition.y, 0.6), 1.9);
+
+ // Convert to local if parent exists
+ if (parent) {
+ parent.worldToLocal(targetPosition);
+ }
+
+ // Update the object position
+ activeObjRef.current.position.copy(targetPosition);
+ }
+ };
+
+ const handlePointerUp = () => {
+ if (controls) (controls as any).enabled = true;
+
+ if (activeObjRef.current) {
+ // Pass the updated position to the onUpdate callback to persist it
+ onUpdate(activeObjRef.current);
+ }
+
+ gl.domElement.removeEventListener("pointermove", handlePointerMove);
+ gl.domElement.removeEventListener("pointerup", handlePointerUp);
+
+ activeObjRef.current = null;
+ };
+
+ return { handlePointerDown };
}