diff --git a/app/src/assets/gltf-glb/box.glb b/app/src/assets/gltf-glb/box.glb new file mode 100644 index 0000000..92ef9d8 Binary files /dev/null and b/app/src/assets/gltf-glb/box.glb differ diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index 85f2c54..31b7d25 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -1,19 +1,28 @@ import React, { useState, useRef, useEffect } from "react"; 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 [speed, setSpeed] = useState(1); + const { speed, setSpeed } = useAnimationPlaySpeed(); const [playSimulation, setPlaySimulation] = useState(false); const { setIsPlaying } = usePlayButtonStore(); const sliderRef = useRef(null); const isDragging = useRef(false); + const { isPaused, setIsPaused } = usePauseButtonStore(); + const { isReset, setReset } = useResetButtonStore(); // Button functions const handleReset = () => { + setReset(true); setSpeed(1); }; const handlePlayStop = () => { + setIsPaused(!isPaused); setPlaySimulation(!playSimulation); }; const handleExit = () => { @@ -27,7 +36,7 @@ const SimulationPlayer: React.FC = () => { }; const calculateHandlePosition = () => { - return ((speed - 0.5) / (50 - 0.5)) * 100; + return ((speed - 0.5) / (8 - 0.5)) * 100; }; const handleMouseDown = () => { @@ -115,7 +124,7 @@ const SimulationPlayer: React.FC = () => { { /> -
50x
+
8x
diff --git a/app/src/modules/simulation/process/animation.Worker.js b/app/src/modules/simulation/process/animation.Worker.js deleted file mode 100644 index faa6587..0000000 --- a/app/src/modules/simulation/process/animation.Worker.js +++ /dev/null @@ -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, - }, - }); - } - } - }); -} diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx index fe62343..d82b800 100644 --- a/app/src/modules/simulation/process/processAnimator.tsx +++ b/app/src/modules/simulation/process/processAnimator.tsx @@ -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; -// 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; -// 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(null); - -// const [animationStates, setAnimationStates] = useState< -// Record -// >({}); - -// // 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 = {}; -// 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" ? ( -// } -// material={obj.material} -// position={obj.position} // Set position directly in the JSX -// > -// -// -// ) : ( -// gltf?.scene && ( -// } -// position={obj.position} // Set position directly in the JSX -// > -// -// -// ) -// ); -// }) -// )} -// -// ); -// }; - -// export default ProcessAnimator; - 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 { 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"; -import camera from "../../../assets/gltf-glb/camera face 2.gltf"; +import crate from "../../../assets/gltf-glb/crate_box.glb"; +import box from "../../../assets/gltf-glb/box.glb"; interface PointAction { uuid: string; @@ -661,15 +87,47 @@ interface ProcessAnimationState { const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ processes, }) => { - const gltf = useLoader(GLTFLoader, boxGltb) as GLTF; - const { isPlaying } = usePlayButtonStore(); + const gltf = useLoader(GLTFLoader, crate) as GLTF; + const { isPlaying, setIsPlaying } = usePlayButtonStore(); + const { isPaused, setIsPaused } = usePauseButtonStore(); + const { isReset, setReset } = useResetButtonStore(); const groupRef = useRef(null); const debugRef = useRef(false); - + const clockRef = useRef(new THREE.Clock()); + const pauseTimeRef = useRef(0); + const elapsedBeforePauseRef = useRef(0); + const animationStatesRef = useRef>({}); + const { speed, setSpeed } = useAnimationPlaySpeed(); + const prevIsPlaying = useRef(null); + const [internalResetFlag, setInternalResetFlag] = useState(false); const [animationStates, setAnimationStates] = useState< Record >({}); + // Store the speed in a ref to access the latest value in animation frames + const speedRef = useRef(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 const baseMaterials = useMemo( () => ({ @@ -680,26 +138,66 @@ 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 useEffect(() => { - if (!isPlaying) { - setAnimationStates({}); - return; + if (isPlaying && !internalResetFlag) { + const newStates: Record = {}; + processes.forEach((process) => { + newStates[process.id] = { + spawnedObjects: {}, + nextSpawnTime: 0, + objectIdCounter: 0, + isProcessDelaying: false, + processDelayStartTime: 0, + processDelayDuration: 0, + }; + }); + setAnimationStates(newStates); + animationStatesRef.current = newStates; + clockRef.current.start(); } - - const newStates: Record = {}; - processes.forEach((process) => { - newStates[process.id] = { - spawnedObjects: {}, - nextSpawnTime: 0, - objectIdCounter: 0, - isProcessDelaying: false, - processDelayStartTime: 0, - processDelayDuration: 0, - }; - }); - setAnimationStates(newStates); - }, [isPlaying, processes]); + }, [isPlaying, processes, internalResetFlag]); const findSpawnPoint = (process: ProcessData): ProcessPoint | null => { for (const path of process.paths || []) { @@ -767,7 +265,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ currentIndex: 0, progress: 0, isAnimating: true, - speed: process.speed || 1, + speed: process.speed || 1, // Process base speed (will be multiplied by global speed) isDelaying: false, delayStartTime: 0, currentDelayDuration: 0, @@ -953,10 +451,12 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ return null; }; - useFrame((state) => { - if (!isPlaying) return; + useFrame(() => { + if (!isPlaying || isPaused) return; + + const currentTime = + clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; - const currentTime = state.clock.getElapsedTime(); setAnimationStates((prev) => { const newStates = { ...prev }; @@ -965,9 +465,13 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ if (!processState) return; if (processState.isProcessDelaying) { + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + processState.processDelayDuration / speedRef.current; + if ( currentTime - processState.processDelayStartTime >= - processState.processDelayDuration + effectiveDelayTime ) { newStates[process.id] = { ...processState, @@ -1009,6 +513,9 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ? spawnAction.spawnInterval : 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) { const objectId = `obj-${process.id}-${processState.objectIdCounter}`; const newObject = createSpawnedObject( @@ -1025,7 +532,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ [objectId]: newObject, }, objectIdCounter: processState.objectIdCounter + 1, - nextSpawnTime: currentTime + spawnInterval, + nextSpawnTime: currentTime + effectiveSpawnInterval, }; } }); @@ -1034,10 +541,12 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ }); }); - useFrame((state, delta) => { - if (!isPlaying) return; + useFrame((_, delta) => { + if (!isPlaying || isPaused) return; + + const currentTime = + clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; - const currentTime = state.clock.getElapsedTime(); setAnimationStates((prev) => { const newStates = { ...prev }; @@ -1046,9 +555,13 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ if (!processState) return; if (processState.isProcessDelaying) { + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + processState.processDelayDuration / speedRef.current; + if ( currentTime - processState.processDelayStartTime >= - processState.processDelayDuration + effectiveDelayTime ) { newStates[process.id] = { ...processState, @@ -1104,10 +617,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const stateRef = obj.state; if (stateRef.isDelaying) { - if ( - currentTime - stateRef.delayStartTime >= - stateRef.currentDelayDuration - ) { + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + stateRef.currentDelayDuration / speedRef.current; + + if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) { stateRef.isDelaying = false; stateRef.delayComplete = true; stateRef.isAnimating = true; @@ -1177,7 +691,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const nextPoint = path[nextPointIdx]; const distance = 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) { stateRef.progress = 0.05; diff --git a/app/src/modules/simulation/process/processObjectRender.tsx b/app/src/modules/simulation/process/processObjectRender.tsx new file mode 100644 index 0000000..7f5a0cc --- /dev/null +++ b/app/src/modules/simulation/process/processObjectRender.tsx @@ -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 = ({ + objectId, + object, + process, + gltf, + showBoundingBox = false, +}) => { + const meshRef = useRef(null); + const boxHelperRef = useRef(null); + const boundingBoxRef = useRef(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 ( + + ); + } + + // Issue 2: Material color type problem + return ( + + + + + ); +}; \ No newline at end of file diff --git a/app/src/modules/simulation/process/types.ts b/app/src/modules/simulation/process/types.ts new file mode 100644 index 0000000..64a3e6a --- /dev/null +++ b/app/src/modules/simulation/process/types.ts @@ -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; +} + +export interface ProcessAnimationState { + spawnedObjects: Record; + nextSpawnTime: number; + objectIdCounter: number; + isProcessDelaying: boolean; + processDelayStartTime: number; + processDelayDuration: number; + isCollisionPaused?: boolean; +} + +export interface SpawnedObject { + ref: React.RefObject; + 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[]; + }; + } diff --git a/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts b/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts index a1ac727..2685ca4 100644 --- a/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts +++ b/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts @@ -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) => { try { const response = await fetch( - `${BackEnd_url}/api/v2/getCatagoryAssets/${categoryName}`, + `${BackEnd_url}/api/v2/getCategoryAssets/${categoryName}`, { method: "GET", headers: { diff --git a/app/src/store/usePlayButtonStore.ts b/app/src/store/usePlayButtonStore.ts index e975a7c..bed2bd7 100644 --- a/app/src/store/usePlayButtonStore.ts +++ b/app/src/store/usePlayButtonStore.ts @@ -4,8 +4,32 @@ type PlayButtonStore = { isPlaying: boolean; // Updated state name to reflect the play/pause status more clearly 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((set) => ({ isPlaying: false, // Default state for play/pause setIsPlaying: (value) => set({ isPlaying: value }), // Update isPlaying state })); +export const useResetButtonStore = create((set) => ({ + isReset: false, // Default state for play/pause + setReset: (value) => set({ isReset: value }), // Update isPlaying state +})); +export const usePauseButtonStore = create((set) => ({ + isPaused: false, // Default state for play/pause + setIsPaused: (value) => set({ isPaused: value }), // Update isPlaying state +})); +export const useAnimationPlaySpeed = create((set) => ({ + speed: 1, + setSpeed: (value) => set({ speed: value }), +}));