feat: add MaterialAnimator component to handle 3D material animation with play/pause functionality.

This commit is contained in:
2025-12-23 10:11:33 +05:30
parent c701b08992
commit 17776679f2

View File

@@ -1,8 +1,8 @@
import React, { useEffect, useState, useRef } from 'react';
import * as THREE from 'three';
import { useFrame, useThree } from '@react-three/fiber';
import { usePauseButtonStore, usePlayButtonStore } from '../../../../../store/ui/usePlayButtonStore';
import { useSceneContext } from '../../../../scene/sceneContext';
import React, { useEffect, useState, useRef } from "react";
import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber";
import { usePauseButtonStore, usePlayButtonStore } from "../../../../../store/ui/usePlayButtonStore";
import { useSceneContext } from "../../../../scene/sceneContext";
interface MaterialAnimatorProps {
matRef: React.RefObject<THREE.Mesh>;
@@ -11,12 +11,7 @@ interface MaterialAnimatorProps {
onAnimationComplete?: () => void;
}
function MaterialAnimator({
matRef,
material,
currentSpeed,
onAnimationComplete
}: MaterialAnimatorProps) {
function MaterialAnimator({ matRef, material, currentSpeed, onAnimationComplete }: MaterialAnimatorProps) {
const { scene } = useThree();
const [targetPosition, setTargetPosition] = useState<THREE.Vector3 | null>(null);
const [isAnimating, setIsAnimating] = useState(false);
@@ -28,7 +23,7 @@ function MaterialAnimator({
totalDistance: 0,
pausedTime: 0,
isPaused: false,
lastFrameTime: 0
lastFrameTime: 0,
});
const { isPlaying } = usePlayButtonStore();
@@ -38,7 +33,7 @@ function MaterialAnimator({
const shouldPause = isGlobalPaused || material.isPaused || conveyor?.isPaused;
const getWorldPosition = (uuid: string): THREE.Vector3 | null => {
const obj = scene.getObjectByProperty('uuid', uuid);
const obj = scene.getObjectByProperty("uuid", uuid);
if (!obj) return null;
const position = new THREE.Vector3();
obj.getWorldPosition(position);
@@ -65,15 +60,18 @@ function MaterialAnimator({
}
const newTarget = getWorldPosition(material.next.pointUuid);
if (newTarget && matRef.current && !material.isPaused) {
animationState.current.startPosition.copy(matRef.current.position);
animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget);
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
animationState.current.pausedTime = 0;
animationState.current.isPaused = false;
setTargetPosition(newTarget);
setIsAnimating(true);
// Only reset if target changed or we were previously stopped/done
if (!isAnimating || !targetPosition || !newTarget.equals(targetPosition)) {
animationState.current.startPosition.copy(matRef.current.position);
animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget);
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
animationState.current.pausedTime = 0;
animationState.current.isPaused = false;
setTargetPosition(newTarget);
setIsAnimating(true);
}
}
}, [material, isPlaying]);
}, [material.current.pointUuid, material.next?.pointUuid, material.isPaused, isPlaying]);
useEffect(() => {
if (shouldPause) {
@@ -100,11 +98,7 @@ function MaterialAnimator({
const elapsed = (currentTime - animationState.current.startTime) / 1000;
const progress = Math.min(1, (currentSpeed * elapsed) / animationState.current.totalDistance);
matRef.current.position.lerpVectors(
animationState.current.startPosition,
targetPosition,
progress
);
matRef.current.position.lerpVectors(animationState.current.startPosition, targetPosition, progress);
if (progress >= 1) {
matRef.current.position.copy(targetPosition);
@@ -116,7 +110,7 @@ function MaterialAnimator({
totalDistance: 0,
pausedTime: 0,
isPaused: false,
lastFrameTime: 0
lastFrameTime: 0,
};
}
});
@@ -124,4 +118,4 @@ function MaterialAnimator({
return null;
}
export default React.memo(MaterialAnimator);
export default React.memo(MaterialAnimator);