refactor: Clean up MaterialSpawner component and improve drag handling logic
This commit is contained in:
@@ -33,7 +33,7 @@ function CurvedConveyorCollider({
|
||||
if (e.button === 2 && hoverState) { // Right click and hovering over conveyor
|
||||
const now = Date.now();
|
||||
if (now - lastClickTime.current < 300) {
|
||||
console.log("log");
|
||||
|
||||
setForward(prev => !prev);
|
||||
}
|
||||
lastClickTime.current = now;
|
||||
@@ -44,34 +44,6 @@ function CurvedConveyorCollider({
|
||||
return () => window.removeEventListener('mousedown', handleClick);
|
||||
}, [forward, hoverState]);
|
||||
|
||||
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, forward]);
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const bezierPoints = useMemo(() => {
|
||||
const segments = 20;
|
||||
@@ -101,6 +73,96 @@ function CurvedConveyorCollider({
|
||||
return allPoints;
|
||||
}, [points, forward]);
|
||||
|
||||
const geometries = useMemo(() => {
|
||||
const width = 1;
|
||||
const segments = 20;
|
||||
const geos: THREE.BufferGeometry[] = [];
|
||||
|
||||
points.forEach(segment => {
|
||||
const vertices: number[] = [];
|
||||
const indices: number[] = [];
|
||||
|
||||
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 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 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());
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
geos.push(ribbonGeometry);
|
||||
});
|
||||
|
||||
setGeometryKey(k => k + 1);
|
||||
return geos;
|
||||
}, [points, asset.position, asset.rotation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (bezierPoints.length >= 2) {
|
||||
const start = bezierPoints[0];
|
||||
const end = bezierPoints[bezierPoints.length - 1];
|
||||
conveyorDirection.current.copy(end).sub(start).normalize();
|
||||
const rotation = new THREE.Euler().fromArray(asset.rotation || [0, 0, 0]);
|
||||
conveyorDirection.current.applyEuler(rotation);
|
||||
}
|
||||
}, [bezierPoints, forward, asset.rotation]);
|
||||
|
||||
|
||||
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(({ clock }) => {
|
||||
if (isPaused) return;
|
||||
|
||||
@@ -165,63 +227,6 @@ function CurvedConveyorCollider({
|
||||
}
|
||||
});
|
||||
|
||||
const geometries = useMemo(() => {
|
||||
const width = 1;
|
||||
const segments = 20;
|
||||
const geos: THREE.BufferGeometry[] = [];
|
||||
|
||||
points.forEach(segment => {
|
||||
const vertices: number[] = [];
|
||||
const indices: number[] = [];
|
||||
|
||||
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 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 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());
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
geos.push(ribbonGeometry);
|
||||
});
|
||||
|
||||
setGeometryKey(k => k + 1);
|
||||
return geos;
|
||||
}, [points, asset.position, asset.rotation]);
|
||||
|
||||
// Create curved direction indicators
|
||||
const directionArrows = useMemo(() => {
|
||||
|
||||
@@ -4,7 +4,7 @@ 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, TransformControls } from '@react-three/drei';
|
||||
import { CameraControls, } from '@react-three/drei';
|
||||
import { generateUniqueId } from '../../../functions/generateUniqueId';
|
||||
|
||||
type MaterialSpawnerProps = {
|
||||
@@ -28,9 +28,11 @@ function MaterialSpawner({ position: initialPos, spawnInterval, spawnCount }: Ma
|
||||
const dragOffset = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||
const initialDepth = useRef<number>(0);
|
||||
const materialTypes = ['Default material', 'Material 1', 'Material 2', 'Material 3'];
|
||||
const [boxPosition, setBoxPosition] = useState<[number, number, number]>(initialPos);
|
||||
const spawnerRef = useRef<THREE.Mesh>(null!);
|
||||
const newPositionRef = useRef<[number, number, number]>([...initialPos]);
|
||||
const [isDraggingSpawner, setIsDraggingSpawner] = useState(false);
|
||||
const spawnerDragOffset = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||
const spawnerInitialDepth = useRef<number>(0);
|
||||
const [boxPosition, setBoxPosition] = useState<[number, number, number]>(initialPos);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingProgress !== 0) return;
|
||||
@@ -49,11 +51,8 @@ function MaterialSpawner({ position: initialPos, spawnInterval, spawnCount }: Ma
|
||||
}
|
||||
spawnedCount.current++;
|
||||
|
||||
const randomMaterialType =
|
||||
materialTypes[Math.floor(Math.random() * materialTypes.length)];
|
||||
const randomMaterialType = materialTypes[Math.floor(Math.random() * materialTypes.length)];
|
||||
|
||||
|
||||
console.log('boxPosition: ', boxPosition);
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
@@ -94,7 +93,6 @@ function MaterialSpawner({ position: initialPos, spawnInterval, spawnCount }: Ma
|
||||
|
||||
}, [loadingProgress, spawnInterval, spawnCount, spawningPaused, boxPosition]);
|
||||
|
||||
|
||||
const handleSleep = (id: string) => {
|
||||
setSpawned(prev => prev.filter(obj => obj.id !== id));
|
||||
};
|
||||
@@ -181,44 +179,83 @@ function MaterialSpawner({ position: initialPos, spawnInterval, spawnCount }: Ma
|
||||
};
|
||||
}, [draggedId, spawned, controls, camera, gl]);
|
||||
|
||||
|
||||
const handleSpawnerPointerDown = (e: any) => {
|
||||
if (e.button !== 2) return; // right click only
|
||||
e.stopPropagation();
|
||||
|
||||
if (controls) (controls as CameraControls).enabled = false;
|
||||
setIsDraggingSpawner(true);
|
||||
|
||||
const worldPos = spawnerRef.current.getWorldPosition(new THREE.Vector3());
|
||||
const screenPos = worldPos.clone().project(camera);
|
||||
|
||||
spawnerDragOffset.current.set(
|
||||
screenPos.x - pointer.x,
|
||||
0,
|
||||
screenPos.y - pointer.y
|
||||
);
|
||||
|
||||
spawnerInitialDepth.current = worldPos.clone().sub(camera.position).length();
|
||||
};
|
||||
|
||||
const handleSpawnerPointerMove = () => {
|
||||
if (!isDraggingSpawner) return;
|
||||
|
||||
const targetScreenPos = new THREE.Vector3(
|
||||
pointer.x + spawnerDragOffset.current.x,
|
||||
0,
|
||||
pointer.y + spawnerDragOffset.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(spawnerInitialDepth.current));
|
||||
|
||||
spawnerRef.current.position.copy(finalPosition);
|
||||
};
|
||||
|
||||
const handleSpawnerPointerUp = () => {
|
||||
if (controls) (controls as CameraControls).enabled = true;
|
||||
setIsDraggingSpawner(false);
|
||||
|
||||
if (spawnerRef.current) {
|
||||
const worldPos = spawnerRef.current.getWorldPosition(new THREE.Vector3());
|
||||
setBoxPosition([worldPos.x, worldPos.y, worldPos.z] as [number, number, number]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = gl.domElement;
|
||||
canvas.addEventListener("pointermove", handleSpawnerPointerMove);
|
||||
canvas.addEventListener("pointerup", handleSpawnerPointerUp);
|
||||
return () => {
|
||||
canvas.removeEventListener("pointermove", handleSpawnerPointerMove);
|
||||
canvas.removeEventListener("pointerup", handleSpawnerPointerUp);
|
||||
};
|
||||
}, [isDraggingSpawner, camera, gl, controls]);
|
||||
|
||||
const handleBoxClick = () => {
|
||||
setSpawningPaused(prev => !prev);
|
||||
};
|
||||
|
||||
const handleBoxContextMenu = () => {
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<TransformControls
|
||||
<mesh
|
||||
name='Spawner Box'
|
||||
ref={spawnerRef}
|
||||
position={boxPosition}
|
||||
scale={[0.5, 0.5, 0.5]}
|
||||
|
||||
onMouseDown={() => {
|
||||
if (controls) (controls as CameraControls).enabled = false;
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
if (controls) (controls as CameraControls).enabled = true;
|
||||
setBoxPosition(newPositionRef.current);
|
||||
}}
|
||||
onObjectChange={() => {
|
||||
if (spawnerRef.current) {
|
||||
const pos = spawnerRef.current.position;
|
||||
newPositionRef.current = [pos.x, pos.y, pos.z]; // Save latest
|
||||
}
|
||||
}}
|
||||
onClick={handleBoxClick}
|
||||
onPointerDown={handleSpawnerPointerDown}
|
||||
>
|
||||
<mesh
|
||||
ref={spawnerRef}
|
||||
// position={boxPosition}
|
||||
onClick={handleBoxClick}
|
||||
// onContextMenu={handleBoxContextMenu}
|
||||
>
|
||||
<boxGeometry args={[1, 1, 1]} />
|
||||
<meshStandardMaterial color={spawningPaused ? "red" : "white"} transparent opacity={0.2} />
|
||||
</mesh>
|
||||
</TransformControls>
|
||||
<boxGeometry args={[1, 1, 1]} />
|
||||
<meshStandardMaterial color={spawningPaused ? "red" : "white"} transparent opacity={0.2} />
|
||||
</mesh>
|
||||
|
||||
{spawned.map(({ id, position, materialType, ref }) => (
|
||||
<RigidBody
|
||||
|
||||
@@ -6,16 +6,16 @@ function PhysicsSimulator() {
|
||||
return (
|
||||
<>
|
||||
|
||||
{/* <MaterialSpawner
|
||||
position={[1, 3, 4]}
|
||||
spawnInterval={1000}
|
||||
spawnCount={50}
|
||||
/> */}
|
||||
<MaterialSpawner
|
||||
position={[3.8, 3, 3]}
|
||||
position={[1, 2, 4]}
|
||||
spawnInterval={1000}
|
||||
spawnCount={50}
|
||||
/>
|
||||
{/* <MaterialSpawner
|
||||
position={[3.8, 3, 3]}
|
||||
spawnInterval={1000}
|
||||
spawnCount={50}
|
||||
/> */}
|
||||
{/* <MaterialSpawner
|
||||
position={[6, 3, -6]}
|
||||
spawnInterval={1000}
|
||||
|
||||
Reference in New Issue
Block a user