v2 #80
|
@ -1,7 +1,7 @@
|
|||
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 { Line, Text } from '@react-three/drei';
|
||||
import {
|
||||
useAnimationPlaySpeed,
|
||||
usePauseButtonStore,
|
||||
|
@ -124,7 +124,6 @@ function RoboticArmAnimator({
|
|||
// 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];
|
||||
|
||||
|
@ -137,31 +136,50 @@ function RoboticArmAnimator({
|
|||
const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints);
|
||||
const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);
|
||||
|
||||
const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + 64) % 64;
|
||||
const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + 64) % 64;
|
||||
const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance;
|
||||
const totalSegments = 64;
|
||||
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);
|
||||
|
||||
const clockwiseForbidden = hasForbiddenDegrees(arcClockwise);
|
||||
const counterClockwiseForbidden = hasForbiddenDegrees(arcCounterClockwise);
|
||||
|
||||
let arcPoints: [number, number, number][] = [];
|
||||
|
||||
if (clockwiseIsShorter) {
|
||||
if (indexOfNearestStart <= indexOfNearestEnd) {
|
||||
arcPoints = circlePoints.slice(indexOfNearestStart, indexOfNearestEnd + 1);
|
||||
if (!clockwiseForbidden && (clockwiseDistance <= counterClockwiseDistance || counterClockwiseForbidden)) {
|
||||
arcPoints = arcClockwise;
|
||||
} else {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
arcPoints = arcCounterClockwise;
|
||||
}
|
||||
|
||||
const pathVectors = [
|
||||
|
@ -174,9 +192,7 @@ function RoboticArmAnimator({
|
|||
new THREE.Vector3(end[0], end[1], end[2])
|
||||
];
|
||||
|
||||
|
||||
const pathSegments: [THREE.Vector3, THREE.Vector3][] = [];
|
||||
|
||||
for (let i = 0; i < pathVectors.length - 1; i++) {
|
||||
pathSegments.push([pathVectors[i], pathVectors[i + 1]]);
|
||||
}
|
||||
|
@ -186,16 +202,12 @@ function RoboticArmAnimator({
|
|||
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]);
|
||||
|
||||
|
||||
|
||||
// Frame update for animation
|
||||
useFrame((state, delta) => {
|
||||
if (!ikSolver) return;
|
||||
|
@ -266,10 +278,52 @@ function RoboticArmAnimator({
|
|||
/>
|
||||
</mesh>
|
||||
)}
|
||||
<mesh position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<group position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]}>
|
||||
{/* Green ring */}
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<ringGeometry args={[CIRCLE_RADIUS, CIRCLE_RADIUS + 0.02, 64]} />
|
||||
<meshBasicMaterial color="green" side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
|
||||
{/* 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 (
|
||||
<mesh key={index} position={[x, y, z]}>
|
||||
<sphereGeometry args={[0.05, 16, 16]} />
|
||||
<meshStandardMaterial color="red" />
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 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 (
|
||||
<Text
|
||||
key={`text-${index}`}
|
||||
position={[x, y, z]}
|
||||
fontSize={0.2}
|
||||
color="yellow"
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{degree}°
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
|
||||
</group>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -164,6 +164,17 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "dropping" && armBot.currentAction) {
|
||||
requestAnimationFrame(firstFrame);
|
||||
}
|
||||
}else{
|
||||
logStatus(armBot.modelUuid, "Simulation Play Exited")
|
||||
setArmBotActive(armBot.modelUuid, false)
|
||||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("init");
|
||||
setPath([])
|
||||
isPausedRef.current = false
|
||||
pauseTimeRef.current = null
|
||||
isPausedRef.current = false
|
||||
startTime = 0
|
||||
removeCurrentAction(armBot.modelUuid)
|
||||
}
|
||||
|
||||
}, [currentPhase, armBot, isPlaying, ikSolver])
|
||||
|
|
|
@ -139,15 +139,16 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
|
|||
}
|
||||
|
||||
// Allow only angles between 90° and 270°
|
||||
if (angleDeg < 95 || angleDeg > 270) {
|
||||
if (angleDeg < 0 || angleDeg > 270) {
|
||||
// Clamp to nearest boundary (90° or 270°)
|
||||
const distanceTo90 = Math.abs(angleDeg - 95);
|
||||
const distanceTo90 = Math.abs(angleDeg - 0);
|
||||
const distanceTo270 = Math.abs(angleDeg - 270);
|
||||
|
||||
if (distanceTo90 < distanceTo270) {
|
||||
angleDeg = 95;
|
||||
angleDeg = 0;
|
||||
} else {
|
||||
angleDeg = 270;
|
||||
return
|
||||
// angleDeg = 270;
|
||||
}
|
||||
|
||||
// Update angle in radians
|
||||
|
|
Loading…
Reference in New Issue