feat: enhance conveyor collider components with direction change functionality and refactor state management

This commit is contained in:
2025-08-28 14:56:38 +05:30
parent 987bc88dfa
commit 6d557e8fde
6 changed files with 218 additions and 265 deletions

View File

@@ -15,10 +15,12 @@ function RibbonCollider({
}) { }) {
const [forward, setForward] = useState(false); const [forward, setForward] = useState(false);
// console.log('ribbonData: ', ribbonData);
return ( return (
<> <>
{ribbonData.type === 'normal' && ( {ribbonData.type === 'normal' && (
<NormalConveyorCollider <NormalConveyorCollider
key={asset.modelUuid}
points={ribbonData.points} points={ribbonData.points}
boundingBox={boundingBox} boundingBox={boundingBox}
asset={asset} asset={asset}
@@ -29,20 +31,24 @@ function RibbonCollider({
)} )}
{ribbonData.type === 'curved' && ( {ribbonData.type === 'curved' && (
<CurvedConveyorCollider <CurvedConveyorCollider
key={asset.modelUuid}
points={ribbonData.points} points={ribbonData.points}
boundingBox={boundingBox} boundingBox={boundingBox}
asset={asset} asset={asset}
forward={forward} forward={forward}
isPaused={false} isPaused={false}
onDirectionChange={setForward}
/> />
)} )}
{ribbonData.type === 'y-Split' && ( {ribbonData.type === 'y-Split' && (
<YSplitConveyorCollider <YSplitConveyorCollider
key={asset.modelUuid}
points={ribbonData.points} points={ribbonData.points}
boundingBox={boundingBox} boundingBox={boundingBox}
asset={asset} asset={asset}
forward={forward} forward={forward}
isPaused={false} isPaused={false}
onDirectionChange={setForward}
/> />
)} )}
</> </>

View File

@@ -7,19 +7,21 @@ function CurvedConveyorCollider({
points, points,
boundingBox, boundingBox,
asset, asset,
forward: initialForward, 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 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 [forward, setForward] = useState(initialForward); // const [forward, setForward] = useState(initialForward);
const [showDirection, setShowDirection] = useState(false); const [showDirection, setShowDirection] = useState(false);
const [hoverState, setHoverState] = useState(false); const [hoverState, setHoverState] = useState(false);
const conveyorSpeed = 2; const conveyorSpeed = 2;
@@ -33,8 +35,10 @@ function CurvedConveyorCollider({
if (e.button === 2 && hoverState) { // Right click and hovering over conveyor if (e.button === 2 && hoverState) { // Right click and hovering over conveyor
const now = Date.now(); const now = Date.now();
if (now - lastClickTime.current < 300) { if (now - lastClickTime.current < 300) {
if (onDirectionChange) {
setForward(prev => !prev); console.log('forwardcurve: ', forward);
onDirectionChange(!forward);
}
} }
lastClickTime.current = now; lastClickTime.current = now;
} }
@@ -131,7 +135,7 @@ function CurvedConveyorCollider({
return geos; return geos;
}, [points, asset.position, asset.rotation]); }, [points, asset.position, asset.rotation]);
useEffect(() => { useEffect(() => {
if (bezierPoints.length >= 2) { if (bezierPoints.length >= 2) {
const start = bezierPoints[0]; const start = bezierPoints[0];
const end = bezierPoints[bezierPoints.length - 1]; const end = bezierPoints[bezierPoints.length - 1];
@@ -139,7 +143,7 @@ useEffect(() => {
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);
} }
}, [bezierPoints, forward, asset.rotation]); }, [bezierPoints, forward, asset.rotation]);
const handleMaterialEnter = (e: CollisionPayload) => { const handleMaterialEnter = (e: CollisionPayload) => {

View File

@@ -3,36 +3,32 @@ 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 NormalConveyorCollider({ interface NormalConveyorColliderProps {
points,
boundingBox,
asset,
forward,
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; onDirectionChange?: (newDirection: boolean) => void;
}) { }
function NormalConveyorCollider({ points, boundingBox, asset, forward, isPaused, onDirectionChange }: NormalConveyorColliderProps) {
const conveyorRefs = useRef<(any)[]>([]); const conveyorRefs = useRef<(any)[]>([]);
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set()); const [objectsOnGeometry, setObjectsOnGeometry] = useState<Map<number, Set<any>>>(new Map());
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3()); const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
const [showDirection, setShowDirection] = useState(false); const [showDirection, setShowDirection] = useState(false);
const conveyorSpeed = 2; const conveyorSpeed = 2;
const lastClickTime = useRef(0); const lastClickTime = useRef(0);
const [hoverState, setHoverState] = useState(false); const [hoverState, setHoverState] = useState(false);
const[localForward,setLocalForward]=useState()
// Toggle direction on double right click
useEffect(() => { useEffect(() => {
const handleClick = (e: MouseEvent) => { const handleClick = (e: MouseEvent) => {
if (e.button === 2) { // Right click if (e.button === 2) {
const now = Date.now(); const now = Date.now();
if (now - lastClickTime.current < 300) { // Double click within 300ms if (now - lastClickTime.current < 300) {
if (onDirectionChange) { if (onDirectionChange) {
console.log('forwardnormal: ', forward);
onDirectionChange(!forward); onDirectionChange(!forward);
} }
} }
@@ -53,22 +49,25 @@ function NormalConveyorCollider({
conveyorDirection.current.applyEuler(rotation); conveyorDirection.current.applyEuler(rotation);
}, [boundingBox, asset.rotation, forward]); }, [boundingBox, asset.rotation, forward]);
const handleMaterialEnter = (e: CollisionPayload) => { const handleMaterialEnter = (e: CollisionPayload, index: number) => {
if (e.other.rigidBody) { if (e.other.rigidBody) {
setObjectsOnConveyor(prev => { setObjectsOnGeometry(prev => {
const newSet = new Set(prev); const newMap = new Map(prev);
newSet.add(e.other.rigidBody); if (!newMap.has(index)) newMap.set(index, new Set());
return newSet; newMap.get(index)!.add(e.other.rigidBody);
return newMap;
}); });
} }
}; };
const handleMaterialExit = (e: CollisionPayload) => { const handleMaterialExit = (e: CollisionPayload, index: number) => {
if (e.other.rigidBody) { if (e.other.rigidBody) {
setObjectsOnConveyor(prev => { setObjectsOnGeometry(prev => {
const newSet = new Set(prev); const newMap = new Map(prev);
newSet.delete(e.other.rigidBody); if (newMap.has(index)) {
return newSet; newMap.get(index)!.delete(e.other.rigidBody);
}
return newMap;
}); });
} }
}; };
@@ -81,35 +80,33 @@ function NormalConveyorCollider({
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[] = []; points.forEach((segment, index) => {
const segmentCurves: THREE.Vector3[][] = []; if (segment.length < 2) return;
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);
segmentCurves.push(curvePoints); if (!forward) curvePoints.reverse();
allCurvePoints.push(...curvePoints);
});
if (!forward) allCurvePoints.reverse(); const bodies = objectsOnGeometry.get(index);
if (!bodies) return;
objectsOnConveyor.forEach(rigidBody => { bodies.forEach(rigidBody => {
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);
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 < curvePoints.length; i++) {
const dist = allCurvePoints[i].distanceToSquared(localPos); const dist = curvePoints[i].distanceToSquared(localPos);
if (dist < minDist) { if (dist < minDist) {
minDist = dist; minDist = dist;
closestIndex = i; closestIndex = i;
} }
} }
const point = allCurvePoints[closestIndex]; const point = curvePoints[closestIndex];
const prev = allCurvePoints[closestIndex - 1] || point; const prev = curvePoints[closestIndex - 1] || point;
const next = allCurvePoints[closestIndex + 1] || point; const next = curvePoints[closestIndex + 1] || point;
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);
@@ -122,10 +119,11 @@ function NormalConveyorCollider({
rigidBody.setLinvel(totalForce, true); rigidBody.setLinvel(totalForce, true);
}); });
}); });
})
const geometries = useMemo(() => { const geometries = useMemo(() => {
const width = 1; const width = 1;
const segments = 30; const segments = 1;
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)));
@@ -156,11 +154,9 @@ function NormalConveyorCollider({
geo.computeVertexNormals(); geo.computeVertexNormals();
return geo; return geo;
}).filter((geo): geo is THREE.BufferGeometry => geo !== null); }).filter((geo): geo is THREE.BufferGeometry => geo !== null);
}, [points, asset.position, asset.rotation]); }, [points, asset.position, asset.rotation, forward]);
// Create direction indicators
const directionArrows = useMemo(() => { const directionArrows = useMemo(() => {
if (!showDirection) return null; if (!showDirection) return null;
const arrows: THREE.Mesh[] = []; const arrows: THREE.Mesh[] = [];
@@ -171,21 +167,26 @@ function NormalConveyorCollider({
if (segment.length < 2) return; if (segment.length < 2) return;
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(10); // Fewer points for arrows const curvePoints = curve.getPoints(10);
for (let i = 0; i < curvePoints.length; i++) { for (let i = 0; i < curvePoints.length; i++) {
const point = curvePoints[i]; const point = curvePoints[i];
const next = curvePoints[i + 1] || point;
const direction = new THREE.Vector3().subVectors(next, point).normalize(); let direction: THREE.Vector3;
if (i < curvePoints.length - 1) {
direction = new THREE.Vector3().subVectors(curvePoints[i + 1], point).normalize();
} else {
direction = new THREE.Vector3().subVectors(point, curvePoints[i - 1]).normalize();
}
if (!forward) {
direction.multiplyScalar(-1);
}
const arrow = new THREE.Mesh(arrowGeometry, arrowMaterial); const arrow = new THREE.Mesh(arrowGeometry, arrowMaterial);
arrow.position.copy(point); arrow.position.copy(point);
arrow.quaternion.setFromUnitVectors( arrow.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
new THREE.Vector3(0, forward ? 1 : -1, 0),
new THREE.Vector3(direction.x, 0.1, direction.z)
);
arrows.push(arrow); arrows.push(arrow);
//
} }
}); });
@@ -204,8 +205,8 @@ function NormalConveyorCollider({
type="fixed" type="fixed"
position={[0, 0.001, 0]} position={[0, 0.001, 0]}
userData={{ isConveyor: true }} userData={{ isConveyor: true }}
onCollisionEnter={handleMaterialEnter} onCollisionEnter={e => handleMaterialEnter(e, index)}
onCollisionExit={handleMaterialExit} onCollisionExit={e => handleMaterialExit(e, index)}
colliders="trimesh" colliders="trimesh"
> >
<mesh geometry={geometry}> <mesh geometry={geometry}>
@@ -220,20 +221,16 @@ function NormalConveyorCollider({
))} ))}
{showDirection && directionArrows?.map((arrow, i) => ( {showDirection && directionArrows?.map((arrow, i) => (
<primitive <primitive key={`arrow-${i}`} object={arrow} />
key={`arrow-${i}`}
object={arrow}
/>
))} ))}
{/* Hover highlight */}
{hoverState && ( {hoverState && (
<group> <group>
{geometries.map((geometry, index) => ( {geometries.map((geometry, index) => (
<mesh <mesh
key={`highlight-${index}`} key={`highlight-${index}`}
geometry={geometry} geometry={geometry}
position={[0, 0.002, 0]} // Slightly above conveyor position={[0, 0.002, 0]}
> >
<meshBasicMaterial <meshBasicMaterial
color={forward ? "#00ff0044" : "#ff000044"} color={forward ? "#00ff0044" : "#ff000044"}

View File

@@ -3,7 +3,7 @@ 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';
interface YSplitConveyorColliderProps { interface YsplitConveyorColliderProps {
points: [number, number, number][][]; points: [number, number, number][][];
boundingBox: THREE.Box3 | null; boundingBox: THREE.Box3 | null;
asset: Asset; asset: Asset;
@@ -12,33 +12,23 @@ interface YSplitConveyorColliderProps {
onDirectionChange?: (newDirection: boolean) => void; onDirectionChange?: (newDirection: boolean) => void;
} }
function YSplitConveyorCollider({ function YSplitConveyorCollider({ points, boundingBox, asset, forward, isPaused, onDirectionChange }: YsplitConveyorColliderProps) {
points, const conveyorRefs = useRef<(any)[]>([]);
boundingBox, const [objectsOnGeometry, setObjectsOnGeometry] = useState<Map<number, Set<any>>>(new Map());
asset,
forward: initialForward,
isPaused,
onDirectionChange
}: YSplitConveyorColliderProps) {
const conveyorRefs = useRef<(any | null)[]>([]);
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 [showDirection, setShowDirection] = useState(false);
const [hoverState, setHoverState] = useState(false);
const conveyorSpeed = 2; const conveyorSpeed = 2;
const lastClickTime = useRef(0); const lastClickTime = useRef(0);
const arrowRefs = useRef<THREE.Group[]>([]); const [hoverState, setHoverState] = useState(false);
useEffect(() => { useEffect(() => {
const handleClick = (e: MouseEvent) => { const handleClick = (e: MouseEvent) => {
if (e.button === 2 && hoverState) { if (e.button === 2) {
const now = Date.now(); const now = Date.now();
if (now - lastClickTime.current < 300) { if (now - lastClickTime.current < 300) {
const newDirection = !forward;
setForward(newDirection);
if (onDirectionChange) { if (onDirectionChange) {
onDirectionChange(newDirection); console.log('forwardySplit: ', forward);
onDirectionChange(!forward);
} }
} }
lastClickTime.current = now; lastClickTime.current = now;
@@ -47,7 +37,7 @@ function YSplitConveyorCollider({
window.addEventListener('mousedown', handleClick); window.addEventListener('mousedown', handleClick);
return () => window.removeEventListener('mousedown', handleClick); return () => window.removeEventListener('mousedown', handleClick);
}, [forward, hoverState, onDirectionChange]); }, [forward]);
useEffect(() => { useEffect(() => {
if (!boundingBox) return; if (!boundingBox) return;
@@ -58,58 +48,64 @@ function YSplitConveyorCollider({
conveyorDirection.current.applyEuler(rotation); conveyorDirection.current.applyEuler(rotation);
}, [boundingBox, asset.rotation, forward]); }, [boundingBox, asset.rotation, forward]);
const handleMaterialEnter = (e: CollisionPayload) => { const handleMaterialEnter = (e: CollisionPayload, index: number) => {
if (e.other.rigidBody) { if (e.other.rigidBody) {
setObjectsOnConveyor(prev => { setObjectsOnGeometry(prev => {
const newSet = new Set(prev); const newMap = new Map(prev);
newSet.add(e.other.rigidBody); if (!newMap.has(index)) newMap.set(index, new Set());
return newSet; newMap.get(index)!.add(e.other.rigidBody);
return newMap;
}); });
} }
}; };
const handleMaterialExit = (e: CollisionPayload) => { const handleMaterialExit = (e: CollisionPayload, index: number) => {
if (e.other.rigidBody) { if (e.other.rigidBody) {
setObjectsOnConveyor(prev => { setObjectsOnGeometry(prev => {
const newSet = new Set(prev); const newMap = new Map(prev);
newSet.delete(e.other.rigidBody); if (newMap.has(index)) {
return newSet; newMap.get(index)!.delete(e.other.rigidBody);
}
return newMap;
}); });
} }
}; };
useFrame(({ clock }) => { useFrame(() => {
if (isPaused) return; 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);
const inverseQuat = assetQuat.clone().invert(); const inverseQuat = assetQuat.clone().invert();
const allCurvePoints: THREE.Vector3[] = []; points.forEach((segment, index) => {
points.forEach(segment => { if (segment.length < 2) return;
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p))); const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
allCurvePoints.push(...curve.getPoints((segment.length - 1) * 30)); const curvePoints = curve.getPoints((segment.length - 1) * 30);
}); if (!forward) curvePoints.reverse();
if (!forward) allCurvePoints.reverse(); const bodies = objectsOnGeometry.get(index);
if (!bodies) return;
objectsOnConveyor.forEach(rigidBody => { bodies.forEach(rigidBody => {
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);
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 < curvePoints.length; i++) {
const dist = allCurvePoints[i].distanceToSquared(localPos); const dist = curvePoints[i].distanceToSquared(localPos);
if (dist < minDist) { if (dist < minDist) {
minDist = dist; minDist = dist;
closestIndex = i; closestIndex = i;
} }
} }
const point = allCurvePoints[closestIndex]; const point = curvePoints[closestIndex];
const prev = allCurvePoints[closestIndex - 1] || point; const prev = curvePoints[closestIndex - 1] || point;
const next = allCurvePoints[closestIndex + 1] || point; const next = curvePoints[closestIndex + 1] || point;
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);
@@ -121,32 +117,12 @@ function YSplitConveyorCollider({
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);
}); });
if (showDirection && arrowRefs.current.length > 0) {
const elapsedTime = clock.getElapsedTime();
arrowRefs.current.forEach((arrowGroup, index) => {
const pulseScale = 0.9 + 0.1 * Math.sin(elapsedTime * 5 + index * 0.5);
arrowGroup.scale.setScalar(pulseScale);
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(() => {
const width = 1; const width = 1;
const segments = 30; const segments = 1;
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)));
@@ -177,113 +153,86 @@ function YSplitConveyorCollider({
geo.computeVertexNormals(); geo.computeVertexNormals();
return geo; return geo;
}).filter((geo): geo is THREE.BufferGeometry => geo !== null); }).filter((geo): geo is THREE.BufferGeometry => geo !== null);
}, [points, asset.position, asset.rotation]); }, [points, asset.position, asset.rotation, forward]);
// Create direction indicators
const directionArrows = useMemo(() => { const directionArrows = useMemo(() => {
if (!showDirection) return null; if (!showDirection) return null;
const arrows: THREE.Group[] = []; const arrows: THREE.Mesh[] = [];
const arrowHeight = 0.2; const arrowGeometry = new THREE.ConeGeometry(0.05, 0.2, 8);
const arrowRadius = 0.05; const arrowMaterial = new THREE.MeshBasicMaterial({ color: forward ? 0x00ff00 : 0xff0000 });
points.forEach(segment => { points.forEach(segment => {
if (segment.length < 2) return; if (segment.length < 2) return;
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(8); // Fewer points for arrows const curvePoints = curve.getPoints(10);
for (let i = 0; i < curvePoints.length; i++) { for (let i = 0; i < curvePoints.length; i++) {
const point = curvePoints[i]; const point = curvePoints[i];
const next = curvePoints[i + 1] || point;
const direction = new THREE.Vector3().subVectors(next, point).normalize(); let direction: THREE.Vector3;
const arrowGroup = new THREE.Group(); if (i < curvePoints.length - 1) {
const shaftLength = arrowHeight * 0.7; direction = new THREE.Vector3().subVectors(curvePoints[i + 1], point).normalize();
const shaftGeometry = new THREE.CylinderGeometry(arrowRadius * 0.3, arrowRadius * 0.3, shaftLength, 8); } else {
const shaftMaterial = new THREE.MeshBasicMaterial({ direction = new THREE.Vector3().subVectors(point, curvePoints[i - 1]).normalize();
color: forward ? 0x00ff00 : 0xff0000 }
});
const shaft = new THREE.Mesh(shaftGeometry, shaftMaterial); if (!forward) {
shaft.position.y = shaftLength / 2; direction.multiplyScalar(-1);
shaft.rotation.x = Math.PI / 2; }
const headGeometry = new THREE.ConeGeometry(arrowRadius, arrowHeight * 0.3, 8);
const headMaterial = new THREE.MeshBasicMaterial({ const arrow = new THREE.Mesh(arrowGeometry, arrowMaterial);
color: forward ? 0x00ff00 : 0xff0000 arrow.position.copy(point);
}); arrow.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
const head = new THREE.Mesh(headGeometry, headMaterial); arrows.push(arrow);
head.position.y = shaftLength;
arrowGroup.add(shaft);
arrowGroup.add(head);
arrowGroup.position.copy(point);
arrowGroup.position.y += 0.1;
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; return arrows;
}, [points, showDirection, forward]); }, [points, showDirection, forward]);
return ( return (
<group <group
onPointerOver={() => { onPointerEnter={() => { setShowDirection(true); setHoverState(true); }}
setShowDirection(true); onPointerLeave={() => { setShowDirection(false); setHoverState(false); }}
setHoverState(true);
}}
onPointerOut={() => {
setShowDirection(false);
setHoverState(false);
}}
> >
{/* Conveyor surface */}
{geometries.map((geometry, index) => ( {geometries.map((geometry, index) => (
<RigidBody <RigidBody
key={`conveyor-${index}`} key={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]}
userData={{ isConveyor: true }} userData={{ isConveyor: true }}
onCollisionEnter={handleMaterialEnter} onCollisionEnter={e => handleMaterialEnter(e, index)}
onCollisionExit={handleMaterialExit} onCollisionExit={e => handleMaterialExit(e, index)}
colliders="trimesh" colliders="trimesh"
> >
<mesh geometry={geometry}> <mesh geometry={geometry}>
<meshStandardMaterial <meshStandardMaterial
visible={false} color={forward ? "skyblue" : "pink"}
color={forward ? "#64b5f6" : "#f48fb1"} // More subtle colors
side={THREE.DoubleSide} side={THREE.DoubleSide}
transparent transparent
opacity={0.7} opacity={0.5}
depthWrite={false}
/> />
</mesh> </mesh>
</RigidBody> </RigidBody>
))} ))}
{showDirection && directionArrows?.map((arrow, i) => ( {showDirection && directionArrows?.map((arrow, i) => (
<primitive <primitive key={`arrow-${i}`} object={arrow} />
key={`arrow-${i}`}
object={arrow}
/>
))} ))}
{/* Hover highlight */}
{hoverState && ( {hoverState && (
<group> <group>
{geometries.map((geometry, index) => ( {geometries.map((geometry, index) => (
<mesh <mesh
// visible={false}
key={`highlight-${index}`} key={`highlight-${index}`}
geometry={geometry} geometry={geometry}
position={[0, 0.002, 0]} position={[0, 0.002, 0]}
> >
<meshBasicMaterial <meshBasicMaterial
color={forward ? "#00ff0044" : "#ff000044"} color={forward ? "green" : "red"}
transparent transparent
opacity={0.3} opacity={0.3}
/> />

View File

@@ -138,7 +138,6 @@ const ColliderProperties: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
let collider = getColliderCondition(selectedCollider?.id || "") let collider = getColliderCondition(selectedCollider?.id || "")
console.log('collider: ', collider);
}, [colliders, selectedCollider]); }, [colliders, selectedCollider]);

View File

@@ -72,12 +72,10 @@ 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} >
<Builder /> <Builder />
{/* <Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} > */}
<Simulation /> <Simulation />
<PhysicsSimulator /> <PhysicsSimulator />
</Physics> </Physics>
<Visualization /> <Visualization />