refactor: Clean up MaterialSpawner component and improve drag handling logic

This commit is contained in:
2025-08-20 10:37:39 +05:30
parent 7794e51d1f
commit 324b7aa40c
3 changed files with 173 additions and 131 deletions

View File

@@ -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