feat: Enhance conveyor actions and event handling

- Added detailed logging for default conveyor actions in useConveyorActions.
- Integrated play and reset button states into useActionHandler for better control flow.
- Updated PointsCreator to conditionally render based on play state and improved event handling.
- Modified MaterialAnimator to support pause and resume functionality based on play state.
- Enhanced MaterialInstance to trigger actions upon animation completion.
- Implemented material clearing logic in Materials component on reset or stop.
- Updated Simulator to respect play and reset states during action handling.
- Improved trigger handling logic to accommodate new event retrieval methods.
- Added utility functions in useProductStore for fetching events by trigger and point UUIDs.
- Created a new file for default action handling in conveyor actions.
This commit is contained in:
Jerald-Golden-B 2025-05-05 20:08:05 +05:30
parent 6b0ee0ae79
commit c89c4234a4
15 changed files with 656 additions and 362 deletions

View File

@ -49,7 +49,9 @@ const SimulationPlayer: React.FC = () => {
// Button functions
const handleReset = () => {
setReset(true);
setIsPaused(false);
setSpeed(1);
setPlaySimulation(false); // local state reset
};
const handlePlayStop = () => {
setIsPaused(!isPaused);
@ -58,6 +60,7 @@ const SimulationPlayer: React.FC = () => {
const handleExit = () => {
setPlaySimulation(false);
setIsPlaying(false);
setIsPaused(false);
setActiveTool("cursor");
};

View File

@ -4,28 +4,44 @@ import { useFrame } from "@react-three/fiber";
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { useSelectedProduct } from "../../../../../store/simulation/useSimulationStore";
import { usePlayButtonStore, useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from "../../../../../store/usePlayButtonStore";
export function useSpawnHandler() {
const { addMaterial } = useMaterialStore();
const { getModelUuidByActionUuid, getPointUuidByActionUuid } = useProductStore();
const { selectedProduct } = useSelectedProduct();
const lastSpawnTime = useRef<number | null>(null);
const startTime = useRef<number | null>(null);
const spawnCountRef = useRef<number>(0);
const spawnParams = useRef<{
interface SpawnInstance {
lastSpawnTime: number | null;
startTime: number;
spawnCount: number;
params: {
material: string;
intervalMs: number;
totalCount: number;
action: ConveyorAction;
} | null>(null);
};
pauseStartTime: number;
remainingTime: number;
isPaused: boolean;
}
const clearCurrentSpawn = useCallback(() => {
lastSpawnTime.current = null;
startTime.current = null;
spawnCountRef.current = 0;
spawnParams.current = null;
export function useSpawnHandler() {
const { addMaterial } = useMaterialStore();
const { getModelUuidByActionUuid, getPointUuidByActionUuid } = useProductStore();
const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore();
const { speed } = useAnimationPlaySpeed();
const { isReset } = useResetButtonStore();
const { selectedProduct } = useSelectedProduct();
const activeSpawns = useRef<Map<string, SpawnInstance>>(new Map());
const clearAllSpawns = useCallback(() => {
activeSpawns.current.clear();
}, []);
useEffect(() => {
if (isReset) {
clearAllSpawns();
}
}, [isReset, clearAllSpawns]);
const spawnLogStatus = (materialUuid: string, status: string) => {
// console.log(`${materialUuid}, ${status}`);
}
@ -64,78 +80,121 @@ export function useSpawnHandler() {
addMaterial(newMaterial);
return newMaterial;
}, [addMaterial]);
}, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productId]);
useEffect(() => {
const currentTime = performance.now();
activeSpawns.current.forEach((spawn) => {
if (isPaused && !spawn.isPaused) {
if (spawn.lastSpawnTime === null) {
spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.startTime));
} else {
spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.lastSpawnTime));
}
spawn.pauseStartTime = currentTime;
spawn.isPaused = true;
} else if (!isPaused && spawn.isPaused) {
if (spawn.remainingTime > 0) {
if (spawn.lastSpawnTime === null) {
spawn.startTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime);
} else {
spawn.lastSpawnTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime);
}
}
spawn.isPaused = false;
spawn.pauseStartTime = 0;
spawn.remainingTime = 0;
}
});
}, [isPaused]);
useFrame(() => {
if (!spawnParams.current || !startTime.current) return;
if (!isPlaying || isPaused || isReset) return;
const currentTime = performance.now();
const { material, intervalMs, totalCount, action } = spawnParams.current;
const isFirstSpawn = lastSpawnTime.current === null;
const elapsed = currentTime - startTime.current;
const completedActions: string[] = [];
// First spawn
if (isFirstSpawn) {
if (elapsed >= intervalMs) {
const createdMaterial = createNewMaterial(material, action);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
activeSpawns.current.forEach((spawn, actionUuid) => {
const { material, intervalMs, totalCount, action } = spawn.params;
const isFirstSpawn = spawn.lastSpawnTime === null;
// First spawn
if (isFirstSpawn) {
const elapsed = currentTime - spawn.startTime;
if (elapsed >= intervalMs) {
const createdMaterial = createNewMaterial(material, action);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
}
spawn.lastSpawnTime = currentTime;
spawn.spawnCount = 1;
if (totalCount <= 1) {
completedActions.push(actionUuid);
}
}
lastSpawnTime.current = currentTime;
spawnCountRef.current = 1;
return;
}
if (totalCount <= 1) {
clearCurrentSpawn();
// Subsequent spawns
if (spawn.lastSpawnTime !== null) {
const timeSinceLast = currentTime - spawn.lastSpawnTime;
if (timeSinceLast >= intervalMs) {
const count = spawn.spawnCount + 1;
const createdMaterial = createNewMaterial(material, action);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${timeSinceLast.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`);
}
spawn.lastSpawnTime = currentTime;
spawn.spawnCount = count;
if (count >= totalCount) {
completedActions.push(actionUuid);
}
}
}
return;
}
});
// Subsequent spawns
if (lastSpawnTime.current !== null) {
const timeSinceLast = currentTime - lastSpawnTime.current;
if (timeSinceLast >= intervalMs) {
const count = spawnCountRef.current + 1;
const createdMaterial = createNewMaterial(material, action);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`);
}
lastSpawnTime.current = currentTime;
spawnCountRef.current = count;
if (count >= totalCount) {
clearCurrentSpawn();
}
}
}
completedActions.forEach(actionUuid => {
activeSpawns.current.delete(actionUuid);
});
});
const handleSpawn = useCallback((action: ConveyorAction) => {
if (!action || action.actionType !== 'spawn') return;
const { material, spawnInterval = 0, spawnCount = 1 } = action;
const { material, spawnInterval = 0, spawnCount = 1, actionUuid } = action;
const intervalMs = spawnInterval * 1000;
clearCurrentSpawn();
if (activeSpawns.current.has(actionUuid)) {
activeSpawns.current.delete(actionUuid);
}
spawnParams.current = {
material,
intervalMs,
totalCount: spawnCount,
action: action
};
startTime.current = performance.now();
}, [clearCurrentSpawn]);
activeSpawns.current.set(actionUuid, {
lastSpawnTime: null,
startTime: performance.now(),
spawnCount: 0,
params: {
material,
intervalMs,
totalCount: spawnCount,
action: action
},
pauseStartTime: 0,
remainingTime: 0,
isPaused: false
});
}, []);
useEffect(() => {
return () => {
clearCurrentSpawn();
clearAllSpawns();
};
}, [clearCurrentSpawn]);
}, [clearAllSpawns]);
return {
handleSpawn,
clearCurrentSpawn
clearCurrentSpawn: clearAllSpawns
};
}

