Merge remote-tracking branch 'origin/simulation-armbot-v2' into v2

This commit is contained in:
2025-05-03 19:18:10 +05:30
4 changed files with 218 additions and 200 deletions

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useFrame } from "@react-three/fiber"; import { useFrame } from '@react-three/fiber';
import * as THREE from "three"; import * as THREE from 'three';
import { Line } from "@react-three/drei"; import { Line } from '@react-three/drei';
import { import {
useAnimationPlaySpeed, useAnimationPlaySpeed,
usePauseButtonStore, usePauseButtonStore,
usePlayButtonStore, usePlayButtonStore,
useResetButtonStore, useResetButtonStore
} from "../../../../../store/usePlayButtonStore"; } from '../../../../../store/usePlayButtonStore';
function RoboticArmAnimator({ function RoboticArmAnimator({
HandleCallback, HandleCallback,
@@ -16,24 +16,21 @@ function RoboticArmAnimator({
targetBone, targetBone,
armBot, armBot,
logStatus, logStatus,
path, path
}: any) { }: any) {
const progressRef = useRef(0); const progressRef = useRef(0);
const curveRef = useRef<THREE.Vector3[] | null>(null); const curveRef = useRef<THREE.Vector3[] | null>(null);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>( const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
[] const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
); const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null);
const [circlePoints, setCirclePoints] = useState<[number, number, number][]>( let curveHeight = 1.75
[] const totalDistanceRef = useRef(0);
); const startTimeRef = useRef<number | null>(null);
const [customCurvePoints, setCustomCurvePoints] = useState< const segmentDistancesRef = useRef<number[]>([]);
THREE.Vector3[] | null
>(null);
// Zustand stores // Zustand stores
const { isPlaying } = usePlayButtonStore(); const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore(); const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore(); const { isReset, setReset } = useResetButtonStore();
const { speed } = useAnimationPlaySpeed(); const { speed } = useAnimationPlaySpeed();
// Update path state whenever `path` prop changes // Update path state whenever `path` prop changes
@@ -41,20 +38,26 @@ function RoboticArmAnimator({
setCurrentPath(path); setCurrentPath(path);
}, [path]); }, [path]);
// Reset logic when `isPlaying` changes
useEffect(() => {
if (!isPlaying) {
setCurrentPath([]);
curveRef.current = null;
}
}, [isPlaying]);
// Handle circle points based on armBot position // Handle circle points based on armBot position
useEffect(() => { useEffect(() => {
const points = generateRingPoints(1.6, 64); const points = generateRingPoints(1.6, 64)
setCirclePoints(points); setCirclePoints(points);
}, [armBot.position]); }, [armBot.position]);
useEffect(() => {
if (isReset || !isPlaying) {
progressRef.current = 0;
curveRef.current = null;
setCurrentPath([]);
setCustomCurvePoints(null);
totalDistanceRef.current = 0;
startTimeRef.current = null;
segmentDistancesRef.current = [];
setReset(false);
}
}, [isReset, isPlaying])
function generateRingPoints(radius: any, segments: any) { function generateRingPoints(radius: any, segments: any) {
const points: [number, number, number][] = []; const points: [number, number, number][] = [];
for (let i = 0; i < segments; i++) { for (let i = 0; i < segments; i++) {
@@ -68,11 +71,7 @@ function RoboticArmAnimator({
return points; return points;
} }
const findNearestIndex = ( const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => {
nearestPoint: [number, number, number],
points: [number, number, number][],
epsilon = 1e-6
) => {
for (let i = 0; i < points.length; i++) { for (let i = 0; i < points.length; i++) {
const [x, y, z] = points[i]; const [x, y, z] = points[i];
if ( if (
@@ -89,168 +88,150 @@ function RoboticArmAnimator({
// Handle nearest points and final path (including arc points) // Handle nearest points and final path (including arc points)
useEffect(() => { useEffect(() => {
if (circlePoints.length > 0 && currentPath.length > 0) { if (circlePoints.length > 0 && currentPath.length > 0) {
const start = currentPath[0]; const start = currentPath[0];
const end = currentPath[currentPath.length - 1]; const end = currentPath[currentPath.length - 1];
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [ const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
number, const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];
number,
number
];
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [
number,
number,
number
];
const findNearest = (target: [number, number, number]) => { const findNearest = (target: [number, number, number]) => {
return circlePoints.reduce((nearest, point) => { return circlePoints.reduce((nearest, point) => {
const distance = Math.hypot( const distance = Math.hypot(target[0] - point[0], target[1] - point[1], target[2] - point[2]);
target[0] - point[0], const nearestDistance = Math.hypot(target[0] - nearest[0], target[1] - nearest[1], target[2] - nearest[2]);
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; return distance < nearestDistance ? point : nearest;
}, circlePoints[0]); }, circlePoints[0]);
}; };
const nearestToStart = findNearest(raisedStart); const nearestToStart = findNearest(raisedStart);
const nearestToEnd = findNearest(raisedEnd); const nearestToEnd = findNearest(raisedEnd);
const indexOfNearestStart = findNearestIndex( const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints);
nearestToStart,
circlePoints
);
const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints); const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);
// Find clockwise and counter-clockwise distances const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + 64) % 64;
const clockwiseDistance = const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + 64) % 64;
(indexOfNearestEnd - indexOfNearestStart + 64) % 64;
const counterClockwiseDistance =
(indexOfNearestStart - indexOfNearestEnd + 64) % 64;
const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance; const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance;
// Collect arc points between start and end
let arcPoints: [number, number, number][] = []; let arcPoints: [number, number, number][] = [];
if (clockwiseIsShorter) { if (clockwiseIsShorter) {
if (indexOfNearestStart <= indexOfNearestEnd) { if (indexOfNearestStart <= indexOfNearestEnd) {
arcPoints = circlePoints.slice( arcPoints = circlePoints.slice(indexOfNearestStart, indexOfNearestEnd + 1);
indexOfNearestStart,
indexOfNearestEnd + 1
);
} else { } else {
// Wrap around
arcPoints = [ arcPoints = [
...circlePoints.slice(indexOfNearestStart, 64), ...circlePoints.slice(indexOfNearestStart, 64),
...circlePoints.slice(0, indexOfNearestEnd + 1), ...circlePoints.slice(0, indexOfNearestEnd + 1)
]; ];
} }
} else if (indexOfNearestStart >= indexOfNearestEnd) { } else {
for ( if (indexOfNearestStart >= indexOfNearestEnd) {
let i = indexOfNearestStart; for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
i !== (indexOfNearestEnd - 1 + 64) % 64; arcPoints.push(circlePoints[i]);
i = (i - 1 + 64) % 64 }
) { } else {
arcPoints.push(circlePoints[i]); 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 = [ const pathVectors = [
new THREE.Vector3(start[0], start[1], start[2]), // start new THREE.Vector3(start[0], start[1], start[2]),
new THREE.Vector3(raisedStart[0], raisedStart[1], raisedStart[2]), // lift up new THREE.Vector3(start[0], curveHeight, start[2]),
new THREE.Vector3(nearestToStart[0], raisedStart[1], nearestToStart[2]), // move to arc start new THREE.Vector3(nearestToStart[0], curveHeight, nearestToStart[2]),
...arcPoints.map( ...arcPoints.map(point => new THREE.Vector3(point[0], curveHeight, point[2])),
(point) => new THREE.Vector3(point[0], raisedStart[1], point[2]) new THREE.Vector3(nearestToEnd[0], curveHeight, nearestToEnd[2]),
), new THREE.Vector3(end[0], curveHeight, end[2]),
new THREE.Vector3(nearestToEnd[0], raisedEnd[1], nearestToEnd[2]), // move from arc end new THREE.Vector3(end[0], end[1], end[2])
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, const pathSegments: [THREE.Vector3, THREE.Vector3][] = [];
false,
"centripetal", for (let i = 0; i < pathVectors.length - 1; i++) {
1 pathSegments.push([pathVectors[i], pathVectors[i + 1]]);
); }
const generatedPoints = customCurve.getPoints(100);
setCustomCurvePoints(generatedPoints); const segmentDistances = pathSegments.map(([p1, p2]) => p1.distanceTo(p2));
segmentDistancesRef.current = segmentDistances;
const totalDistance = segmentDistances.reduce((sum, d) => sum + d, 0);
totalDistanceRef.current = totalDistance;
const movementSpeed = speed * armBot.speed;
const totalMoveTime = totalDistance / movementSpeed;
const segmentTimes = segmentDistances.map(distance => (distance / totalDistance) * totalMoveTime);
setCustomCurvePoints(pathVectors);
} }
}, [circlePoints, currentPath]); }, [circlePoints, currentPath]);
// Frame update for animation // Frame update for animation
useFrame((_, delta) => { useFrame((state, delta) => {
if (!ikSolver) return; if (!ikSolver) return;
const bone = ikSolver.mesh.skeleton.bones.find( const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
(b: any) => b.name === targetBone
);
if (!bone) return; if (!bone) return;
if (isPlaying) { if (isPlaying) {
if (!isPaused && customCurvePoints && currentPath.length > 0) { if (!isPaused && customCurvePoints && customCurvePoints.length > 0) {
const curvePoints = customCurvePoints; const distances = segmentDistancesRef.current; // distances between each pair of points
const speedAdjustedProgress = const totalDistance = totalDistanceRef.current;
progressRef.current + speed * armBot.speed;
const index = Math.floor(speedAdjustedProgress);
if (index >= curvePoints.length) { progressRef.current += delta * (speed * armBot.speed);
// Reached the end of the curve const coveredDistance = progressRef.current;
let index = 0;
let accumulatedDistance = 0;
// Find which segment we are currently in
while (index < distances.length && coveredDistance > accumulatedDistance + distances[index]) {
accumulatedDistance += distances[index];
index++;
}
if (index < distances.length) {
const startPoint = customCurvePoints[index];
const endPoint = customCurvePoints[index + 1];
const segmentDistance = distances[index];
const t = (coveredDistance - accumulatedDistance) / segmentDistance;
if (startPoint && endPoint) {
const position = startPoint.clone().lerp(endPoint, t);
bone.position.copy(position);
}
}
if (progressRef.current >= totalDistance) {
HandleCallback(); HandleCallback();
setCurrentPath([]); setCurrentPath([]);
setCustomCurvePoints([]);
curveRef.current = null; curveRef.current = null;
progressRef.current = 0; progressRef.current = 0;
} else { startTimeRef.current = null;
const point = curvePoints[index];
bone.position.copy(point);
progressRef.current = speedAdjustedProgress;
} }
} else if (isPaused) {
logStatus(armBot.modelUuid, "Simulation Paused");
}
ikSolver.update(); ikSolver.update();
} else if (!isPlaying && currentPath.length === 0) { }
// Not playing anymore, reset to rest } else if ((!isPlaying && currentPath.length === 0) || isReset) {
bone.position.copy(restPosition); bone.position.copy(restPosition);
ikSolver.update(); ikSolver.update();
} }
}); });
return ( return (
<> <>
{customCurvePoints && currentPath && isPlaying && ( {customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && (
<mesh rotation={armBot.rotation} position={armBot.position}> <mesh rotation={armBot.rotation} position={armBot.position}>
<Line <Line
points={customCurvePoints.map( points={customCurvePoints.map((p) => [p.x, p.y, p.z] as [number, number, number])}
(p) => [p.x, p.y, p.z] as [number, number, number]
)}
color="green" color="green"
lineWidth={5} lineWidth={5}
dashed={false} dashed={false}
/> />
</mesh> </mesh>
)} )}
<mesh <mesh position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]} rotation={[-Math.PI / 2, 0, 0]}>
position={[
armBot.position[0],
armBot.position[1] + 1.5,
armBot.position[2],
]}
rotation={[-Math.PI / 2, 0, 0]}
visible={false}
>
<ringGeometry args={[1.59, 1.61, 64]} /> <ringGeometry args={[1.59, 1.61, 64]} />
<meshBasicMaterial color="green" side={THREE.DoubleSide} /> <meshBasicMaterial color="green" side={THREE.DoubleSide} />
</mesh> </mesh>
@@ -258,4 +239,4 @@ function RoboticArmAnimator({
); );
} }
export default RoboticArmAnimator; export default RoboticArmAnimator;

View File

@@ -51,13 +51,12 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
pauseTimeRef.current = null; pauseTimeRef.current = null;
} }
const elapsedTime = performance.now() - startTime; const elapsedTime = performance.now() - startTime;
if (elapsedTime < 1500) { if (elapsedTime < 1000) {
// Wait until 1500ms has passed // Wait until 1500ms has passed
requestAnimationFrame(step); requestAnimationFrame(step);
return; return;
} }
if (currentPhase === "picking") { if (currentPhase === "picking") {
setArmBotActive(armBot.modelUuid, true); setArmBotActive(armBot.modelUuid, true);
setArmBotState(armBot.modelUuid, "running"); setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("start-to-end"); setCurrentPhase("start-to-end");
@@ -75,7 +74,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
} }
logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
} else if (currentPhase === "dropping") { } else if (currentPhase === "dropping") {
setArmBotActive(armBot.modelUuid, true); setArmBotActive(armBot.modelUuid, true);
setArmBotState(armBot.modelUuid, "running"); setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("end-to-rest"); setCurrentPhase("end-to-rest");
@@ -91,35 +89,26 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
} }
logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
} }
} }
useEffect(() => { useEffect(() => {
isPausedRef.current = isPaused; isPausedRef.current = isPaused;
}, [isPaused]); }, [isPaused]);
useEffect(() => { useEffect(() => {
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid); if (isReset || !isPlaying) {
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") logStatus(armBot.modelUuid, "Simulation Play Reset Successfully")
removeCurrentAction(armBot.modelUuid) removeCurrentAction(armBot.modelUuid)
setArmBotActive(armBot.modelUuid, true) setArmBotActive(armBot.modelUuid, false)
setArmBotState(armBot.modelUuid, "running") setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("init-to-rest"); setCurrentPhase("init");
isPausedRef.current = false isPausedRef.current = false
pauseTimeRef.current = null pauseTimeRef.current = null
isPausedRef.current = false
startTime = 0 startTime = 0
const targetBones = ikSolver?.mesh.skeleton.bones.find(
(b: any) => b.name === targetBone
);
if (targetBones) { if (targetBones) {
let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position) let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
if (curve) { if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z])); setPath(curve.points.map(point => [point.x, point.y, point.z]));
} }
@@ -127,6 +116,16 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setReset(false); setReset(false);
logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.")
} }
}, [isReset, isPlaying])
useEffect(() => {
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
if (targetMesh) {
targetMesh.visible = activeModule !== "simulation"
}
const targetBones = ikSolver?.mesh.skeleton.bones.find(
(b: any) => b.name === targetBone
);
if (isPlaying) { if (isPlaying) {
//Moving armBot from initial point to rest position. //Moving armBot from initial point to rest position.
@@ -136,7 +135,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setArmBotState(armBot.modelUuid, "running") setArmBotState(armBot.modelUuid, "running")
setCurrentPhase("init-to-rest"); setCurrentPhase("init-to-rest");
if (targetBones) { if (targetBones) {
let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition) let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position)
if (curve) { if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z])); setPath(curve.points.map(point => [point.x, point.y, point.z]));
} }
@@ -157,10 +156,9 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setArmBotActive(armBot.modelUuid, true); setArmBotActive(armBot.modelUuid, true);
setArmBotState(armBot.modelUuid, "running"); setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("rest-to-start"); setCurrentPhase("rest-to-start");
let actiondata = getActionByUuid(selectedProduct.productId, selectedAction.actionId)
const startPoint = armBot.point.actions[0].process.startPoint; const startPoint = armBot.point.actions[0].process.startPoint;
if (startPoint) { if (startPoint) {
let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2])); let curve = createCurveBetweenTwoPoints(targetBones.position, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
if (curve) { if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z])); setPath(curve.points.map(point => [point.x, point.y, point.z]));
} }
@@ -206,16 +204,19 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
// logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") // logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
} }
} else { } else {
logStatus(armBot.modelUuid, "Simulation Play Stopped") logStatus(armBot.modelUuid, "Simulation Play Exited")
setArmBotActive(armBot.modelUuid, false) setArmBotActive(armBot.modelUuid, false)
setArmBotState(armBot.modelUuid, "idle") setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("init"); setCurrentPhase("init");
setPath([]) setPath([])
isPausedRef.current = false
pauseTimeRef.current = null
isPausedRef.current = false
startTime = 0
removeCurrentAction(armBot.modelUuid) removeCurrentAction(armBot.modelUuid)
} }
}, [currentPhase, armBot, isPlaying, ikSolver, isReset]) }, [currentPhase, armBot, isPlaying, ikSolver])
function createCurveBetweenTwoPoints(p1: any, p2: any) { function createCurveBetweenTwoPoints(p1: any, p2: any) {
@@ -258,6 +259,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
} }
} }
const logStatus = (id: string, status: string) => { const logStatus = (id: string, status: string) => {
// //
} }

