diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx
index 9521531..5df0b7c 100644
--- a/app/src/modules/builder/asset/models/model/model.tsx
+++ b/app/src/modules/builder/asset/models/model/model.tsx
@@ -91,7 +91,6 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
useEffect(() => {
if (!ribbonData && boundingBox && asset.eventData && asset.eventData.type === 'Conveyor') {
- // console.log('asset: ', asset);
getAssetConveyorPoints(asset.assetId).then((data) => {
console.log('asset.assetI: ', asset.assetId);
console.log('data: ', data);
@@ -102,6 +101,43 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
}
}, [asset.modelUuid, asset.eventData, ribbonData, boundingBox])
+ // useEffect(() => {
+ // if (!ribbonData && boundingBox && asset.eventData && asset.eventData.type === 'Conveyor') {
+ // console.log('asset: ', asset);
+ // if (asset.assetId === "97e037828ce57fa7bd1cc615") {
+ // setRibbonData({
+ // type: 'normal',
+ // points: [
+ // [
+ // [-2.4697049405553173e-9, 0.8729155659675598, -2.6850852955950217],
+ // [-2.4697049405553173e-9, 0.8729155659675598, 2.6950024154767225]
+ // ],
+ // [
+ // [-2.4697049405553173e-9, 1, -2.6850852955950217],
+ // [-2.4697049405553173e-9, 1, 2.6950024154767225]
+ // ]
+ // ]
+ // })
+ // }
+ // if (asset.assetId === "7ce992f669caaf2d384b9e76") {
+ // setRibbonData({
+ // type: 'curved',
+ // points: [
+ // [
+ // [-0.08963948491646367, 1.2324171172287208, 0.0013611617557632294],
+ // [2.745753362991343, 1.2324171172287208, -0.20188181291400256],
+ // [3.0696383388490056, 1.2324171172287208, -3.044220906761294],
+ // ],
+ // [
+ // [-0.08963948491646367, 2, 0.0013611617557632294],
+ // [2.745753362991343, 2, -0.20188181291400256],
+ // [3.0696383388490056, 2, -3.044220906761294],
+ // ]
+ // ],
+ // })
+ // }
+ // }
+ // }, [asset.modelUuid, asset.eventData, ribbonData, boundingBox])
useEffect(() => {
if (gltfScene) {
diff --git a/app/src/modules/scene/physics/conveyor/ribbonCollider.tsx b/app/src/modules/scene/physics/conveyor/ribbonCollider.tsx
index 36f710f..e67b72e 100644
--- a/app/src/modules/scene/physics/conveyor/ribbonCollider.tsx
+++ b/app/src/modules/scene/physics/conveyor/ribbonCollider.tsx
@@ -1,12 +1,14 @@
import * as THREE from 'three';
import NormalConveyorCollider from './types/normalConveyorCollider';
import CurvedConveyorCollider from './types/curvedConveyorCollider';
+import YSplitConveyorCollider from './types/ySplitConveyorCollider';
function RibbonCollider({ ribbonData, boundingBox, asset }: {
ribbonData: ConveyorPoints,
boundingBox: THREE.Box3 | null,
asset: Asset,
}) {
+ // console.log('ribbonData: ', ribbonData);
return (
<>
@@ -28,6 +30,15 @@ function RibbonCollider({ ribbonData, boundingBox, asset }: {
isPaused={false}
/>
}
+ {ribbonData.type === 'y-Split' &&
+
+ }
>
);
}
diff --git a/app/src/modules/scene/physics/conveyor/splineCreator.tsx b/app/src/modules/scene/physics/conveyor/splineCreator.tsx
index f767cf3..8e6c85b 100644
--- a/app/src/modules/scene/physics/conveyor/splineCreator.tsx
+++ b/app/src/modules/scene/physics/conveyor/splineCreator.tsx
@@ -8,99 +8,92 @@ export function SplineCreator() {
const { raycaster, camera, gl, pointer, controls, scene } = useThree()
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), [])
- const [points, setPoints] = useState([])
- const draggingRef = useRef(null)
- const [geometryKey, setGeometryKey] = useState(0)
+ const [splines, setSplines] = useState([])
+ const [_, setTempPoints] = useState([])
+ const draggingRef = useRef<{ splineIndex: number, pointIndex: number } | null>(null)
useFrame(() => {
if (draggingRef.current !== null) {
- const i = draggingRef.current
- const y = points[i]?.y ?? 0
-
+ const { splineIndex, pointIndex } = draggingRef.current
+ const y = splines[splineIndex][pointIndex]?.y ?? 0
plane.constant = -y
raycaster.setFromCamera(pointer, camera)
const intersectionPoint = new THREE.Vector3()
const position = raycaster.ray.intersectPlane(plane, intersectionPoint) || intersectionPoint
- setPoints(prev => {
+ setSplines(prev => {
const next = [...prev]
- next[i] = new THREE.Vector3(position.x, y, position.z)
+ const spline = [...next[splineIndex]]
+ spline[pointIndex] = new THREE.Vector3(position.x, y, position.z)
+ next[splineIndex] = spline
return next
})
}
})
- const geometry = useMemo(() => {
+ const geometries = useMemo(() => {
const width = 1
const segments = 20
- const vertices: number[] = []
- const indices: number[] = []
+ const groupGeometries: THREE.BufferGeometry[] = []
- if (points.length < 3) return null
+ for (const spline of splines) {
+ for (let i = 0; i + 2 < spline.length; i += 3) {
+ const p0 = spline[i]
+ const p1 = spline[i + 1]
+ const p2 = spline[i + 2]
+ const vertices: number[] = []
+ const indices: number[] = []
- for (let group = 0; group + 2 < points.length; group += 2) {
- const p0 = points[group]
- const p1 = points[group + 1]
- const p2 = points[group + 2]
+ for (let j = 0; j <= segments; j++) {
+ const t = j / segments
+ const point = new THREE.Vector3()
+ .copy(p0).multiplyScalar((1 - t) ** 2)
+ .addScaledVector(p1, 2 * (1 - t) * t)
+ .addScaledVector(p2, t ** 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()
- 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)
- 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())
+ }
- vertices.push(...left.toArray())
- vertices.push(...right.toArray())
+ for (let j = 0; j < segments; j++) {
+ const base = j * 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()
+ groupGeometries.push(ribbonGeometry)
}
}
- const totalSegments = ((points.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()
- setGeometryKey(k => k + 1)
- return ribbonGeometry
- }, [points])
+ return groupGeometries
+ }, [splines])
useEffect(() => {
const canvas = gl.domElement
-
- const onMouseUp = (evt: MouseEvent) => {
+ const onMouseUp = () => {
(controls as CameraControls).enabled = true
- if (evt.button === 0 && draggingRef.current !== null) {
- draggingRef.current = null
- setGeometryKey(k => k + 1)
- }
+ draggingRef.current = null
}
-
canvas.addEventListener('mouseup', onMouseUp)
return () => canvas.removeEventListener('mouseup', onMouseUp)
}, [camera])
useEffect(() => {
const handleClick = (e: MouseEvent) => {
- if (points.length >= 3) return
e.preventDefault()
raycaster.setFromCamera(pointer, camera)
const intersections = raycaster.intersectObject(scene, true)
@@ -108,47 +101,55 @@ export function SplineCreator() {
if (intersections.length > 0) {
const hitPoint = intersections[0].point.clone()
- setPoints(prev => {
- if (prev.length === 0) return [hitPoint]
- if (prev.length === 1) {
- const p0 = prev[0]
- const p2 = hitPoint
+ setTempPoints(temp => {
+ const nextTemp = [...temp, hitPoint]
+ if (nextTemp.length === 2) {
+ const [p0, p2] = nextTemp
const mid = new THREE.Vector3().addVectors(p0, p2).multiplyScalar(0.5)
- return [p0, mid, p2]
+ const newSpline = [p0, mid, p2]
+ setSplines(prev => [...prev, newSpline])
+ return []
}
- return prev
+ return nextTemp
})
}
}
const canvas = gl.domElement
- canvas.addEventListener('click', handleClick)
- return () => canvas.removeEventListener('click', handleClick)
- }, [points, raycaster, pointer, camera, gl, scene])
+ canvas.addEventListener('dblclick', handleClick)
+ return () => canvas.removeEventListener('dblclick', handleClick)
+ }, [raycaster, pointer, camera, gl, scene])
+
+ useEffect(() => {
+
+ console.log(splines);
+ }, [splines])
return (
<>
- {points.map((p, i) => (
- {
- draggingRef.current = i;
- (controls as CameraControls).enabled = false
- }}
- >
-
-
- ))}
+ {splines.map((spline, splineIndex) =>
+ spline.map((point, pointIndex) => (
+ {
+ draggingRef.current = { splineIndex, pointIndex };
+ (controls as CameraControls).enabled = false
+ }}
+ >
+
+
+ ))
+ )}
- {geometry && (
-
-
+ {geometries.map((geom, idx) => (
+
+
- )}
+ ))}
>
)
}
diff --git a/app/src/modules/scene/physics/conveyor/types/curvedConveyorCollider.tsx b/app/src/modules/scene/physics/conveyor/types/curvedConveyorCollider.tsx
index 1bf9d6d..779c573 100644
--- a/app/src/modules/scene/physics/conveyor/types/curvedConveyorCollider.tsx
+++ b/app/src/modules/scene/physics/conveyor/types/curvedConveyorCollider.tsx
@@ -4,7 +4,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useFrame } from '@react-three/fiber';
function CurvedConveyorCollider({ points, boundingBox, asset, forward, isPaused }: {
- points: [number, number, number][],
+ points: [number, number, number][][],
boundingBox: THREE.Box3 | null,
asset: Asset,
forward: boolean
@@ -46,30 +46,35 @@ function CurvedConveyorCollider({ points, boundingBox, asset, forward, isPaused
}
};
- useFrame(() => {
- const vectorPoints = points.map(p => new THREE.Vector3(...p));
- if (!forward) {
- vectorPoints.reverse();
- }
+ const bezierPoints = useMemo(() => {
const segments = 20;
- const bezierPoints: THREE.Vector3[] = [];
+ const allPoints: THREE.Vector3[] = [];
- for (let group = 0; group + 2 < vectorPoints.length; group += 2) {
- const p0 = vectorPoints[group];
- const p1 = vectorPoints[group + 1];
- const p2 = vectorPoints[group + 2];
+ points.forEach(segment => {
+ let vectorPoints = segment.map(p => new THREE.Vector3(...p));
+ if (!forward) vectorPoints.reverse();
- 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);
- bezierPoints.push(point);
+ 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);
@@ -107,77 +112,85 @@ function CurvedConveyorCollider({ points, boundingBox, asset, forward, isPaused
});
});
- const geometry = useMemo(() => {
- const width = 1
- const segments = 20
- const vertices: number[] = []
- const indices: number[] = []
+ const geometries = useMemo(() => {
+ const width = 1;
+ const segments = 20;
+ const geos: THREE.BufferGeometry[] = [];
- const vectorPoint = points.map((point) => new THREE.Vector3(...point))
+ points.forEach(segment => {
+ const vertices: number[] = [];
+ const indices: number[] = [];
- if (vectorPoint.length < 3) return null
+ const vectorPoint = segment.map(p => new THREE.Vector3(...p));
+ if (vectorPoint.length < 3) return;
- for (let group = 0; group + 2 < vectorPoint.length; group += 2) {
- const p0 = vectorPoint[group]
- const p1 = vectorPoint[group + 1]
- const p2 = vectorPoint[group + 2]
+ for (let group = 0; group + 2 < vectorPoint.length; group += 2) {
+ const p0 = vectorPoint[group];
+ const p1 = vectorPoint[group + 1];
+ const p2 = vectorPoint[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)
+ 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()
+ 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)
+ 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())
- vertices.push(...right.toArray())
+ vertices.push(...left.toArray(), ...right.toArray());
+ }
}
- }
- const totalSegments = ((points.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 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()
- setGeometryKey(k => k + 1)
- return ribbonGeometry
+ 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]);
return (
- geometry && (
-
-
-
-
-
- )
+ <>
+ {geometries.length > 0 && (
+
+ {geometries.map((geometry, index) => (
+
+
+
+ ))}
+
+ )}
+ >
);
}
diff --git a/app/src/modules/scene/physics/conveyor/types/normalConveyorCollider.tsx b/app/src/modules/scene/physics/conveyor/types/normalConveyorCollider.tsx
index dd797be..59e1fae 100644
--- a/app/src/modules/scene/physics/conveyor/types/normalConveyorCollider.tsx
+++ b/app/src/modules/scene/physics/conveyor/types/normalConveyorCollider.tsx
@@ -3,18 +3,23 @@ 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 }: {
- points: [number, number, number][],
- boundingBox: THREE.Box3 | null,
- asset: Asset,
- forward: boolean
- isPaused: boolean
+function NormalConveyorCollider({
+ points,
+ boundingBox,
+ asset,
+ forward,
+ isPaused,
+}: {
+ points: [number, number, number][][];
+ boundingBox: THREE.Box3 | null;
+ asset: Asset;
+ forward: boolean;
+ isPaused: boolean;
}) {
- const conveyorRef = useRef(null);
+ const conveyorRefs = useRef([]);
const [objectsOnConveyor, setObjectsOnConveyor] = useState>(new Set());
const conveyorDirection = useRef(new THREE.Vector3());
const conveyorSpeed = 2;
- const [geometryKey, setGeometryKey] = useState(0);
useEffect(() => {
if (!boundingBox) return;
@@ -47,35 +52,42 @@ function NormalConveyorCollider({ points, boundingBox, asset, forward, isPaused
};
useFrame(() => {
- const curve = new THREE.CatmullRomCurve3(points.map(p => new THREE.Vector3(...p)));
-
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[] = [];
+ const segmentCurves: THREE.Vector3[][] = [];
+
+ // Build all curve points across segments
+ 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) allCurvePoints.reverse();
+
objectsOnConveyor.forEach(rigidBody => {
const worldPos = new THREE.Vector3().copy(rigidBody.translation());
const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
- const curvePoints = curve.getPoints(100);
- if (!forward) {
- curvePoints.reverse();
- }
-
+ // Find closest point on full conveyor
let closestIndex = 0;
let minDist = Infinity;
- for (let i = 0; i < curvePoints.length; i++) {
- const dist = curvePoints[i].distanceToSquared(localPos);
+ for (let i = 0; i < allCurvePoints.length; i++) {
+ const dist = allCurvePoints[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 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);
@@ -90,61 +102,64 @@ function NormalConveyorCollider({ points, boundingBox, asset, forward, isPaused
});
});
- const geometry = useMemo(() => {
- if (points.length < 2) return null;
-
+ const geometries = useMemo(() => {
const width = 1;
const segments = 30;
- const curve = new THREE.CatmullRomCurve3(points.map(p => new THREE.Vector3(...p)));
- const curvePoints = curve.getPoints((points.length - 1) * segments);
- const vertices: number[] = [];
- const indices: number[] = [];
+ return points.map(segment => {
+ if (segment.length < 2) return null;
- for (let i = 0; i < curvePoints.length; i++) {
- const point = curvePoints[i];
- const prev = curvePoints[i - 1] || point;
- const next = curvePoints[i + 1] || point;
+ const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
+ const curvePoints = curve.getPoints((segment.length - 1) * segments);
+ const vertices: number[] = [];
+ const indices: number[] = [];
- const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
- const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
+ for (let i = 0; i < curvePoints.length; i++) {
+ const point = curvePoints[i];
+ const prev = curvePoints[i - 1] || point;
+ const next = curvePoints[i + 1] || point;
- const left = point.clone().addScaledVector(normal, -width / 2);
- const right = point.clone().addScaledVector(normal, width / 2);
+ const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
+ const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
- vertices.push(...left.toArray(), ...right.toArray());
- }
+ const left = point.clone().addScaledVector(normal, -width / 2);
+ const right = point.clone().addScaledVector(normal, width / 2);
- for (let i = 0; i < curvePoints.length - 1; i++) {
- const base = i * 2;
- indices.push(base, base + 1, base + 2);
- indices.push(base + 1, base + 3, base + 2);
- }
+ vertices.push(...left.toArray(), ...right.toArray());
+ }
- const geo = new THREE.BufferGeometry();
- geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
- geo.setIndex(indices);
- geo.computeVertexNormals();
- setGeometryKey(k => k + 1);
- return geo;
+ for (let i = 0; i < curvePoints.length - 1; i++) {
+ const base = i * 2;
+ indices.push(base, base + 1, base + 2);
+ indices.push(base + 1, base + 3, base + 2);
+ }
+
+ const geo = new THREE.BufferGeometry();
+ geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+ geo.setIndex(indices);
+ geo.computeVertexNormals();
+ return geo;
+ }).filter(Boolean);
}, [points, asset.position, asset.rotation]);
return (
- geometry && (
-
-
-
-
-
- )
+ <>
+ {geometries.map((geometry, index) => (
+ (conveyorRefs.current[index] = el)}
+ type="fixed"
+ position={[0, 0.001, 0]}
+ userData={{ isConveyor: true }}
+ onCollisionEnter={handleMaterialEnter}
+ onCollisionExit={handleMaterialExit}
+ colliders="trimesh"
+ >
+
+
+
+
+ ))}
+ >
);
}
diff --git a/app/src/modules/scene/physics/conveyor/types/ySplitConveyorCollider.tsx b/app/src/modules/scene/physics/conveyor/types/ySplitConveyorCollider.tsx
new file mode 100644
index 0000000..fce829c
--- /dev/null
+++ b/app/src/modules/scene/physics/conveyor/types/ySplitConveyorCollider.tsx
@@ -0,0 +1,167 @@
+import * as THREE from 'three';
+import { CollisionPayload, RigidBody } from '@react-three/rapier';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import { useFrame } from '@react-three/fiber';
+
+function YSplitConveyorCollider({
+ points,
+ boundingBox,
+ asset,
+ forward,
+ isPaused,
+}: {
+ points: [number, number, number][][];
+ boundingBox: THREE.Box3 | null;
+ asset: Asset;
+ forward: boolean;
+ isPaused: boolean;
+}) {
+ const conveyorRefs = useRef([]);
+ const [objectsOnConveyor, setObjectsOnConveyor] = useState>(new Set());
+ const conveyorDirection = useRef(new THREE.Vector3());
+ const conveyorSpeed = 2;
+
+ useEffect(() => {
+ 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]);
+
+ 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;
+ });
+ }
+ };
+
+ 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();
+
+ const allCurvePoints: THREE.Vector3[] = [];
+ const segmentCurves: THREE.Vector3[][] = [];
+
+ // Build all curve points across segments
+ 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) allCurvePoints.reverse();
+
+ objectsOnConveyor.forEach(rigidBody => {
+ const worldPos = new THREE.Vector3().copy(rigidBody.translation());
+ const localPos = worldPos.clone().sub(assetPos).applyQuaternion(inverseQuat);
+
+ // Find closest point on full conveyor
+ 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);
+ });
+ });
+
+ const geometries = useMemo(() => {
+ const width = 1;
+ const segments = 30;
+ return points.map(segment => {
+ if (segment.length < 2) return null;
+
+ const curve = new THREE.CatmullRomCurve3(segment.map(p => new THREE.Vector3(...p)));
+ const curvePoints = curve.getPoints((segment.length - 1) * segments);
+ const vertices: number[] = [];
+ const indices: number[] = [];
+
+ for (let i = 0; i < curvePoints.length; i++) {
+ const point = curvePoints[i];
+ const prev = curvePoints[i - 1] || point;
+ const next = curvePoints[i + 1] || point;
+
+ const tangent = new THREE.Vector3().subVectors(next, prev).normalize();
+ const normal = new THREE.Vector3().crossVectors(tangent, new THREE.Vector3(0, 1, 0)).normalize();
+
+ const left = point.clone().addScaledVector(normal, -width / 2);
+ const right = point.clone().addScaledVector(normal, width / 2);
+
+ vertices.push(...left.toArray(), ...right.toArray());
+ }
+
+ for (let i = 0; i < curvePoints.length - 1; i++) {
+ const base = i * 2;
+ indices.push(base, base + 1, base + 2);
+ indices.push(base + 1, base + 3, base + 2);
+ }
+
+ const geo = new THREE.BufferGeometry();
+ geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+ geo.setIndex(indices);
+ geo.computeVertexNormals();
+ return geo;
+ }).filter(Boolean);
+ }, [points, asset.position, asset.rotation]);
+
+ return (
+ <>
+ {geometries.map((geometry, index) => (
+ (conveyorRefs.current[index] = el)}
+ type="fixed"
+ position={[0, 0.001, 0]}
+ userData={{ isConveyor: true }}
+ onCollisionEnter={handleMaterialEnter}
+ onCollisionExit={handleMaterialExit}
+ colliders="trimesh"
+ >
+
+
+
+
+ ))}
+ >
+ );
+}
+
+export default YSplitConveyorCollider;
+
diff --git a/app/src/modules/scene/physics/physicsSimulator.tsx b/app/src/modules/scene/physics/physicsSimulator.tsx
index 02a8b32..1758345 100644
--- a/app/src/modules/scene/physics/physicsSimulator.tsx
+++ b/app/src/modules/scene/physics/physicsSimulator.tsx
@@ -24,7 +24,7 @@ function PhysicsSimulator() {
- {/* */}
+
>
)
}
diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts
index fda8c43..225cdc4 100644
--- a/app/src/types/builderTypes.d.ts
+++ b/app/src/types/builderTypes.d.ts
@@ -49,17 +49,17 @@ type Assets = Asset[];
type NormalConveyor = {
type: 'normal';
- points: [number, number, number][];
+ points: [number, number, number][][];
}
type YJunctionConveyor = {
- type: 'y-junction';
+ type: 'y-Split';
points: [number, number, number][][];
}
type CurvedConveyor = {
type: 'curved';
- points: [number, number, number][];
+ points: [number, number, number][][];
}
type ConveyorPoints = NormalConveyor | YJunctionConveyor | CurvedConveyor;