add initial components and utility functions for simulation and builder modules
This commit is contained in:
77
app/src/modules/simulation/behaviour/behaviour.tsx
Normal file
77
app/src/modules/simulation/behaviour/behaviour.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useFloorItems } from '../../../store/store';
|
||||
import * as THREE from 'three';
|
||||
import * as Types from '../../../types/world/worldTypes';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
|
||||
interface Path {
|
||||
modeluuid: string;
|
||||
points: {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
events: { uuid: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | [];
|
||||
triggers: { uuid: string; type: string; isUsed: boolean }[] | [];
|
||||
}[];
|
||||
pathPosition: [number, number, number];
|
||||
pathRotation: [number, number, number];
|
||||
speed: number;
|
||||
}
|
||||
|
||||
function Behaviour({ setSimulationPaths }: { setSimulationPaths: any }) {
|
||||
const { floorItems } = useFloorItems();
|
||||
|
||||
useEffect(() => {
|
||||
const newPaths: Path[] = [];
|
||||
|
||||
floorItems.forEach((item: Types.FloorItemType) => {
|
||||
if (item.modelfileID === "6633215057b31fe671145959") {
|
||||
const point1Position = new THREE.Vector3(0, 1.25, 3.3);
|
||||
const middlePointPosition = new THREE.Vector3(0, 1.25, 0);
|
||||
const point2Position = new THREE.Vector3(0, 1.25, -3.3);
|
||||
|
||||
const point1UUID = THREE.MathUtils.generateUUID();
|
||||
const point2UUID = THREE.MathUtils.generateUUID();
|
||||
const middlePointUUID = THREE.MathUtils.generateUUID();
|
||||
|
||||
const newPath: Path = {
|
||||
modeluuid: item.modeluuid,
|
||||
points: [
|
||||
{
|
||||
uuid: point1UUID,
|
||||
position: [point1Position.x, point1Position.y, point1Position.z],
|
||||
rotation: [0, 0, 0],
|
||||
events: [{ uuid: THREE.MathUtils.generateUUID(), type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: false }],
|
||||
triggers: [],
|
||||
},
|
||||
{
|
||||
uuid: middlePointUUID,
|
||||
position: [middlePointPosition.x, middlePointPosition.y, middlePointPosition.z],
|
||||
rotation: [0, 0, 0],
|
||||
events: [{ uuid: THREE.MathUtils.generateUUID(), type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: false }],
|
||||
triggers: [],
|
||||
},
|
||||
{
|
||||
uuid: point2UUID,
|
||||
position: [point2Position.x, point2Position.y, point2Position.z],
|
||||
rotation: [0, 0, 0],
|
||||
events: [{ uuid: THREE.MathUtils.generateUUID(), type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: false }],
|
||||
triggers: [],
|
||||
},
|
||||
],
|
||||
pathPosition: [...item.position],
|
||||
pathRotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
speed: 1,
|
||||
};
|
||||
|
||||
newPaths.push(newPath);
|
||||
}
|
||||
});
|
||||
|
||||
setSimulationPaths(newPaths);
|
||||
}, [floorItems]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default Behaviour;
|
||||
55
app/src/modules/simulation/events.tsx/eventsControl.tsx
Normal file
55
app/src/modules/simulation/events.tsx/eventsControl.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { useControls } from 'leva';
|
||||
import { useSelectedEventSphere } from '../../../store/store';
|
||||
|
||||
interface Path {
|
||||
modeluuid: string;
|
||||
points: {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
events: string;
|
||||
triggers: string;
|
||||
}[];
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
}
|
||||
|
||||
const EventsControl = ({ simulationPaths, setSimulationPaths }: { simulationPaths: Path[], setSimulationPaths: any }) => {
|
||||
const { selectedEventSphere, setSelectedEventSphere } = useSelectedEventSphere();
|
||||
|
||||
const { events, triggers }: any = useControls({
|
||||
events: {
|
||||
value: selectedEventSphere?.point?.userData?.events || '',
|
||||
options: ['Event1', 'Event2', 'Event3'],
|
||||
onChange: (newEvent: string) => updatePathData(newEvent, 'events')
|
||||
},
|
||||
triggers: {
|
||||
value: selectedEventSphere?.point?.userData?.triggers || '',
|
||||
options: ['None', 'Trigger1', 'Trigger2', 'Trigger3'],
|
||||
onChange: (newTrigger: string) => updatePathData(newTrigger, 'triggers')
|
||||
},
|
||||
});
|
||||
|
||||
function updatePathData(value: string, key: 'events' | 'triggers') {
|
||||
if (!selectedEventSphere) return;
|
||||
|
||||
const updatedPaths = simulationPaths.map((path) =>
|
||||
path.modeluuid === selectedEventSphere.path.modeluuid
|
||||
? {
|
||||
...path,
|
||||
points: path.points.map((point) =>
|
||||
point.uuid === selectedEventSphere.point.uuid
|
||||
? { ...point, [key]: value }
|
||||
: point
|
||||
),
|
||||
}
|
||||
: path
|
||||
);
|
||||
console.log('updatedPaths: ', updatedPaths);
|
||||
setSimulationPaths(updatedPaths);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default EventsControl;
|
||||
287
app/src/modules/simulation/path/pathConnector.tsx
Normal file
287
app/src/modules/simulation/path/pathConnector.tsx
Normal file
@@ -0,0 +1,287 @@
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { QuadraticBezierLine } from '@react-three/drei';
|
||||
import { useConnections, useIsConnecting, useSimulationPaths } from '../../../store/store';
|
||||
import useModuleStore from '../../../store/useModuleStore';
|
||||
|
||||
function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject<THREE.Group> }) {
|
||||
const { activeModule } = useModuleStore();
|
||||
const { gl, raycaster, scene, pointer, camera } = useThree();
|
||||
const { connections, setConnections, addConnection } = useConnections();
|
||||
const { isConnecting, setIsConnecting } = useIsConnecting();
|
||||
const { simulationPaths, setSimulationPaths } = useSimulationPaths();
|
||||
|
||||
const [firstSelected, setFirstSelected] = useState<{ pathUUID: string; sphereUUID: string; position: THREE.Vector3; isCorner: boolean; } | null>(null);
|
||||
const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3, end: THREE.Vector3, mid: THREE.Vector3 } | null>(null);
|
||||
const [hoveredSphere, setHoveredSphere] = useState<{ sphereUUID: string, position: THREE.Vector3 } | null>(null);
|
||||
const [helperlineColor, setHelperLineColor] = useState<string>('red');
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
let drag = false;
|
||||
let MouseDown = false;
|
||||
|
||||
const onMouseDown = () => {
|
||||
MouseDown = true;
|
||||
drag = false;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
MouseDown = false;
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (MouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onContextMenu = (evt: MouseEvent) => {
|
||||
evt.preventDefault();
|
||||
if (drag || evt.button === 0) return;
|
||||
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(pathsGroupRef.current.children, true);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const intersected = intersects[0].object;
|
||||
|
||||
if (intersected.name.includes("event-sphere")) {
|
||||
const pathUUID = intersected.userData.path.modeluuid;
|
||||
const sphereUUID = intersected.uuid;
|
||||
const worldPosition = new THREE.Vector3();
|
||||
intersected.getWorldPosition(worldPosition);
|
||||
|
||||
const isStartOrEnd = intersected.userData.path.points.length > 0 && (
|
||||
sphereUUID === intersected.userData.path.points[0].uuid ||
|
||||
sphereUUID === intersected.userData.path.points[intersected.userData.path.points.length - 1].uuid
|
||||
);
|
||||
|
||||
if (pathUUID) {
|
||||
const isAlreadyConnected = connections.some((connection) =>
|
||||
connection.fromUUID === sphereUUID ||
|
||||
connection.toConnections.some(conn => conn.toUUID === sphereUUID)
|
||||
);
|
||||
|
||||
if (isAlreadyConnected) {
|
||||
console.log("Sphere is already connected. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!firstSelected) {
|
||||
setFirstSelected({
|
||||
pathUUID,
|
||||
sphereUUID,
|
||||
position: worldPosition,
|
||||
isCorner: isStartOrEnd
|
||||
});
|
||||
setIsConnecting(true);
|
||||
} else {
|
||||
if (firstSelected.sphereUUID === sphereUUID) return;
|
||||
|
||||
if (firstSelected.pathUUID === pathUUID) {
|
||||
console.log("Cannot connect spheres on the same path.");
|
||||
return;
|
||||
}
|
||||
if (!firstSelected.isCorner && !isStartOrEnd) {
|
||||
console.log("At least one of the selected spheres must be a start or end point.");
|
||||
return;
|
||||
}
|
||||
|
||||
addConnection({
|
||||
fromPathUUID: firstSelected.pathUUID,
|
||||
fromUUID: firstSelected.sphereUUID,
|
||||
toConnections: [{ toPathUUID: pathUUID, toUUID: sphereUUID }]
|
||||
});
|
||||
|
||||
setFirstSelected(null);
|
||||
setCurrentLine(null);
|
||||
setIsConnecting(false);
|
||||
setHoveredSphere(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setFirstSelected(null);
|
||||
setCurrentLine(null);
|
||||
setIsConnecting(false);
|
||||
setHoveredSphere(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (activeModule === 'simulation') {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||
} else {
|
||||
setFirstSelected(null);
|
||||
setCurrentLine(null);
|
||||
setIsConnecting(false);
|
||||
setHoveredSphere(null);
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||
};
|
||||
}, [camera, scene, raycaster, firstSelected, connections]);
|
||||
|
||||
useFrame(() => {
|
||||
if (firstSelected) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(scene.children, true).filter((intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.userData.isPathObject &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
|
||||
let point: THREE.Vector3 | null = null;
|
||||
let snappedSphere: { sphereUUID: string, position: THREE.Vector3, pathUUID: string, isCorner: boolean } | null = null;
|
||||
let isInvalidConnection = false;
|
||||
|
||||
if (intersects.length > 0) {
|
||||
point = intersects[0].point;
|
||||
|
||||
if (point.y < 0.05) {
|
||||
point = new THREE.Vector3(point.x, 0.05, point.z);
|
||||
}
|
||||
}
|
||||
|
||||
const sphereIntersects = raycaster.intersectObjects(pathsGroupRef.current.children, true).filter((obj) =>
|
||||
obj.object.name.includes("event-sphere")
|
||||
);
|
||||
|
||||
if (sphereIntersects.length > 0) {
|
||||
const sphere = sphereIntersects[0].object;
|
||||
const sphereUUID = sphere.uuid;
|
||||
const spherePosition = new THREE.Vector3();
|
||||
sphere.getWorldPosition(spherePosition);
|
||||
const pathUUID = sphere.userData.path.modeluuid;
|
||||
|
||||
const isStartOrEnd = sphere.userData.path.points.length > 0 && (
|
||||
sphereUUID === sphere.userData.path.points[0].uuid ||
|
||||
sphereUUID === sphere.userData.path.points[sphere.userData.path.points.length - 1].uuid
|
||||
);
|
||||
|
||||
const isAlreadyConnected = connections.some((connection) =>
|
||||
connection.fromUUID === sphereUUID ||
|
||||
connection.toConnections.some(conn => conn.toUUID === sphereUUID)
|
||||
);
|
||||
|
||||
if (
|
||||
!isAlreadyConnected &&
|
||||
firstSelected.sphereUUID !== sphereUUID &&
|
||||
firstSelected.pathUUID !== pathUUID &&
|
||||
(firstSelected.isCorner || isStartOrEnd)
|
||||
) {
|
||||
snappedSphere = { sphereUUID, position: spherePosition, pathUUID, isCorner: isStartOrEnd };
|
||||
} else {
|
||||
isInvalidConnection = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (snappedSphere) {
|
||||
setHoveredSphere(snappedSphere);
|
||||
point = snappedSphere.position;
|
||||
} else {
|
||||
setHoveredSphere(null);
|
||||
}
|
||||
|
||||
if (point) {
|
||||
const distance = firstSelected.position.distanceTo(point);
|
||||
const heightFactor = Math.max(0.5, distance * 0.2);
|
||||
const midPoint = new THREE.Vector3(
|
||||
(firstSelected.position.x + point.x) / 2,
|
||||
Math.max(firstSelected.position.y, point.y) + heightFactor,
|
||||
(firstSelected.position.z + point.z) / 2
|
||||
);
|
||||
|
||||
setCurrentLine({
|
||||
start: firstSelected.position,
|
||||
end: point,
|
||||
mid: midPoint,
|
||||
});
|
||||
|
||||
setIsConnecting(true);
|
||||
|
||||
if (sphereIntersects.length > 0) {
|
||||
setHelperLineColor(isInvalidConnection ? 'red' : '#6cf542');
|
||||
} else {
|
||||
setHelperLineColor('yellow');
|
||||
}
|
||||
} else {
|
||||
setCurrentLine(null);
|
||||
setIsConnecting(false);
|
||||
}
|
||||
} else {
|
||||
setCurrentLine(null);
|
||||
setIsConnecting(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log('connections: ', connections);
|
||||
}, [connections]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{connections.map((connection, index) => {
|
||||
const fromSphere = scene.getObjectByProperty('uuid', connection.fromUUID);
|
||||
const toSphere = scene.getObjectByProperty('uuid', connection.toConnections[0].toUUID);
|
||||
|
||||
if (fromSphere && toSphere) {
|
||||
const fromWorldPosition = new THREE.Vector3();
|
||||
const toWorldPosition = new THREE.Vector3();
|
||||
fromSphere.getWorldPosition(fromWorldPosition);
|
||||
toSphere.getWorldPosition(toWorldPosition);
|
||||
|
||||
const distance = fromWorldPosition.distanceTo(toWorldPosition);
|
||||
const heightFactor = Math.max(0.5, distance * 0.2);
|
||||
|
||||
const midPoint = new THREE.Vector3(
|
||||
(fromWorldPosition.x + toWorldPosition.x) / 2,
|
||||
Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor,
|
||||
(fromWorldPosition.z + toWorldPosition.z) / 2
|
||||
);
|
||||
|
||||
return (
|
||||
<QuadraticBezierLine
|
||||
key={index}
|
||||
start={fromWorldPosition.toArray()}
|
||||
end={toWorldPosition.toArray()}
|
||||
mid={midPoint.toArray()}
|
||||
color="white"
|
||||
lineWidth={4}
|
||||
dashed
|
||||
dashSize={1}
|
||||
dashScale={20}
|
||||
userData={connection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
|
||||
{currentLine && (
|
||||
<QuadraticBezierLine
|
||||
start={currentLine.start.toArray()}
|
||||
end={currentLine.end.toArray()}
|
||||
mid={currentLine.mid.toArray()}
|
||||
color={helperlineColor}
|
||||
lineWidth={4}
|
||||
dashed
|
||||
dashSize={1}
|
||||
dashScale={20}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PathConnector;
|
||||
163
app/src/modules/simulation/path/pathCreation.tsx
Normal file
163
app/src/modules/simulation/path/pathCreation.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import * as THREE from 'three';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { Sphere, TransformControls } from '@react-three/drei';
|
||||
import { useIsConnecting, useRenderDistance, useSelectedEventSphere, useSelectedPath, useSimulationPaths } from '../../../store/store';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
|
||||
interface Path {
|
||||
modeluuid: string;
|
||||
points: {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
events: { uuid: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | [];
|
||||
triggers: { uuid: string; type: string; isUsed: boolean }[] | [];
|
||||
}[];
|
||||
pathPosition: [number, number, number];
|
||||
pathRotation: [number, number, number];
|
||||
speed: number;
|
||||
}
|
||||
|
||||
function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject<THREE.Group> }) {
|
||||
const { renderDistance } = useRenderDistance();
|
||||
const { setSelectedEventSphere, selectedEventSphere } = useSelectedEventSphere();
|
||||
const { setSelectedPath } = useSelectedPath();
|
||||
const { simulationPaths, setSimulationPaths } = useSimulationPaths();
|
||||
const { isConnecting, setIsConnecting } = useIsConnecting();
|
||||
const { camera } = useThree();
|
||||
|
||||
const groupRefs = useRef<{ [key: string]: THREE.Group }>({});
|
||||
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
|
||||
const transformRef = useRef<any>(null);
|
||||
const [transformMode, setTransformMode] = useState<'translate' | 'rotate' | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setTransformMode(null);
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!selectedEventSphere) return;
|
||||
if (e.key === 'g') {
|
||||
setTransformMode(prev => prev === 'translate' ? null : 'translate');
|
||||
}
|
||||
if (e.key === 'r') {
|
||||
setTransformMode(prev => prev === 'rotate' ? null : 'rotate');
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [selectedEventSphere]);
|
||||
|
||||
useFrame(() => {
|
||||
Object.values(groupRefs.current).forEach(group => {
|
||||
if (group) {
|
||||
const distance = new THREE.Vector3(...group.position.toArray()).distanceTo(camera.position);
|
||||
group.visible = distance <= renderDistance;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const updateSimulationPaths = () => {
|
||||
if (!selectedEventSphere) return;
|
||||
|
||||
const updatedPaths: Path[] = simulationPaths.map((path) => ({
|
||||
...path,
|
||||
points: path.points.map((point) =>
|
||||
point.uuid === selectedEventSphere.point.uuid
|
||||
? {
|
||||
...point,
|
||||
position: [
|
||||
selectedEventSphere.point.position.x,
|
||||
selectedEventSphere.point.position.y,
|
||||
selectedEventSphere.point.position.z,
|
||||
],
|
||||
rotation: [
|
||||
selectedEventSphere.point.rotation.x,
|
||||
selectedEventSphere.point.rotation.y,
|
||||
selectedEventSphere.point.rotation.z,
|
||||
]
|
||||
}
|
||||
: point
|
||||
),
|
||||
}));
|
||||
|
||||
setSimulationPaths(updatedPaths);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<group name='simulation-simulationPaths-group' ref={pathsGroupRef} >
|
||||
{simulationPaths.map((path) => {
|
||||
const points = path.points.map(point => new THREE.Vector3(...point.position));
|
||||
|
||||
return (
|
||||
<group
|
||||
name={`${path.modeluuid}-event-path`}
|
||||
key={path.modeluuid}
|
||||
ref={el => (groupRefs.current[path.modeluuid] = el!)}
|
||||
position={path.pathPosition}
|
||||
rotation={path.pathRotation}
|
||||
onClick={(e) => {
|
||||
if (isConnecting) return;
|
||||
e.stopPropagation();
|
||||
setSelectedPath({ path, group: groupRefs.current[path.modeluuid] });
|
||||
setSelectedEventSphere(null);
|
||||
setTransformMode(null);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPath(null);
|
||||
}}
|
||||
>
|
||||
{path.points.map((point, index) => (
|
||||
<Sphere
|
||||
key={point.uuid}
|
||||
uuid={point.uuid}
|
||||
position={point.position}
|
||||
args={[0.15, 32, 32]}
|
||||
name='event-sphere'
|
||||
ref={el => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
if (isConnecting) return;
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere({
|
||||
path,
|
||||
point: sphereRefs.current[point.uuid]
|
||||
});
|
||||
setSelectedPath(null);
|
||||
}}
|
||||
userData={{ point, path }}
|
||||
onPointerMissed={() => setSelectedEventSphere(null)}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={index === 0 ? 'orange' : index === path.points.length - 1 ? 'blue' : 'green'}
|
||||
/>
|
||||
</Sphere>
|
||||
))}
|
||||
|
||||
{points.slice(0, -1).map((point, index) => {
|
||||
const nextPoint = points[index + 1];
|
||||
const segmentCurve = new THREE.CatmullRomCurve3([point, nextPoint]);
|
||||
const tubeGeometry = new THREE.TubeGeometry(segmentCurve, 20, 0.1, 16, false);
|
||||
|
||||
return (
|
||||
<mesh name='event-connection-tube' key={`tube-${index}`} geometry={tubeGeometry}>
|
||||
<meshStandardMaterial transparent opacity={0.9} color="red" />
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
})}
|
||||
|
||||
{selectedEventSphere && transformMode && (
|
||||
<TransformControls
|
||||
ref={transformRef}
|
||||
object={selectedEventSphere.point}
|
||||
mode={transformMode}
|
||||
onObjectChange={updateSimulationPaths}
|
||||
/>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default PathCreation;
|
||||
46
app/src/modules/simulation/simulation.tsx
Normal file
46
app/src/modules/simulation/simulation.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useConnections, useFloorItems, useSelectedEventSphere, useSelectedPath, useSimulationPaths } from '../../store/store';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import Behaviour from './behaviour/behaviour';
|
||||
import PathCreation from './path/pathCreation';
|
||||
import PathConnector from './path/pathConnector';
|
||||
import useModuleStore from '../../store/useModuleStore';
|
||||
|
||||
function Simulation() {
|
||||
const { activeModule } = useModuleStore();
|
||||
const pathsGroupRef = useRef() as React.MutableRefObject<THREE.Group>;
|
||||
const { simulationPaths, setSimulationPaths } = useSimulationPaths();
|
||||
const { connections, setConnections, addConnection, removeConnection } = useConnections();
|
||||
const [processes, setProcesses] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('simulationPaths: ', simulationPaths);
|
||||
}, [simulationPaths]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (selectedEventSphere) {
|
||||
// console.log('selectedEventSphere: ', selectedEventSphere);
|
||||
// }
|
||||
// }, [selectedEventSphere]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (selectedPath) {
|
||||
// console.log('selectedPath: ', selectedPath);
|
||||
// }
|
||||
// }, [selectedPath]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{activeModule === 'simulation' && (
|
||||
<>
|
||||
<Behaviour setSimulationPaths={setSimulationPaths} />
|
||||
<PathCreation pathsGroupRef={pathsGroupRef} />
|
||||
<PathConnector pathsGroupRef={pathsGroupRef} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Simulation;
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
function ColliderCreator() {
|
||||
return (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
export default ColliderCreator
|
||||
404
app/src/modules/simulation/simulationtemp/path/pathCreator.tsx
Normal file
404
app/src/modules/simulation/simulationtemp/path/pathCreator.tsx
Normal file
@@ -0,0 +1,404 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useThree, useFrame } from '@react-three/fiber';
|
||||
import { Line, TransformControls } from '@react-three/drei';
|
||||
import { useDrawMaterialPath } from '../../../../store/store';
|
||||
|
||||
type PathPoint = {
|
||||
position: THREE.Vector3;
|
||||
rotation: THREE.Quaternion;
|
||||
uuid: string;
|
||||
};
|
||||
|
||||
type PathCreatorProps = {
|
||||
simulationPaths: PathPoint[][];
|
||||
setSimulationPaths: React.Dispatch<React.SetStateAction<PathPoint[][]>>;
|
||||
connections: { start: PathPoint; end: PathPoint }[];
|
||||
setConnections: React.Dispatch<React.SetStateAction<{ start: PathPoint; end: PathPoint }[]>>
|
||||
};
|
||||
|
||||
const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConnections }: PathCreatorProps) => {
|
||||
const { camera, scene, raycaster, pointer, gl } = useThree();
|
||||
const { drawMaterialPath } = useDrawMaterialPath();
|
||||
|
||||
const [currentPath, setCurrentPath] = useState<{ position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string }[]>([]);
|
||||
const [temporaryPoint, setTemporaryPoint] = useState<THREE.Vector3 | null>(null);
|
||||
const [selectedPoint, setSelectedPoint] = useState<{ position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string } | null>(null);
|
||||
const [selectedConnectionPoint, setSelectedConnectionPoint] = useState<{ point: PathPoint; pathIndex: number } | null>(null);
|
||||
const [previewConnection, setPreviewConnection] = useState<{ start: PathPoint; end?: THREE.Vector3 } | null>(null);
|
||||
const [transformMode, setTransformMode] = useState<'translate' | 'rotate'>('translate');
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (selectedPoint) {
|
||||
if (event.key === 'g') {
|
||||
setTransformMode('translate');
|
||||
} else if (event.key === 'r') {
|
||||
setTransformMode('rotate');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [selectedPoint]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
let drag = false;
|
||||
let MouseDown = false;
|
||||
|
||||
const onMouseDown = () => {
|
||||
MouseDown = true;
|
||||
drag = false;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
MouseDown = false;
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (MouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onContextMenu = (e: any) => {
|
||||
e.preventDefault();
|
||||
if (drag || e.button === 0) return;
|
||||
if (currentPath.length > 1) {
|
||||
setSimulationPaths((prevPaths) => [...prevPaths, currentPath]);
|
||||
}
|
||||
setCurrentPath([]);
|
||||
setTemporaryPoint(null);
|
||||
setPreviewConnection(null);
|
||||
setSelectedConnectionPoint(null);
|
||||
};
|
||||
|
||||
const onMouseClick = (evt: any) => {
|
||||
if (drag || evt.button !== 0) return;
|
||||
|
||||
evt.preventDefault();
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
|
||||
let intersects = raycaster.intersectObjects(scene.children, true);
|
||||
|
||||
if (intersects.some((intersect) => intersect.object.name.includes("path-point"))) {
|
||||
intersects = [];
|
||||
} else {
|
||||
intersects = intersects.filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.userData.isPathObject &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
}
|
||||
|
||||
if (intersects.length > 0 && selectedPoint === null) {
|
||||
let point = intersects[0].point;
|
||||
if (point.y < 0.05) {
|
||||
point = new THREE.Vector3(point.x, 0.05, point.z);
|
||||
}
|
||||
const newPoint = {
|
||||
position: point,
|
||||
rotation: new THREE.Quaternion(),
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
};
|
||||
setCurrentPath((prevPath) => [...prevPath, newPoint]);
|
||||
setTemporaryPoint(null);
|
||||
} else {
|
||||
setSelectedPoint(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (drawMaterialPath) {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener("click", onMouseClick);
|
||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||
} else {
|
||||
if (currentPath.length > 1) {
|
||||
setSimulationPaths((prevPaths) => [...prevPaths, currentPath]);
|
||||
}
|
||||
setCurrentPath([]);
|
||||
setTemporaryPoint(null);
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener("click", onMouseClick);
|
||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||
};
|
||||
}, [camera, scene, raycaster, currentPath, drawMaterialPath, selectedPoint]);
|
||||
|
||||
useFrame(() => {
|
||||
if (drawMaterialPath && currentPath.length > 0) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
|
||||
const intersects = raycaster.intersectObjects(scene.children, true).filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.userData.isPathObject &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
let point = intersects[0].point;
|
||||
if (point.y < 0.05) {
|
||||
point = new THREE.Vector3(point.x, 0.05, point.z);
|
||||
}
|
||||
setTemporaryPoint(point);
|
||||
} else {
|
||||
setTemporaryPoint(null);
|
||||
}
|
||||
} else {
|
||||
setTemporaryPoint(null);
|
||||
}
|
||||
});
|
||||
|
||||
const handlePointClick = (point: { position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string }) => {
|
||||
if (currentPath.length === 0 && drawMaterialPath) {
|
||||
setSelectedPoint(point);
|
||||
} else {
|
||||
setSelectedPoint(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTransform = (e: any) => {
|
||||
if (selectedPoint) {
|
||||
const updatedPosition = e.target.object.position.clone();
|
||||
const updatedRotation = e.target.object.quaternion.clone();
|
||||
const updatedPaths = simulationPaths.map((path) =>
|
||||
path.map((p) =>
|
||||
p.uuid === selectedPoint.uuid ? { ...p, position: updatedPosition, rotation: updatedRotation } : p
|
||||
)
|
||||
);
|
||||
setSimulationPaths(updatedPaths);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const meshContext = (uuid: string) => {
|
||||
const pathIndex = simulationPaths.findIndex(path => path.some(point => point.uuid === uuid));
|
||||
if (pathIndex === -1) return;
|
||||
|
||||
const clickedPoint = simulationPaths[pathIndex].find(point => point.uuid === uuid);
|
||||
if (!clickedPoint) return;
|
||||
|
||||
const isStart = simulationPaths[pathIndex][0].uuid === uuid;
|
||||
const isEnd = simulationPaths[pathIndex][simulationPaths[pathIndex].length - 1].uuid === uuid;
|
||||
|
||||
if (pathIndex === 0 && isStart) {
|
||||
console.log("The first-ever point is not connectable.");
|
||||
setSelectedConnectionPoint(null);
|
||||
setPreviewConnection(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isStart && !isEnd) {
|
||||
console.log("Selected point is not a valid connection point (not start or end)");
|
||||
setSelectedConnectionPoint(null);
|
||||
setPreviewConnection(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connections.some(conn => conn.start.uuid === uuid || conn.end.uuid === uuid)) {
|
||||
console.log("The selected point is already connected.");
|
||||
setSelectedConnectionPoint(null);
|
||||
setPreviewConnection(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedConnectionPoint) {
|
||||
setSelectedConnectionPoint({ point: clickedPoint, pathIndex });
|
||||
setPreviewConnection({ start: clickedPoint });
|
||||
console.log("First point selected for connection:", clickedPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedConnectionPoint.pathIndex === pathIndex) {
|
||||
console.log("Cannot connect points within the same path.");
|
||||
setSelectedConnectionPoint(null);
|
||||
setPreviewConnection(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connections.some(conn => conn.start.uuid === clickedPoint.uuid || conn.end.uuid === clickedPoint.uuid)) {
|
||||
console.log("The target point is already connected.");
|
||||
setSelectedConnectionPoint(null);
|
||||
setPreviewConnection(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setConnections(prevConnections => [
|
||||
...prevConnections,
|
||||
{ start: selectedConnectionPoint.point, end: clickedPoint },
|
||||
]);
|
||||
|
||||
|
||||
setSelectedConnectionPoint(null);
|
||||
setPreviewConnection(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedConnectionPoint) {
|
||||
setPreviewConnection(null);
|
||||
}
|
||||
}, [selectedConnectionPoint, connections]);
|
||||
|
||||
useFrame(() => {
|
||||
if (selectedConnectionPoint) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
|
||||
const intersects = raycaster.intersectObjects(scene.children, true).filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.userData.isPathObject &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
let point = intersects[0].point;
|
||||
if (point.y < 0.05) {
|
||||
point = new THREE.Vector3(point.x, 0.05, point.z);
|
||||
}
|
||||
setPreviewConnection({ start: selectedConnectionPoint.point, end: point });
|
||||
} else {
|
||||
setPreviewConnection(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<group name='pathObjects'>
|
||||
{/* Render finalized simulationPaths */}
|
||||
{simulationPaths.map((path, pathIndex) => (
|
||||
<group key={`path-line-${pathIndex}`}>
|
||||
<Line
|
||||
name={`path-line-${pathIndex}`}
|
||||
points={path.map((point) => point.position)}
|
||||
color="yellow"
|
||||
lineWidth={5}
|
||||
userData={{ isPathObject: true }}
|
||||
/>
|
||||
</group>
|
||||
))}
|
||||
|
||||
{/* Render finalized points */}
|
||||
{simulationPaths.map((path) =>
|
||||
path.map((point) => (
|
||||
<mesh
|
||||
key={`path-point-${point.uuid}`}
|
||||
name={`path-point-${point.uuid}`}
|
||||
uuid={`${point.uuid}`}
|
||||
position={point.position}
|
||||
userData={{ isPathObject: true }}
|
||||
onClick={() => handlePointClick(point)}
|
||||
onPointerMissed={() => { setSelectedPoint(null) }}
|
||||
onContextMenu={() => { meshContext(point.uuid); }}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="blue" wireframe />
|
||||
</mesh>
|
||||
))
|
||||
)}
|
||||
|
||||
{connections.map((conn, index) => (
|
||||
<Line
|
||||
key={`connection-${index}`}
|
||||
points={[conn.start.position, conn.end.position]}
|
||||
color="white"
|
||||
dashed
|
||||
lineWidth={4}
|
||||
dashSize={1}
|
||||
dashScale={15}
|
||||
userData={{ isPathObject: true }}
|
||||
/>
|
||||
))}
|
||||
|
||||
</group>
|
||||
|
||||
{/* Render current path */}
|
||||
{currentPath.length > 1 && (
|
||||
<group>
|
||||
<Line
|
||||
points={currentPath.map((point) => point.position)}
|
||||
color="red"
|
||||
lineWidth={5}
|
||||
userData={{ isPathObject: true }}
|
||||
/>
|
||||
</group>
|
||||
)}
|
||||
|
||||
{/* Render current path points */}
|
||||
{currentPath.map((point) => (
|
||||
<mesh
|
||||
key={`current-point-${point.uuid}`}
|
||||
position={point.position}
|
||||
userData={{ isPathObject: true }}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="red" />
|
||||
</mesh>
|
||||
))}
|
||||
|
||||
{/* Render temporary indicator line */}
|
||||
{temporaryPoint && currentPath.length > 0 && (
|
||||
<group>
|
||||
<Line
|
||||
points={[currentPath[currentPath.length - 1].position, temporaryPoint]}
|
||||
color="white"
|
||||
lineWidth={2}
|
||||
userData={{ isPathObject: true }}
|
||||
/>
|
||||
</group>
|
||||
)}
|
||||
|
||||
{/* Render dashed preview connection */}
|
||||
{previewConnection && previewConnection.end && (
|
||||
<Line
|
||||
points={[previewConnection.start.position, previewConnection.end]}
|
||||
color="white"
|
||||
dashed
|
||||
lineWidth={4}
|
||||
dashSize={1}
|
||||
dashScale={15}
|
||||
userData={{ isPathObject: true }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Render temporary point */}
|
||||
{temporaryPoint && (
|
||||
<mesh
|
||||
position={temporaryPoint}
|
||||
userData={{ isPathObject: true }}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="white" />
|
||||
</mesh>
|
||||
)}
|
||||
|
||||
{/* Attach TransformControls to the selected point */}
|
||||
{selectedPoint && (
|
||||
<TransformControls
|
||||
object={scene.getObjectByProperty('uuid', selectedPoint.uuid)}
|
||||
mode={transformMode}
|
||||
onObjectChange={handleTransform}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PathCreator;
|
||||
164
app/src/modules/simulation/simulationtemp/path/pathFlow.tsx
Normal file
164
app/src/modules/simulation/simulationtemp/path/pathFlow.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import * as THREE from 'three';
|
||||
import { useState, useEffect, useRef, useMemo } from "react";
|
||||
import { useLoader, useFrame } from "@react-three/fiber";
|
||||
import { GLTFLoader } from "three-stdlib";
|
||||
import crate from "../../../../assets/models/gltf-glb/crate_box.glb";
|
||||
import { useOrganization } from '../../../../store/store';
|
||||
import { useControls } from 'leva';
|
||||
|
||||
type PathPoint = {
|
||||
position: THREE.Vector3;
|
||||
rotation: THREE.Quaternion;
|
||||
uuid: string;
|
||||
};
|
||||
|
||||
type PathFlowProps = {
|
||||
path: PathPoint[];
|
||||
connections: { start: PathPoint; end: PathPoint }[];
|
||||
};
|
||||
|
||||
export default function PathFlow({ path, connections }: PathFlowProps) {
|
||||
const { organization } = useOrganization();
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [isStopped, setIsStopped] = useState(false);
|
||||
|
||||
const { spawnInterval, speed, pauseResume, startStop } = useControls({
|
||||
spawnInterval: { value: 1000, min: 500, max: 5000, step: 100 },
|
||||
speed: { value: 2, min: 1, max: 20, step: 0.5 },
|
||||
pauseResume: { value: false, label: "Pause/Resume" },
|
||||
startStop: { value: false, label: "Start/Stop" },
|
||||
});
|
||||
|
||||
const [meshes, setMeshes] = useState<{ id: number }[]>([]);
|
||||
const gltf = useLoader(GLTFLoader, crate);
|
||||
|
||||
const meshIdRef = useRef(0);
|
||||
const lastSpawnTime = useRef(performance.now());
|
||||
const totalPausedTime = useRef(0);
|
||||
const pauseStartTime = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setIsPaused(pauseResume);
|
||||
setIsStopped(startStop);
|
||||
}, [pauseResume, startStop]);
|
||||
|
||||
const removeMesh = (id: number) => {
|
||||
setMeshes((prev) => prev.filter((m) => m.id !== id));
|
||||
};
|
||||
|
||||
useFrame(() => {
|
||||
if (organization !== 'hexrfactory' || isStopped || !path) return;
|
||||
|
||||
const now = performance.now();
|
||||
|
||||
if (isPaused) {
|
||||
if (pauseStartTime.current === null) {
|
||||
pauseStartTime.current = now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pauseStartTime.current !== null) {
|
||||
totalPausedTime.current += now - pauseStartTime.current;
|
||||
pauseStartTime.current = null;
|
||||
}
|
||||
|
||||
const adjustedTime = now - totalPausedTime.current;
|
||||
|
||||
if (adjustedTime - lastSpawnTime.current >= spawnInterval) {
|
||||
setMeshes((prev) => [...prev, { id: meshIdRef.current++ }]);
|
||||
lastSpawnTime.current = adjustedTime;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{meshes.map((mesh) => (
|
||||
<MovingMesh
|
||||
key={mesh.id}
|
||||
meshId={mesh.id}
|
||||
points={path}
|
||||
speed={speed}
|
||||
gltf={gltf}
|
||||
removeMesh={removeMesh}
|
||||
isPaused={isPaused}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MovingMesh({ meshId, points, speed, gltf, removeMesh, isPaused }: any) {
|
||||
const meshRef = useRef<any>();
|
||||
const startTime = useRef<number | null>(null); // Initialize as null
|
||||
const pausedTime = useRef(0);
|
||||
const pauseStartTime = useRef<number | null>(null);
|
||||
|
||||
const distances = useMemo(() => {
|
||||
if (!points || points.length < 2) return [];
|
||||
return points.slice(1).map((point: any, i: number) => points[i].position.distanceTo(point.position));
|
||||
}, [points]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!points || points.length < 2) return;
|
||||
|
||||
if (startTime.current === null && points.length > 0) {
|
||||
startTime.current = performance.now();
|
||||
}
|
||||
|
||||
if (!meshRef.current) return;
|
||||
|
||||
if (isPaused) {
|
||||
if (pauseStartTime.current === null) {
|
||||
pauseStartTime.current = performance.now();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pauseStartTime.current !== null) {
|
||||
pausedTime.current += performance.now() - pauseStartTime.current;
|
||||
pauseStartTime.current = null;
|
||||
}
|
||||
|
||||
if (startTime.current === null) return;
|
||||
|
||||
const elapsed = performance.now() - startTime.current - pausedTime.current;
|
||||
|
||||
const distanceTraveled = elapsed / 1000 * speed;
|
||||
|
||||
let remainingDistance = distanceTraveled;
|
||||
let currentSegmentIndex = 0;
|
||||
|
||||
while (currentSegmentIndex < distances.length && remainingDistance > distances[currentSegmentIndex]) {
|
||||
remainingDistance -= distances[currentSegmentIndex];
|
||||
currentSegmentIndex++;
|
||||
}
|
||||
|
||||
if (currentSegmentIndex >= distances.length) {
|
||||
removeMesh(meshId);
|
||||
return;
|
||||
}
|
||||
|
||||
const progress = remainingDistance / distances[currentSegmentIndex];
|
||||
const start = points[currentSegmentIndex].position;
|
||||
const end = points[currentSegmentIndex + 1].position;
|
||||
|
||||
meshRef.current.position.lerpVectors(start, end, Math.min(progress, 1));
|
||||
|
||||
const startRotation = points[currentSegmentIndex].rotation;
|
||||
const endRotation = points[currentSegmentIndex + 1].rotation;
|
||||
const interpolatedRotation = new THREE.Quaternion().slerpQuaternions(startRotation, endRotation, Math.min(progress, 1));
|
||||
|
||||
meshRef.current.quaternion.copy(interpolatedRotation);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{points && points.length > 0 &&
|
||||
<mesh ref={meshRef}>
|
||||
<primitive object={gltf.scene.clone()} />
|
||||
</mesh>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
function ProcessCreator() {
|
||||
return (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProcessCreator
|
||||
26
app/src/modules/simulation/simulationtemp/simulation.tsx
Normal file
26
app/src/modules/simulation/simulationtemp/simulation.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import PathCreator from './path/pathCreator';
|
||||
import PathFlow from './path/pathFlow';
|
||||
|
||||
type PathPoint = {
|
||||
position: THREE.Vector3;
|
||||
rotation: THREE.Quaternion;
|
||||
uuid: string;
|
||||
};
|
||||
|
||||
function Simulation() {
|
||||
const [simulationPaths, setSimulationPaths] = useState<{ position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string }[][]>([]);
|
||||
const [connections, setConnections] = useState<{ start: PathPoint; end: PathPoint }[]>([]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PathCreator simulationPaths={simulationPaths} setSimulationPaths={setSimulationPaths} connections={connections} setConnections={setConnections} />
|
||||
{simulationPaths.map((path, index) => (
|
||||
<PathFlow key={index} path={path} connections={connections} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Simulation;
|
||||
Reference in New Issue
Block a user