feat: Add simulation analysis component and 3D material animator.

This commit is contained in:
2025-12-29 14:35:15 +05:30
parent 74b864b1a0
commit f83d4ad732
2 changed files with 34 additions and 40 deletions

View File

@@ -1,11 +1,12 @@
import { useEffect, useRef } from "react";
import { useSceneContext } from "../../scene/sceneContext";
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from "../../../store/ui/usePlayButtonStore";
import { AnalyzerStatePayload } from "./worker/analyzer.types";
function Analyzer() {
const { isPaused } = usePauseButtonStore();
const { isPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
const { speed } = useAnimationPlaySpeed();
const { conveyorStore, machineStore, armBotStore, humanStore, vehicleStore, craneStore, storageUnitStore, materialStore, analysisStore, humanEventManagerRef, getSimulationTime } =
useSceneContext();
@@ -45,12 +46,12 @@ function Analyzer() {
// Handle Reset
useEffect(() => {
if (!isPlaying) {
if (!isPlaying || isReset) {
setAnalysis(null);
setAnalyzing(false);
workerRef.current?.postMessage({ type: "RESET" });
}
}, [isPlaying, setAnalysis, setAnalyzing]);
}, [isPlaying, isReset, setAnalysis, setAnalyzing]);
// Helper to sanitize payload (strip functions)
const sanitizePayload = (payload: AnalyzerStatePayload) => {
@@ -59,7 +60,7 @@ function Analyzer() {
// Send Updates to Worker
useEffect(() => {
if (!isPlaying) return;
if (!isPlaying || isReset) return;
// Rate limit updates
if (Date.now() - lastUpdateRef.current < 100) return;
@@ -88,11 +89,11 @@ function Analyzer() {
type: "UPDATE",
payload: sanitizePayload(payload),
});
}, [conveyors, vehicles, machines, armBots, humans, cranes, storageUnits, materials, materialHistory, speed, isPlaying, isPaused, getSimulationTime, humanEventManagerRef, setAnalyzing]);
}, [conveyors, vehicles, machines, armBots, humans, cranes, storageUnits, materials, materialHistory, speed, isPlaying, isPaused, getSimulationTime, humanEventManagerRef, setAnalyzing, isReset]);
// Periodic heartbeat/time update
useEffect(() => {
if (!isPlaying || isPaused) return;
if (!isPlaying || isPaused || isReset) return;
const interval = setInterval(() => {
const payload: AnalyzerStatePayload = {
@@ -116,7 +117,7 @@ function Analyzer() {
}, 1000);
return () => clearInterval(interval);
}, [conveyors, machines, armBots, humans, vehicles, cranes, storageUnits, materials, materialHistory, isPlaying, isPaused, speed, getSimulationTime, setAnalyzing]);
}, [conveyors, machines, armBots, humans, vehicles, cranes, storageUnits, materials, materialHistory, isPlaying, isPaused, speed, getSimulationTime, setAnalyzing, isReset]);
return null;
}

View File

@@ -18,12 +18,10 @@ function MaterialAnimator({ matRef, material, currentSpeed, onAnimationComplete
const { conveyorStore } = useSceneContext();
const { getConveyorById } = conveyorStore();
const animationState = useRef({
startTime: 0,
accumulatedDistance: 0,
startPosition: new THREE.Vector3(),
totalDistance: 0,
pausedTime: 0,
isPaused: false,
lastFrameTime: 0,
});
const { isPlaying } = usePlayButtonStore();
@@ -47,11 +45,13 @@ function MaterialAnimator({ matRef, material, currentSpeed, onAnimationComplete
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.accumulatedDistance = 0;
animationState.current.isPaused = false;
setTargetPosition(newTarget);
setIsAnimating(true);
if (animationState.current.totalDistance > 0.01) {
matRef.current.lookAt(newTarget);
}
}
} else {
setIsAnimating(false);
@@ -60,43 +60,43 @@ function MaterialAnimator({ matRef, material, currentSpeed, onAnimationComplete
}
const newTarget = getWorldPosition(material.next.pointUuid);
if (newTarget && matRef.current && !material.isPaused) {
// Only reset if target changed or we were previously stopped/done
if (!isAnimating || !targetPosition || !newTarget.equals(targetPosition)) {
// Only reset if target changed significantly to avoid jitter
if (!isAnimating || !targetPosition || newTarget.distanceTo(targetPosition) > 0.001) {
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.accumulatedDistance = 0;
animationState.current.isPaused = false;
setTargetPosition(newTarget);
setIsAnimating(true);
if (animationState.current.totalDistance > 0.01) {
matRef.current.lookAt(newTarget);
}
}
}
}, [material.current.pointUuid, material.next?.pointUuid, material.isPaused, isPlaying]);
useEffect(() => {
if (shouldPause) {
// Pause the animation
animationState.current.isPaused = true;
setIsAnimating(false);
animationState.current.pausedTime = performance.now() - animationState.current.startTime;
} else {
// Resume the animation
animationState.current.isPaused = false;
if (isPlaying && targetPosition && !isAnimating) {
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
setIsAnimating(true);
}
animationState.current.isPaused = !!shouldPause;
if (!shouldPause && isPlaying && targetPosition && !isAnimating) {
setIsAnimating(true);
}
}, [shouldPause, isPlaying]);
useFrame(() => {
useFrame((state, delta) => {
if (!matRef.current || !targetPosition || !isAnimating || animationState.current.isPaused || !isPlaying) {
return;
}
const currentTime = performance.now();
const elapsed = (currentTime - animationState.current.startTime) / 1000;
const progress = Math.min(1, (currentSpeed * elapsed) / animationState.current.totalDistance);
// Prevent large delta jumps (e.g. on tab switch)
const safeDelta = Math.min(delta, 0.1);
animationState.current.accumulatedDistance += currentSpeed * safeDelta;
const totalDist = animationState.current.totalDistance;
// Avoid division by zero
let progress = totalDist > 0.0001 ? animationState.current.accumulatedDistance / totalDist : 1;
progress = Math.min(1, progress);
matRef.current.position.lerpVectors(animationState.current.startPosition, targetPosition, progress);
@@ -104,14 +104,7 @@ function MaterialAnimator({ matRef, material, currentSpeed, onAnimationComplete
matRef.current.position.copy(targetPosition);
setIsAnimating(false);
onAnimationComplete?.();
animationState.current = {
startTime: 0,
startPosition: new THREE.Vector3(),
totalDistance: 0,
pausedTime: 0,
isPaused: false,
lastFrameTime: 0,
};
animationState.current.accumulatedDistance = 0;
}
});