feat: enhance conveyor collider components with direction change functionality and refactor state management
This commit is contained in:
@@ -15,10 +15,12 @@ function RibbonCollider({
|
||||
}) {
|
||||
const [forward, setForward] = useState(false);
|
||||
|
||||
// console.log('ribbonData: ', ribbonData);
|
||||
return (
|
||||
<>
|
||||
{ribbonData.type === 'normal' && (
|
||||
<NormalConveyorCollider
|
||||
key={asset.modelUuid}
|
||||
points={ribbonData.points}
|
||||
boundingBox={boundingBox}
|
||||
asset={asset}
|
||||
@@ -29,20 +31,24 @@ function RibbonCollider({
|
||||
)}
|
||||
{ribbonData.type === 'curved' && (
|
||||
<CurvedConveyorCollider
|
||||
key={asset.modelUuid}
|
||||
points={ribbonData.points}
|
||||
boundingBox={boundingBox}
|
||||
asset={asset}
|
||||
forward={forward}
|
||||
isPaused={false}
|
||||
onDirectionChange={setForward}
|
||||
/>
|
||||
)}
|
||||
{ribbonData.type === 'y-Split' && (
|
||||
<YSplitConveyorCollider
|
||||
key={asset.modelUuid}
|
||||
points={ribbonData.points}
|
||||
boundingBox={boundingBox}
|
||||
asset={asset}
|
||||
forward={forward}
|
||||
isPaused={false}
|
||||
onDirectionChange={setForward}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -3,23 +3,25 @@ import { CollisionPayload, RigidBody } from '@react-three/rapier';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
|
||||
function CurvedConveyorCollider({
|
||||
points,
|
||||
boundingBox,
|
||||
asset,
|
||||
forward: initialForward,
|
||||
function CurvedConveyorCollider({
|
||||
points,
|
||||
boundingBox,
|
||||
asset,
|
||||
forward,
|
||||
isPaused,
|
||||
onDirectionChange
|
||||
}: {
|
||||
points: [number, number, number][][];
|
||||
boundingBox: THREE.Box3 | null;
|
||||
asset: Asset;
|
||||
forward: boolean;
|
||||
isPaused: boolean;
|
||||
onDirectionChange?: (newDirection: boolean) => void;
|
||||
}) {
|
||||
const conveyorRef = useRef<any>(null);
|
||||
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||
const [forward, setForward] = useState(initialForward);
|
||||
// const [forward, setForward] = useState(initialForward);
|
||||
const [showDirection, setShowDirection] = useState(false);
|
||||
const [hoverState, setHoverState] = useState(false);
|
||||
const conveyorSpeed = 2;
|
||||
@@ -32,9 +34,11 @@ function CurvedConveyorCollider({
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (e.button === 2 && hoverState) { // Right click and hovering over conveyor
|
||||
const now = Date.now();
|
||||
if (now - lastClickTime.current < 300) {
|
||||
|
||||
setForward(prev => !prev);
|
||||
if (now - lastClickTime.current < 300) {
|
||||
if (onDirectionChange) {
|
||||
console.log('forwardcurve: ', forward);
|
||||
onDirectionChange(!forward);
|
||||
}
|
||||
}
|
||||
lastClickTime.current = now;
|
||||
}
|
||||
@@ -44,7 +48,7 @@ function CurvedConveyorCollider({
|
||||
return () => window.removeEventListener('mousedown', handleClick);
|
||||
}, [forward, hoverState]);
|
||||
|
||||
|
||||
|
||||
const bezierPoints = useMemo(() => {
|
||||
const segments = 20;
|
||||
const allPoints: THREE.Vector3[] = [];
|
||||
@@ -72,7 +76,7 @@ function CurvedConveyorCollider({
|
||||
|
||||
return allPoints;
|
||||
}, [points, forward]);
|
||||
|
||||
|
||||
const geometries = useMemo(() => {
|
||||
const width = 1;
|
||||
const segments = 20;
|
||||
@@ -131,15 +135,15 @@ function CurvedConveyorCollider({
|
||||
return geos;
|
||||
}, [points, asset.position, asset.rotation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (bezierPoints.length >= 2) {
|
||||
const start = bezierPoints[0];
|
||||
const end = bezierPoints[bezierPoints.length - 1];
|
||||
conveyorDirection.current.copy(end).sub(start).normalize();
|
||||
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
||||
conveyorDirection.current.applyEuler(rotation);
|
||||
}
|
||||
}, [bezierPoints, forward, asset.rotation]);
|
||||
useEffect(() => {
|
||||
if (bezierPoints.length >= 2) {
|
||||
const start = bezierPoints[0];
|
||||
const end = bezierPoints[bezierPoints.length - 1];
|
||||
conveyorDirection.current.copy(end).sub(start).normalize();
|
||||
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
||||
conveyorDirection.current.applyEuler(rotation);
|
||||
}
|
||||
}, [bezierPoints, forward, asset.rotation]);
|
||||
|
||||
|
||||
const handleMaterialEnter = (e: CollisionPayload) => {
|
||||
@@ -165,7 +169,7 @@ useEffect(() => {
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (isPaused) return;
|
||||
|
||||
|
||||
// Physics simulation
|
||||
const assetPos = new THREE.Vector3(...(asset.position || [0, 0, 0]));
|
||||
const assetRot = new THREE.Euler(...(asset.rotation || [0, 0, 0]));
|
||||
@@ -210,7 +214,7 @@ useEffect(() => {
|
||||
// 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 => {
|
||||
@@ -231,12 +235,12 @@ useEffect(() => {
|
||||
// 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();
|
||||
@@ -263,25 +267,25 @@ useEffect(() => {
|
||||
|
||||
// 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 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 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);
|
||||
@@ -291,12 +295,12 @@ useEffect(() => {
|
||||
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]);
|
||||
@@ -326,22 +330,22 @@ useEffect(() => {
|
||||
>
|
||||
{geometries.map((geometry, index) => (
|
||||
<mesh key={index} geometry={geometry}>
|
||||
<meshStandardMaterial
|
||||
color={forward ? "#64b5f6" : "#f48fb1"}
|
||||
side={THREE.DoubleSide}
|
||||
transparent
|
||||
opacity={0.7}
|
||||
<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>
|
||||
@@ -351,9 +355,9 @@ useEffect(() => {
|
||||
geometry={geometry}
|
||||
position={[0, 0.002, 0]} // Slightly above conveyor
|
||||
>
|
||||
<meshBasicMaterial
|
||||
color={forward ? "#00ff0044" : "#ff000044"}
|
||||
transparent
|
||||
<meshBasicMaterial
|
||||
color={forward ? "#00ff0044" : "#ff000044"}
|
||||
transparent
|
||||
opacity={0.3}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
@@ -3,36 +3,32 @@ import { CollisionPayload, RigidBody } from '@react-three/rapier';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
|
||||
function NormalConveyorCollider({
|
||||
points,
|
||||
boundingBox,
|
||||
asset,
|
||||
forward,
|
||||
isPaused,
|
||||
onDirectionChange
|
||||
}: {
|
||||
interface NormalConveyorColliderProps {
|
||||
points: [number, number, number][][];
|
||||
boundingBox: THREE.Box3 | null;
|
||||
asset: Asset;
|
||||
forward: boolean;
|
||||
isPaused: boolean;
|
||||
onDirectionChange?: (newDirection: boolean) => void;
|
||||
}) {
|
||||
}
|
||||
|
||||
function NormalConveyorCollider({ points, boundingBox, asset, forward, isPaused, onDirectionChange }: NormalConveyorColliderProps) {
|
||||
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 [showDirection, setShowDirection] = useState(false);
|
||||
const conveyorSpeed = 2;
|
||||
const lastClickTime = useRef(0);
|
||||
const [hoverState, setHoverState] = useState(false);
|
||||
const[localForward,setLocalForward]=useState()
|
||||
|
||||
// Toggle direction on double right click
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (e.button === 2) { // Right click
|
||||
if (e.button === 2) {
|
||||
const now = Date.now();
|
||||
if (now - lastClickTime.current < 300) { // Double click within 300ms
|
||||
if (now - lastClickTime.current < 300) {
|
||||
if (onDirectionChange) {
|
||||
console.log('forwardnormal: ', forward);
|
||||
onDirectionChange(!forward);
|
||||
}
|
||||
}
|
||||
@@ -53,22 +49,25 @@ function NormalConveyorCollider({
|
||||
conveyorDirection.current.applyEuler(rotation);
|
||||
}, [boundingBox, asset.rotation, forward]);
|
||||
|
||||
const handleMaterialEnter = (e: CollisionPayload) => {
|
||||
const handleMaterialEnter = (e: CollisionPayload, index: number) => {
|
||||
if (e.other.rigidBody) {
|
||||
setObjectsOnConveyor(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(e.other.rigidBody);
|
||||
return newSet;
|
||||
setObjectsOnGeometry(prev => {
|
||||
const newMap = new Map(prev);
|
||||
if (!newMap.has(index)) newMap.set(index, new Set());
|
||||
newMap.get(index)!.add(e.other.rigidBody);
|
||||
return newMap;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMaterialExit = (e: CollisionPayload) => {
|
||||
const handleMaterialExit = (e: CollisionPayload, index: number) => {
|
||||
if (e.other.rigidBody) {
|
||||
setObjectsOnConveyor(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(e.other.rigidBody);
|
||||
return newSet;
|
||||
setObjectsOnGeometry(prev => {
|
||||
const newMap = new Map(prev);
|
||||
if (newMap.has(index)) {
|
||||
newMap.get(index)!.delete(e.other.rigidBody);
|
||||
}
|
||||
return newMap;
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -81,51 +80,50 @@ function NormalConveyorCollider({
|
||||
const assetQuat = new THREE.Quaternion().setFromEuler(assetRot);
|
||||
const inverseQuat = assetQuat.clone().invert();
|
||||
|
||||
const allCurvePoints: THREE.Vector3[] = [];
|
||||
const segmentCurves: THREE.Vector3[][] = [];
|
||||
points.forEach((segment, index) => {
|
||||
if (segment.length < 2) return;
|
||||
|
||||
points.forEach(segment => {
|
||||
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||
const curvePoints = curve.getPoints((segment.length - 1) * 30);
|
||||
segmentCurves.push(curvePoints);
|
||||
allCurvePoints.push(...curvePoints);
|
||||
});
|
||||
if (!forward) curvePoints.reverse();
|
||||
|
||||
if (!forward) allCurvePoints.reverse();
|
||||
const bodies = objectsOnGeometry.get(index);
|
||||
if (!bodies) return;
|
||||
|
||||
objectsOnConveyor.forEach(rigidBody => {
|
||||
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
|
||||
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
|
||||
bodies.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 < allCurvePoints.length; i++) {
|
||||
const dist = allCurvePoints[i].distanceToSquared(localPos);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closestIndex = i;
|
||||
let closestIndex = 0;
|
||||
let minDist = Infinity;
|
||||
for (let i = 0; i < curvePoints.length; i++) {
|
||||
const dist = curvePoints[i].distanceToSquared(localPos);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closestIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const point = allCurvePoints[closestIndex];
|
||||
const prev = allCurvePoints[closestIndex - 1] || point;
|
||||
const next = allCurvePoints[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);
|
||||
const point = curvePoints[closestIndex];
|
||||
const prev = curvePoints[closestIndex - 1] || point;
|
||||
const next = curvePoints[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);
|
||||
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||
rigidBody.setLinvel(totalForce, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
const geometries = useMemo(() => {
|
||||
const width = 1;
|
||||
const segments = 30;
|
||||
const segments = 1;
|
||||
return points.map(segment => {
|
||||
if (segment.length < 2) return null;
|
||||
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||
@@ -156,11 +154,9 @@ function NormalConveyorCollider({
|
||||
geo.computeVertexNormals();
|
||||
return geo;
|
||||
}).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(() => {
|
||||
|
||||
if (!showDirection) return null;
|
||||
|
||||
const arrows: THREE.Mesh[] = [];
|
||||
@@ -171,21 +167,26 @@ function NormalConveyorCollider({
|
||||
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
|
||||
const curvePoints = curve.getPoints(10);
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
arrow.position.copy(point);
|
||||
arrow.quaternion.setFromUnitVectors(
|
||||
new THREE.Vector3(0, forward ? 1 : -1, 0),
|
||||
new THREE.Vector3(direction.x, 0.1, direction.z)
|
||||
);
|
||||
arrow.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
||||
arrows.push(arrow);
|
||||
//
|
||||
}
|
||||
});
|
||||
|
||||
@@ -204,8 +205,8 @@ function NormalConveyorCollider({
|
||||
type="fixed"
|
||||
position={[0, 0.001, 0]}
|
||||
userData={{ isConveyor: true }}
|
||||
onCollisionEnter={handleMaterialEnter}
|
||||
onCollisionExit={handleMaterialExit}
|
||||
onCollisionEnter={e => handleMaterialEnter(e, index)}
|
||||
onCollisionExit={e => handleMaterialExit(e, index)}
|
||||
colliders="trimesh"
|
||||
>
|
||||
<mesh geometry={geometry}>
|
||||
@@ -220,20 +221,16 @@ function NormalConveyorCollider({
|
||||
))}
|
||||
|
||||
{showDirection && directionArrows?.map((arrow, i) => (
|
||||
<primitive
|
||||
key={`arrow-${i}`}
|
||||
object={arrow}
|
||||
/>
|
||||
<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
|
||||
position={[0, 0.002, 0]}
|
||||
>
|
||||
<meshBasicMaterial
|
||||
color={forward ? "#00ff0044" : "#ff000044"}
|
||||
@@ -248,4 +245,4 @@ function NormalConveyorCollider({
|
||||
);
|
||||
}
|
||||
|
||||
export default NormalConveyorCollider;
|
||||
export default NormalConveyorCollider;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CollisionPayload, RigidBody } from '@react-three/rapier';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
|
||||
interface YSplitConveyorColliderProps {
|
||||
interface YsplitConveyorColliderProps {
|
||||
points: [number, number, number][][];
|
||||
boundingBox: THREE.Box3 | null;
|
||||
asset: Asset;
|
||||
@@ -12,33 +12,23 @@ interface YSplitConveyorColliderProps {
|
||||
onDirectionChange?: (newDirection: boolean) => void;
|
||||
}
|
||||
|
||||
function YSplitConveyorCollider({
|
||||
points,
|
||||
boundingBox,
|
||||
asset,
|
||||
forward: initialForward,
|
||||
isPaused,
|
||||
onDirectionChange
|
||||
}: YSplitConveyorColliderProps) {
|
||||
const conveyorRefs = useRef<(any | null)[]>([]);
|
||||
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||
function YSplitConveyorCollider({ points, boundingBox, asset, forward, isPaused, onDirectionChange }: YsplitConveyorColliderProps) {
|
||||
const conveyorRefs = useRef<(any)[]>([]);
|
||||
const [objectsOnGeometry, setObjectsOnGeometry] = useState<Map<number, Set<any>>>(new Map());
|
||||
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 lastClickTime = useRef(0);
|
||||
const arrowRefs = useRef<THREE.Group[]>([]);
|
||||
const [hoverState, setHoverState] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (e.button === 2 && hoverState) {
|
||||
if (e.button === 2) {
|
||||
const now = Date.now();
|
||||
if (now - lastClickTime.current < 300) {
|
||||
const newDirection = !forward;
|
||||
setForward(newDirection);
|
||||
if (onDirectionChange) {
|
||||
onDirectionChange(newDirection);
|
||||
if (onDirectionChange) {
|
||||
console.log('forwardySplit: ', forward);
|
||||
onDirectionChange(!forward);
|
||||
}
|
||||
}
|
||||
lastClickTime.current = now;
|
||||
@@ -47,7 +37,7 @@ function YSplitConveyorCollider({
|
||||
|
||||
window.addEventListener('mousedown', handleClick);
|
||||
return () => window.removeEventListener('mousedown', handleClick);
|
||||
}, [forward, hoverState, onDirectionChange]);
|
||||
}, [forward]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!boundingBox) return;
|
||||
@@ -58,95 +48,81 @@ function YSplitConveyorCollider({
|
||||
conveyorDirection.current.applyEuler(rotation);
|
||||
}, [boundingBox, asset.rotation, forward]);
|
||||
|
||||
const handleMaterialEnter = (e: CollisionPayload) => {
|
||||
const handleMaterialEnter = (e: CollisionPayload, index: number) => {
|
||||
if (e.other.rigidBody) {
|
||||
setObjectsOnConveyor(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(e.other.rigidBody);
|
||||
return newSet;
|
||||
setObjectsOnGeometry(prev => {
|
||||
const newMap = new Map(prev);
|
||||
if (!newMap.has(index)) newMap.set(index, new Set());
|
||||
newMap.get(index)!.add(e.other.rigidBody);
|
||||
return newMap;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMaterialExit = (e: CollisionPayload) => {
|
||||
const handleMaterialExit = (e: CollisionPayload, index: number) => {
|
||||
if (e.other.rigidBody) {
|
||||
setObjectsOnConveyor(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(e.other.rigidBody);
|
||||
return newSet;
|
||||
setObjectsOnGeometry(prev => {
|
||||
const newMap = new Map(prev);
|
||||
if (newMap.has(index)) {
|
||||
newMap.get(index)!.delete(e.other.rigidBody);
|
||||
}
|
||||
return newMap;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
useFrame(() => {
|
||||
if (isPaused) 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();
|
||||
|
||||
const allCurvePoints: THREE.Vector3[] = [];
|
||||
points.forEach(segment => {
|
||||
points.forEach((segment, index) => {
|
||||
if (segment.length < 2) return;
|
||||
|
||||
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 => {
|
||||
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
|
||||
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
|
||||
bodies.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 < allCurvePoints.length; i++) {
|
||||
const dist = allCurvePoints[i].distanceToSquared(localPos);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
const point = allCurvePoints[closestIndex];
|
||||
const prev = allCurvePoints[closestIndex - 1] || point;
|
||||
const next = allCurvePoints[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);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
let closestIndex = 0;
|
||||
let minDist = Infinity;
|
||||
for (let i = 0; i < curvePoints.length; i++) {
|
||||
const dist = curvePoints[i].distanceToSquared(localPos);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closestIndex = i;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const point = curvePoints[closestIndex];
|
||||
const prev = curvePoints[closestIndex - 1] || point;
|
||||
const next = curvePoints[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(() => {
|
||||
const width = 1;
|
||||
const segments = 30;
|
||||
const segments = 1;
|
||||
return points.map(segment => {
|
||||
if (segment.length < 2) return null;
|
||||
const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
|
||||
@@ -177,113 +153,86 @@ function YSplitConveyorCollider({
|
||||
geo.computeVertexNormals();
|
||||
return geo;
|
||||
}).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(() => {
|
||||
if (!showDirection) return null;
|
||||
|
||||
const arrows: THREE.Group[] = [];
|
||||
const arrowHeight = 0.2;
|
||||
const arrowRadius = 0.05;
|
||||
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(8); // Fewer points for arrows
|
||||
const curvePoints = curve.getPoints(10);
|
||||
|
||||
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 arrowGroup = new THREE.Group();
|
||||
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;
|
||||
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;
|
||||
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);
|
||||
|
||||
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);
|
||||
arrow.position.copy(point);
|
||||
arrow.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
||||
arrows.push(arrow);
|
||||
}
|
||||
});
|
||||
|
||||
arrowRefs.current = arrows;
|
||||
return arrows;
|
||||
}, [points, showDirection, forward]);
|
||||
|
||||
return (
|
||||
<group
|
||||
onPointerOver={() => {
|
||||
setShowDirection(true);
|
||||
setHoverState(true);
|
||||
}}
|
||||
onPointerOut={() => {
|
||||
setShowDirection(false);
|
||||
setHoverState(false);
|
||||
}}
|
||||
onPointerEnter={() => { setShowDirection(true); setHoverState(true); }}
|
||||
onPointerLeave={() => { setShowDirection(false); setHoverState(false); }}
|
||||
>
|
||||
{/* Conveyor surface */}
|
||||
{geometries.map((geometry, index) => (
|
||||
<RigidBody
|
||||
key={`conveyor-${index}`}
|
||||
key={index}
|
||||
ref={el => (conveyorRefs.current[index] = el)}
|
||||
type="fixed"
|
||||
position={[0, 0.001, 0]}
|
||||
userData={{ isConveyor: true }}
|
||||
onCollisionEnter={handleMaterialEnter}
|
||||
onCollisionExit={handleMaterialExit}
|
||||
onCollisionEnter={e => handleMaterialEnter(e, index)}
|
||||
onCollisionExit={e => handleMaterialExit(e, index)}
|
||||
colliders="trimesh"
|
||||
>
|
||||
<mesh geometry={geometry}>
|
||||
<meshStandardMaterial
|
||||
visible={false}
|
||||
color={forward ? "#64b5f6" : "#f48fb1"} // More subtle colors
|
||||
color={forward ? "skyblue" : "pink"}
|
||||
side={THREE.DoubleSide}
|
||||
transparent
|
||||
opacity={0.7}
|
||||
depthWrite={false}
|
||||
|
||||
opacity={0.5}
|
||||
/>
|
||||
</mesh>
|
||||
</RigidBody>
|
||||
))}
|
||||
|
||||
{showDirection && directionArrows?.map((arrow, i) => (
|
||||
<primitive
|
||||
key={`arrow-${i}`}
|
||||
object={arrow}
|
||||
/>
|
||||
<primitive key={`arrow-${i}`} object={arrow} />
|
||||
))}
|
||||
|
||||
{/* Hover highlight */}
|
||||
{hoverState && (
|
||||
<group>
|
||||
{geometries.map((geometry, index) => (
|
||||
<mesh
|
||||
// visible={false}
|
||||
key={`highlight-${index}`}
|
||||
geometry={geometry}
|
||||
position={[0, 0.002, 0]}
|
||||
>
|
||||
<meshBasicMaterial
|
||||
color={forward ? "#00ff0044" : "#ff000044"}
|
||||
color={forward ? "green" : "red"}
|
||||
transparent
|
||||
opacity={0.3}
|
||||
/>
|
||||
@@ -295,4 +244,4 @@ function YSplitConveyorCollider({
|
||||
);
|
||||
}
|
||||
|
||||
export default YSplitConveyorCollider;
|
||||
export default YSplitConveyorCollider;
|
||||
|
||||
@@ -138,7 +138,6 @@ const ColliderProperties: React.FC = () => {
|
||||
};
|
||||
useEffect(() => {
|
||||
let collider = getColliderCondition(selectedCollider?.id || "")
|
||||
console.log('collider: ', collider);
|
||||
}, [colliders, selectedCollider]);
|
||||
|
||||
|
||||
|
||||
@@ -72,12 +72,10 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co
|
||||
>
|
||||
<Setup />
|
||||
<Collaboration />
|
||||
{/* <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} debug >
|
||||
<Builder />
|
||||
|
||||
{/* <Physics gravity={[0, -9.81, 0]} allowedLinearError={50} numSolverIterations={50} > */}
|
||||
<Simulation />
|
||||
|
||||
<PhysicsSimulator />
|
||||
</Physics>
|
||||
<Visualization />
|
||||
|
||||
Reference in New Issue
Block a user