feat: Implement Y-Split conveyor collider and update related types; refactor conveyor handling across components
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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' &&
|
||||
<YSplitConveyorCollider
|
||||
points={ribbonData.points}
|
||||
boundingBox={boundingBox}
|
||||
asset={asset}
|
||||
forward={false}
|
||||
isPaused={false}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<THREE.Vector3[]>([])
|
||||
const draggingRef = useRef<number | null>(null)
|
||||
const [geometryKey, setGeometryKey] = useState(0)
|
||||
const [splines, setSplines] = useState<THREE.Vector3[][]>([])
|
||||
const [_, setTempPoints] = useState<THREE.Vector3[]>([])
|
||||
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) => (
|
||||
<Sphere
|
||||
key={i}
|
||||
position={p}
|
||||
args={[0.1, 16, 16]}
|
||||
onPointerDown={() => {
|
||||
draggingRef.current = i;
|
||||
(controls as CameraControls).enabled = false
|
||||
}}
|
||||
>
|
||||
<meshStandardMaterial color={'red'} />
|
||||
</Sphere>
|
||||
))}
|
||||
{splines.map((spline, splineIndex) =>
|
||||
spline.map((point, pointIndex) => (
|
||||
<Sphere
|
||||
key={`${splineIndex}-${pointIndex}`}
|
||||
position={point}
|
||||
args={[0.1, 16, 16]}
|
||||
onPointerDown={() => {
|
||||
draggingRef.current = { splineIndex, pointIndex };
|
||||
(controls as CameraControls).enabled = false
|
||||
}}
|
||||
>
|
||||
<meshStandardMaterial color="red" />
|
||||
</Sphere>
|
||||
))
|
||||
)}
|
||||
|
||||
{geometry && (
|
||||
<RigidBody key={geometryKey} type="fixed" colliders="trimesh">
|
||||
<mesh geometry={geometry}>
|
||||
{geometries.map((geom, idx) => (
|
||||
<RigidBody key={idx} type="fixed" colliders="trimesh">
|
||||
<mesh geometry={geom}>
|
||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
</RigidBody>
|
||||
)}
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 && (
|
||||
<RigidBody
|
||||
key={geometryKey}
|
||||
ref={conveyorRef}
|
||||
type="fixed"
|
||||
position={[0, 0.001, 0]}
|
||||
userData={{ isConveyor: true }}
|
||||
onCollisionEnter={handleMaterialEnter}
|
||||
onCollisionExit={handleMaterialExit}
|
||||
colliders="trimesh"
|
||||
>
|
||||
<mesh geometry={geometry}>
|
||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} transparent opacity={0.5} />
|
||||
</mesh>
|
||||
</RigidBody>
|
||||
)
|
||||
<>
|
||||
{geometries.length > 0 && (
|
||||
<RigidBody
|
||||
key={geometryKey}
|
||||
ref={conveyorRef}
|
||||
type="fixed"
|
||||
position={[0, 0.001, 0]}
|
||||
userData={{ isConveyor: true }}
|
||||
onCollisionEnter={handleMaterialEnter}
|
||||
onCollisionExit={handleMaterialExit}
|
||||
colliders="trimesh"
|
||||
>
|
||||
{geometries.map((geometry, index) => (
|
||||
<mesh key={index} geometry={geometry}>
|
||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} transparent opacity={0.5} />
|
||||
</mesh>
|
||||
))}
|
||||
</RigidBody>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<any>(null);
|
||||
const conveyorRefs = useRef<any[]>([]);
|
||||
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||
const conveyorDirection = useRef<THREE.Vector3>(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 && (
|
||||
<RigidBody
|
||||
key={geometryKey}
|
||||
ref={conveyorRef}
|
||||
type="fixed"
|
||||
position={[0, 0.001, 0]}
|
||||
userData={{ isConveyor: true }}
|
||||
onCollisionEnter={handleMaterialEnter}
|
||||
onCollisionExit={handleMaterialExit}
|
||||
colliders="trimesh"
|
||||
>
|
||||
<mesh geometry={geometry}>
|
||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} transparent opacity={0.5} />
|
||||
</mesh>
|
||||
</RigidBody>
|
||||
)
|
||||
<>
|
||||
{geometries.map((geometry, index) => (
|
||||
<RigidBody
|
||||
key={index}
|
||||
ref={el => (conveyorRefs.current[index] = el)}
|
||||
type="fixed"
|
||||
position={[0, 0.001, 0]}
|
||||
userData={{ isConveyor: true }}
|
||||
onCollisionEnter={handleMaterialEnter}
|
||||
onCollisionExit={handleMaterialExit}
|
||||
colliders="trimesh"
|
||||
>
|
||||
<mesh geometry={geometry!}>
|
||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} transparent opacity={0.5} />
|
||||
</mesh>
|
||||
</RigidBody>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||
const conveyorDirection = useRef<THREE.Vector3>(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) => (
|
||||
<RigidBody
|
||||
key={index}
|
||||
ref={el => (conveyorRefs.current[index] = el)}
|
||||
type="fixed"
|
||||
position={[0, 0.001, 0]}
|
||||
userData={{ isConveyor: true }}
|
||||
onCollisionEnter={handleMaterialEnter}
|
||||
onCollisionExit={handleMaterialExit}
|
||||
colliders="trimesh"
|
||||
>
|
||||
<mesh geometry={geometry!}>
|
||||
<meshStandardMaterial color="skyblue" side={THREE.DoubleSide} transparent opacity={0.5} />
|
||||
</mesh>
|
||||
</RigidBody>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default YSplitConveyorCollider;
|
||||
|
||||
@@ -24,7 +24,7 @@ function PhysicsSimulator() {
|
||||
|
||||
<ColliderCreator />
|
||||
|
||||
{/* <SplineCreator /> */}
|
||||
<SplineCreator />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
6
app/src/types/builderTypes.d.ts
vendored
6
app/src/types/builderTypes.d.ts
vendored
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user