diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 95a531c..04bfbba 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -1,82 +1,40 @@ +import { useEffect, useState } from 'react'; import * as THREE from 'three'; -import { useEffect, useMemo } from 'react'; -import { useThree } from '@react-three/fiber'; +import { useFrame, useThree } from '@react-three/fiber'; +import { Sphere, Box } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; -function PillarJibAnimator({ crane }: { crane: CraneStatus }) { +function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THREE.Vector3, THREE.Vector3]; }) { const { scene } = useThree(); + const { assetStore } = useSceneContext(); + const { resetAsset } = assetStore(); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + + const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); + const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); + const [animationPhase, setAnimationPhase] = useState('idle'); // 0: idle, 1: init-hook-adjust, 2: 'rotate-base', 3: 'move-trolley', 4: 'final-hook-adjust' + + useEffect(() => { + if (!isPlaying) { + resetAsset(crane.modelUuid); + setAnimationPhase('idle'); + } else if (animationPhase === 'idle') { + setAnimationPhase('init-hook-adjust'); + } + }, [isPlaying, scene, crane.modelUuid]); useEffect(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); - - if (model) { - const base = model.getObjectByName('base'); - const trolley = model.getObjectByName('trolley'); - const hook = model.getObjectByName('hook'); - - if (base && trolley && hook) { - let trolleyDir = 1; - let hookDir = 1; - - const trolleySpeed = 0.01; - const hookSpeed = 0.01; - const rotationSpeed = 0.005; - - const trolleyMinOffset = -1; - const trolleyMaxOffset = 1.75; - const hookMinOffset = 0.25; - const hookMaxOffset = -1.5; - - const originalTrolleyX = trolley.position.x; - const originalHookY = hook.position.y; - - const animate = () => { - if (base) { - base.rotation.y += rotationSpeed; - } - - if (trolley) { - trolley.position.x += trolleyDir * trolleySpeed; - if (trolley.position.x >= originalTrolleyX + trolleyMaxOffset || - trolley.position.x <= originalTrolleyX + trolleyMinOffset) { - trolleyDir *= -1; - } - } - - if (hook) { - hook.position.y += hookDir * hookSpeed; - if (hook.position.y >= originalHookY + hookMinOffset || - hook.position.y <= originalHookY + hookMaxOffset) { - hookDir *= -1; - } - } - - requestAnimationFrame(animate); - }; - - animate(); - } - } - }, [crane, scene]); - - return ( - - ); -} - -export default PillarJibAnimator; - -function PillarJibHelper({ crane }: { crane: CraneStatus }) { - const { scene } = useThree(); - - const { geometry, position } = useMemo(() => { - const model = scene.getObjectByProperty('uuid', crane.modelUuid); - if (!model) return { geometry: null, position: null }; + if (!model) return; const base = model.getObjectByName('base'); const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook) return { geometry: null, position: null }; + if (!base || !trolley || !hook) return; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); @@ -87,47 +45,186 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) { const hookWorld = new THREE.Vector3(); hook.getWorldPosition(hookWorld); + const trolleyMinOffset = -1; + const trolleyMaxOffset = 1.75; + const hookMinOffset = 0.25; + const hookMaxOffset = -1.5; + const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); - const outerRadius = distFromBase + 1.75; - const innerRadius = Math.max(distFromBase - 1, 0.05); - const height = (0.25 - (-1.5)); - const cylinderYPosition = hookWorld.y + (height / 2) + (-1.5 + 0.25) / 2; + const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05); + const outerRadius = Math.max( + new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length() + trolleyMaxOffset, + innerRadius + ); - const shape = new THREE.Shape(); - shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + const yMin = hookWorld.y + hookMaxOffset; + const yMax = hookWorld.y + hookMinOffset; - const hole = new THREE.Path(); - hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); - shape.holes.push(hole); + function clampToCylinder(pos: THREE.Vector3) { + const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z); + const distance = xzDist.length(); - const extrudeSettings = { - depth: height, - bevelEnabled: false, - steps: 1 - }; + let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius); + if (distance > 0) { + clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius)); + } - const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); - const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance); + const y = THREE.MathUtils.clamp(pos.y, yMin, yMax); - return { geometry, position }; - }, [scene, crane.modelUuid]); + return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y); + } - if (!geometry || !position) return null; + const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()]; + const newIsInside: [boolean, boolean] = [false, false]; + + points.forEach((point, i) => { + const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length(); + const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius; + const insideY = point.y >= yMin && point.y <= yMax; + newIsInside[i] = insideXZ && insideY; + + newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point); + }); + + setClampedPoints(newClampedPoints); + setIsInside(newIsInside); + }, [scene, points, crane.modelUuid]); + + useFrame(() => { + if (!isPlaying || isPaused || !points || animationPhase === 'idle') return; + + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!model) return; + + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (!base || !trolley || !hook || !clampedPoints || !trolley.parent) return; + + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + if (!model.userData.animationData) { + model.userData.animationData = { + originalHookY: hook.position.y, + targetHookY: points[0].y - baseWorld.y + 0.5, + targetDirection: new THREE.Vector2(), + targetTrolleyX: 0, + targetWorldPosition: points[0].clone(), + finalHookTargetY: 0, + }; + } + + const { animationData } = model.userData; + const hookSpeed = 0.01 * speed; + const rotationSpeed = 0.005 * speed; + const trolleySpeed = 0.01 * speed; + + switch (animationPhase) { + case 'init-hook-adjust': { + const direction = Math.sign(animationData.targetHookY - hook.position.y); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { + hook.position.y = animationData.targetHookY; + setAnimationPhase('rotate-base'); + } + break; + } + + case 'rotate-base': { + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + 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 targetDir = new THREE.Vector2( + targetWorld.x - baseWorld.x, + targetWorld.z - baseWorld.z + ).normalize(); + + const currentAngle = Math.atan2(currentDir.y, currentDir.x); + const targetAngle = Math.atan2(targetDir.y, targetDir.x); + let angleDiff = currentAngle - targetAngle; + + angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff)); + + if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) { + base.rotation.y += Math.sign(angleDiff) * rotationSpeed; + } else { + base.rotation.y += angleDiff; + const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); + animationData.targetTrolleyX = localTarget?.x; + setAnimationPhase('move-trolley'); + } + + break; + } + + case 'move-trolley': { + const dx = animationData.targetTrolleyX - trolley.position.x; + const direction = Math.sign(dx); + trolley.position.x += direction * trolleySpeed; + + if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { + trolley.position.x = animationData.targetTrolleyX; + + animationData.finalHookTargetY = hook.position.y - 0.5; + setAnimationPhase('final-hook-adjust'); + } + break; + } + + case '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'); + console.log('hii'); + } + break; + } + } + }); + + if (!clampedPoints) return null; return ( - - - + <> + {points.map((point, i) => ( + + + + ))} + + {clampedPoints.map((point, i) => ( + + + + ))} + ); } + +export default PillarJibAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx new file mode 100644 index 0000000..945cf2f --- /dev/null +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -0,0 +1,77 @@ +import { useMemo } from "react"; +import * as THREE from "three"; +import { useThree } from "@react-three/fiber"; + +function PillarJibHelper({ crane }: { crane: CraneStatus }) { + const { scene } = useThree(); + + const { geometry, position } = useMemo(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!model) return { geometry: null, position: null }; + + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (!base || !trolley || !hook) return { geometry: null, position: null }; + + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const trolleyWorld = new THREE.Vector3(); + trolley.getWorldPosition(trolleyWorld); + + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + const trolleyMinOffset = -1; + const trolleyMaxOffset = 1.75; + const hookMinOffset = 0.25; + const hookMaxOffset = -1.5; + + const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); + const outerRadius = distFromBase + trolleyMaxOffset; + const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05); + const height = (hookMinOffset - (hookMaxOffset)); + const cylinderYPosition = hookWorld.y + (height / 2) + (hookMaxOffset + hookMinOffset) / 2; + + const shape = new THREE.Shape(); + shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + + const hole = new THREE.Path(); + hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); + shape.holes.push(hole); + + const extrudeSettings = { + depth: height, + bevelEnabled: false, + steps: 1 + }; + + const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); + const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + + return { geometry, position }; + }, [scene, crane.modelUuid]); + + if (!geometry || !position) return null; + + return ( + + + + ); +} + +export default PillarJibHelper; \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index bef6a17..a736a24 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -1,14 +1,27 @@ +import * as THREE from 'three' import PillarJibAnimator from '../animator/pillarJibAnimator' +import PillarJibHelper from '../helper/pillarJibHelper' function PillarJibInstance({ crane }: { crane: CraneStatus }) { + const position1: [number, number, number] = [5, 1, -4]; + const position2: [number, number, number] = [-2, 2, -2]; + return ( <> - + + + ) } -export default PillarJibInstance \ No newline at end of file +export default PillarJibInstance; \ No newline at end of file