feat: Enhance conveyor collider functionality with direction toggle and visual indicators
This commit is contained in:
@@ -6,8 +6,8 @@ import RenameInput from "../../../../../ui/inputs/RenameInput";
|
|||||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||||
import Trigger from "../trigger/Trigger";
|
import Trigger from "../trigger/Trigger";
|
||||||
import ActionsList from "../components/ActionsList";
|
import ActionsList from "../components/ActionsList";
|
||||||
import WorkerAction from "../actions/workerAction";
|
import WorkerAction from "../actions/WorkerAction";
|
||||||
import AssemblyAction from "../actions/assemblyAction";
|
import AssemblyAction from "../actions/AssemblyAction";
|
||||||
|
|
||||||
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
||||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||||
|
|||||||
@@ -2,43 +2,49 @@ import * as THREE from 'three';
|
|||||||
import NormalConveyorCollider from './types/normalConveyorCollider';
|
import NormalConveyorCollider from './types/normalConveyorCollider';
|
||||||
import CurvedConveyorCollider from './types/curvedConveyorCollider';
|
import CurvedConveyorCollider from './types/curvedConveyorCollider';
|
||||||
import YSplitConveyorCollider from './types/ySplitConveyorCollider';
|
import YSplitConveyorCollider from './types/ySplitConveyorCollider';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
function RibbonCollider({ ribbonData, boundingBox, asset }: {
|
function RibbonCollider({
|
||||||
|
ribbonData,
|
||||||
|
boundingBox,
|
||||||
|
asset
|
||||||
|
}: {
|
||||||
ribbonData: ConveyorPoints,
|
ribbonData: ConveyorPoints,
|
||||||
boundingBox: THREE.Box3 | null,
|
boundingBox: THREE.Box3 | null,
|
||||||
asset: Asset,
|
asset: Asset,
|
||||||
}) {
|
}) {
|
||||||
// console.log('ribbonData: ', ribbonData);
|
const [forward, setForward] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ribbonData.type === 'normal' &&
|
{ribbonData.type === 'normal' && (
|
||||||
<NormalConveyorCollider
|
<NormalConveyorCollider
|
||||||
points={ribbonData.points}
|
points={ribbonData.points}
|
||||||
boundingBox={boundingBox}
|
boundingBox={boundingBox}
|
||||||
asset={asset}
|
asset={asset}
|
||||||
forward={false}
|
forward={forward}
|
||||||
isPaused={false}
|
isPaused={false}
|
||||||
|
onDirectionChange={setForward}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{ribbonData.type === 'curved' &&
|
{ribbonData.type === 'curved' && (
|
||||||
<CurvedConveyorCollider
|
<CurvedConveyorCollider
|
||||||
points={ribbonData.points}
|
points={ribbonData.points}
|
||||||
boundingBox={boundingBox}
|
boundingBox={boundingBox}
|
||||||
asset={asset}
|
asset={asset}
|
||||||
forward={false}
|
forward={forward}
|
||||||
isPaused={false}
|
isPaused={false}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{ribbonData.type === 'y-Split' &&
|
{ribbonData.type === 'y-Split' && (
|
||||||
<YSplitConveyorCollider
|
<YSplitConveyorCollider
|
||||||
points={ribbonData.points}
|
points={ribbonData.points}
|
||||||
boundingBox={boundingBox}
|
boundingBox={boundingBox}
|
||||||
asset={asset}
|
asset={asset}
|
||||||
forward={false}
|
forward={forward}
|
||||||
isPaused={false}
|
isPaused={false}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,195 +3,360 @@ import { CollisionPayload, RigidBody } from '@react-three/rapier';
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useFrame } from '@react-three/fiber';
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
|
||||||
function CurvedConveyorCollider({ points, boundingBox, asset, forward, isPaused }: {
|
function CurvedConveyorCollider({
|
||||||
points: [number, number, number][][],
|
points,
|
||||||
boundingBox: THREE.Box3 | null,
|
boundingBox,
|
||||||
asset: Asset,
|
asset,
|
||||||
forward: boolean
|
forward: initialForward,
|
||||||
isPaused: boolean
|
isPaused,
|
||||||
|
}: {
|
||||||
|
points: [number, number, number][][];
|
||||||
|
boundingBox: THREE.Box3 | null;
|
||||||
|
asset: Asset;
|
||||||
|
forward: boolean;
|
||||||
|
isPaused: boolean;
|
||||||
}) {
|
}) {
|
||||||
const conveyorRef = useRef<any>(null);
|
const conveyorRef = useRef<any>(null);
|
||||||
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||||
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||||
const conveyorSpeed = 2;
|
const [forward, setForward] = useState(initialForward);
|
||||||
const [geometryKey, setGeometryKey] = useState(0);
|
const [showDirection, setShowDirection] = useState(false);
|
||||||
|
const [hoverState, setHoverState] = useState(false);
|
||||||
|
const conveyorSpeed = 2;
|
||||||
|
const lastClickTime = useRef(0);
|
||||||
|
const arrowRefs = useRef<THREE.Group[]>([]);
|
||||||
|
const [geometryKey, setGeometryKey] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
// Toggle direction on double right click
|
||||||
if (!boundingBox) return;
|
useEffect(() => {
|
||||||
const size = boundingBox.getSize(new THREE.Vector3());
|
const handleClick = (e: MouseEvent) => {
|
||||||
const [width, depth] = [size.x, size.z];
|
if (e.button === 2 && hoverState) { // Right click and hovering over conveyor
|
||||||
conveyorDirection.current.set(width < depth ? 0 : 1, 0, width < depth ? 1 : 0);
|
const now = Date.now();
|
||||||
|
if (now - lastClickTime.current < 300) {
|
||||||
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
console.log("log");
|
||||||
conveyorDirection.current.applyEuler(rotation);
|
setForward(prev => !prev);
|
||||||
}, [boundingBox, asset.rotation]);
|
|
||||||
|
|
||||||
const handleMaterialEnter = (e: CollisionPayload) => {
|
|
||||||
if (e.other.rigidBody) {
|
|
||||||
setObjectsOnConveyor(prev => {
|
|
||||||
const newSet = new Set(prev);
|
|
||||||
newSet.add(e.other.rigidBody);
|
|
||||||
return newSet;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
lastClickTime.current = now;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMaterialExit = (e: CollisionPayload) => {
|
window.addEventListener('mousedown', handleClick);
|
||||||
if (e.other.rigidBody) {
|
return () => window.removeEventListener('mousedown', handleClick);
|
||||||
setObjectsOnConveyor(prev => {
|
}, [forward, hoverState]);
|
||||||
const newSet = new Set(prev);
|
|
||||||
newSet.delete(e.other.rigidBody);
|
useEffect(() => {
|
||||||
return newSet;
|
if (!boundingBox) return;
|
||||||
});
|
const size = boundingBox.getSize(new THREE.Vector3());
|
||||||
|
const [width, depth] = [size.x, size.z];
|
||||||
|
conveyorDirection.current.set(width < depth ? 0 : 1, 0, width < depth ? 1 : 0);
|
||||||
|
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
||||||
|
conveyorDirection.current.applyEuler(rotation);
|
||||||
|
}, [boundingBox, asset.rotation, forward]);
|
||||||
|
|
||||||
|
const handleMaterialEnter = (e: CollisionPayload) => {
|
||||||
|
if (e.other.rigidBody) {
|
||||||
|
setObjectsOnConveyor(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.add(e.other.rigidBody);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMaterialExit = (e: CollisionPayload) => {
|
||||||
|
if (e.other.rigidBody) {
|
||||||
|
setObjectsOnConveyor(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(e.other.rigidBody);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const bezierPoints = useMemo(() => {
|
||||||
|
const segments = 20;
|
||||||
|
const allPoints: THREE.Vector3[] = [];
|
||||||
|
|
||||||
|
points.forEach(segment => {
|
||||||
|
let vectorPoints = segment.map(p => new THREE.Vector3(...p));
|
||||||
|
if (!forward) vectorPoints.reverse();
|
||||||
|
|
||||||
|
for (let group = 0; group + 2 < vectorPoints.length; group += 2) {
|
||||||
|
const p0 = vectorPoints[group];
|
||||||
|
const p1 = vectorPoints[group + 1];
|
||||||
|
const p2 = vectorPoints[group + 2];
|
||||||
|
|
||||||
|
for (let i = 0; i <= segments; i++) {
|
||||||
|
const t = i / segments;
|
||||||
|
const point = new THREE.Vector3()
|
||||||
|
.copy(p0)
|
||||||
|
.multiplyScalar((1 - t) ** 2)
|
||||||
|
.addScaledVector(p1, 2 * (1 - t) * t)
|
||||||
|
.addScaledVector(p2, t ** 2);
|
||||||
|
allPoints.push(point);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const bezierPoints = useMemo(() => {
|
|
||||||
const segments = 20;
|
|
||||||
const allPoints: THREE.Vector3[] = [];
|
|
||||||
|
|
||||||
points.forEach(segment => {
|
|
||||||
let vectorPoints = segment.map(p => new THREE.Vector3(...p));
|
|
||||||
if (!forward) vectorPoints.reverse();
|
|
||||||
|
|
||||||
for (let group = 0; group + 2 < vectorPoints.length; group += 2) {
|
|
||||||
const p0 = vectorPoints[group];
|
|
||||||
const p1 = vectorPoints[group + 1];
|
|
||||||
const p2 = vectorPoints[group + 2];
|
|
||||||
|
|
||||||
for (let i = 0; i <= segments; i++) {
|
|
||||||
const t = i / segments;
|
|
||||||
const point = new THREE.Vector3()
|
|
||||||
.copy(p0)
|
|
||||||
.multiplyScalar((1 - t) ** 2)
|
|
||||||
.addScaledVector(p1, 2 * (1 - t) * t)
|
|
||||||
.addScaledVector(p2, t ** 2);
|
|
||||||
allPoints.push(point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return allPoints;
|
|
||||||
}, [points, forward]);
|
|
||||||
|
|
||||||
useFrame(() => {
|
|
||||||
const assetPos = new THREE.Vector3(...(asset.position || [0, 0, 0]));
|
|
||||||
const assetRot = new THREE.Euler(...(asset.rotation || [0, 0, 0]));
|
|
||||||
const assetQuat = new THREE.Quaternion().setFromEuler(assetRot);
|
|
||||||
const inverseQuat = assetQuat.clone().invert();
|
|
||||||
|
|
||||||
objectsOnConveyor.forEach(rigidBody => {
|
|
||||||
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
|
|
||||||
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
|
|
||||||
|
|
||||||
let closestIndex = 0;
|
|
||||||
let minDist = Infinity;
|
|
||||||
for (let i = 0; i < bezierPoints.length; i++) {
|
|
||||||
const dist = bezierPoints[i].distanceToSquared(localPos);
|
|
||||||
if (dist < minDist) {
|
|
||||||
minDist = dist;
|
|
||||||
closestIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const point = bezierPoints[closestIndex];
|
|
||||||
const prev = bezierPoints[closestIndex - 1] || point;
|
|
||||||
const next = bezierPoints[closestIndex + 1] || point;
|
|
||||||
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
|
||||||
|
|
||||||
const side = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
|
||||||
const relative = new THREE.Vector3().subVectors(localPos, point);
|
|
||||||
const sideOffset = relative.dot(side);
|
|
||||||
|
|
||||||
const centeringForce = side.clone().multiplyScalar(-sideOffset * 10);
|
|
||||||
const forwardForce = tangent.clone().multiplyScalar(conveyorSpeed);
|
|
||||||
const totalForce = forwardForce.add(centeringForce).applyQuaternion(assetQuat);
|
|
||||||
|
|
||||||
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
|
||||||
rigidBody.setLinvel(totalForce, true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const geometries = useMemo(() => {
|
return allPoints;
|
||||||
const width = 1;
|
}, [points, forward]);
|
||||||
const segments = 20;
|
|
||||||
const geos: THREE.BufferGeometry[] = [];
|
|
||||||
|
|
||||||
points.forEach(segment => {
|
useFrame(({ clock }) => {
|
||||||
const vertices: number[] = [];
|
if (isPaused) return;
|
||||||
const indices: number[] = [];
|
|
||||||
|
|
||||||
const vectorPoint = segment.map(p => new THREE.Vector3(...p));
|
// Physics simulation
|
||||||
if (vectorPoint.length < 3) return;
|
const assetPos = new THREE.Vector3(...(asset.position || [0, 0, 0]));
|
||||||
|
const assetRot = new THREE.Euler(...(asset.rotation || [0, 0, 0]));
|
||||||
|
const assetQuat = new THREE.Quaternion().setFromEuler(assetRot);
|
||||||
|
const inverseQuat = assetQuat.clone().invert();
|
||||||
|
|
||||||
for (let group = 0; group + 2 < vectorPoint.length; group += 2) {
|
objectsOnConveyor.forEach(rigidBody => {
|
||||||
const p0 = vectorPoint[group];
|
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
|
||||||
const p1 = vectorPoint[group + 1];
|
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
|
||||||
const p2 = vectorPoint[group + 2];
|
|
||||||
|
|
||||||
for (let i = 0; i <= segments; i++) {
|
let closestIndex = 0;
|
||||||
const t = i / segments;
|
let minDist = Infinity;
|
||||||
const point = new THREE.Vector3()
|
for (let i = 0; i < bezierPoints.length; i++) {
|
||||||
.copy(p0)
|
const dist = bezierPoints[i].distanceToSquared(localPos);
|
||||||
.multiplyScalar((1 - t) ** 2)
|
if (dist < minDist) {
|
||||||
.addScaledVector(p1, 2 * (1 - t) * t)
|
minDist = dist;
|
||||||
.addScaledVector(p2, t ** 2);
|
closestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tangent = new THREE.Vector3()
|
const point = bezierPoints[closestIndex];
|
||||||
.copy(p0)
|
const prev = bezierPoints[closestIndex - 1] || point;
|
||||||
.multiplyScalar(-2 * (1 - t))
|
const next = bezierPoints[closestIndex + 1] || point;
|
||||||
.addScaledVector(p1, 2 - 4 * t)
|
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
||||||
.addScaledVector(p2, 2 * t)
|
|
||||||
.normalize();
|
|
||||||
|
|
||||||
const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
const side = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
||||||
const left = new THREE.Vector3().copy(point).addScaledVector(normal, -width / 2);
|
const relative = new THREE.Vector3().subVectors(localPos, point);
|
||||||
const right = new THREE.Vector3().copy(point).addScaledVector(normal, width / 2);
|
const sideOffset = relative.dot(side);
|
||||||
|
|
||||||
vertices.push(...left.toArray(), ...right.toArray());
|
const centeringForce = side.clone().multiplyScalar(-sideOffset * 10);
|
||||||
}
|
const forwardForce = tangent.clone().multiplyScalar(conveyorSpeed);
|
||||||
|
const totalForce = forwardForce.add(centeringForce).applyQuaternion(assetQuat);
|
||||||
|
|
||||||
|
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
|
rigidBody.setLinvel(totalForce, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Arrow animations
|
||||||
|
if (showDirection && arrowRefs.current.length > 0) {
|
||||||
|
const elapsedTime = clock.getElapsedTime();
|
||||||
|
arrowRefs.current.forEach((arrowGroup, index) => {
|
||||||
|
// Pulse animation
|
||||||
|
const pulseScale = 0.9 + 0.1 * Math.sin(elapsedTime * 5 + index * 0.5);
|
||||||
|
arrowGroup.scale.setScalar(pulseScale);
|
||||||
|
|
||||||
|
// Flow animation (color intensity)
|
||||||
|
const intensity = 0.7 + 0.3 * Math.sin(elapsedTime * 3 + index * 0.3);
|
||||||
|
arrowGroup.children.forEach(child => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
const material = child.material as THREE.MeshBasicMaterial;
|
||||||
|
if (forward) {
|
||||||
|
material.color.setRGB(0, intensity, 0);
|
||||||
|
} else {
|
||||||
|
material.color.setRGB(intensity, 0, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const totalSegments = ((vectorPoint.length - 1) / 2) * segments;
|
|
||||||
for (let i = 0; i < totalSegments; i++) {
|
|
||||||
const base = i * 2;
|
|
||||||
indices.push(base, base + 1, base + 2);
|
|
||||||
indices.push(base + 1, base + 3, base + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ribbonGeometry = new THREE.BufferGeometry();
|
|
||||||
ribbonGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
||||||
ribbonGeometry.setIndex(indices);
|
|
||||||
ribbonGeometry.computeVertexNormals();
|
|
||||||
geos.push(ribbonGeometry);
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setGeometryKey(k => k + 1);
|
const geometries = useMemo(() => {
|
||||||
return geos;
|
const width = 1;
|
||||||
}, [points, asset.position, asset.rotation]);
|
const segments = 20;
|
||||||
|
const geos: THREE.BufferGeometry[] = [];
|
||||||
|
|
||||||
return (
|
points.forEach(segment => {
|
||||||
<>
|
const vertices: number[] = [];
|
||||||
{geometries.length > 0 && (
|
const indices: number[] = [];
|
||||||
<RigidBody
|
|
||||||
key={geometryKey}
|
const vectorPoint = segment.map(p => new THREE.Vector3(...p));
|
||||||
ref={conveyorRef}
|
if (vectorPoint.length < 3) return;
|
||||||
type="fixed"
|
|
||||||
position={[0, 0.001, 0]}
|
for (let group = 0; group + 2 < vectorPoint.length; group += 2) {
|
||||||
userData={{ isConveyor: true }}
|
const p0 = vectorPoint[group];
|
||||||
onCollisionEnter={handleMaterialEnter}
|
const p1 = vectorPoint[group + 1];
|
||||||
onCollisionExit={handleMaterialExit}
|
const p2 = vectorPoint[group + 2];
|
||||||
colliders="trimesh"
|
|
||||||
>
|
for (let i = 0; i <= segments; i++) {
|
||||||
{geometries.map((geometry, index) => (
|
const t = i / segments;
|
||||||
<mesh key={index} geometry={geometry}>
|
const point = new THREE.Vector3()
|
||||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} transparent opacity={0.5} visible={false}/>
|
.copy(p0)
|
||||||
</mesh>
|
.multiplyScalar((1 - t) ** 2)
|
||||||
))}
|
.addScaledVector(p1, 2 * (1 - t) * t)
|
||||||
</RigidBody>
|
.addScaledVector(p2, t ** 2);
|
||||||
)}
|
|
||||||
</>
|
const tangent = new THREE.Vector3()
|
||||||
);
|
.copy(p0)
|
||||||
|
.multiplyScalar(-2 * (1 - t))
|
||||||
|
.addScaledVector(p1, 2 - 4 * t)
|
||||||
|
.addScaledVector(p2, 2 * t)
|
||||||
|
.normalize();
|
||||||
|
|
||||||
|
const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
||||||
|
const left = new THREE.Vector3().copy(point).addScaledVector(normal, -width / 2);
|
||||||
|
const right = new THREE.Vector3().copy(point).addScaledVector(normal, width / 2);
|
||||||
|
|
||||||
|
vertices.push(...left.toArray(), ...right.toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalSegments = ((vectorPoint.length - 1) / 2) * segments;
|
||||||
|
for (let i = 0; i < totalSegments; i++) {
|
||||||
|
const base = i * 2;
|
||||||
|
indices.push(base, base + 1, base + 2);
|
||||||
|
indices.push(base + 1, base + 3, base + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ribbonGeometry = new THREE.BufferGeometry();
|
||||||
|
ribbonGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
||||||
|
ribbonGeometry.setIndex(indices);
|
||||||
|
ribbonGeometry.computeVertexNormals();
|
||||||
|
geos.push(ribbonGeometry);
|
||||||
|
});
|
||||||
|
|
||||||
|
setGeometryKey(k => k + 1);
|
||||||
|
return geos;
|
||||||
|
}, [points, asset.position, asset.rotation]);
|
||||||
|
|
||||||
|
// Create curved direction indicators
|
||||||
|
const directionArrows = useMemo(() => {
|
||||||
|
if (!showDirection) return null;
|
||||||
|
|
||||||
|
const arrows: THREE.Group[] = [];
|
||||||
|
const arrowHeight = 0.2;
|
||||||
|
const arrowRadius = 0.05;
|
||||||
|
const segments = 8; // Fewer arrows for curved conveyors
|
||||||
|
|
||||||
|
points.forEach(segment => {
|
||||||
|
let vectorPoints = segment.map(p => new THREE.Vector3(...p));
|
||||||
|
if (!forward) vectorPoints.reverse();
|
||||||
|
|
||||||
|
for (let group = 0; group + 2 < vectorPoints.length; group += 2) {
|
||||||
|
const p0 = vectorPoints[group];
|
||||||
|
const p1 = vectorPoints[group + 1];
|
||||||
|
const p2 = vectorPoints[group + 2];
|
||||||
|
|
||||||
|
for (let i = 0; i <= segments; i++) {
|
||||||
|
const t = i / segments;
|
||||||
|
const point = new THREE.Vector3()
|
||||||
|
.copy(p0)
|
||||||
|
.multiplyScalar((1 - t) ** 2)
|
||||||
|
.addScaledVector(p1, 2 * (1 - t) * t)
|
||||||
|
.addScaledVector(p2, t ** 2);
|
||||||
|
|
||||||
|
const tangent = new THREE.Vector3()
|
||||||
|
.copy(p0)
|
||||||
|
.multiplyScalar(-2 * (1 - t))
|
||||||
|
.addScaledVector(p1, 2 - 4 * t)
|
||||||
|
.addScaledVector(p2, 2 * t)
|
||||||
|
.normalize();
|
||||||
|
|
||||||
|
// Create arrow group
|
||||||
|
const arrowGroup = new THREE.Group();
|
||||||
|
|
||||||
|
// Arrow shaft (cylinder)
|
||||||
|
const shaftLength = arrowHeight * 0.7;
|
||||||
|
const shaftGeometry = new THREE.CylinderGeometry(arrowRadius * 0.3, arrowRadius * 0.3, shaftLength, 8);
|
||||||
|
const shaftMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: forward ? 0x00ff00 : 0xff0000
|
||||||
|
});
|
||||||
|
const shaft = new THREE.Mesh(shaftGeometry, shaftMaterial);
|
||||||
|
shaft.position.y = shaftLength / 2;
|
||||||
|
shaft.rotation.x = Math.PI / 2;
|
||||||
|
|
||||||
|
// Arrow head (cone)
|
||||||
|
const headGeometry = new THREE.ConeGeometry(arrowRadius, arrowHeight * 0.3, 8);
|
||||||
|
const headMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: forward ? 0x00ff00 : 0xff0000
|
||||||
|
});
|
||||||
|
const head = new THREE.Mesh(headGeometry, headMaterial);
|
||||||
|
head.position.y = shaftLength;
|
||||||
|
|
||||||
|
// Position and orient the entire arrow
|
||||||
|
arrowGroup.add(shaft);
|
||||||
|
arrowGroup.add(head);
|
||||||
|
arrowGroup.position.copy(point);
|
||||||
|
arrowGroup.position.y += 0.1; // Slightly above conveyor
|
||||||
|
arrowGroup.quaternion.setFromUnitVectors(
|
||||||
|
new THREE.Vector3(0, 1, 0),
|
||||||
|
new THREE.Vector3(tangent.x, 0.1, tangent.z)
|
||||||
|
);
|
||||||
|
|
||||||
|
arrows.push(arrowGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
arrowRefs.current = arrows;
|
||||||
|
return arrows;
|
||||||
|
}, [points, showDirection, forward]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group
|
||||||
|
onPointerOver={() => {
|
||||||
|
setShowDirection(true);
|
||||||
|
setHoverState(true);
|
||||||
|
}}
|
||||||
|
onPointerOut={() => {
|
||||||
|
setShowDirection(false);
|
||||||
|
setHoverState(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Conveyor surface */}
|
||||||
|
{geometries.length > 0 && (
|
||||||
|
<RigidBody
|
||||||
|
key={geometryKey}
|
||||||
|
ref={conveyorRef}
|
||||||
|
type="fixed"
|
||||||
|
position={[0, 0.001, 0]}
|
||||||
|
userData={{ isConveyor: true }}
|
||||||
|
onCollisionEnter={handleMaterialEnter}
|
||||||
|
onCollisionExit={handleMaterialExit}
|
||||||
|
colliders="trimesh"
|
||||||
|
>
|
||||||
|
{geometries.map((geometry, index) => (
|
||||||
|
<mesh key={index} geometry={geometry}>
|
||||||
|
<meshStandardMaterial
|
||||||
|
color={forward ? "#64b5f6" : "#f48fb1"}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
transparent
|
||||||
|
opacity={0.7}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</RigidBody>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Direction indicators */}
|
||||||
|
{showDirection && directionArrows?.map((arrow, i) => (
|
||||||
|
<primitive key={`arrow-${i}`} object={arrow} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Hover highlight */}
|
||||||
|
{hoverState && (
|
||||||
|
<group>
|
||||||
|
{geometries.map((geometry, index) => (
|
||||||
|
<mesh
|
||||||
|
key={`highlight-${index}`}
|
||||||
|
geometry={geometry}
|
||||||
|
position={[0, 0.002, 0]} // Slightly above conveyor
|
||||||
|
>
|
||||||
|
<meshBasicMaterial
|
||||||
|
color={forward ? "#00ff0044" : "#ff000044"}
|
||||||
|
transparent
|
||||||
|
opacity={0.3}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CurvedConveyorCollider;
|
export default CurvedConveyorCollider;
|
||||||
@@ -9,27 +9,49 @@ function NormalConveyorCollider({
|
|||||||
asset,
|
asset,
|
||||||
forward,
|
forward,
|
||||||
isPaused,
|
isPaused,
|
||||||
|
onDirectionChange
|
||||||
}: {
|
}: {
|
||||||
points: [number, number, number][][];
|
points: [number, number, number][][];
|
||||||
boundingBox: THREE.Box3 | null;
|
boundingBox: THREE.Box3 | null;
|
||||||
asset: Asset;
|
asset: Asset;
|
||||||
forward: boolean;
|
forward: boolean;
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
|
onDirectionChange?: (newDirection: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const conveyorRefs = useRef<any[]>([]);
|
const conveyorRefs = useRef<(any)[]>([]);
|
||||||
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||||
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||||
|
const [showDirection, setShowDirection] = useState(false);
|
||||||
const conveyorSpeed = 2;
|
const conveyorSpeed = 2;
|
||||||
|
const lastClickTime = useRef(0);
|
||||||
|
const [hoverState, setHoverState] = useState(false);
|
||||||
|
|
||||||
|
// Toggle direction on double right click
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClick = (e: MouseEvent) => {
|
||||||
|
if (e.button === 2) { // Right click
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastClickTime.current < 300) { // Double click within 300ms
|
||||||
|
if (onDirectionChange) {
|
||||||
|
onDirectionChange(!forward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastClickTime.current = now;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('mousedown', handleClick);
|
||||||
|
return () => window.removeEventListener('mousedown', handleClick);
|
||||||
|
}, [forward]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!boundingBox) return;
|
if (!boundingBox) return;
|
||||||
const size = boundingBox.getSize(new THREE.Vector3());
|
const size = boundingBox.getSize(new THREE.Vector3());
|
||||||
const [width, depth] = [size.x, size.z];
|
const [width, depth] = [size.x, size.z];
|
||||||
conveyorDirection.current.set(width < depth ? 0 : 1, 0, width < depth ? 1 : 0);
|
conveyorDirection.current.set(width < depth ? 0 : 1, 0, width < depth ? 1 : 0);
|
||||||
|
|
||||||
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
||||||
conveyorDirection.current.applyEuler(rotation);
|
conveyorDirection.current.applyEuler(rotation);
|
||||||
}, [boundingBox, asset.rotation]);
|
}, [boundingBox, asset.rotation, forward]);
|
||||||
|
|
||||||
const handleMaterialEnter = (e: CollisionPayload) => {
|
const handleMaterialEnter = (e: CollisionPayload) => {
|
||||||
if (e.other.rigidBody) {
|
if (e.other.rigidBody) {
|
||||||
@@ -52,6 +74,8 @@ function NormalConveyorCollider({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
|
if (isPaused) return;
|
||||||
|
|
||||||
const assetPos = new THREE.Vector3(...(asset.position || [0, 0, 0]));
|
const assetPos = new THREE.Vector3(...(asset.position || [0, 0, 0]));
|
||||||
const assetRot = new THREE.Euler(...(asset.rotation || [0, 0, 0]));
|
const assetRot = new THREE.Euler(...(asset.rotation || [0, 0, 0]));
|
||||||
const assetQuat = new THREE.Quaternion().setFromEuler(assetRot);
|
const assetQuat = new THREE.Quaternion().setFromEuler(assetRot);
|
||||||
@@ -60,7 +84,6 @@ function NormalConveyorCollider({
|
|||||||
const allCurvePoints: THREE.Vector3[] = [];
|
const allCurvePoints: THREE.Vector3[] = [];
|
||||||
const segmentCurves: THREE.Vector3[][] = [];
|
const segmentCurves: THREE.Vector3[][] = [];
|
||||||
|
|
||||||
// Build all curve points across segments
|
|
||||||
points.forEach(segment => {
|
points.forEach(segment => {
|
||||||
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||||
const curvePoints = curve.getPoints((segment.length - 1) * 30);
|
const curvePoints = curve.getPoints((segment.length - 1) * 30);
|
||||||
@@ -74,7 +97,6 @@ function NormalConveyorCollider({
|
|||||||
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
|
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
|
||||||
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
|
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
|
||||||
|
|
||||||
// Find closest point on full conveyor
|
|
||||||
let closestIndex = 0;
|
let closestIndex = 0;
|
||||||
let minDist = Infinity;
|
let minDist = Infinity;
|
||||||
for (let i = 0; i < allCurvePoints.length; i++) {
|
for (let i = 0; i < allCurvePoints.length; i++) {
|
||||||
@@ -91,12 +113,11 @@ function NormalConveyorCollider({
|
|||||||
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
||||||
const side = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
const side = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
||||||
const relative = new THREE.Vector3().subVectors(localPos, point);
|
const relative = new THREE.Vector3().subVectors(localPos, point);
|
||||||
|
|
||||||
const sideOffset = relative.dot(side);
|
const sideOffset = relative.dot(side);
|
||||||
const centeringForce = side.clone().multiplyScalar(-sideOffset * 10);
|
const centeringForce = side.clone().multiplyScalar(-sideOffset * 10);
|
||||||
const forwardForce = tangent.clone().multiplyScalar(conveyorSpeed);
|
const forwardForce = tangent.clone().multiplyScalar(conveyorSpeed);
|
||||||
|
|
||||||
const totalForce = forwardForce.add(centeringForce).applyQuaternion(assetQuat);
|
const totalForce = forwardForce.add(centeringForce).applyQuaternion(assetQuat);
|
||||||
|
|
||||||
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
rigidBody.setLinvel(totalForce, true);
|
rigidBody.setLinvel(totalForce, true);
|
||||||
});
|
});
|
||||||
@@ -107,7 +128,6 @@ function NormalConveyorCollider({
|
|||||||
const segments = 30;
|
const segments = 30;
|
||||||
return points.map(segment => {
|
return points.map(segment => {
|
||||||
if (segment.length < 2) return null;
|
if (segment.length < 2) return null;
|
||||||
|
|
||||||
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||||
const curvePoints = curve.getPoints((segment.length - 1) * segments);
|
const curvePoints = curve.getPoints((segment.length - 1) * segments);
|
||||||
const vertices: number[] = [];
|
const vertices: number[] = [];
|
||||||
@@ -117,13 +137,10 @@ function NormalConveyorCollider({
|
|||||||
const point = curvePoints[i];
|
const point = curvePoints[i];
|
||||||
const prev = curvePoints[i - 1] || point;
|
const prev = curvePoints[i - 1] || point;
|
||||||
const next = curvePoints[i + 1] || point;
|
const next = curvePoints[i + 1] || point;
|
||||||
|
|
||||||
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
||||||
const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
||||||
|
|
||||||
const left = point.clone().addScaledVector(normal, -width / 2);
|
const left = point.clone().addScaledVector(normal, -width / 2);
|
||||||
const right = point.clone().addScaledVector(normal, width / 2);
|
const right = point.clone().addScaledVector(normal, width / 2);
|
||||||
|
|
||||||
vertices.push(...left.toArray(), ...right.toArray());
|
vertices.push(...left.toArray(), ...right.toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,11 +155,48 @@ function NormalConveyorCollider({
|
|||||||
geo.setIndex(indices);
|
geo.setIndex(indices);
|
||||||
geo.computeVertexNormals();
|
geo.computeVertexNormals();
|
||||||
return geo;
|
return geo;
|
||||||
}).filter(Boolean);
|
}).filter((geo): geo is THREE.BufferGeometry => geo !== null);
|
||||||
}, [points, asset.position, asset.rotation]);
|
}, [points, asset.position, asset.rotation]);
|
||||||
|
|
||||||
|
// Create direction indicators
|
||||||
|
const directionArrows = useMemo(() => {
|
||||||
|
|
||||||
|
if (!showDirection) return null;
|
||||||
|
|
||||||
|
const arrows: THREE.Mesh[] = [];
|
||||||
|
const arrowGeometry = new THREE.ConeGeometry(0.05, 0.2, 8);
|
||||||
|
const arrowMaterial = new THREE.MeshBasicMaterial({ color: forward ? 0x00ff00 : 0xff0000 });
|
||||||
|
|
||||||
|
points.forEach(segment => {
|
||||||
|
if (segment.length < 2) return;
|
||||||
|
|
||||||
|
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||||
|
const curvePoints = curve.getPoints(10); // Fewer points for arrows
|
||||||
|
|
||||||
|
for (let i = 0; i < curvePoints.length; i++) {
|
||||||
|
const point = curvePoints[i];
|
||||||
|
const next = curvePoints[i + 1] || point;
|
||||||
|
const direction = new THREE.Vector3().subVectors(next, point).normalize();
|
||||||
|
|
||||||
|
const arrow = new THREE.Mesh(arrowGeometry, arrowMaterial);
|
||||||
|
arrow.position.copy(point);
|
||||||
|
arrow.quaternion.setFromUnitVectors(
|
||||||
|
new THREE.Vector3(0, forward ? 1 : -1, 0),
|
||||||
|
new THREE.Vector3(direction.x, 0.1, direction.z)
|
||||||
|
);
|
||||||
|
arrows.push(arrow);
|
||||||
|
//
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return arrows;
|
||||||
|
}, [points, showDirection, forward]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<group
|
||||||
|
onPointerEnter={() => { setShowDirection(true); setHoverState(true); }}
|
||||||
|
onPointerLeave={() => { setShowDirection(false); setHoverState(false); }}
|
||||||
|
>
|
||||||
{geometries.map((geometry, index) => (
|
{geometries.map((geometry, index) => (
|
||||||
<RigidBody
|
<RigidBody
|
||||||
key={index}
|
key={index}
|
||||||
@@ -153,14 +207,44 @@ function NormalConveyorCollider({
|
|||||||
onCollisionEnter={handleMaterialEnter}
|
onCollisionEnter={handleMaterialEnter}
|
||||||
onCollisionExit={handleMaterialExit}
|
onCollisionExit={handleMaterialExit}
|
||||||
colliders="trimesh"
|
colliders="trimesh"
|
||||||
// visible={false}
|
|
||||||
>
|
>
|
||||||
<mesh geometry={geometry!}>
|
<mesh geometry={geometry}>
|
||||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} transparent opacity={0.5} visible={false} />
|
<meshStandardMaterial
|
||||||
|
color={forward ? "skyblue" : "pink"}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
transparent
|
||||||
|
opacity={0.5}
|
||||||
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
</RigidBody>
|
</RigidBody>
|
||||||
))}
|
))}
|
||||||
</>
|
|
||||||
|
{showDirection && directionArrows?.map((arrow, i) => (
|
||||||
|
<primitive
|
||||||
|
key={`arrow-${i}`}
|
||||||
|
object={arrow}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Hover highlight */}
|
||||||
|
{hoverState && (
|
||||||
|
<group>
|
||||||
|
{geometries.map((geometry, index) => (
|
||||||
|
<mesh
|
||||||
|
key={`highlight-${index}`}
|
||||||
|
geometry={geometry}
|
||||||
|
position={[0, 0.002, 0]} // Slightly above conveyor
|
||||||
|
>
|
||||||
|
<meshBasicMaterial
|
||||||
|
color={forward ? "#00ff0044" : "#ff000044"}
|
||||||
|
transparent
|
||||||
|
opacity={0.3}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,33 +3,61 @@ import { CollisionPayload, RigidBody } from '@react-three/rapier';
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useFrame } from '@react-three/fiber';
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
|
||||||
function YSplitConveyorCollider({
|
interface YSplitConveyorColliderProps {
|
||||||
points,
|
|
||||||
boundingBox,
|
|
||||||
asset,
|
|
||||||
forward,
|
|
||||||
isPaused,
|
|
||||||
}: {
|
|
||||||
points: [number, number, number][][];
|
points: [number, number, number][][];
|
||||||
boundingBox: THREE.Box3 | null;
|
boundingBox: THREE.Box3 | null;
|
||||||
asset: Asset;
|
asset: Asset;
|
||||||
forward: boolean;
|
forward: boolean;
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
}) {
|
onDirectionChange?: (newDirection: boolean) => void;
|
||||||
const conveyorRefs = useRef<any[]>([]);
|
}
|
||||||
|
|
||||||
|
function YSplitConveyorCollider({
|
||||||
|
points,
|
||||||
|
boundingBox,
|
||||||
|
asset,
|
||||||
|
forward: initialForward,
|
||||||
|
isPaused,
|
||||||
|
onDirectionChange
|
||||||
|
}: YSplitConveyorColliderProps) {
|
||||||
|
const conveyorRefs = useRef<(any | null)[]>([]);
|
||||||
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||||
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||||
|
const [forward, setForward] = useState(initialForward);
|
||||||
|
const [showDirection, setShowDirection] = useState(false);
|
||||||
|
const [hoverState, setHoverState] = useState(false);
|
||||||
const conveyorSpeed = 2;
|
const conveyorSpeed = 2;
|
||||||
|
const lastClickTime = useRef(0);
|
||||||
|
const arrowRefs = useRef<THREE.Group[]>([]);
|
||||||
|
|
||||||
|
// Toggle direction on double right click
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClick = (e: MouseEvent) => {
|
||||||
|
if (e.button === 2 && hoverState) { // Right click and hovering over conveyor
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastClickTime.current < 300) { // Double click within 300ms
|
||||||
|
const newDirection = !forward;
|
||||||
|
setForward(newDirection);
|
||||||
|
if (onDirectionChange) {
|
||||||
|
onDirectionChange(newDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastClickTime.current = now;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('mousedown', handleClick);
|
||||||
|
return () => window.removeEventListener('mousedown', handleClick);
|
||||||
|
}, [forward, hoverState, onDirectionChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!boundingBox) return;
|
if (!boundingBox) return;
|
||||||
const size = boundingBox.getSize(new THREE.Vector3());
|
const size = boundingBox.getSize(new THREE.Vector3());
|
||||||
const [width, depth] = [size.x, size.z];
|
const [width, depth] = [size.x, size.z];
|
||||||
conveyorDirection.current.set(width < depth ? 0 : 1, 0, width < depth ? 1 : 0);
|
conveyorDirection.current.set(width < depth ? 0 : 1, 0, width < depth ? 1 : 0);
|
||||||
|
|
||||||
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
||||||
conveyorDirection.current.applyEuler(rotation);
|
conveyorDirection.current.applyEuler(rotation);
|
||||||
}, [boundingBox, asset.rotation]);
|
}, [boundingBox, asset.rotation, forward]);
|
||||||
|
|
||||||
const handleMaterialEnter = (e: CollisionPayload) => {
|
const handleMaterialEnter = (e: CollisionPayload) => {
|
||||||
if (e.other.rigidBody) {
|
if (e.other.rigidBody) {
|
||||||
@@ -51,21 +79,18 @@ function YSplitConveyorCollider({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(({ clock }) => {
|
||||||
|
if (isPaused) return;
|
||||||
|
// Update physics
|
||||||
const assetPos = new THREE.Vector3(...(asset.position || [0, 0, 0]));
|
const assetPos = new THREE.Vector3(...(asset.position || [0, 0, 0]));
|
||||||
const assetRot = new THREE.Euler(...(asset.rotation || [0, 0, 0]));
|
const assetRot = new THREE.Euler(...(asset.rotation || [0, 0, 0]));
|
||||||
const assetQuat = new THREE.Quaternion().setFromEuler(assetRot);
|
const assetQuat = new THREE.Quaternion().setFromEuler(assetRot);
|
||||||
const inverseQuat = assetQuat.clone().invert();
|
const inverseQuat = assetQuat.clone().invert();
|
||||||
|
|
||||||
const allCurvePoints: THREE.Vector3[] = [];
|
const allCurvePoints: THREE.Vector3[] = [];
|
||||||
const segmentCurves: THREE.Vector3[][] = [];
|
|
||||||
|
|
||||||
// Build all curve points across segments
|
|
||||||
points.forEach(segment => {
|
points.forEach(segment => {
|
||||||
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||||
const curvePoints = curve.getPoints((segment.length - 1) * 30);
|
allCurvePoints.push(...curve.getPoints((segment.length - 1) * 30));
|
||||||
segmentCurves.push(curvePoints);
|
|
||||||
allCurvePoints.push(...curvePoints);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!forward) allCurvePoints.reverse();
|
if (!forward) allCurvePoints.reverse();
|
||||||
@@ -74,7 +99,6 @@ function YSplitConveyorCollider({
|
|||||||
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
|
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
|
||||||
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
|
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
|
||||||
|
|
||||||
// Find closest point on full conveyor
|
|
||||||
let closestIndex = 0;
|
let closestIndex = 0;
|
||||||
let minDist = Infinity;
|
let minDist = Infinity;
|
||||||
for (let i = 0; i < allCurvePoints.length; i++) {
|
for (let i = 0; i < allCurvePoints.length; i++) {
|
||||||
@@ -91,15 +115,37 @@ function YSplitConveyorCollider({
|
|||||||
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
||||||
const side = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
const side = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
||||||
const relative = new THREE.Vector3().subVectors(localPos, point);
|
const relative = new THREE.Vector3().subVectors(localPos, point);
|
||||||
|
|
||||||
const sideOffset = relative.dot(side);
|
const sideOffset = relative.dot(side);
|
||||||
const centeringForce = side.clone().multiplyScalar(-sideOffset * 10);
|
const centeringForce = side.clone().multiplyScalar(-sideOffset * 10);
|
||||||
const forwardForce = tangent.clone().multiplyScalar(conveyorSpeed);
|
const forwardForce = tangent.clone().multiplyScalar(conveyorSpeed);
|
||||||
|
|
||||||
const totalForce = forwardForce.add(centeringForce).applyQuaternion(assetQuat);
|
const totalForce = forwardForce.add(centeringForce).applyQuaternion(assetQuat);
|
||||||
|
|
||||||
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||||
rigidBody.setLinvel(totalForce, true);
|
rigidBody.setLinvel(totalForce, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Animate direction arrows
|
||||||
|
if (showDirection && arrowRefs.current.length > 0) {
|
||||||
|
const elapsedTime = clock.getElapsedTime();
|
||||||
|
arrowRefs.current.forEach((arrowGroup, index) => {
|
||||||
|
// Pulse animation
|
||||||
|
const pulseScale = 0.9 + 0.1 * Math.sin(elapsedTime * 5 + index * 0.5);
|
||||||
|
arrowGroup.scale.setScalar(pulseScale);
|
||||||
|
|
||||||
|
// Flow animation (color intensity)
|
||||||
|
const intensity = 0.7 + 0.3 * Math.sin(elapsedTime * 3 + index * 0.3);
|
||||||
|
arrowGroup.children.forEach(child => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
const material = child.material as THREE.MeshBasicMaterial;
|
||||||
|
if (forward) {
|
||||||
|
material.color.setRGB(0, intensity, 0);
|
||||||
|
} else {
|
||||||
|
material.color.setRGB(intensity, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const geometries = useMemo(() => {
|
const geometries = useMemo(() => {
|
||||||
@@ -107,7 +153,6 @@ function YSplitConveyorCollider({
|
|||||||
const segments = 30;
|
const segments = 30;
|
||||||
return points.map(segment => {
|
return points.map(segment => {
|
||||||
if (segment.length < 2) return null;
|
if (segment.length < 2) return null;
|
||||||
|
|
||||||
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||||
const curvePoints = curve.getPoints((segment.length - 1) * segments);
|
const curvePoints = curve.getPoints((segment.length - 1) * segments);
|
||||||
const vertices: number[] = [];
|
const vertices: number[] = [];
|
||||||
@@ -117,13 +162,10 @@ function YSplitConveyorCollider({
|
|||||||
const point = curvePoints[i];
|
const point = curvePoints[i];
|
||||||
const prev = curvePoints[i - 1] || point;
|
const prev = curvePoints[i - 1] || point;
|
||||||
const next = curvePoints[i + 1] || point;
|
const next = curvePoints[i + 1] || point;
|
||||||
|
|
||||||
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
|
||||||
const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
|
||||||
|
|
||||||
const left = point.clone().addScaledVector(normal, -width / 2);
|
const left = point.clone().addScaledVector(normal, -width / 2);
|
||||||
const right = point.clone().addScaledVector(normal, width / 2);
|
const right = point.clone().addScaledVector(normal, width / 2);
|
||||||
|
|
||||||
vertices.push(...left.toArray(), ...right.toArray());
|
vertices.push(...left.toArray(), ...right.toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,14 +180,82 @@ function YSplitConveyorCollider({
|
|||||||
geo.setIndex(indices);
|
geo.setIndex(indices);
|
||||||
geo.computeVertexNormals();
|
geo.computeVertexNormals();
|
||||||
return geo;
|
return geo;
|
||||||
}).filter(Boolean);
|
}).filter((geo): geo is THREE.BufferGeometry => geo !== null);
|
||||||
}, [points, asset.position, asset.rotation]);
|
}, [points, asset.position, asset.rotation]);
|
||||||
|
|
||||||
|
// Create direction indicators
|
||||||
|
const directionArrows = useMemo(() => {
|
||||||
|
if (!showDirection) return null;
|
||||||
|
|
||||||
|
const arrows: THREE.Group[] = [];
|
||||||
|
const arrowHeight = 0.2;
|
||||||
|
const arrowRadius = 0.05;
|
||||||
|
|
||||||
|
points.forEach(segment => {
|
||||||
|
if (segment.length < 2) return;
|
||||||
|
|
||||||
|
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||||
|
const curvePoints = curve.getPoints(8); // Fewer points for arrows
|
||||||
|
|
||||||
|
for (let i = 0; i < curvePoints.length; i++) {
|
||||||
|
const point = curvePoints[i];
|
||||||
|
const next = curvePoints[i + 1] || point;
|
||||||
|
const direction = new THREE.Vector3().subVectors(next, point).normalize();
|
||||||
|
|
||||||
|
// Create arrow group
|
||||||
|
const arrowGroup = new THREE.Group();
|
||||||
|
|
||||||
|
// Arrow shaft (cylinder)
|
||||||
|
const shaftLength = arrowHeight * 0.7;
|
||||||
|
const shaftGeometry = new THREE.CylinderGeometry(arrowRadius * 0.3, arrowRadius * 0.3, shaftLength, 8);
|
||||||
|
const shaftMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: forward ? 0x00ff00 : 0xff0000
|
||||||
|
});
|
||||||
|
const shaft = new THREE.Mesh(shaftGeometry, shaftMaterial);
|
||||||
|
shaft.position.y = shaftLength / 2;
|
||||||
|
shaft.rotation.x = Math.PI / 2;
|
||||||
|
|
||||||
|
// Arrow head (cone)
|
||||||
|
const headGeometry = new THREE.ConeGeometry(arrowRadius, arrowHeight * 0.3, 8);
|
||||||
|
const headMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: forward ? 0x00ff00 : 0xff0000
|
||||||
|
});
|
||||||
|
const head = new THREE.Mesh(headGeometry, headMaterial);
|
||||||
|
head.position.y = shaftLength;
|
||||||
|
|
||||||
|
// Position and orient the entire arrow
|
||||||
|
arrowGroup.add(shaft);
|
||||||
|
arrowGroup.add(head);
|
||||||
|
arrowGroup.position.copy(point);
|
||||||
|
arrowGroup.position.y += 0.1; // Slightly above conveyor
|
||||||
|
arrowGroup.quaternion.setFromUnitVectors(
|
||||||
|
new THREE.Vector3(0, 1, 0),
|
||||||
|
new THREE.Vector3(direction.x, 0.1, direction.z)
|
||||||
|
);
|
||||||
|
|
||||||
|
arrows.push(arrowGroup);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
arrowRefs.current = arrows;
|
||||||
|
return arrows;
|
||||||
|
}, [points, showDirection, forward]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<group
|
||||||
|
onPointerOver={() => {
|
||||||
|
setShowDirection(true);
|
||||||
|
setHoverState(true);
|
||||||
|
}}
|
||||||
|
onPointerOut={() => {
|
||||||
|
setShowDirection(false);
|
||||||
|
setHoverState(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Conveyor surface */}
|
||||||
{geometries.map((geometry, index) => (
|
{geometries.map((geometry, index) => (
|
||||||
<RigidBody
|
<RigidBody
|
||||||
key={index}
|
key={`conveyor-${index}`}
|
||||||
ref={el => (conveyorRefs.current[index] = el)}
|
ref={el => (conveyorRefs.current[index] = el)}
|
||||||
type="fixed"
|
type="fixed"
|
||||||
position={[0, 0.001, 0]}
|
position={[0, 0.001, 0]}
|
||||||
@@ -154,14 +264,45 @@ function YSplitConveyorCollider({
|
|||||||
onCollisionExit={handleMaterialExit}
|
onCollisionExit={handleMaterialExit}
|
||||||
colliders="trimesh"
|
colliders="trimesh"
|
||||||
>
|
>
|
||||||
<mesh geometry={geometry!}>
|
<mesh geometry={geometry}>
|
||||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} transparent opacity={0.5} visible={false}/>
|
<meshStandardMaterial
|
||||||
|
color={forward ? "#64b5f6" : "#f48fb1"} // More subtle colors
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
transparent
|
||||||
|
opacity={0.7}
|
||||||
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
</RigidBody>
|
</RigidBody>
|
||||||
))}
|
))}
|
||||||
</>
|
|
||||||
|
{/* Direction indicators */}
|
||||||
|
{showDirection && directionArrows?.map((arrow, i) => (
|
||||||
|
<primitive
|
||||||
|
key={`arrow-${i}`}
|
||||||
|
object={arrow}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Hover highlight */}
|
||||||
|
{hoverState && (
|
||||||
|
<group>
|
||||||
|
{geometries.map((geometry, index) => (
|
||||||
|
<mesh
|
||||||
|
key={`highlight-${index}`}
|
||||||
|
geometry={geometry}
|
||||||
|
position={[0, 0.002, 0]} // Slightly above conveyor
|
||||||
|
>
|
||||||
|
<meshBasicMaterial
|
||||||
|
color={forward ? "#00ff0044" : "#ff000044"}
|
||||||
|
transparent
|
||||||
|
opacity={0.3}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default YSplitConveyorCollider;
|
export default YSplitConveyorCollider;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useLoadingProgress } from '../../../store/builder/store';
|
|||||||
import { MaterialModel } from '../../simulation/materials/instances/material/materialModel';
|
import { MaterialModel } from '../../simulation/materials/instances/material/materialModel';
|
||||||
import { useThree } from '@react-three/fiber';
|
import { useThree } from '@react-three/fiber';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { CameraControls } from '@react-three/drei';
|
import { CameraControls, TransformControls } from '@react-three/drei';
|
||||||
import { generateUniqueId } from '../../../functions/generateUniqueId';
|
import { generateUniqueId } from '../../../functions/generateUniqueId';
|
||||||
|
|
||||||
type MaterialSpawnerProps = {
|
type MaterialSpawnerProps = {
|
||||||
@@ -13,7 +13,7 @@ type MaterialSpawnerProps = {
|
|||||||
spawnCount: number;
|
spawnCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawnerProps) {
|
function MaterialSpawner({ position: initialPos, spawnInterval, spawnCount }: MaterialSpawnerProps) {
|
||||||
const { loadingProgress } = useLoadingProgress();
|
const { loadingProgress } = useLoadingProgress();
|
||||||
const [spawned, setSpawned] = useState<{
|
const [spawned, setSpawned] = useState<{
|
||||||
id: string;
|
id: string;
|
||||||
@@ -28,6 +28,9 @@ function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawne
|
|||||||
const dragOffset = useRef<THREE.Vector3>(new THREE.Vector3());
|
const dragOffset = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||||
const initialDepth = useRef<number>(0);
|
const initialDepth = useRef<number>(0);
|
||||||
const materialTypes = ['Default material', 'Material 1', 'Material 2', 'Material 3'];
|
const materialTypes = ['Default material', 'Material 1', 'Material 2', 'Material 3'];
|
||||||
|
const [boxPosition, setBoxPosition] = useState<[number, number, number]>(initialPos);
|
||||||
|
const spawnerRef = useRef<THREE.Mesh>(null!);
|
||||||
|
const newPositionRef = useRef<[number, number, number]>([...initialPos]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loadingProgress !== 0) return;
|
if (loadingProgress !== 0) return;
|
||||||
@@ -46,13 +49,16 @@ function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawne
|
|||||||
}
|
}
|
||||||
spawnedCount.current++;
|
spawnedCount.current++;
|
||||||
|
|
||||||
const randomMaterialType = materialTypes[Math.floor(Math.random() * materialTypes.length)];
|
const randomMaterialType =
|
||||||
|
materialTypes[Math.floor(Math.random() * materialTypes.length)];
|
||||||
|
|
||||||
|
|
||||||
|
console.log('boxPosition: ', boxPosition);
|
||||||
return [
|
return [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
id: generateUniqueId(),
|
id: generateUniqueId(),
|
||||||
position,
|
position: [...boxPosition] as [number, number, number], // use latest position state
|
||||||
ref: React.createRef<RapierRigidBody>(),
|
ref: React.createRef<RapierRigidBody>(),
|
||||||
materialType: randomMaterialType,
|
materialType: randomMaterialType,
|
||||||
}
|
}
|
||||||
@@ -85,7 +91,9 @@ function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawne
|
|||||||
stopSpawning();
|
stopSpawning();
|
||||||
document.removeEventListener('visibilitychange', handleVisibility);
|
document.removeEventListener('visibilitychange', handleVisibility);
|
||||||
};
|
};
|
||||||
}, [loadingProgress, spawnInterval, spawnCount, position, spawningPaused]);
|
|
||||||
|
}, [loadingProgress, spawnInterval, spawnCount, spawningPaused, boxPosition]);
|
||||||
|
|
||||||
|
|
||||||
const handleSleep = (id: string) => {
|
const handleSleep = (id: string) => {
|
||||||
setSpawned(prev => prev.filter(obj => obj.id !== id));
|
setSpawned(prev => prev.filter(obj => obj.id !== id));
|
||||||
@@ -180,16 +188,37 @@ function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawne
|
|||||||
const handleBoxContextMenu = () => {
|
const handleBoxContextMenu = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<mesh
|
<TransformControls
|
||||||
position={position}
|
position={boxPosition}
|
||||||
onClick={handleBoxClick}
|
scale={[0.5, 0.5, 0.5]}
|
||||||
onContextMenu={handleBoxContextMenu}
|
|
||||||
|
onMouseDown={() => {
|
||||||
|
if (controls) (controls as CameraControls).enabled = false;
|
||||||
|
}}
|
||||||
|
onMouseUp={() => {
|
||||||
|
if (controls) (controls as CameraControls).enabled = true;
|
||||||
|
setBoxPosition(newPositionRef.current);
|
||||||
|
}}
|
||||||
|
onObjectChange={() => {
|
||||||
|
if (spawnerRef.current) {
|
||||||
|
const pos = spawnerRef.current.position;
|
||||||
|
newPositionRef.current = [pos.x, pos.y, pos.z]; // Save latest
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
<mesh
|
||||||
<meshStandardMaterial color={spawningPaused ? "red" : "white"} transparent opacity={0.2} />
|
ref={spawnerRef}
|
||||||
</mesh>
|
// position={boxPosition}
|
||||||
|
onClick={handleBoxClick}
|
||||||
|
// onContextMenu={handleBoxContextMenu}
|
||||||
|
>
|
||||||
|
<boxGeometry args={[1, 1, 1]} />
|
||||||
|
<meshStandardMaterial color={spawningPaused ? "red" : "white"} transparent opacity={0.2} />
|
||||||
|
</mesh>
|
||||||
|
</TransformControls>
|
||||||
|
|
||||||
{spawned.map(({ id, position, materialType, ref }) => (
|
{spawned.map(({ id, position, materialType, ref }) => (
|
||||||
<RigidBody
|
<RigidBody
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ function PhysicsSimulator() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<MaterialSpawner
|
|
||||||
position={[-2, 5, 3]}
|
|
||||||
spawnInterval={1000}
|
|
||||||
spawnCount={5}
|
|
||||||
/>
|
|
||||||
{/* <MaterialSpawner
|
{/* <MaterialSpawner
|
||||||
position={[6, 3, 6]}
|
position={[1, 3, 4]}
|
||||||
spawnInterval={1000}
|
spawnInterval={1000}
|
||||||
spawnCount={5}
|
spawnCount={50}
|
||||||
/> */}
|
/> */}
|
||||||
|
<MaterialSpawner
|
||||||
|
position={[3.8, 3, 3]}
|
||||||
|
spawnInterval={1000}
|
||||||
|
spawnCount={50}
|
||||||
|
/>
|
||||||
{/* <MaterialSpawner
|
{/* <MaterialSpawner
|
||||||
position={[6, 3, -6]}
|
position={[6, 3, -6]}
|
||||||
spawnInterval={1000}
|
spawnInterval={1000}
|
||||||
@@ -24,7 +24,7 @@ function PhysicsSimulator() {
|
|||||||
|
|
||||||
<ColliderCreator />
|
<ColliderCreator />
|
||||||
|
|
||||||
<SplineCreator />
|
{/* <SplineCreator /> */}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||||||
>
|
>
|
||||||
<Setup />
|
<Setup />
|
||||||
<Collaboration />
|
<Collaboration />
|
||||||
{/* <Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} debug > */}
|
<Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} debug >
|
||||||
<Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} >
|
{/* <Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} > */}
|
||||||
<Builder />
|
<Builder />
|
||||||
|
|
||||||
<Simulation />
|
<Simulation />
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
|||||||
// setSelectedChartId(null);
|
// setSelectedChartId(null);
|
||||||
|
|
||||||
let response = await getSelect2dZoneData(zoneUuid, organization, projectId, selectedVersion?.versionId || '');
|
let response = await getSelect2dZoneData(zoneUuid, organization, projectId, selectedVersion?.versionId || '');
|
||||||
|
console.log('response: ', response);
|
||||||
|
|
||||||
//
|
//
|
||||||
let res = await getFloatingZoneData(zoneUuid, organization, projectId, selectedVersion?.versionId || '');
|
let res = await getFloatingZoneData(zoneUuid, organization, projectId, selectedVersion?.versionId || '');
|
||||||
|
|||||||
Reference in New Issue
Block a user