crane animator

This commit is contained in:
2025-08-07 16:49:14 +05:30
parent 54740ffbfc
commit 2760e73ab8
3 changed files with 67 additions and 21 deletions

View File

@@ -5,7 +5,19 @@ import { Sphere, Box } from '@react-three/drei';
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
import { useSceneContext } from '../../../../scene/sceneContext';
function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THREE.Vector3, THREE.Vector3]; }) {
function PillarJibAnimator({
crane,
points,
animationPhase,
setAnimationPhase,
onAnimationComplete
}: {
crane: CraneStatus;
points: [THREE.Vector3, THREE.Vector3];
animationPhase: string;
setAnimationPhase: (phase: string) => void;
onAnimationComplete: (action: string) => void;
}) {
const { scene } = useThree();
const { assetStore } = useSceneContext();
const { resetAsset } = assetStore();
@@ -15,12 +27,13 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);
const [animationPhase, setAnimationPhase] = useState<string>('idle'); // 0: idle, 1: init-hook-adjust, 2: 'rotate-base', 3: 'move-trolley', 4: 'final-hook-adjust'
const [currentTargetIndex, setCurrentTargetIndex] = useState<number>(0);
useEffect(() => {
if (!isPlaying) {
resetAsset(crane.modelUuid);
setAnimationPhase('idle');
setCurrentTargetIndex(0);
} else if (animationPhase === 'idle') {
setAnimationPhase('init-hook-adjust');
}
@@ -89,10 +102,10 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
setClampedPoints(newClampedPoints);
setIsInside(newIsInside);
}, [scene, points, crane.modelUuid]);
}, [crane.modelUuid]);
useFrame(() => {
if (!isPlaying || isPaused || !points || animationPhase === 'idle') return;
if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === 'idle') return;
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
if (!model) return;
@@ -101,7 +114,7 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
const trolley = model.getObjectByName('trolley');
const hook = model.getObjectByName('hook');
if (!base || !trolley || !hook || !clampedPoints || !trolley.parent) return;
if (!base || !trolley || !hook || !trolley.parent) return;
const baseWorld = new THREE.Vector3();
base.getWorldPosition(baseWorld);
@@ -112,10 +125,10 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
if (!model.userData.animationData) {
model.userData.animationData = {
originalHookY: hook.position.y,
targetHookY: points[0].y - baseWorld.y + 0.5,
targetHookY: clampedPoints[currentTargetIndex].y - baseWorld.y + 0.5,
targetDirection: new THREE.Vector2(),
targetTrolleyX: 0,
targetWorldPosition: points[0].clone(),
targetWorldPosition: clampedPoints[currentTargetIndex].clone(),
finalHookTargetY: 0,
};
}
@@ -132,22 +145,19 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) {
hook.position.y = animationData.targetHookY;
setAnimationPhase('rotate-base');
setAnimationPhase('init-rotate-base');
}
break;
}
case 'rotate-base': {
const baseWorld = new THREE.Vector3();
base.getWorldPosition(baseWorld);
case 'init-rotate-base': {
const baseForward = new THREE.Vector3(1, 0, 0);
base.localToWorld(baseForward);
baseForward.sub(baseWorld);
const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();
const targetWorld = clampedPoints[0];
const targetWorld = clampedPoints[currentTargetIndex];
const targetDir = new THREE.Vector2(
targetWorld.x - baseWorld.x,
targetWorld.z - baseWorld.z
@@ -163,15 +173,14 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
base.rotation.y += Math.sign(angleDiff) * rotationSpeed;
} else {
base.rotation.y += angleDiff;
const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone());
const localTarget = trolley.parent.worldToLocal(clampedPoints[currentTargetIndex].clone());
animationData.targetTrolleyX = localTarget?.x;
setAnimationPhase('move-trolley');
setAnimationPhase('init-move-trolley');
}
break;
}
case 'move-trolley': {
case 'init-move-trolley': {
const dx = animationData.targetTrolleyX - trolley.position.x;
const direction = Math.sign(dx);
trolley.position.x += direction * trolleySpeed;
@@ -180,19 +189,38 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
trolley.position.x = animationData.targetTrolleyX;
animationData.finalHookTargetY = hook.position.y - 0.5;
setAnimationPhase('final-hook-adjust');
setAnimationPhase('init-final-hook-adjust');
}
break;
}
case 'final-hook-adjust': {
case 'init-final-hook-adjust': {
const dy = animationData.finalHookTargetY - hook.position.y;
const direction = Math.sign(dy);
hook.position.y += direction * hookSpeed;
if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) {
hook.position.y = animationData.finalHookTargetY;
setAnimationPhase('idle');
if (currentTargetIndex < points.length - 1) {
setCurrentTargetIndex(currentTargetIndex + 1);
model.userData.animationData = {
originalHookY: hook.position.y,
targetHookY: clampedPoints[currentTargetIndex + 1].y - baseWorld.y + 0.5,
targetDirection: new THREE.Vector2(),
targetTrolleyX: 0,
targetWorldPosition: clampedPoints[currentTargetIndex + 1].clone(),
finalHookTargetY: 0,
};
setAnimationPhase('picking');
onAnimationComplete('picking');
} else {
setAnimationPhase('dropping');
onAnimationComplete('dropping');
}
}
break;
}

View File

@@ -12,7 +12,7 @@ function CraneInstances() {
<React.Fragment key={crane.modelUuid}>
{crane.subType === "pillarJib" &&
<PillarJibInstance crane={crane} />
<PillarJibInstance key={crane.modelUuid} crane={crane} />
}
</React.Fragment>

View File

@@ -1,21 +1,39 @@
import { useState } from 'react';
import * as THREE from 'three'
import PillarJibAnimator from '../animator/pillarJibAnimator'
import PillarJibHelper from '../helper/pillarJibHelper'
function PillarJibInstance({ crane }: { crane: CraneStatus }) {
const [animationPhase, setAnimationPhase] = useState<string>('idle');
const position1: [number, number, number] = [5, 1, -4];
const position2: [number, number, number] = [-2, 2, -2];
const handleAnimationComplete = (action: string) => {
if (action === 'picking') {
setTimeout(() => {
setAnimationPhase('init-hook-adjust');
}, 3000)
} else if (action === 'dropping') {
setTimeout(() => {
setAnimationPhase('idle');
}, 3000)
}
}
return (
<>
<PillarJibAnimator
key={crane.modelUuid}
crane={crane}
points={[
new THREE.Vector3(...position1),
new THREE.Vector3(...position2)
]}
animationPhase={animationPhase}
setAnimationPhase={setAnimationPhase}
onAnimationComplete={handleAnimationComplete}
/>
<PillarJibHelper crane={crane} />