164 lines
6.9 KiB
TypeScript
164 lines
6.9 KiB
TypeScript
|
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;
|