"updated overall animation logics"

This commit is contained in:
SreeNath14 2025-04-04 18:02:53 +05:30
parent 280fe59a14
commit c5ca0da1c5
8 changed files with 367 additions and 1540 deletions

Binary file not shown.

View File

@ -1,19 +1,28 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import { ExitIcon, PlayStopIcon, ResetIcon } from "../../icons/SimulationIcons"; import { ExitIcon, PlayStopIcon, ResetIcon } from "../../icons/SimulationIcons";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import {
useAnimationPlaySpeed,
usePauseButtonStore,
usePlayButtonStore,
useResetButtonStore,
} from "../../../store/usePlayButtonStore";
const SimulationPlayer: React.FC = () => { const SimulationPlayer: React.FC = () => {
const [speed, setSpeed] = useState<number>(1); const { speed, setSpeed } = useAnimationPlaySpeed();
const [playSimulation, setPlaySimulation] = useState(false); const [playSimulation, setPlaySimulation] = useState(false);
const { setIsPlaying } = usePlayButtonStore(); const { setIsPlaying } = usePlayButtonStore();
const sliderRef = useRef<HTMLDivElement>(null); const sliderRef = useRef<HTMLDivElement>(null);
const isDragging = useRef(false); const isDragging = useRef(false);
const { isPaused, setIsPaused } = usePauseButtonStore();
const { isReset, setReset } = useResetButtonStore();
// Button functions // Button functions
const handleReset = () => { const handleReset = () => {
setReset(true);
setSpeed(1); setSpeed(1);
}; };
const handlePlayStop = () => { const handlePlayStop = () => {
setIsPaused(!isPaused);
setPlaySimulation(!playSimulation); setPlaySimulation(!playSimulation);
}; };
const handleExit = () => { const handleExit = () => {
@ -27,7 +36,7 @@ const SimulationPlayer: React.FC = () => {
}; };
const calculateHandlePosition = () => { const calculateHandlePosition = () => {
return ((speed - 0.5) / (50 - 0.5)) * 100; return ((speed - 0.5) / (8 - 0.5)) * 100;
}; };
const handleMouseDown = () => { const handleMouseDown = () => {
@ -115,7 +124,7 @@ const SimulationPlayer: React.FC = () => {
<input <input
type="range" type="range"
min="0.5" min="0.5"
max="50" max="8"
step="0.1" step="0.1"
value={speed} value={speed}
onChange={handleSpeedChange} onChange={handleSpeedChange}
@ -123,7 +132,7 @@ const SimulationPlayer: React.FC = () => {
/> />
</div> </div>
</div> </div>
<div className="max-value">50x</div> <div className="max-value">8x</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,916 +0,0 @@
// // animation-worker.js
// // This web worker handles animation calculations off the main thread
// /* eslint-disable no-restricted-globals */
// // The above disables the ESLint rule for this file since 'self' is valid in web workers
// // Store process data, animation states, and objects
// let processes = [];
// let animationStates = {};
// let lastTimestamp = 0;
// // Message handler for communication with main thread
// self.onmessage = function (event) {
// const { type, data } = event.data;
// switch (type) {
// case "initialize":
// processes = data.processes;
// initializeAnimationStates();
// break;
// case "update":
// const { timestamp, isPlaying } = data;
// if (isPlaying) {
// const delta = (timestamp - lastTimestamp) / 1000; // Convert to seconds
// updateAnimations(delta, timestamp);
// }
// lastTimestamp = timestamp;
// break;
// case "reset":
// resetAnimations();
// break;
// case "togglePlay":
// // If resuming from pause, recalculate the time delta
// lastTimestamp = data.timestamp;
// break;
// }
// };
// // Initialize animation states for all processes
// function initializeAnimationStates() {
// animationStates = {};
// processes.forEach((process) => {
// animationStates[process.id] = {
// spawnedObjects: {},
// nextSpawnTime: 0,
// objectIdCounter: 0,
// };
// });
// // Send initial states back to main thread
// self.postMessage({
// type: "statesInitialized",
// data: { animationStates },
// });
// }
// // Reset all animations
// function resetAnimations() {
// initializeAnimationStates();
// }
// // Find spawn point in a process
// function findSpawnPoint(process) {
// for (const path of process.paths || []) {
// for (const point of path.points || []) {
// const spawnAction = point.actions?.find(
// (a) => a.isUsed && a.type === "Spawn"
// );
// if (spawnAction) {
// return { point, path };
// }
// }
// }
// return null;
// }
// // Create a new spawned object with proper initial position
// function createSpawnedObject(process, spawnPoint, currentTime, materialType) {
// // Extract spawn position from the actual spawn point
// const position = spawnPoint.point.position
// ? [...spawnPoint.point.position]
// : [0, 0, 0];
// // Get the path position and add it to the spawn point position
// const pathPosition = spawnPoint.path.pathPosition || [0, 0, 0];
// const absolutePosition = [
// position[0] + pathPosition[0],
// position[1] + pathPosition[1],
// position[2] + pathPosition[2],
// ];
// return {
// id: `obj-${process.id}-${animationStates[process.id].objectIdCounter}`,
// position: absolutePosition,
// state: {
// currentIndex: 0,
// progress: 0,
// isAnimating: true,
// speed: process.speed || 1,
// isDelaying: false,
// delayStartTime: 0,
// currentDelayDuration: 0,
// delayComplete: false,
// currentPathIndex: 0,
// // Store the spawn point index to start animation from correct path point
// spawnPointIndex: getPointIndexInProcess(process, spawnPoint.point),
// },
// visible: true,
// materialType: materialType || "Default",
// spawnTime: currentTime,
// };
// }
// // Get the index of a point within the process animation path
// function getPointIndexInProcess(process, point) {
// if (!process.paths) return 0;
// let cumulativePoints = 0;
// for (const path of process.paths) {
// for (let i = 0; i < (path.points?.length || 0); i++) {
// if (path.points[i].uuid === point.uuid) {
// return cumulativePoints + i;
// }
// }
// cumulativePoints += path.points?.length || 0;
// }
// return 0;
// }
// // Get point data for current animation index
// function getPointDataForAnimationIndex(process, index) {
// if (!process.paths) return null;
// let cumulativePoints = 0;
// for (const path of process.paths) {
// const pointCount = path.points?.length || 0;
// if (index < cumulativePoints + pointCount) {
// const pointIndex = index - cumulativePoints;
// return path.points?.[pointIndex] || null;
// }
// cumulativePoints += pointCount;
// }
// return null;
// }
// // Convert process paths to Vector3 format
// function getProcessPath(process) {
// return process.animationPath?.map((p) => ({ x: p.x, y: p.y, z: p.z })) || [];
// }
// // Handle material swap for an object
// function handleMaterialSwap(processId, objectId, materialType) {
// const processState = animationStates[processId];
// if (!processState || !processState.spawnedObjects[objectId]) return;
// processState.spawnedObjects[objectId].materialType = materialType;
// // Notify main thread about material change
// self.postMessage({
// type: "materialChanged",
// data: {
// processId,
// objectId,
// materialType,
// },
// });
// }
// // Handle point actions for an object
// function handlePointActions(processId, objectId, actions = [], currentTime) {
// let shouldStopAnimation = false;
// const processState = animationStates[processId];
// if (!processState || !processState.spawnedObjects[objectId]) return false;
// const objectState = processState.spawnedObjects[objectId];
// actions.forEach((action) => {
// if (!action.isUsed) return;
// switch (action.type) {
// case "Delay":
// if (objectState.state.isDelaying) return;
// const delayDuration =
// typeof action.delay === "number"
// ? action.delay
// : parseFloat(action.delay || "0");
// if (delayDuration > 0) {
// objectState.state.isDelaying = true;
// objectState.state.delayStartTime = currentTime;
// objectState.state.currentDelayDuration = delayDuration;
// objectState.state.delayComplete = false;
// shouldStopAnimation = true;
// }
// break;
// case "Despawn":
// delete processState.spawnedObjects[objectId];
// shouldStopAnimation = true;
// // Notify main thread about despawn
// self.postMessage({
// type: "objectDespawned",
// data: {
// processId,
// objectId,
// },
// });
// break;
// case "Swap":
// if (action.material) {
// handleMaterialSwap(processId, objectId, action.material);
// }
// break;
// default:
// break;
// }
// });
// return shouldStopAnimation;
// }
// // Check if point has non-inherit actions
// function hasNonInheritActions(actions = []) {
// return actions.some((action) => action.isUsed && action.type !== "Inherit");
// }
// // Calculate vector lerp (linear interpolation)
// function lerpVectors(v1, v2, alpha) {
// return {
// x: v1.x + (v2.x - v1.x) * alpha,
// y: v1.y + (v2.y - v1.y) * alpha,
// z: v1.z + (v2.z - v1.z) * alpha,
// };
// }
// // Calculate vector distance
// function distanceBetweenVectors(v1, v2) {
// const dx = v2.x - v1.x;
// const dy = v2.y - v1.y;
// const dz = v2.z - v1.z;
// return Math.sqrt(dx * dx + dy * dy + dz * dz);
// }
// // Process spawn logic
// function processSpawns(currentTime) {
// processes.forEach((process) => {
// const processState = animationStates[process.id];
// if (!processState) return;
// const spawnPointData = findSpawnPoint(process);
// if (!spawnPointData || !spawnPointData.point.actions) return;
// const spawnAction = spawnPointData.point.actions.find(
// (a) => a.isUsed && a.type === "Spawn"
// );
// if (!spawnAction) return;
// const spawnInterval =
// typeof spawnAction.spawnInterval === "number"
// ? spawnAction.spawnInterval
// : parseFloat(spawnAction.spawnInterval || "0");
// if (currentTime >= processState.nextSpawnTime) {
// const newObject = createSpawnedObject(
// process,
// spawnPointData,
// currentTime,
// spawnAction.material || "Default"
// );
// processState.spawnedObjects[newObject.id] = newObject;
// processState.objectIdCounter++;
// processState.nextSpawnTime = currentTime + spawnInterval;
// // Notify main thread about new object
// self.postMessage({
// type: "objectSpawned",
// data: {
// processId: process.id,
// object: newObject,
// },
// });
// }
// });
// }
// // Update all animations
// function updateAnimations(delta, currentTime) {
// // First handle spawning of new objects
// processSpawns(currentTime);
// // Then animate existing objects
// processes.forEach((process) => {
// const processState = animationStates[process.id];
// if (!processState) return;
// const path = getProcessPath(process);
// if (path.length < 2) return;
// const updatedObjects = {};
// let hasChanges = false;
// Object.entries(processState.spawnedObjects).forEach(([objectId, obj]) => {
// if (!obj.visible || !obj.state.isAnimating) return;
// const stateRef = obj.state;
// // Use the spawnPointIndex as starting point if it's the initial movement
// if (stateRef.currentIndex === 0 && stateRef.progress === 0) {
// stateRef.currentIndex = stateRef.spawnPointIndex || 0;
// }
// // Get current point data
// const currentPointData = getPointDataForAnimationIndex(
// process,
// stateRef.currentIndex
// );
// // Execute actions when arriving at a new point
// if (stateRef.progress === 0 && currentPointData?.actions) {
// const shouldStop = handlePointActions(
// process.id,
// objectId,
// currentPointData.actions,
// currentTime
// );
// if (shouldStop) return;
// }
// // Handle delays
// if (stateRef.isDelaying) {
// if (
// currentTime - stateRef.delayStartTime >=
// stateRef.currentDelayDuration
// ) {
// stateRef.isDelaying = false;
// stateRef.delayComplete = true;
// } else {
// updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
// return; // Keep waiting
// }
// }
// const nextPointIdx = stateRef.currentIndex + 1;
// const isLastPoint = nextPointIdx >= path.length;
// if (isLastPoint) {
// if (currentPointData?.actions) {
// const shouldStop = !hasNonInheritActions(currentPointData.actions);
// if (shouldStop) {
// // Reached the end of path with no more actions
// delete processState.spawnedObjects[objectId];
// // Notify main thread to remove the object
// self.postMessage({
// type: "objectCompleted",
// data: {
// processId: process.id,
// objectId,
// },
// });
// return;
// }
// }
// }
// if (!isLastPoint) {
// const currentPos = path[stateRef.currentIndex];
// const nextPos = path[nextPointIdx];
// const distance = distanceBetweenVectors(currentPos, nextPos);
// const movement = stateRef.speed * delta;
// // Update progress based on distance and speed
// const oldProgress = stateRef.progress;
// stateRef.progress += movement / distance;
// if (stateRef.progress >= 1) {
// // Reached next point
// stateRef.currentIndex = nextPointIdx;
// stateRef.progress = 0;
// stateRef.delayComplete = false;
// obj.position = [nextPos.x, nextPos.y, nextPos.z];
// } else {
// // Interpolate position
// const lerpedPos = lerpVectors(currentPos, nextPos, stateRef.progress);
// obj.position = [lerpedPos.x, lerpedPos.y, lerpedPos.z];
// }
// // Only send updates when there's meaningful movement
// if (Math.abs(oldProgress - stateRef.progress) > 0.01) {
// hasChanges = true;
// }
// }
// updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
// });
// // Update animation state with modified objects
// if (Object.keys(updatedObjects).length > 0) {
// processState.spawnedObjects = {
// ...processState.spawnedObjects,
// ...updatedObjects,
// };
// // Only send position updates when there are meaningful changes
// if (hasChanges) {
// self.postMessage({
// type: "positionsUpdated",
// data: {
// processId: process.id,
// objects: updatedObjects,
// },
// });
// }
// }
// });
// }
// animation-worker.js
// This web worker handles animation calculations off the main thread
/* eslint-disable no-restricted-globals */
// The above disables the ESLint rule for this file since 'self' is valid in web workers
// Store process data, animation states, and objects
let processes = [];
let animationStates = {};
let lastTimestamp = 0;
let debugMode = true;
// Logger function for debugging
function log(...args) {
if (debugMode) {
self.postMessage({
type: "debug",
data: { message: args.join(' ') }
});
}
}
// Message handler for communication with main thread
self.onmessage = function (event) {
const { type, data } = event.data;
log(`Worker received message: ${type}`);
switch (type) {
case "initialize":
processes = data.processes;
log(`Initialized with ${processes.length} processes`);
initializeAnimationStates();
break;
case "update":
const { timestamp, isPlaying } = data;
if (isPlaying) {
const delta = lastTimestamp === 0 ? 0.016 : (timestamp - lastTimestamp) / 1000; // Convert to seconds
updateAnimations(delta, timestamp);
}
lastTimestamp = timestamp;
break;
case "reset":
log("Resetting animations");
resetAnimations();
break;
case "togglePlay":
// If resuming from pause, recalculate the time delta
log(`Toggle play: ${data.isPlaying}`);
lastTimestamp = data.timestamp;
break;
case "setDebug":
debugMode = data.enabled;
log(`Debug mode: ${debugMode}`);
break;
}
};
// Initialize animation states for all processes
function initializeAnimationStates() {
animationStates = {};
processes.forEach((process) => {
if (!process || !process.id) {
log("Invalid process found:", process);
return;
}
animationStates[process.id] = {
spawnedObjects: {},
nextSpawnTime: 0,
objectIdCounter: 0,
};
});
// Send initial states back to main thread
self.postMessage({
type: "statesInitialized",
data: { animationStates },
});
}
// Reset all animations
function resetAnimations() {
initializeAnimationStates();
}
// Find spawn point in a process
function findSpawnPoint(process) {
if (!process || !process.paths) {
log(`No paths found for process ${process?.id}`);
return null;
}
for (const path of process.paths) {
if (!path || !path.points) continue;
for (const point of path.points) {
if (!point || !point.actions) continue;
const spawnAction = point.actions.find(
(a) => a && a.isUsed && a.type === "Spawn"
);
if (spawnAction) {
return { point, path };
}
}
}
log(`No spawn points found for process ${process.id}`);
return null;
}
// Create a new spawned object with proper initial position
function createSpawnedObject(process, spawnPoint, currentTime, materialType) {
// Extract spawn position from the actual spawn point
const position = spawnPoint.point.position
? [...spawnPoint.point.position]
: [0, 0, 0];
// Get the path position and add it to the spawn point position
const pathPosition = spawnPoint.path.pathPosition || [0, 0, 0];
const absolutePosition = [
position[0] + pathPosition[0],
position[1] + pathPosition[1],
position[2] + pathPosition[2],
];
return {
id: `obj-${process.id}-${animationStates[process.id].objectIdCounter}`,
position: absolutePosition,
state: {
currentIndex: 0,
progress: 0,
isAnimating: true,
speed: process.speed || 1,
isDelaying: false,
delayStartTime: 0,
currentDelayDuration: 0,
delayComplete: false,
currentPathIndex: 0,
// Store the spawn point index to start animation from correct path point
spawnPointIndex: getPointIndexInProcess(process, spawnPoint.point),
},
visible: true,
materialType: materialType || "Default",
spawnTime: currentTime,
};
}
// Get the index of a point within the process animation path
function getPointIndexInProcess(process, point) {
if (!process.paths) return 0;
let cumulativePoints = 0;
for (const path of process.paths) {
for (let i = 0; i < (path.points?.length || 0); i++) {
if (path.points[i].uuid === point.uuid) {
return cumulativePoints + i;
}
}
cumulativePoints += path.points?.length || 0;
}
return 0;
}
// Get point data for current animation index
function getPointDataForAnimationIndex(process, index) {
if (!process.paths) return null;
let cumulativePoints = 0;
for (const path of process.paths) {
const pointCount = path.points?.length || 0;
if (index < cumulativePoints + pointCount) {
const pointIndex = index - cumulativePoints;
return path.points?.[pointIndex] || null;
}
cumulativePoints += pointCount;
}
return null;
}
// Convert process paths to Vector3 format
function getProcessPath(process) {
if (!process.animationPath) {
log(`No animation path for process ${process.id}`);
return [];
}
return process.animationPath.map((p) => ({ x: p.x, y: p.y, z: p.z })) || [];
}
// Handle material swap for an object
function handleMaterialSwap(processId, objectId, materialType) {
const processState = animationStates[processId];
if (!processState || !processState.spawnedObjects[objectId]) return;
processState.spawnedObjects[objectId].materialType = materialType;
// Notify main thread about material change
self.postMessage({
type: "materialChanged",
data: {
processId,
objectId,
materialType,
},
});
}
// Handle point actions for an object
function handlePointActions(processId, objectId, actions = [], currentTime) {
let shouldStopAnimation = false;
const processState = animationStates[processId];
if (!processState || !processState.spawnedObjects[objectId]) return false;
const objectState = processState.spawnedObjects[objectId];
actions.forEach((action) => {
if (!action || !action.isUsed) return;
switch (action.type) {
case "Delay":
if (objectState.state.isDelaying) return;
const delayDuration =
typeof action.delay === "number"
? action.delay
: parseFloat(action.delay || "0");
if (delayDuration > 0) {
objectState.state.isDelaying = true;
objectState.state.delayStartTime = currentTime;
objectState.state.currentDelayDuration = delayDuration;
objectState.state.delayComplete = false;
shouldStopAnimation = true;
}
break;
case "Despawn":
delete processState.spawnedObjects[objectId];
shouldStopAnimation = true;
// Notify main thread about despawn
self.postMessage({
type: "objectDespawned",
data: {
processId,
objectId,
},
});
break;
case "Swap":
if (action.material) {
handleMaterialSwap(processId, objectId, action.material);
}
break;
default:
break;
}
});
return shouldStopAnimation;
}
// Check if point has non-inherit actions
function hasNonInheritActions(actions = []) {
return actions.some((action) => action && action.isUsed && action.type !== "Inherit");
}
// Calculate vector lerp (linear interpolation)
function lerpVectors(v1, v2, alpha) {
return {
x: v1.x + (v2.x - v1.x) * alpha,
y: v1.y + (v2.y - v1.y) * alpha,
z: v1.z + (v2.z - v1.z) * alpha,
};
}
// Calculate vector distance
function distanceBetweenVectors(v1, v2) {
const dx = v2.x - v1.x;
const dy = v2.y - v1.y;
const dz = v2.z - v1.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
// Process spawn logic
function processSpawns(currentTime) {
processes.forEach((process) => {
const processState = animationStates[process.id];
if (!processState) return;
const spawnPointData = findSpawnPoint(process);
if (!spawnPointData || !spawnPointData.point.actions) return;
const spawnAction = spawnPointData.point.actions.find(
(a) => a.isUsed && a.type === "Spawn"
);
if (!spawnAction) return;
const spawnInterval =
typeof spawnAction.spawnInterval === "number"
? spawnAction.spawnInterval
: parseFloat(spawnAction.spawnInterval || "2"); // Default to 2 seconds if not specified
if (currentTime >= processState.nextSpawnTime) {
const newObject = createSpawnedObject(
process,
spawnPointData,
currentTime,
spawnAction.material || "Default"
);
processState.spawnedObjects[newObject.id] = newObject;
processState.objectIdCounter++;
processState.nextSpawnTime = currentTime + spawnInterval;
log(`Spawned object ${newObject.id} for process ${process.id}`);
// Notify main thread about new object
self.postMessage({
type: "objectSpawned",
data: {
processId: process.id,
object: newObject,
},
});
}
});
}
// Update all animations
function updateAnimations(delta, currentTime) {
// First handle spawning of new objects
processSpawns(currentTime);
// Then animate existing objects
processes.forEach((process) => {
const processState = animationStates[process.id];
if (!processState) return;
const path = getProcessPath(process);
if (path.length < 2) {
log(`Path too short for process ${process.id}, length: ${path.length}`);
return;
}
const updatedObjects = {};
let hasChanges = false;
Object.entries(processState.spawnedObjects).forEach(([objectId, obj]) => {
if (!obj.visible || !obj.state.isAnimating) return;
const stateRef = obj.state;
// Use the spawnPointIndex as starting point if it's the initial movement
if (stateRef.currentIndex === 0 && stateRef.progress === 0) {
stateRef.currentIndex = stateRef.spawnPointIndex || 0;
}
// Get current point data
const currentPointData = getPointDataForAnimationIndex(
process,
stateRef.currentIndex
);
// Execute actions when arriving at a new point
if (stateRef.progress === 0 && currentPointData?.actions) {
const shouldStop = handlePointActions(
process.id,
objectId,
currentPointData.actions,
currentTime
);
if (shouldStop) return;
}
// Handle delays
if (stateRef.isDelaying) {
if (
currentTime - stateRef.delayStartTime >=
stateRef.currentDelayDuration
) {
stateRef.isDelaying = false;
stateRef.delayComplete = true;
} else {
updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
return; // Keep waiting
}
}
const nextPointIdx = stateRef.currentIndex + 1;
const isLastPoint = nextPointIdx >= path.length;
if (isLastPoint) {
if (currentPointData?.actions) {
const shouldStop = !hasNonInheritActions(currentPointData.actions);
if (shouldStop) {
// Reached the end of path with no more actions
delete processState.spawnedObjects[objectId];
log(`Object ${objectId} completed path`);
// Notify main thread to remove the object
self.postMessage({
type: "objectCompleted",
data: {
processId: process.id,
objectId,
},
});
return;
}
}
}
if (!isLastPoint) {
const currentPos = path[stateRef.currentIndex];
const nextPos = path[nextPointIdx];
const distance = distanceBetweenVectors(currentPos, nextPos);
// Ensure we don't divide by zero
if (distance > 0) {
const movement = stateRef.speed * delta;
// Update progress based on distance and speed
const oldProgress = stateRef.progress;
stateRef.progress += movement / distance;
if (stateRef.progress >= 1) {
// Reached next point
stateRef.currentIndex = nextPointIdx;
stateRef.progress = 0;
stateRef.delayComplete = false;
obj.position = [nextPos.x, nextPos.y, nextPos.z];
} else {
// Interpolate position
const lerpedPos = lerpVectors(currentPos, nextPos, stateRef.progress);
obj.position = [lerpedPos.x, lerpedPos.y, lerpedPos.z];
}
// Only send updates when there's meaningful movement
if (Math.abs(oldProgress - stateRef.progress) > 0.01) {
hasChanges = true;
}
} else {
// Skip to next point if distance is zero
stateRef.currentIndex = nextPointIdx;
stateRef.progress = 0;
obj.position = [nextPos.x, nextPos.y, nextPos.z];
hasChanges = true;
}
}
updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
});
// Update animation state with modified objects
if (Object.keys(updatedObjects).length > 0) {
processState.spawnedObjects = {
...processState.spawnedObjects,
...updatedObjects,
};
// Only send position updates when there are meaningful changes
if (hasChanges) {
self.postMessage({
type: "positionsUpdated",
data: {
processId: process.id,
objects: updatedObjects,
},
});
}
}
});
}

View File

@ -1,590 +1,16 @@
// import React, { useRef, useState, useEffect, useMemo } from "react";
// import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
// import { GLTFLoader } from "three-stdlib";
// import { useLoader, useFrame } from "@react-three/fiber";
// import * as THREE from "three";
// import { GLTF } from "three-stdlib";
// import boxGltb from "../../../assets/gltf-glb/crate_box.glb";
// interface PointAction {
// uuid: string;
// name: string;
// type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
// objectType: string;
// material: string;
// delay: string | number;
// spawnInterval: string | number;
// isUsed: boolean;
// }
// interface ProcessPoint {
// uuid: string;
// position: number[];
// rotation: number[];
// actions: PointAction[];
// connections: {
// source: { pathUUID: string; pointUUID: string };
// targets: { pathUUID: string; pointUUID: string }[];
// };
// }
// interface ProcessPath {
// modeluuid: string;
// modelName: string;
// points: ProcessPoint[];
// pathPosition: number[];
// pathRotation: number[];
// speed: number;
// }
// interface ProcessData {
// id: string;
// paths: ProcessPath[];
// animationPath: { x: number; y: number; z: number }[];
// pointActions: PointAction[][];
// speed: number;
// customMaterials?: Record<string, THREE.Material>;
// renderAs?: "box" | "custom";
// }
// interface AnimationState {
// currentIndex: number;
// progress: number;
// isAnimating: boolean;
// speed: number;
// isDelaying: boolean;
// delayStartTime: number;
// currentDelayDuration: number;
// delayComplete: boolean;
// currentPathIndex: number;
// }
// interface SpawnedObject {
// ref: React.RefObject<THREE.Group | THREE.Mesh>;
// state: AnimationState;
// visible: boolean;
// material: THREE.Material;
// spawnTime: number;
// currentMaterialType: string;
// position: THREE.Vector3; // The position of the object
// }
// interface ProcessAnimationState {
// spawnedObjects: { [objectId: string]: SpawnedObject };
// nextSpawnTime: number;
// objectIdCounter: number;
// }
// const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
// processes,
// }) => {
//
// const gltf = useLoader(GLTFLoader, boxGltb) as GLTF;
// const { isPlaying } = usePlayButtonStore();
// const groupRef = useRef<THREE.Group>(null);
// const [animationStates, setAnimationStates] = useState<
// Record<string, ProcessAnimationState>
// >({});
// // Base materials
// const baseMaterials = useMemo(
// () => ({
// Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
// Box: new THREE.MeshStandardMaterial({
// color: 0xcccccc,
// metalness: 0.8,
// roughness: 0.2,
// }),
// Crate: new THREE.MeshStandardMaterial({
// color: 0x00aaff,
// metalness: 0.1,
// roughness: 0.5,
// }),
// Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
// }),
// []
// );
// // Initialize animation states when processes or play state changes
// useEffect(() => {
// if (!isPlaying) {
// setAnimationStates({});
// return;
// }
// const newStates: Record<string, ProcessAnimationState> = {};
// processes.forEach((process) => {
// newStates[process.id] = {
// spawnedObjects: {},
// nextSpawnTime: 0,
// objectIdCounter: 0,
// };
// });
// setAnimationStates(newStates);
// }, [isPlaying, processes]);
// // Find spawn point in a process
// const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
// for (const path of process.paths || []) {
// for (const point of path.points || []) {
// const spawnAction = point.actions?.find(
// (a) => a.isUsed && a.type === "Spawn"
// );
// if (spawnAction) {
// return point;
// }
// }
// }
// return null;
// };
// // Find the corresponding animation path point for a spawn point
// const findAnimationPathPoint = (process: ProcessData, spawnPoint: ProcessPoint): THREE.Vector3 => {
// // If we have an animation path, use the first point
// if (process.animationPath && process.animationPath.length > 0) {
// // Find the index of this point in the path
// let pointIndex = 0;
// // Try to find the corresponding point in the animation path
// for (const path of process.paths || []) {
// for (let i = 0; i < (path.points?.length || 0); i++) {
// const point = path.points?.[i];
// if (point && point.uuid === spawnPoint.uuid) {
// // Found the matching point
// if (process.animationPath[pointIndex]) {
// const p = process.animationPath[pointIndex];
// return new THREE.Vector3(p.x, p.y, p.z);
// }
// }
// pointIndex++;
// }
// }
// // Fallback to the spawn point's position
// return new THREE.Vector3(
// spawnPoint.position[0],
// spawnPoint.position[1],
// spawnPoint.position[2]
// );
// }
// // If no animation path, use the spawn point's position
// return new THREE.Vector3(
// spawnPoint.position[0],
// spawnPoint.position[1],
// spawnPoint.position[2]
// );
// };
// // Create a new spawned object
// const createSpawnedObject = (
// process: ProcessData,
// currentTime: number,
// materialType: string,
// spawnPoint: ProcessPoint
// ): SpawnedObject => {
// const processMaterials = {
// ...baseMaterials,
// ...(process.customMaterials || {}),
// };
// // Get the position where we should spawn
// const spawnPosition = findAnimationPathPoint(process, spawnPoint);
// return {
// ref: React.createRef(),
// state: {
// currentIndex: 0,
// progress: 0,
// isAnimating: true,
// speed: process.speed || 1,
// isDelaying: false,
// delayStartTime: 0,
// currentDelayDuration: 0,
// delayComplete: false,
// currentPathIndex: 0,
// },
// visible: true,
// material:
// processMaterials[materialType as keyof typeof processMaterials] ||
// baseMaterials.Default,
// currentMaterialType: materialType,
// spawnTime: currentTime,
// position: spawnPosition, // Store the position directly
// };
// };
// // Handle material swap for an object
// const handleMaterialSwap = (
// processId: string,
// objectId: string,
// materialType: string
// ) => {
// setAnimationStates((prev) => {
// const processState = prev[processId];
// if (!processState || !processState.spawnedObjects[objectId]) return prev;
// const process = processes.find((p) => p.id === processId);
// const processMaterials = {
// ...baseMaterials,
// ...(process?.customMaterials || {}),
// };
// const newMaterial =
// processMaterials[materialType as keyof typeof processMaterials] ||
// baseMaterials.Default;
// return {
// ...prev,
// [processId]: {
// ...processState,
// spawnedObjects: {
// ...processState.spawnedObjects,
// [objectId]: {
// ...processState.spawnedObjects[objectId],
// material: newMaterial,
// currentMaterialType: materialType,
// },
// },
// },
// };
// });
// };
// // Handle point actions for an object
// const handlePointActions = (
// processId: string,
// objectId: string,
// actions: PointAction[] = [],
// currentTime: number
// ): boolean => {
// let shouldStopAnimation = false;
// actions.forEach((action) => {
// if (!action.isUsed) return;
// switch (action.type) {
// case "Delay":
// setAnimationStates((prev) => {
// const processState = prev[processId];
// if (
// !processState ||
// !processState.spawnedObjects[objectId] ||
// processState.spawnedObjects[objectId].state.isDelaying
// ) {
// return prev;
// }
// const delayDuration =
// typeof action.delay === "number"
// ? action.delay
// : parseFloat(action.delay as string) || 0;
// if (delayDuration > 0) {
// return {
// ...prev,
// [processId]: {
// ...processState,
// spawnedObjects: {
// ...processState.spawnedObjects,
// [objectId]: {
// ...processState.spawnedObjects[objectId],
// state: {
// ...processState.spawnedObjects[objectId].state,
// isDelaying: true,
// delayStartTime: currentTime,
// currentDelayDuration: delayDuration,
// delayComplete: false,
// },
// },
// },
// },
// };
// }
// return prev;
// });
// shouldStopAnimation = true;
// break;
// case "Despawn":
// setAnimationStates((prev) => {
// const processState = prev[processId];
// if (!processState) return prev;
// const newSpawnedObjects = { ...processState.spawnedObjects };
// delete newSpawnedObjects[objectId];
// return {
// ...prev,
// [processId]: {
// ...processState,
// spawnedObjects: newSpawnedObjects,
// },
// };
// });
// shouldStopAnimation = true;
// break;
// case "Swap":
// if (action.material) {
// handleMaterialSwap(processId, objectId, action.material);
// }
// break;
// default:
// break;
// }
// });
// return shouldStopAnimation;
// };
// // Check if point has non-inherit actions
// const hasNonInheritActions = (actions: PointAction[] = []): boolean => {
// return actions.some((action) => action.isUsed && action.type !== "Inherit");
// };
// // Get point data for current animation index
// const getPointDataForAnimationIndex = (
// process: ProcessData,
// index: number
// ): ProcessPoint | null => {
// if (!process.paths) return null;
// let cumulativePoints = 0;
// for (const path of process.paths) {
// const pointCount = path.points?.length || 0;
// if (index < cumulativePoints + pointCount) {
// const pointIndex = index - cumulativePoints;
// return path.points?.[pointIndex] || null;
// }
// cumulativePoints += pointCount;
// }
// return null;
// };
// // Spawn objects for all processes
// useFrame((state) => {
// if (!isPlaying) return;
// const currentTime = state.clock.getElapsedTime();
// setAnimationStates((prev) => {
// const newStates = { ...prev };
// processes.forEach((process) => {
// const processState = newStates[process.id];
// if (!processState) return;
// const spawnPoint = findSpawnPoint(process);
// if (!spawnPoint || !spawnPoint.actions) return;
// const spawnAction = spawnPoint.actions.find(
// (a) => a.isUsed && a.type === "Spawn"
// );
// if (!spawnAction) return;
// const spawnInterval =
// typeof spawnAction.spawnInterval === "number"
// ? spawnAction.spawnInterval
// : parseFloat(spawnAction.spawnInterval as string) || 0;
// if (currentTime >= processState.nextSpawnTime) {
// const objectId = `obj-${process.id}-${processState.objectIdCounter}`;
// // Create the new object with the spawn point
// const newObject = createSpawnedObject(
// process,
// currentTime,
// spawnAction.material || "Default",
// spawnPoint
// );
// newStates[process.id] = {
// ...processState,
// spawnedObjects: {
// ...processState.spawnedObjects,
// [objectId]: newObject,
// },
// objectIdCounter: processState.objectIdCounter + 1,
// nextSpawnTime: currentTime + spawnInterval,
// };
// }
// });
// return newStates;
// });
// });
// // Animate objects for all processes
// useFrame((state, delta) => {
// if (!isPlaying) return;
// const currentTime = state.clock.getElapsedTime();
// setAnimationStates((prev) => {
// const newStates = { ...prev };
// processes.forEach((process) => {
// const processState = newStates[process.id];
// if (!processState) return;
// const path =
// process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) ||
// [];
// if (path.length < 2) return;
// const updatedObjects = { ...processState.spawnedObjects };
// Object.entries(processState.spawnedObjects).forEach(
// ([objectId, obj]) => {
// if (!obj.visible || !obj.state.isAnimating) return;
// const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current;
// if (!currentRef) return;
// // Set the position when the reference is first available
// if (obj.position && obj.state.currentIndex === 0 && obj.state.progress === 0) {
// currentRef.position.copy(obj.position);
// }
// const stateRef = obj.state;
// // Get current point data
// const currentPointData = getPointDataForAnimationIndex(
// process,
// stateRef.currentIndex
// );
// // Execute actions when arriving at a new point
// if (stateRef.progress === 0 && currentPointData?.actions) {
// const shouldStop = handlePointActions(
// process.id,
// objectId,
// currentPointData.actions,
// currentTime
// );
// if (shouldStop) return;
// }
// // Handle delays
// if (stateRef.isDelaying) {
// if (
// currentTime - stateRef.delayStartTime >=
// stateRef.currentDelayDuration
// ) {
// stateRef.isDelaying = false;
// stateRef.delayComplete = true;
// } else {
// return; // Keep waiting
// }
// }
// const nextPointIdx = stateRef.currentIndex + 1;
// const isLastPoint = nextPointIdx >= path.length;
// if (isLastPoint) {
// if (currentPointData?.actions) {
// const shouldStop = !hasNonInheritActions(
// currentPointData.actions
// );
// if (shouldStop) {
// currentRef.position.copy(path[stateRef.currentIndex]);
// delete updatedObjects[objectId];
// return;
// }
// }
// }
// if (!isLastPoint) {
// const nextPoint = path[nextPointIdx];
// const distance =
// path[stateRef.currentIndex].distanceTo(nextPoint);
// const movement = stateRef.speed * delta;
// stateRef.progress += movement / distance;
// if (stateRef.progress >= 1) {
// stateRef.currentIndex = nextPointIdx;
// stateRef.progress = 0;
// stateRef.delayComplete = false;
// currentRef.position.copy(nextPoint);
// } else {
// currentRef.position.lerpVectors(
// path[stateRef.currentIndex],
// nextPoint,
// stateRef.progress
// );
// }
// }
// updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
// }
// );
// newStates[process.id] = {
// ...processState,
// spawnedObjects: updatedObjects,
// };
// });
// return newStates;
// });
// });
// if (!processes || processes.length === 0) {
// return null;
// }
// return (
// <>
// {Object.entries(animationStates).flatMap(([processId, processState]) =>
// Object.entries(processState.spawnedObjects)
// .filter(([_, obj]) => obj.visible)
// .map(([objectId, obj]) => {
// const process = processes.find((p) => p.id === processId);
// const renderAs = process?.renderAs || "custom";
// return renderAs === "box" ? (
// <mesh
// key={objectId}
// ref={obj.ref as React.RefObject<THREE.Mesh>}
// material={obj.material}
// position={obj.position} // Set position directly in the JSX
// >
// <boxGeometry args={[1, 1, 1]} />
// </mesh>
// ) : (
// gltf?.scene && (
// <group
// key={objectId}
// ref={obj.ref as React.RefObject<THREE.Group>}
// position={obj.position} // Set position directly in the JSX
// >
// <primitive
// object={gltf.scene.clone()}
// material={obj.material}
// />
// </group>
// )
// );
// })
// )}
// </>
// );
// };
// export default ProcessAnimator;
import React, { useRef, useState, useEffect, useMemo } from "react"; import React, { useRef, useState, useEffect, useMemo } from "react";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import {
useAnimationPlaySpeed,
usePauseButtonStore,
usePlayButtonStore,
useResetButtonStore,
} from "../../../store/usePlayButtonStore";
import { GLTFLoader } from "three-stdlib"; import { GLTFLoader } from "three-stdlib";
import { useLoader, useFrame } from "@react-three/fiber"; import { useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three"; import * as THREE from "three";
import { GLTF } from "three-stdlib"; import { GLTF } from "three-stdlib";
import boxGltb from "../../../assets/gltf-glb/crate_box.glb"; import crate from "../../../assets/gltf-glb/crate_box.glb";
import camera from "../../../assets/gltf-glb/camera face 2.gltf"; import box from "../../../assets/gltf-glb/box.glb";
interface PointAction { interface PointAction {
uuid: string; uuid: string;
@ -661,15 +87,47 @@ interface ProcessAnimationState {
const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
processes, processes,
}) => { }) => {
const gltf = useLoader(GLTFLoader, boxGltb) as GLTF; const gltf = useLoader(GLTFLoader, crate) as GLTF;
const { isPlaying } = usePlayButtonStore(); const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { isPaused, setIsPaused } = usePauseButtonStore();
const { isReset, setReset } = useResetButtonStore();
const groupRef = useRef<THREE.Group>(null); const groupRef = useRef<THREE.Group>(null);
const debugRef = useRef<boolean>(false); const debugRef = useRef<boolean>(false);
const clockRef = useRef<THREE.Clock>(new THREE.Clock());
const pauseTimeRef = useRef<number>(0);
const elapsedBeforePauseRef = useRef<number>(0);
const animationStatesRef = useRef<Record<string, ProcessAnimationState>>({});
const { speed, setSpeed } = useAnimationPlaySpeed();
const prevIsPlaying = useRef<boolean | null>(null);
const [internalResetFlag, setInternalResetFlag] = useState(false);
const [animationStates, setAnimationStates] = useState< const [animationStates, setAnimationStates] = useState<
Record<string, ProcessAnimationState> Record<string, ProcessAnimationState>
>({}); >({});
// Store the speed in a ref to access the latest value in animation frames
const speedRef = useRef<number>(speed);
// Update the ref when speed changes
useEffect(() => {
speedRef.current = speed;
}, [speed]);
useEffect(() => {
if (prevIsPlaying.current !== null) {
setAnimationStates({});
}
// Update ref to current isPlaying after effect
prevIsPlaying.current = isPlaying;
// setAnimationStates({});
}, [isPlaying]);
// Sync ref with state
useEffect(() => {
animationStatesRef.current = animationStates;
}, [animationStates]);
// Base materials // Base materials
const baseMaterials = useMemo( const baseMaterials = useMemo(
() => ({ () => ({
@ -680,13 +138,50 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
[] []
); );
// Replace your reset effect with this:
useEffect(() => {
if (isReset) {
// 1. Mark that we're doing an internal reset
setInternalResetFlag(true);
// 2. Pause the animation first
setIsPlaying(false);
setIsPaused(false);
// 3. Reset all animation states
setAnimationStates({});
animationStatesRef.current = {};
// 4. Reset timing references
clockRef.current = new THREE.Clock();
elapsedBeforePauseRef.current = 0;
pauseTimeRef.current = 0;
// 5. Clear the external reset flag
setReset(false);
// 6. After state updates are complete, restart
setTimeout(() => {
setInternalResetFlag(false);
setIsPlaying(true);
}, 0);
}
}, [isReset, setReset, setIsPlaying, setIsPaused]);
// Handle pause state changes
useEffect(() => {
if (isPaused) {
pauseTimeRef.current = clockRef.current.getElapsedTime();
} else if (pauseTimeRef.current > 0) {
const pausedDuration =
clockRef.current.getElapsedTime() - pauseTimeRef.current;
elapsedBeforePauseRef.current += pausedDuration;
}
}, [isPaused]);
// Initialize animation states when processes or play state changes // Initialize animation states when processes or play state changes
useEffect(() => { useEffect(() => {
if (!isPlaying) { if (isPlaying && !internalResetFlag) {
setAnimationStates({});
return;
}
const newStates: Record<string, ProcessAnimationState> = {}; const newStates: Record<string, ProcessAnimationState> = {};
processes.forEach((process) => { processes.forEach((process) => {
newStates[process.id] = { newStates[process.id] = {
@ -699,7 +194,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
}; };
}); });
setAnimationStates(newStates); setAnimationStates(newStates);
}, [isPlaying, processes]); animationStatesRef.current = newStates;
clockRef.current.start();
}
}, [isPlaying, processes, internalResetFlag]);
const findSpawnPoint = (process: ProcessData): ProcessPoint | null => { const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
for (const path of process.paths || []) { for (const path of process.paths || []) {
@ -767,7 +265,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
currentIndex: 0, currentIndex: 0,
progress: 0, progress: 0,
isAnimating: true, isAnimating: true,
speed: process.speed || 1, speed: process.speed || 1, // Process base speed (will be multiplied by global speed)
isDelaying: false, isDelaying: false,
delayStartTime: 0, delayStartTime: 0,
currentDelayDuration: 0, currentDelayDuration: 0,
@ -953,10 +451,12 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
return null; return null;
}; };
useFrame((state) => { useFrame(() => {
if (!isPlaying) return; if (!isPlaying || isPaused) return;
const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
const currentTime = state.clock.getElapsedTime();
setAnimationStates((prev) => { setAnimationStates((prev) => {
const newStates = { ...prev }; const newStates = { ...prev };
@ -965,9 +465,13 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
if (!processState) return; if (!processState) return;
if (processState.isProcessDelaying) { if (processState.isProcessDelaying) {
// Apply global speed to delays (faster speed = shorter delays)
const effectiveDelayTime =
processState.processDelayDuration / speedRef.current;
if ( if (
currentTime - processState.processDelayStartTime >= currentTime - processState.processDelayStartTime >=
processState.processDelayDuration effectiveDelayTime
) { ) {
newStates[process.id] = { newStates[process.id] = {
...processState, ...processState,
@ -1009,6 +513,9 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
? spawnAction.spawnInterval ? spawnAction.spawnInterval
: parseFloat(spawnAction.spawnInterval as string) || 0; : parseFloat(spawnAction.spawnInterval as string) || 0;
// Apply global speed to spawn intervals (faster speed = more frequent spawns)
const effectiveSpawnInterval = spawnInterval / speedRef.current;
if (currentTime >= processState.nextSpawnTime) { if (currentTime >= processState.nextSpawnTime) {
const objectId = `obj-${process.id}-${processState.objectIdCounter}`; const objectId = `obj-${process.id}-${processState.objectIdCounter}`;
const newObject = createSpawnedObject( const newObject = createSpawnedObject(
@ -1025,7 +532,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
[objectId]: newObject, [objectId]: newObject,
}, },
objectIdCounter: processState.objectIdCounter + 1, objectIdCounter: processState.objectIdCounter + 1,
nextSpawnTime: currentTime + spawnInterval, nextSpawnTime: currentTime + effectiveSpawnInterval,
}; };
} }
}); });
@ -1034,10 +541,12 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
}); });
}); });
useFrame((state, delta) => { useFrame((_, delta) => {
if (!isPlaying) return; if (!isPlaying || isPaused) return;
const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
const currentTime = state.clock.getElapsedTime();
setAnimationStates((prev) => { setAnimationStates((prev) => {
const newStates = { ...prev }; const newStates = { ...prev };
@ -1046,9 +555,13 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
if (!processState) return; if (!processState) return;
if (processState.isProcessDelaying) { if (processState.isProcessDelaying) {
// Apply global speed to delays (faster speed = shorter delays)
const effectiveDelayTime =
processState.processDelayDuration / speedRef.current;
if ( if (
currentTime - processState.processDelayStartTime >= currentTime - processState.processDelayStartTime >=
processState.processDelayDuration effectiveDelayTime
) { ) {
newStates[process.id] = { newStates[process.id] = {
...processState, ...processState,
@ -1104,10 +617,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const stateRef = obj.state; const stateRef = obj.state;
if (stateRef.isDelaying) { if (stateRef.isDelaying) {
if ( // Apply global speed to delays (faster speed = shorter delays)
currentTime - stateRef.delayStartTime >= const effectiveDelayTime =
stateRef.currentDelayDuration stateRef.currentDelayDuration / speedRef.current;
) {
if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) {
stateRef.isDelaying = false; stateRef.isDelaying = false;
stateRef.delayComplete = true; stateRef.delayComplete = true;
stateRef.isAnimating = true; stateRef.isAnimating = true;
@ -1177,7 +691,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const nextPoint = path[nextPointIdx]; const nextPoint = path[nextPointIdx];
const distance = const distance =
path[stateRef.currentIndex].distanceTo(nextPoint); path[stateRef.currentIndex].distanceTo(nextPoint);
const movement = stateRef.speed * delta;
// Apply both process-specific speed and global speed multiplier
const effectiveSpeed = stateRef.speed * speedRef.current;
const movement = effectiveSpeed * delta;
if (stateRef.delayComplete && stateRef.progress < 0.01) { if (stateRef.delayComplete && stateRef.progress < 0.01) {
stateRef.progress = 0.05; stateRef.progress = 0.05;

View File

@ -0,0 +1,114 @@
import React, { useRef, useEffect } from "react";
import { useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import { Box3Helper } from "three";
import { SpawnedObject, ProcessData } from "./types";
interface ProcessObjectRendererProps {
objectId: string;
object: SpawnedObject;
process: ProcessData;
gltf: GLTF;
showBoundingBox?: boolean;
}
export const ProcessObjectRenderer: React.FC<ProcessObjectRendererProps> = ({
objectId,
object,
process,
gltf,
showBoundingBox = false,
}) => {
const meshRef = useRef<THREE.Mesh>(null);
const boxHelperRef = useRef<THREE.Box3Helper | null>(null);
const boundingBoxRef = useRef<THREE.Box3>(new THREE.Box3());
// Issue 1: Can't assign to ref.current as it's read-only
useEffect(() => {
if (object.ref && meshRef.current) {
// Instead of direct assignment, we need to store the mesh reference another way
// Option 1: If you can modify the SpawnedObject interface, add a setMesh method
if (typeof (object as any).setMesh === 'function') {
(object as any).setMesh(meshRef.current);
}
// Option 2: Store the mesh in a property that isn't ref.current
// This requires modifying your SpawnedObject interface to include this property
(object as any).meshInstance = meshRef.current;
// Option 3: If you need to maintain compatibility, you could use Object.defineProperty
// But this is a hack and not recommended
// Object.defineProperty(object.ref, 'current', { value: meshRef.current, writable: true });
}
}, [object.ref]);
// Create a bounding box helper for visualization
useFrame(() => {
if (meshRef.current && showBoundingBox) {
// Update the bounding box to match the mesh position
if (!boxHelperRef.current) {
// Get the size of the mesh
const size = new THREE.Vector3(1, 1, 1);
// If the mesh has geometry, use its dimensions
if (meshRef.current.geometry) {
const box = new THREE.Box3().setFromObject(meshRef.current);
box.getSize(size);
}
// Create a new bounding box centered on the mesh
boundingBoxRef.current = new THREE.Box3().setFromCenterAndSize(
meshRef.current.position,
size
);
// Create a helper to visualize the box
boxHelperRef.current = new Box3Helper(
boundingBoxRef.current,
new THREE.Color(0xff0000)
);
// Add the helper to the scene
meshRef.current.parent?.add(boxHelperRef.current);
} else {
// Update the box position to match the mesh
boundingBoxRef.current.setFromCenterAndSize(
meshRef.current.position,
boundingBoxRef.current.getSize(new THREE.Vector3())
);
// Force the helper to update
boxHelperRef.current.updateMatrixWorld(true);
}
}
});
if (gltf?.scene) {
return (
<primitive
ref={meshRef}
object={gltf.scene.clone()}
position={[0, 0, 0]}
material={object.material}
/>
);
}
// Issue 2: Material color type problem
return (
<mesh ref={meshRef}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial
// Fix the color property access
color={
object.material && 'color' in object.material
? (object.material as THREE.MeshStandardMaterial).color
: "#00ff00"
}
metalness={0.5}
roughness={0.3}
/>
</mesh>
);
};

View File

@ -0,0 +1,79 @@
import * as THREE from "three";
import React from "react";
export interface ProcessPoint {
uuid: string;
position: number[];
actions?: PointAction[];
}
export interface PointAction {
type: string;
isUsed: boolean;
spawnInterval?: number | string;
material?: string;
delay?: number | string;
}
export interface ProcessData {
id: string;
name: string;
paths?: {
points?: ProcessPoint[];
}[];
animationPath?: { x: number; y: number; z: number }[];
speed?: number;
customMaterials?: Record<string, THREE.Material>;
}
export interface ProcessAnimationState {
spawnedObjects: Record<string, SpawnedObject | SpawnedObjectWithCollision>;
nextSpawnTime: number;
objectIdCounter: number;
isProcessDelaying: boolean;
processDelayStartTime: number;
processDelayDuration: number;
isCollisionPaused?: boolean;
}
export interface SpawnedObject {
ref: React.RefObject<THREE.Object3D>;
state: {
currentIndex: number;
progress: number;
isAnimating: boolean;
speed: number;
isDelaying: boolean;
delayStartTime: number;
currentDelayDuration: number;
delayComplete: boolean;
currentPathIndex: number;
};
visible: boolean;
material: THREE.Material;
currentMaterialType: string;
spawnTime: number;
position: THREE.Vector3;
collision?: {
boundingBox: THREE.Box3;
isColliding: boolean;
colliding: boolean; // Added this property
};
}
// For use in your processAnimator.tsx
// Update the CollisionState interface to include all required properties
interface CollisionState {
boundingBox: THREE.Box3;
isColliding: boolean;
colliding: boolean; // This was missing
collidingWith: string[];
}
export interface SpawnedObjectWithCollision extends SpawnedObject {
collision: {
boundingBox: THREE.Box3;
isColliding: boolean;
colliding: boolean;
collidingWith: string[];
};
}

View File

@ -1,8 +1,8 @@
let BackEnd_url = `http://${process.env.REACT_APP_SERVER_ASSET_LIBRARY_URL}`; let BackEnd_url = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
export const getCategoryAsset = async (categoryName: any) => { export const getCategoryAsset = async (categoryName: any) => {
try { try {
const response = await fetch( const response = await fetch(
`${BackEnd_url}/api/v2/getCatagoryAssets/${categoryName}`, `${BackEnd_url}/api/v2/getCategoryAssets/${categoryName}`,
{ {
method: "GET", method: "GET",
headers: { headers: {

View File

@ -4,8 +4,32 @@ type PlayButtonStore = {
isPlaying: boolean; // Updated state name to reflect the play/pause status more clearly isPlaying: boolean; // Updated state name to reflect the play/pause status more clearly
setIsPlaying: (value: boolean) => void; // Updated setter function name for clarity setIsPlaying: (value: boolean) => void; // Updated setter function name for clarity
}; };
type PauseButtonStore = {
isPaused: boolean; // Updated state name to reflect the play/pause status more clearly
setIsPaused: (value: boolean) => void; // Updated setter function name for clarity
};
type ResetButtonStore = {
isReset: boolean; // Updated state name to reflect the play/pause status more clearly
setReset: (value: boolean) => void; // Updated setter function name for clarity
};
interface AnimationSpeedState {
speed: number;
setSpeed: (value: number) => void;
}
export const usePlayButtonStore = create<PlayButtonStore>((set) => ({ export const usePlayButtonStore = create<PlayButtonStore>((set) => ({
isPlaying: false, // Default state for play/pause isPlaying: false, // Default state for play/pause
setIsPlaying: (value) => set({ isPlaying: value }), // Update isPlaying state setIsPlaying: (value) => set({ isPlaying: value }), // Update isPlaying state
})); }));
export const useResetButtonStore = create<ResetButtonStore>((set) => ({
isReset: false, // Default state for play/pause
setReset: (value) => set({ isReset: value }), // Update isPlaying state
}));
export const usePauseButtonStore = create<PauseButtonStore>((set) => ({
isPaused: false, // Default state for play/pause
setIsPaused: (value) => set({ isPaused: value }), // Update isPlaying state
}));
export const useAnimationPlaySpeed = create<AnimationSpeedState>((set) => ({
speed: 1,
setSpeed: (value) => set({ speed: value }),
}));