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;