upstream pull + signIn/Up

This commit is contained in:
2025-03-25 17:34:20 +05:30
199 changed files with 40127 additions and 40128 deletions

View File

@@ -1,287 +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}
/>
)}
</>
);
}
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;

View File

@@ -1,171 +1,171 @@
import * as THREE from 'three';
import { useRef, useState, useEffect } from 'react';
import { Sphere, TransformControls } from '@react-three/drei';
import { useIsConnecting, useRenderDistance, useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../../store/store';
import { useFrame, useThree } from '@react-three/fiber';
import { useSubModuleStore } from '../../../store/useModuleStore';
interface Path {
modeluuid: string;
modelName: string;
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { 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 { setSubModule } = useSubModuleStore();
const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere();
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 (!selectedActionSphere) 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);
}, [selectedActionSphere]);
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 (!selectedActionSphere) return;
const updatedPaths: Path[] = simulationPaths.map((path) => ({
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.point.uuid
? {
...point,
position: [
selectedActionSphere.point.position.x,
selectedActionSphere.point.position.y,
selectedActionSphere.point.position.z,
],
rotation: [
selectedActionSphere.point.rotation.x,
selectedActionSphere.point.rotation.y,
selectedActionSphere.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] });
setSelectedActionSphere(null);
setTransformMode(null);
}}
onPointerMissed={() => {
setSelectedPath(null);
setSubModule('properties');
}}
>
{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();
setSelectedActionSphere({
path,
point: sphereRefs.current[point.uuid]
});
setSubModule('mechanics');
setSelectedPath(null);
}}
userData={{ point, path }}
onPointerMissed={() => {
setSubModule('properties');
setSelectedActionSphere(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>
);
})}
{selectedActionSphere && transformMode && (
<TransformControls
ref={transformRef}
object={selectedActionSphere.point}
mode={transformMode}
onObjectChange={updateSimulationPaths}
/>
)}
</group>
);
}
export default PathCreation;
import * as THREE from 'three';
import { useRef, useState, useEffect } from 'react';
import { Sphere, TransformControls } from '@react-three/drei';
import { useIsConnecting, useRenderDistance, useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../../store/store';
import { useFrame, useThree } from '@react-three/fiber';
import { useSubModuleStore } from '../../../store/useModuleStore';
interface Path {
modeluuid: string;
modelName: string;
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { 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 { setSubModule } = useSubModuleStore();
const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere();
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 (!selectedActionSphere) 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);
}, [selectedActionSphere]);
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 (!selectedActionSphere) return;
const updatedPaths: Path[] = simulationPaths.map((path) => ({
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.point.uuid
? {
...point,
position: [
selectedActionSphere.point.position.x,
selectedActionSphere.point.position.y,
selectedActionSphere.point.position.z,
],
rotation: [
selectedActionSphere.point.rotation.x,
selectedActionSphere.point.rotation.y,
selectedActionSphere.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] });
setSelectedActionSphere(null);
setTransformMode(null);
}}
onPointerMissed={() => {
setSelectedPath(null);
setSubModule('properties');
}}
>
{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();
setSelectedActionSphere({
path,
point: sphereRefs.current[point.uuid]
});
setSubModule('mechanics');
setSelectedPath(null);
}}
userData={{ point, path }}
onPointerMissed={() => {
setSubModule('properties');
setSelectedActionSphere(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>
);
})}
{selectedActionSphere && transformMode && (
<TransformControls
ref={transformRef}
object={selectedActionSphere.point}
mode={transformMode}
onObjectChange={updateSimulationPaths}
/>
)}
</group>
);
}
export default PathCreation;