View File

@ -5,7 +5,8 @@ export function useConveyorActions() {
const { handleSpawn, clearCurrentSpawn } = useSpawnHandler();
const handleDefaultAction = useCallback((action: ConveyorAction) => {
// console.log(`Default conveyor action ${action.actionUuid}`);
console.log('action: ', action);
console.log(`Default conveyor action ${action.actionUuid}`);
}, []);
const handleSpawnAction = useCallback((action: ConveyorAction) => {

View File

@ -1,3 +1,4 @@
import { usePlayButtonStore, useResetButtonStore } from "../../../store/usePlayButtonStore";
import { useConveyorActions } from "./conveyor/useConveyorActions";
import { useMachineActions } from "./machine/useMachineActions";
import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions";
@ -6,6 +7,8 @@ import { useVehicleActions } from "./vehicle/useVehicleActions";
import { useCallback, useEffect } from "react";
export function useActionHandler() {
const { isReset } = useResetButtonStore();
const { isPlaying } = usePlayButtonStore();
const { handleConveyorAction, cleanup: cleanupConveyor } = useConveyorActions();
const { handleVehicleAction, cleanup: cleanupVehicle } = useVehicleActions();
const { handleRoboticArmAction, cleanup: cleanupRoboticArm } = useRoboticArmActions();
@ -52,7 +55,7 @@ export function useActionHandler() {
return () => {
cleanup();
};
}, [cleanup]);
}, [cleanup, isReset, isPlaying]);
return {
handleAction,

View File

@ -1,276 +1,265 @@
import React, { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
import useModuleStore, {
useSubModuleStore,
} from "../../../../../store/useModuleStore";
import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore";
import { TransformControls } from "@react-three/drei";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import {
useSelectedEventSphere,
useSelectedEventData,
} from "../../../../../store/simulation/useSimulationStore";
import { useSelectedEventSphere, useSelectedEventData, } from "../../../../../store/simulation/useSimulationStore";
import { useThree } from "@react-three/fiber";
import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore";
function PointsCreator() {
const { gl, raycaster, scene, pointer, camera } = useThree();
const { subModule } = useSubModuleStore();
const { events, updatePoint, getPointByUuid, getEventByModelUuid } =
useEventsStore();
const { activeModule } = useModuleStore();
const transformRef = useRef<any>(null);
const [transformMode, setTransformMode] = useState<
"translate" | "rotate" | null
>(null);
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
const {
selectedEventSphere,
setSelectedEventSphere,
clearSelectedEventSphere,
} = useSelectedEventSphere();
const { setSelectedEventData, clearSelectedEventData } =
useSelectedEventData();
const { gl, raycaster, scene, pointer, camera } = useThree();
const { subModule } = useSubModuleStore();
const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
const { activeModule } = useModuleStore();
const transformRef = useRef<any>(null);
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere();
const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
const { isPlaying } = usePlayButtonStore();
useEffect(() => {
if (selectedEventSphere) {
const eventData = getEventByModelUuid(
selectedEventSphere.userData.modelUuid
);
useEffect(() => {
if (selectedEventSphere) {
const eventData = getEventByModelUuid(
selectedEventSphere.userData.modelUuid
);
if (eventData) {
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
} else {
clearSelectedEventData();
}
} else {
clearSelectedEventData();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedEventSphere]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e);
if (!selectedEventSphere) return;
if (keyCombination === "G") {
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
}
if (keyCombination === "R") {
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [selectedEventSphere]);
const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
let point = JSON.parse(
JSON.stringify(
getPointByUuid(
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid
)
)
);
if (point) {
point.position = [
selectedEventSphere.position.x,
selectedEventSphere.position.y,
selectedEventSphere.position.z,
];
updatePoint(
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid,
point
);
}
};
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isMouseDown = false;
const onMouseDown = () => {
isMouseDown = true;
drag = false;
};
const onMouseUp = () => {
if (selectedEventSphere && !drag) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter((intersect) => intersect.object.name === "Event-Sphere");
if (intersects.length === 0) {
clearSelectedEventSphere();
setTransformMode(null);
if (eventData) {
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
} else {
clearSelectedEventData();
}
} else {
clearSelectedEventData();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedEventSphere]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e);
if (!selectedEventSphere) return;
if (keyCombination === "G") {
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
}
if (keyCombination === "R") {
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [selectedEventSphere]);
const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
let point = JSON.parse(
JSON.stringify(
getPointByUuid(
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid
)
)
);
if (point) {
point.position = [
selectedEventSphere.position.x,
selectedEventSphere.position.y,
selectedEventSphere.position.z,
];
updatePoint(
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid,
point
);
}
}
};
const onMouseMove = () => {
if (isMouseDown) {
drag = true;
}
};
useEffect(() => {
const canvasElement = gl.domElement;
if (subModule === "mechanics") {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
}
let drag = false;
let isMouseDown = false;
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [gl, subModule, selectedEventSphere]);
const onMouseDown = () => {
isMouseDown = true;
drag = false;
};
return (
<>
{activeModule === "simulation" && (
const onMouseUp = () => {
if (selectedEventSphere && !drag) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter((intersect) => intersect.object.name === "Event-Sphere");
if (intersects.length === 0) {
clearSelectedEventSphere();
setTransformMode(null);
}
}
};
const onMouseMove = () => {
if (isMouseDown) {
drag = true;
}
};
if (subModule === "mechanics") {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [gl, subModule, selectedEventSphere]);
return (
<>
<group name="EventPointsGroup">
{events.map((event, index) => {
if (event.type === "transfer") {
return (
<group
key={`${index}-${event.modelUuid}`}
position={event.position}
rotation={event.rotation}
>
{event.points.map((point, j) => (
<mesh
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}}
key={`${index}-${point.uuid}`}
position={new THREE.Vector3(...point.position)}
userData={{
modelUuid: event.modelUuid,
pointUuid: point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="orange" />
</mesh>
))}
</group>
);
} else if (event.type === "vehicle") {
return (
<group
key={`${index}-${event.modelUuid}`}
position={event.position}
rotation={event.rotation}
>
<mesh
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{
modelUuid: event.modelUuid,
pointUuid: event.point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="blue" />
</mesh>
</group>
);
} else if (event.type === "roboticArm") {
return (
<group
key={`${index}-${event.modelUuid}`}
position={event.position}
rotation={event.rotation}
>
<mesh
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{
modelUuid: event.modelUuid,
pointUuid: event.point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="green" />
</mesh>
</group>
);
} else if (event.type === "machine") {
return (
<group
key={`${index}-${event.modelUuid}`}
position={event.position}
rotation={event.rotation}
>
<mesh
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{
modelUuid: event.modelUuid,
pointUuid: event.point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="purple" />
</mesh>
</group>
);
} else {
return null;
}
})}
</group>
{selectedEventSphere && transformMode && (
<TransformControls
ref={transformRef}
object={selectedEventSphere}
mode={transformMode}
onMouseUp={(e) => {
updatePointToState(selectedEventSphere);
}}
/>
)}
{activeModule === "simulation" && (
<>
<group name="EventPointsGroup" visible={!isPlaying}>
{events.map((event, index) => {
if (event.type === "transfer") {
return (
<group
key={`${index}-${event.modelUuid}`}
position={event.position}
rotation={event.rotation}
>
{event.points.map((point, j) => (
<mesh
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}}
key={`${index}-${point.uuid}`}
position={new THREE.Vector3(...point.position)}
userData={{
modelUuid: event.modelUuid,
pointUuid: point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="orange" />
</mesh>
))}
</group>
);
} else if (event.type === "vehicle") {
return (
<group
key={`${index}-${event.modelUuid}`}
position={event.position}
rotation={event.rotation}
>
<mesh
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{
modelUuid: event.modelUuid,
pointUuid: event.point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="blue" />
</mesh>
</group>
);
} else if (event.type === "roboticArm") {
return (
<group
key={`${index}-${event.modelUuid}`}
position={event.position}
rotation={event.rotation}
>
<mesh
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{
modelUuid: event.modelUuid,
pointUuid: event.point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="green" />
</mesh>
</group>
);
} else if (event.type === "machine") {
return (
<group
key={`${index}-${event.modelUuid}`}
position={event.position}
rotation={event.rotation}
>
<mesh
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{
modelUuid: event.modelUuid,
pointUuid: event.point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="purple" />
</mesh>
</group>
);
} else {
return null;
}
})}
</group>
{selectedEventSphere && transformMode && (
<TransformControls
ref={transformRef}
object={selectedEventSphere}
mode={transformMode}
onMouseUp={(e) => {
updatePointToState(selectedEventSphere);
}}
/>
)}
</>
)}
</>
)}
</>
);
);
}
export default PointsCreator;

View File

@ -1,26 +1,35 @@
import React, { useEffect, useState, useRef } from 'react';
import * as THREE from 'three';
import { useFrame, useThree } from '@react-three/fiber';
import { usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
interface MaterialAnimatorProps {
matRef: React.RefObject<THREE.Mesh>;
material: MaterialSchema;
speed: number; // units per second
currentSpeed: number;
onAnimationComplete?: () => void;
}
function MaterialAnimator({
matRef,
material,
speed,
currentSpeed,
onAnimationComplete
}: MaterialAnimatorProps) {
const { scene } = useThree();
const [targetPosition, setTargetPosition] = useState<THREE.Vector3 | null>(null);
const [isAnimating, setIsAnimating] = useState(false);
const animationStartTime = useRef<number>(0);
const startPosition = useRef<THREE.Vector3>(new THREE.Vector3());
const totalDistance = useRef<number>(0);
const animationState = useRef({
startTime: 0,
startPosition: new THREE.Vector3(),
totalDistance: 0,
pausedTime: 0,
isPaused: false,
lastFrameTime: 0
});
const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore();
const getWorldPosition = (uuid: string): THREE.Vector3 | null => {
const obj = scene.getObjectByProperty('uuid', uuid);
@ -30,37 +39,58 @@ function MaterialAnimator({
return position;
};
// Handle target position changes and play state
useEffect(() => {
if (!material.next?.pointUuid) {
setTargetPosition(null);
if (!isPlaying || !material.next?.pointUuid) {
setIsAnimating(false);
return;
}
const newTarget = getWorldPosition(material.next.pointUuid);
if (newTarget && matRef.current) {
startPosition.current.copy(matRef.current.position);
totalDistance.current = startPosition.current.distanceTo(newTarget);
animationStartTime.current = performance.now();
animationState.current.startPosition.copy(matRef.current.position);
animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget);
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
animationState.current.pausedTime = 0;
animationState.current.isPaused = false;
setTargetPosition(newTarget);
setIsAnimating(true);
}
}, [material.next?.pointUuid]);
}, [material.next?.pointUuid, isPlaying]);
// Handle pause/unpause
useEffect(() => {
if (isPaused) {
animationState.current.isPaused = true;
setIsAnimating(false);
// Record the time when paused
animationState.current.pausedTime = performance.now() - animationState.current.startTime;
} else {
animationState.current.isPaused = false;
if (isPlaying && targetPosition && !isAnimating) {
// Resume from where we left off
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
setIsAnimating(true);
}
}
}, [isPaused]);
useFrame(() => {
if (!matRef.current || !targetPosition || !isAnimating) return;
if (!matRef.current || !targetPosition || !isAnimating || animationState.current.isPaused || !isPlaying) {
return;
}
const currentTime = performance.now();
// Calculate elapsed time since animation start, minus any paused time
const elapsed = (currentTime - animationState.current.startTime) / 1000;
const progress = Math.min(1, (currentSpeed * elapsed) / animationState.current.totalDistance);
// Calculate exact position based on constant speed
const elapsed = (performance.now() - animationStartTime.current) / 1000;
const progress = Math.min(1, (speed * elapsed) / totalDistance.current);
matRef.current.position.lerpVectors(
startPosition.current,
animationState.current.startPosition,
targetPosition,
progress
);
// Check if animation is complete
if (progress >= 1) {
matRef.current.position.copy(targetPosition);
setIsAnimating(false);

View File

@ -5,12 +5,16 @@ import { useProductStore } from '../../../../../store/simulation/useProductStore
import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
import { MaterialModel } from '../material/materialModel';
import { useThree } from '@react-three/fiber';
import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore';
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
function MaterialInstance({ material }: { material: MaterialSchema }) {
const matRef: any = useRef();
const { scene } = useThree();
const { getModelUuidByPointUuid, getPointByUuid, getEventByModelUuid } = useProductStore();
const { getModelUuidByPointUuid, getPointByUuid, getEventByModelUuid, getActionByUuid } = useProductStore();
const { selectedProduct } = useSelectedProduct();
const { speed } = useAnimationPlaySpeed();
const { triggerPointActions } = useTriggerHandler();
const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => {
const pointObj = scene.getObjectByProperty("uuid", pointUuid);
@ -21,32 +25,32 @@ function MaterialInstance({ material }: { material: MaterialSchema }) {
return worldPosition;
};
const { position, rotation, speed } = useMemo(() => {
const { position, rotation, currentSpeed } = useMemo(() => {
if (!material.current?.pointUuid) {
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 };
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), currentSpeed: 1 };
}
const modelUuid = getModelUuidByPointUuid(selectedProduct.productId, material.current.pointUuid);
if (!modelUuid) {
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 };
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), currentSpeed: 1 };
}
const speed = getCurrentSpeed(selectedProduct.productId, modelUuid);
const currentSpeed = getCurrentSpeed(selectedProduct.productId, modelUuid);
const point = getPointByUuid(selectedProduct.productId, modelUuid, material.current.pointUuid);
if (!point) {
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 };
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), currentSpeed: 1 };
}
const position = getWorldPositionFromScene(point.uuid);
if (position) {
return { position: position, rotation: new THREE.Vector3(0, 0, 0), speed: 1 };
return { position: position, rotation: new THREE.Vector3(0, 0, 0), currentSpeed: 1 };
}
return {
position: new THREE.Vector3(...point.position),
rotation: new THREE.Vector3(...point.rotation),
speed: speed || 1
currentSpeed: currentSpeed || 1
};
}, [material, getPointByUuid]);
@ -82,16 +86,25 @@ function MaterialInstance({ material }: { material: MaterialSchema }) {
// console.log('material: ', material);
}, [material])
const callTrigger = () => {
const action = getActionByUuid(selectedProduct.productId, material.current.actionUuid)
if (action) {
triggerPointActions(action);
}
}
return (
<>
<MaterialModel matRef={matRef} materialType={material.materialType} position={position} />
{material.isRendered &&
<MaterialModel matRef={matRef} materialType={material.materialType} visible={material.isVisible} position={position} />
}
<MaterialAnimator
matRef={matRef}
material={material}
speed={speed}
onAnimationComplete={() => { console.log('123');}}
currentSpeed={currentSpeed * speed}
onAnimationComplete={() => { callTrigger() }}
/>
</>
)

View File

@ -6,7 +6,7 @@ function MaterialInstances() {
const { materials } = useMaterialStore();
useEffect(() => {
// console.log('materials: ', materials);
console.log('materials: ', materials);
}, [materials])
return (

View File

@ -1,11 +1,27 @@
import React from 'react'
import React, { useEffect } from 'react'
import MaterialInstances from './instances/materialInstances'
import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore';
import { useMaterialStore } from '../../../store/simulation/useMaterialStore';
function Materials() {
const { clearMaterials } = useMaterialStore();
const { isPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
useEffect(() => {
if (isReset || !isPlaying) {
clearMaterials();
}
}, [isReset, isPlaying]);
return (
<>
<MaterialInstances />
{isPlaying &&
<MaterialInstances />
}
</>
)

View File

@ -1,21 +1,23 @@
import { useEffect } from 'react';
import { useProductStore } from '../../../store/simulation/useProductStore';
import { useActionHandler } from '../actions/useActionHandler';
import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore';
function Simulator() {
const { products } = useProductStore();
const { handleAction } = useActionHandler();
const { isPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
useEffect(() => {
if (!isPlaying || isReset) return;
const executionOrder = determineExecutionOrder(products);
executionOrder.forEach(point => {
if ('actions' in point) {
handleAction(point.actions[0]);
} else {
handleAction(point.action);
}
executionOrder.map(point => {
const action = 'actions' in point ? point.actions[0] : point.action;
handleAction(action);
});
}, [products, handleAction]);
}, [products, handleAction, isPlaying, isReset]);
function determineExecutionOrder(products: productsSchema): PointsScheme[] {
// Create maps for all events and points

View File

@ -10,6 +10,7 @@ import { handleAddEventToProduct } from "../../events/points/functions/handleAdd
import { QuadraticBezierLine } from "@react-three/drei";
import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
import { useDeleteTool } from "../../../../store/store";
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
interface ConnectionLine {
id: string;
@ -29,6 +30,7 @@ function TriggerConnector() {
const [helperlineColor, setHelperLineColor] = useState<string>("red");
const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null);
const { deleteTool } = useDeleteTool();
const { isPlaying } = usePlayButtonStore();
const [firstSelectedPoint, setFirstSelectedPoint] = useState<{
productId: string;
@ -424,7 +426,7 @@ function TriggerConnector() {
};
return (
<group name="simulationConnectionGroup" >
<group name="simulationConnectionGroup" visible={!isPlaying}>
{connections.map((connection) => {
const startPoint = getWorldPositionFromScene(connection.startPointUuid);
const endPoint = getWorldPositionFromScene(connection.endPointUuid);

View File

@ -1,13 +1,128 @@
import { useCallback, useEffect, useRef } from 'react';
import { useActionHandler } from '../../actions/useActionHandler';
import { useProductStore } from '../../../../store/simulation/useProductStore';
import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore';
import { useMaterialStore } from '../../../../store/simulation/useMaterialStore';
export function useTriggerHandler() {
const { getActionByUuid } = useProductStore();
const { getActionByUuid, getEventByTriggerUuid, getEventByModelUuid } = useProductStore();
const { handleAction } = useActionHandler();
const { getMaterialByCurrentModelUuid, setCurrentLocation, setNextLocation } = useMaterialStore();
const { selectedProduct } = useSelectedProduct();
const handleTrigger = (trigger: TriggerSchema) => {
const handleTrigger = (trigger: TriggerSchema, actionUuid: string) => {
// const fromEvent = getEventByTriggerUuid(selectedProduct.productId, trigger.triggerUuid);
// console.log('fromEvent: ', fromEvent);
// const toEvent = getEventByModelUuid(selectedProduct.productId, trigger.triggeredAsset?.triggeredModel.modelUuid || '');
// console.log('toEvent: ', toEvent);
// if (fromEvent?.type === 'transfer') {
// if (toEvent?.type === 'transfer') {
// // console.log('toEvent: ', toEvent.type);
// // Transfer to Transfer
// const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset?.triggeredAction?.actionUuid || '');
// if (action && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
// const material = getMaterialByCurrentModelUuid(fromEvent.modelUuid);
// if (material) {
// if (material.next &&
// action.triggers[0].triggeredAsset?.triggeredAction?.actionUuid &&
// action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid) {
// setCurrentLocation(material.materialId, material.next);
// setNextLocation(material.materialId, {
// modelUuid: toEvent.modelUuid,
// pointUuid: action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid,
// actionUuid: action.triggers[0].triggeredAsset?.triggeredAction?.actionUuid
// });
// }
// handleAction(action);
// }
// }
// } else if (toEvent?.type === 'vehicle') {
// // Transfer to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Transfer to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Transfer to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Transfer to Storage Unit
// }
// } else if (fromEvent?.type === 'vehicle') {
// if (toEvent?.type === 'transfer') {
// // Vehicle to Transfer
// } else if (toEvent?.type === 'vehicle') {
// // Vehicle to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Vehicle to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Vehicle to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Vehicle to Storage Unit
// }
// } else if (fromEvent?.type === 'machine') {
// if (toEvent?.type === 'transfer') {
// // Machine to Transfer
// } else if (toEvent?.type === 'vehicle') {
// // Machine to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Machine to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Machine to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Machine to Storage Unit
// }
// } else if (fromEvent?.type === 'roboticArm') {
// if (toEvent?.type === 'transfer') {
// // Robotic Arm to Transfer
// } else if (toEvent?.type === 'vehicle') {
// // Robotic Arm to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Robotic Arm to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Robotic Arm to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Robotic Arm to Storage Unit
// }
// } else if (fromEvent?.type === 'storageUnit') {
// if (toEvent?.type === 'transfer') {
// // Storage Unit to Transfer
// } else if (toEvent?.type === 'vehicle') {
// // Storage Unit to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Storage Unit to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Storage Unit to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Storage Unit to Storage Unit
// }
// }
}
const triggerPointActions = useCallback((action: Action) => {
@ -18,7 +133,7 @@ export function useTriggerHandler() {
case 'onStart':
break;
case 'onComplete':
handleTrigger(trigger);
handleTrigger(trigger, action.actionUuid);
break;
case 'onStop':
break;

View File

@ -6,6 +6,7 @@ type MaterialsStore = {
addMaterial: (material: MaterialSchema) => MaterialSchema | undefined;
removeMaterial: (materialId: string) => MaterialSchema | undefined;
clearMaterials: () => void;
updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => MaterialSchema | undefined;
setCurrentLocation: (
@ -35,6 +36,7 @@ type MaterialsStore = {
setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined;
getMaterialById: (materialId: string) => MaterialSchema | undefined;
getMaterialByCurrentModelUuid: (currentModelUuid: string) => MaterialSchema | undefined;
getMaterialsByPoint: (pointUuid: string) => MaterialSchema[];
getMaterialsByModel: (modelUuid: string) => MaterialSchema[];
};
@ -63,6 +65,12 @@ export const useMaterialStore = create<MaterialsStore>()(
return updatedMaterial;
},
clearMaterials: () => {
set((state) => {
state.materials = [];
});
},
updateMaterial: (materialId, updates) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
@ -186,6 +194,10 @@ export const useMaterialStore = create<MaterialsStore>()(
getMaterialById: (materialId) => {
return get().materials.find(m => m.materialId === materialId);
},
getMaterialByCurrentModelUuid: (currentModelUuid) => {
return get().materials.find(m => m.current?.modelUuid === currentModelUuid);
},
getMaterialsByPoint: (pointUuid) => {
return get().materials.filter(m =>

View File

@ -61,6 +61,8 @@ type ProductsStore = {
// Helper functions
getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined;
getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined;
getEventByTriggerUuid: (productId: string, triggerUuid: string) => EventsSchema | undefined;
getEventByPointUuid: (productId: string, pointUuid: string) => EventsSchema | undefined;
getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined;
getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined;
getModelUuidByPointUuid: (productId: string, actionUuid: string) => (string) | undefined;
@ -540,6 +542,53 @@ export const useProductStore = create<ProductsStore>()(
return product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
},
getEventByTriggerUuid: (productId, triggerUuid) => {
const product = get().getProductById(productId);
if (!product) return undefined;
for (const event of product.eventDatas) {
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
if (point.action?.triggers?.some(t => t.triggerUuid === triggerUuid)) {
return event;
}
}
} else if ('point' in event) {
const point = (event as any).point;
if ('action' in point) {
if (point.action?.triggers?.some((t: any) => t.triggerUuid === triggerUuid)) {
return event;
}
} else if ('actions' in point) {
for (const action of point.actions) {
if (action.triggers?.some((t: any) => t.triggerUuid === triggerUuid)) {
return event;
}
}
}
}
}
return undefined;
},
getEventByPointUuid: (productId, pointUuid) => {
const product = get().getProductById(productId);
if (!product) return undefined;
for (const event of product.eventDatas) {
if ('points' in event) {
if ((event as ConveyorEventSchema).points.some(p => p.uuid === pointUuid)) {
return event;
}
} else if ('point' in event) {
if ((event as any).point?.uuid === pointUuid) {
return event;
}
}
}
return undefined;
},
getPointByUuid: (productId, modelUuid, pointUuid) => {
const event = get().getEventByModelUuid(productId, modelUuid);
if (!event) return undefined;