feat: Add simulation analysis component and 3D material animator.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user