View File

@@ -70,7 +70,7 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKIns
setSelectedArm(OOI.Target_Bone); setSelectedArm(OOI.Target_Bone);
// scene.add(helper); scene.add(helper);
}, [cloned, gltf, setIkSolver]); }, [cloned, gltf, setIkSolver]);

View File

@@ -1,10 +1,19 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import * as THREE from "three"; import * as THREE from "three";
import { ThreeEvent, useThree } from "@react-three/fiber"; import { ThreeEvent, useThree } from "@react-three/fiber";
import { useProductStore } from "../../../../store/simulation/useProductStore";
import {
useSelectedEventData,
useSelectedProduct,
} from "../../../../store/simulation/useSimulationStore";
type OnUpdateCallback = (object: THREE.Object3D) => void; type OnUpdateCallback = (object: THREE.Object3D) => void;
export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
const { getEventByModelUuid, updateAction, getActionByUuid } =
useProductStore();
const { selectedEventData } = useSelectedEventData();
const { selectedProduct } = useSelectedProduct();
const { camera, gl, controls } = useThree(); const { camera, gl, controls } = useThree();
const activeObjRef = useRef<THREE.Object3D | null>(null); const activeObjRef = useRef<THREE.Object3D | null>(null);
const planeRef = useRef<THREE.Plane>( const planeRef = useRef<THREE.Plane>(
@@ -34,7 +43,6 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
activeObjRef.current = obj; activeObjRef.current = obj;
initialPositionRef.current.copy(obj.position); initialPositionRef.current.copy(obj.position);
// Get world position // Get world position
setObjectWorldPos(obj.getWorldPosition(objectWorldPos)); setObjectWorldPos(obj.getWorldPosition(objectWorldPos));
@@ -62,57 +70,84 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
const handlePointerMove = (e: PointerEvent) => { const handlePointerMove = (e: PointerEvent) => {
if (!activeObjRef.current) return; 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;
// Check if Shift key is pressed // Get the mouse position relative to the canvas
const isShiftKeyPressed = e.shiftKey; 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;
// Get the mouse position relative to the canvas // Update raycaster to point to the mouse position
const rect = gl.domElement.getBoundingClientRect(); raycaster.setFromCamera(pointer, camera);
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 // Create a vector to store intersection point
raycaster.setFromCamera(pointer, camera); const intersection = new THREE.Vector3();
const intersects = raycaster.ray.intersectPlane(
planeRef.current,
intersection
);
if (!intersects) return;
// Create a vector to store intersection point // Add offset for dragging
const intersection = new THREE.Vector3(); intersection.add(offsetRef.current);
const intersects = raycaster.ray.intersectPlane(
planeRef.current,
intersection
);
if (!intersects) return;
// Add offset for dragging // Get the parent's world matrix if exists
intersection.add(offsetRef.current); const parent = activeObjRef.current.parent;
const targetPosition = new THREE.Vector3();
// Get the parent's world matrix if exists // OnPointerDown
const parent = activeObjRef.current.parent; initialPositionRef.current.copy(objectWorldPos);
const targetPosition = new THREE.Vector3();
// OnPointerDown // OnPointerMove
initialPositionRef.current.copy(objectWorldPos); if (isShiftKeyPressed) {
const { x: initialX, y: initialY } = initialPositionRef.current;
const { x: objectX, z: objectZ } = objectWorldPos;
// OnPointerMove const deltaX = intersection.x - initialX;
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);
}
targetPosition.set(objectX, initialY + deltaX, objectZ); // CONSTRAIN MOVEMENT HERE:
} else { const centerX = selectedArmBot.position[0];
// For free movement const centerZ = selectedArmBot.position[2];
targetPosition.copy(intersection); const minDistance = 1.2; // 1.4 radius
const maxDistance = 2; // 2 radius
const deltaX = targetPosition.x - centerX;
const deltaZ = targetPosition.z - centerZ;
const distance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ);
if (distance < minDistance || distance > maxDistance) {
const angle = Math.atan2(deltaZ, deltaX);
const clampedDistance = Math.min(
Math.max(distance, minDistance),
maxDistance
);
targetPosition.x = centerX + Math.cos(angle) * clampedDistance;
targetPosition.z = centerZ + Math.sin(angle) * clampedDistance;
}
targetPosition.y = Math.min(Math.max(targetPosition.y, 0.6), 1.5);
// 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);
} }
// 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);
}; };
const handlePointerUp = () => { const handlePointerUp = () => {