crane animator
This commit is contained in:
@@ -5,7 +5,19 @@ import { Sphere, Box } from '@react-three/drei';
|
|||||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
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 { scene } = useThree();
|
||||||
const { assetStore } = useSceneContext();
|
const { assetStore } = useSceneContext();
|
||||||
const { resetAsset } = assetStore();
|
const { resetAsset } = assetStore();
|
||||||
@@ -15,12 +27,13 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
|
|||||||
|
|
||||||
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
||||||
const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
resetAsset(crane.modelUuid);
|
resetAsset(crane.modelUuid);
|
||||||
setAnimationPhase('idle');
|
setAnimationPhase('idle');
|
||||||
|
setCurrentTargetIndex(0);
|
||||||
} else if (animationPhase === 'idle') {
|
} else if (animationPhase === 'idle') {
|
||||||
setAnimationPhase('init-hook-adjust');
|
setAnimationPhase('init-hook-adjust');
|
||||||
}
|
}
|
||||||
@@ -89,10 +102,10 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
|
|||||||
|
|
||||||
setClampedPoints(newClampedPoints);
|
setClampedPoints(newClampedPoints);
|
||||||
setIsInside(newIsInside);
|
setIsInside(newIsInside);
|
||||||
}, [scene, points, crane.modelUuid]);
|
}, [crane.modelUuid]);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (!isPlaying || isPaused || !points || animationPhase === 'idle') return;
|
if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === 'idle') return;
|
||||||
|
|
||||||
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
@@ -101,7 +114,7 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
|
|||||||
const trolley = model.getObjectByName('trolley');
|
const trolley = model.getObjectByName('trolley');
|
||||||
const hook = model.getObjectByName('hook');
|
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();
|
const baseWorld = new THREE.Vector3();
|
||||||
base.getWorldPosition(baseWorld);
|
base.getWorldPosition(baseWorld);
|
||||||
@@ -112,10 +125,10 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
|
|||||||
if (!model.userData.animationData) {
|
if (!model.userData.animationData) {
|
||||||
model.userData.animationData = {
|
model.userData.animationData = {
|
||||||
originalHookY: hook.position.y,
|
originalHookY: hook.position.y,
|
||||||
targetHookY: points[0].y - baseWorld.y + 0.5,
|
targetHookY: clampedPoints[currentTargetIndex].y - baseWorld.y + 0.5,
|
||||||
targetDirection: new THREE.Vector2(),
|
targetDirection: new THREE.Vector2(),
|
||||||
targetTrolleyX: 0,
|
targetTrolleyX: 0,
|
||||||
targetWorldPosition: points[0].clone(),
|
targetWorldPosition: clampedPoints[currentTargetIndex].clone(),
|
||||||
finalHookTargetY: 0,
|
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) {
|
if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) {
|
||||||
hook.position.y = animationData.targetHookY;
|
hook.position.y = animationData.targetHookY;
|
||||||
setAnimationPhase('rotate-base');
|
setAnimationPhase('init-rotate-base');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'rotate-base': {
|
case 'init-rotate-base': {
|
||||||
const baseWorld = new THREE.Vector3();
|
|
||||||
base.getWorldPosition(baseWorld);
|
|
||||||
|
|
||||||
const baseForward = new THREE.Vector3(1, 0, 0);
|
const baseForward = new THREE.Vector3(1, 0, 0);
|
||||||
base.localToWorld(baseForward);
|
base.localToWorld(baseForward);
|
||||||
baseForward.sub(baseWorld);
|
baseForward.sub(baseWorld);
|
||||||
|
|
||||||
const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();
|
const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();
|
||||||
|
|
||||||
const targetWorld = clampedPoints[0];
|
const targetWorld = clampedPoints[currentTargetIndex];
|
||||||
const targetDir = new THREE.Vector2(
|
const targetDir = new THREE.Vector2(
|
||||||
targetWorld.x - baseWorld.x,
|
targetWorld.x - baseWorld.x,
|
||||||
targetWorld.z - baseWorld.z
|
targetWorld.z - baseWorld.z
|
||||||
@@ -163,15 +173,14 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
|
|||||||
base.rotation.y += Math.sign(angleDiff) * rotationSpeed;
|
base.rotation.y += Math.sign(angleDiff) * rotationSpeed;
|
||||||
} else {
|
} else {
|
||||||
base.rotation.y += angleDiff;
|
base.rotation.y += angleDiff;
|
||||||
const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone());
|
const localTarget = trolley.parent.worldToLocal(clampedPoints[currentTargetIndex].clone());
|
||||||
animationData.targetTrolleyX = localTarget?.x;
|
animationData.targetTrolleyX = localTarget?.x;
|
||||||
setAnimationPhase('move-trolley');
|
setAnimationPhase('init-move-trolley');
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'move-trolley': {
|
case 'init-move-trolley': {
|
||||||
const dx = animationData.targetTrolleyX - trolley.position.x;
|
const dx = animationData.targetTrolleyX - trolley.position.x;
|
||||||
const direction = Math.sign(dx);
|
const direction = Math.sign(dx);
|
||||||
trolley.position.x += direction * trolleySpeed;
|
trolley.position.x += direction * trolleySpeed;
|
||||||
@@ -180,19 +189,38 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR
|
|||||||
trolley.position.x = animationData.targetTrolleyX;
|
trolley.position.x = animationData.targetTrolleyX;
|
||||||
|
|
||||||
animationData.finalHookTargetY = hook.position.y - 0.5;
|
animationData.finalHookTargetY = hook.position.y - 0.5;
|
||||||
setAnimationPhase('final-hook-adjust');
|
setAnimationPhase('init-final-hook-adjust');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'final-hook-adjust': {
|
case 'init-final-hook-adjust': {
|
||||||
const dy = animationData.finalHookTargetY - hook.position.y;
|
const dy = animationData.finalHookTargetY - hook.position.y;
|
||||||
const direction = Math.sign(dy);
|
const direction = Math.sign(dy);
|
||||||
hook.position.y += direction * hookSpeed;
|
hook.position.y += direction * hookSpeed;
|
||||||
|
|
||||||
if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) {
|
if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) {
|
||||||
hook.position.y = animationData.finalHookTargetY;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function CraneInstances() {
|
|||||||
<React.Fragment key={crane.modelUuid}>
|
<React.Fragment key={crane.modelUuid}>
|
||||||
|
|
||||||
{crane.subType === "pillarJib" &&
|
{crane.subType === "pillarJib" &&
|
||||||
<PillarJibInstance crane={crane} />
|
<PillarJibInstance key={crane.modelUuid} crane={crane} />
|
||||||
}
|
}
|
||||||
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
@@ -1,21 +1,39 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import PillarJibAnimator from '../animator/pillarJibAnimator'
|
import PillarJibAnimator from '../animator/pillarJibAnimator'
|
||||||
import PillarJibHelper from '../helper/pillarJibHelper'
|
import PillarJibHelper from '../helper/pillarJibHelper'
|
||||||
|
|
||||||
function PillarJibInstance({ crane }: { crane: CraneStatus }) {
|
function PillarJibInstance({ crane }: { crane: CraneStatus }) {
|
||||||
|
const [animationPhase, setAnimationPhase] = useState<string>('idle');
|
||||||
|
|
||||||
const position1: [number, number, number] = [5, 1, -4];
|
const position1: [number, number, number] = [5, 1, -4];
|
||||||
const position2: [number, number, number] = [-2, 2, -2];
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<PillarJibAnimator
|
<PillarJibAnimator
|
||||||
|
key={crane.modelUuid}
|
||||||
crane={crane}
|
crane={crane}
|
||||||
points={[
|
points={[
|
||||||
new THREE.Vector3(...position1),
|
new THREE.Vector3(...position1),
|
||||||
new THREE.Vector3(...position2)
|
new THREE.Vector3(...position2)
|
||||||
]}
|
]}
|
||||||
|
animationPhase={animationPhase}
|
||||||
|
setAnimationPhase={setAnimationPhase}
|
||||||
|
onAnimationComplete={handleAnimationComplete}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PillarJibHelper crane={crane} />
|
<PillarJibHelper crane={crane} />
|
||||||
|
|||||||
Reference in New Issue
Block a user