feat: Refactor conveyor collider logic, enhance material spawning, and remove unused colliders component
This commit is contained in:
@@ -13,7 +13,6 @@ function ConveyorCollider({ boundingBox, asset, conveyorPlaneSize, onReachEnd }:
|
||||
const [objectsOnConveyor, setObjectsOnConveyor] = useState<Set<any>>(new Set());
|
||||
const conveyorDirection = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||
const conveyorSpeed = 2;
|
||||
const reached = useRef<Set<RapierRigidBody>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
if (!boundingBox || !conveyorPlaneSize) return;
|
||||
@@ -49,72 +48,67 @@ function ConveyorCollider({ boundingBox, asset, conveyorPlaneSize, onReachEnd }:
|
||||
}
|
||||
};
|
||||
|
||||
// useFrame(() => {
|
||||
// const forward = conveyorDirection.current.clone().normalize();
|
||||
// const side = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize(); // perpendicular vector
|
||||
useFrame(() => {
|
||||
const forward = conveyorDirection.current.clone().normalize();
|
||||
const side = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize();
|
||||
|
||||
const force = forward.clone().multiplyScalar(conveyorSpeed);
|
||||
|
||||
objectsOnConveyor.forEach(rigidBody => {
|
||||
if (rigidBody) {
|
||||
const position = rigidBody.translation();
|
||||
|
||||
const centerLine = conveyorRef.current.translation();
|
||||
const relative = new THREE.Vector3().subVectors(position, centerLine);
|
||||
const sideOffset = relative.dot(side);
|
||||
|
||||
const centeringStrength = 10;
|
||||
const centeringForce = side.clone().multiplyScalar(-sideOffset * centeringStrength);
|
||||
|
||||
const totalForce = force.clone().add(centeringForce);
|
||||
|
||||
rigidBody.setAngvel(new THREE.Vector3(0, 0, 0), true);
|
||||
rigidBody.setLinvel(totalForce, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// useFrame(() => {
|
||||
// if (
|
||||
// !boundingBox ||
|
||||
// !conveyorPlaneSize ||
|
||||
// !conveyorRef.current ||
|
||||
// typeof conveyorRef.current.translation !== 'function'
|
||||
// ) return;
|
||||
|
||||
// const forward = conveyorDirection.current.clone().normalize();
|
||||
// const side = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize();
|
||||
// const force = forward.clone().multiplyScalar(conveyorSpeed);
|
||||
// const start = conveyorRef.current.translation();
|
||||
// const conveyorLength = Math.max(conveyorPlaneSize[0], conveyorPlaneSize[1]);
|
||||
// const halfLength = conveyorLength / 2;
|
||||
|
||||
// objectsOnConveyor.forEach(rigidBody => {
|
||||
// if (rigidBody) {
|
||||
// const position = rigidBody.translation();
|
||||
// if (!rigidBody) return;
|
||||
|
||||
// const centerLine = conveyorRef.current.translation();
|
||||
// const relative = new THREE.Vector3().subVectors(position, centerLine);
|
||||
// const sideOffset = relative.dot(side);
|
||||
// const position = rigidBody.translation();
|
||||
// const relative = new THREE.Vector3().subVectors(position, start);
|
||||
// const forwardOffset = relative.dot(forward);
|
||||
// const sideOffset = relative.dot(side);
|
||||
// const atEnd = forwardOffset >= halfLength - 0.5;
|
||||
|
||||
// const centeringStrength = 10;
|
||||
// if (!atEnd) {
|
||||
// const centeringStrength = 10;
|
||||
// const centeringForce = side.clone().multiplyScalar(-sideOffset * centeringStrength);
|
||||
|
||||
// const totalForce = force.clone().add(centeringForce);
|
||||
|
||||
// rigidBody.setAngvel(new THREE.Vector3(0, 0, 0), true);
|
||||
// rigidBody.setLinvel(totalForce, true);
|
||||
// } else {
|
||||
// rigidBody.setLinvel(new THREE.Vector3(0, 0, 0), true);
|
||||
// rigidBody.setAngvel(new THREE.Vector3(0, 0, 0), true);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
useFrame(() => {
|
||||
if (
|
||||
!boundingBox ||
|
||||
!conveyorPlaneSize ||
|
||||
!conveyorRef.current ||
|
||||
typeof conveyorRef.current.translation !== 'function'
|
||||
) return;
|
||||
|
||||
const forward = conveyorDirection.current.clone().normalize();
|
||||
const side = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize();
|
||||
const force = forward.clone().multiplyScalar(conveyorSpeed);
|
||||
const start = conveyorRef.current.translation();
|
||||
const conveyorLength = Math.max(conveyorPlaneSize[0], conveyorPlaneSize[1]);
|
||||
const halfLength = conveyorLength / 2;
|
||||
|
||||
objectsOnConveyor.forEach(rigidBody => {
|
||||
if (!rigidBody) return;
|
||||
|
||||
const position = rigidBody.translation();
|
||||
const relative = new THREE.Vector3().subVectors(position, start);
|
||||
const forwardOffset = relative.dot(forward);
|
||||
const sideOffset = relative.dot(side);
|
||||
const atEnd = forwardOffset >= halfLength - 0.5;
|
||||
|
||||
if (!atEnd) {
|
||||
const centeringStrength = 10;
|
||||
const centeringForce = side.clone().multiplyScalar(-sideOffset * centeringStrength);
|
||||
const totalForce = force.clone().add(centeringForce);
|
||||
rigidBody.setAngvel(new THREE.Vector3(0, 0, 0), true);
|
||||
rigidBody.setLinvel(totalForce, true);
|
||||
} else {
|
||||
rigidBody.setLinvel(new THREE.Vector3(0, 0, 0), true);
|
||||
rigidBody.setAngvel(new THREE.Vector3(0, 0, 0), true);
|
||||
|
||||
if (!reached.current.has(rigidBody)) {
|
||||
reached.current.add(rigidBody);
|
||||
console.log("✅ Triggering spawn from conveyor");
|
||||
onReachEnd?.(rigidBody); // ✅ trigger here
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -111,6 +111,11 @@ const CopyPasteControls = ({
|
||||
const copySelection = () => {
|
||||
if (selectedAssets.length > 0) {
|
||||
const newClones = selectedAssets.map((asset: any) => {
|
||||
if (asset.userData.rigidBodyRef) {
|
||||
let userData = { ...asset.userData };
|
||||
delete userData.rigidBodyRef;
|
||||
asset.userData = userData;
|
||||
}
|
||||
const clone = SkeletonUtils.clone(asset);
|
||||
clone.position.copy(asset.position);
|
||||
return clone;
|
||||
|
||||
@@ -106,6 +106,11 @@ const DuplicationControls = ({
|
||||
const duplicateSelection = () => {
|
||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||
const newClones = selectedAssets.map((asset: any) => {
|
||||
if (asset.userData.rigidBodyRef) {
|
||||
let userData = { ...asset.userData };
|
||||
delete userData.rigidBodyRef;
|
||||
asset.userData = userData;
|
||||
}
|
||||
const clone = SkeletonUtils.clone(asset);
|
||||
clone.position.copy(asset.position);
|
||||
return clone;
|
||||
|
||||
@@ -25,18 +25,26 @@ const Ground = ({ plane }: any) => {
|
||||
<RigidBody
|
||||
type="fixed"
|
||||
colliders='cuboid'
|
||||
includeInvisible
|
||||
>
|
||||
<mesh
|
||||
ref={plane}
|
||||
rotation-x={CONSTANTS.planeConfig.rotation}
|
||||
position={!toggleView ? CONSTANTS.planeConfig.position3D : CONSTANTS.planeConfig.position2D}
|
||||
name="Plane"
|
||||
receiveShadow
|
||||
position={[0, 0, 0]}
|
||||
visible={false}
|
||||
>
|
||||
<planeGeometry args={[planeValue.width, planeValue.height]} />
|
||||
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
|
||||
</mesh>
|
||||
</RigidBody>
|
||||
<mesh
|
||||
ref={plane}
|
||||
rotation-x={CONSTANTS.planeConfig.rotation}
|
||||
position={!toggleView ? CONSTANTS.planeConfig.position3D : CONSTANTS.planeConfig.position2D}
|
||||
name="Plane"
|
||||
receiveShadow
|
||||
>
|
||||
<planeGeometry args={[planeValue.width, planeValue.height]} />
|
||||
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
|
||||
</mesh>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
// import { RigidBody, RapierRigidBody } from '@react-three/rapier'
|
||||
// import { useRef } from 'react';
|
||||
// import { useLoadingProgress } from '../../../store/builder/store'
|
||||
// import { MaterialModel } from '../../simulation/materials/instances/material/materialModel';
|
||||
|
||||
// function Colliders() {
|
||||
// const { loadingProgress } = useLoadingProgress();
|
||||
// const rigidBodyRef = useRef<RapierRigidBody>(null);
|
||||
|
||||
// const handleSleep = () => {
|
||||
// console.log("slept");
|
||||
// const body = rigidBodyRef.current;
|
||||
// if (body) {
|
||||
// body.setTranslation({ x: 0, y: 10, z: 0 }, true);
|
||||
// body.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||
// body.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||
// body.wakeUp();
|
||||
// }
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// {loadingProgress === 0 && (
|
||||
// <RigidBody
|
||||
// ref={rigidBodyRef}
|
||||
// position={[0, 10, 0]}
|
||||
// colliders="cuboid"
|
||||
// angularDamping={5}
|
||||
// linearDamping={1}
|
||||
// restitution={0.1}
|
||||
// onSleep={handleSleep}
|
||||
// >
|
||||
// <MaterialModel materialId='123' materialType={"Default material"} />
|
||||
// </RigidBody>
|
||||
// )}
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// export default Colliders;
|
||||
|
||||
|
||||
import { RigidBody, RapierRigidBody } from '@react-three/rapier';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useLoadingProgress } from '../../../store/builder/store';
|
||||
import { MaterialModel } from '../../simulation/materials/instances/material/materialModel';
|
||||
import { generateUniqueId } from '../../../functions/generateUniqueId';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
|
||||
type MaterialInstance = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
function Colliders() {
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const [materials, setMaterials] = useState<MaterialInstance[]>([]);
|
||||
|
||||
const spawnNewMaterial = () => {
|
||||
setMaterials(prev => [...prev, { id: generateUniqueId() }]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingProgress === 0 && materials.length === 0) {
|
||||
spawnNewMaterial(); // spawn one initially
|
||||
}
|
||||
}, [loadingProgress]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{materials.map((mat) => (
|
||||
<SingleMaterial
|
||||
key={mat.id}
|
||||
onReachEnd={spawnNewMaterial}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default Colliders;
|
||||
|
||||
function SingleMaterial({ onReachEnd }: { onReachEnd: () => void }) {
|
||||
const rigidBodyRef = useRef<RapierRigidBody>(null);
|
||||
const hasReachedEnd = useRef(false);
|
||||
|
||||
useFrame(() => {
|
||||
const body = rigidBodyRef.current;
|
||||
if (!body || hasReachedEnd.current) return;
|
||||
|
||||
const position = body.translation();
|
||||
if (position && position.z > 5) {
|
||||
console.log('✅ Reached end — triggering spawn');
|
||||
hasReachedEnd.current = true;
|
||||
body.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||
body.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||
onReachEnd();
|
||||
body.setTranslation({ x: 0, y: -100, z: 0 }, true);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<RigidBody
|
||||
ref={rigidBodyRef}
|
||||
position={[0, 10, 0]}
|
||||
colliders="cuboid"
|
||||
angularDamping={5}
|
||||
linearDamping={1}
|
||||
restitution={0.1}
|
||||
>
|
||||
<MaterialModel materialId='123' materialType='Default material' />
|
||||
</RigidBody>
|
||||
);
|
||||
}
|
||||
194
app/src/modules/scene/physics/materialSpawner.tsx
Normal file
194
app/src/modules/scene/physics/materialSpawner.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
import { RigidBody, RapierRigidBody } from '@react-three/rapier';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useLoadingProgress } from '../../../store/builder/store';
|
||||
import { MaterialModel } from '../../simulation/materials/instances/material/materialModel';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { CameraControls } from '@react-three/drei';
|
||||
|
||||
type MaterialSpawnerProps = {
|
||||
position: [number, number, number];
|
||||
spawnInterval: number;
|
||||
spawnCount?: number;
|
||||
};
|
||||
|
||||
function MaterialSpawner({ position, spawnInterval, spawnCount }: MaterialSpawnerProps) {
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const [spawned, setSpawned] = useState<{ id: string; position: [number, number, number]; ref: React.RefObject<RapierRigidBody> }[]>([]);
|
||||
const spawnedCount = useRef(0);
|
||||
const { gl, camera, pointer, controls } = useThree();
|
||||
const [draggedId, setDraggedId] = useState<string | null>(null);
|
||||
const dragOffset = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||
const initialDepth = useRef<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingProgress !== 0) return;
|
||||
|
||||
let interval: NodeJS.Timeout | null = null;
|
||||
|
||||
const startSpawning = () => {
|
||||
if (!interval) {
|
||||
interval = setInterval(() => {
|
||||
setSpawned(prev => {
|
||||
if (spawnCount !== undefined && spawnedCount.current >= spawnCount) {
|
||||
clearInterval(interval!);
|
||||
interval = null;
|
||||
return prev;
|
||||
}
|
||||
spawnedCount.current++;
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
position,
|
||||
ref: React.createRef<RapierRigidBody>(),
|
||||
}
|
||||
];
|
||||
});
|
||||
}, spawnInterval);
|
||||
}
|
||||
};
|
||||
|
||||
const stopSpawning = () => {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
interval = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleVisibility = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
startSpawning();
|
||||
} else {
|
||||
stopSpawning();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibility);
|
||||
handleVisibility();
|
||||
|
||||
return () => {
|
||||
stopSpawning();
|
||||
document.removeEventListener('visibilitychange', handleVisibility);
|
||||
};
|
||||
}, [loadingProgress, spawnInterval, spawnCount, position]);
|
||||
|
||||
const handleSleep = (id: string) => {
|
||||
setSpawned(prev => prev.filter(obj => obj.id !== id));
|
||||
};
|
||||
|
||||
const handlePointerDown = (id: string) => {
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = false;
|
||||
}
|
||||
setDraggedId(id);
|
||||
|
||||
const obj = spawned.find(o => o.id === id);
|
||||
if (!obj || !obj.ref.current) return;
|
||||
|
||||
const currentPosition = obj.ref.current.translation();
|
||||
|
||||
const screenPosition = new THREE.Vector3(currentPosition.x, currentPosition.y, currentPosition.z).project(camera);
|
||||
|
||||
dragOffset.current = new THREE.Vector3(
|
||||
screenPosition.x - pointer.x,
|
||||
0,
|
||||
screenPosition.y - pointer.y
|
||||
);
|
||||
|
||||
initialDepth.current = new THREE.Vector3(currentPosition.x, currentPosition.y, currentPosition.z)
|
||||
.sub(camera.position)
|
||||
.length();
|
||||
|
||||
obj.ref.current.setGravityScale(0, true);
|
||||
obj.ref.current.setLinearDamping(10);
|
||||
obj.ref.current.setAngularDamping(10);
|
||||
};
|
||||
|
||||
const handlePointerMove = () => {
|
||||
if (!draggedId) return;
|
||||
|
||||
const obj = spawned.find(o => o.id === draggedId);
|
||||
if (!obj || !obj.ref.current) return;
|
||||
|
||||
const targetScreenPos = new THREE.Vector3(
|
||||
pointer.x + dragOffset.current.x,
|
||||
0,
|
||||
pointer.y + dragOffset.current.z
|
||||
);
|
||||
|
||||
const targetWorldPos = new THREE.Vector3(
|
||||
targetScreenPos.x,
|
||||
targetScreenPos.z,
|
||||
0.5
|
||||
).unproject(camera);
|
||||
|
||||
const direction = targetWorldPos.sub(camera.position).normalize();
|
||||
const finalPosition = camera.position.clone().add(direction.multiplyScalar(initialDepth.current));
|
||||
|
||||
const currentPosition = obj.ref.current.translation();
|
||||
|
||||
const moveDirection = new THREE.Vector3().subVectors(finalPosition, currentPosition);
|
||||
|
||||
obj.ref.current.setLinvel({
|
||||
x: moveDirection.x * 20,
|
||||
y: moveDirection.y * 20,
|
||||
z: moveDirection.z * 20
|
||||
}, true);
|
||||
};
|
||||
|
||||
const handlePointerUp = () => {
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
if (!draggedId) return;
|
||||
|
||||
const obj = spawned.find(o => o.id === draggedId);
|
||||
if (obj?.ref.current) {
|
||||
obj.ref.current.setGravityScale(1, true);
|
||||
obj.ref.current.setLinearDamping(0.5);
|
||||
obj.ref.current.setAngularDamping(0.5);
|
||||
}
|
||||
|
||||
setDraggedId(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
canvasElement.addEventListener('pointermove', handlePointerMove);
|
||||
canvasElement.addEventListener('pointerup', handlePointerUp);
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener('pointermove', handlePointerMove);
|
||||
canvasElement.removeEventListener('pointerup', handlePointerUp);
|
||||
};
|
||||
}, [draggedId, spawned, controls, camera, gl]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{spawned.map(({ id, position, ref }) => (
|
||||
<RigidBody
|
||||
key={id}
|
||||
ref={ref}
|
||||
position={position}
|
||||
colliders="cuboid"
|
||||
angularDamping={0.5}
|
||||
linearDamping={0.5}
|
||||
restitution={0.1}
|
||||
// onSleep={() => handleSleep(id)}
|
||||
>
|
||||
<MaterialModel
|
||||
materialId={id}
|
||||
materialType="Default material"
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePointerDown(id);
|
||||
}}
|
||||
/>
|
||||
</RigidBody>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MaterialSpawner;
|
||||
@@ -15,7 +15,7 @@ import { getUserData } from "../../functions/getUserData";
|
||||
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
||||
import { Color } from "three";
|
||||
import { Physics } from "@react-three/rapier";
|
||||
import Colliders from "./physics/colliders";
|
||||
import MaterialSpawner from "./physics/materialSpawner";
|
||||
|
||||
export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) {
|
||||
const map = useMemo(() => [
|
||||
@@ -73,11 +73,20 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
||||
>
|
||||
<Setup />
|
||||
<Collaboration />
|
||||
<Physics gravity={[0, -9.81, 0]} debug >
|
||||
<Physics gravity={[0, -9.81, 0]} allowedLinearError={20} numSolverIterations={20} debug >
|
||||
<Builder />
|
||||
<Simulation />
|
||||
|
||||
<Colliders />
|
||||
<MaterialSpawner
|
||||
position={[0, 3, 0]}
|
||||
spawnInterval={1000}
|
||||
spawnCount={50}
|
||||
/>
|
||||
<MaterialSpawner
|
||||
position={[-21, 3, -8]}
|
||||
spawnInterval={1000}
|
||||
spawnCount={50}
|
||||
/>
|
||||
</Physics>
|
||||
<Visualization />
|
||||
</Canvas>
|
||||
|
||||
Reference in New Issue
Block a user