feat: Implement ColliderCreator and ColliderInstance components, enhance PhysicsSimulator with multiple MaterialSpawner instances
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import { CameraControls } from '@react-three/drei';
|
||||
import { useThree, useFrame } from '@react-three/fiber';
|
||||
import { CollisionPayload, RapierRigidBody, RigidBody } from '@react-three/rapier';
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import * as THREE from 'three';
|
||||
|
||||
function ColliderInstance({ id, colliders, setColliders, position }: {
|
||||
id: string;
|
||||
colliders: { id: string; position: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[];
|
||||
setColliders: React.Dispatch<React.SetStateAction<{ id: string; position: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[]>>;
|
||||
position: [number, number, number];
|
||||
}) {
|
||||
const { camera, gl, pointer, controls } = useThree();
|
||||
const [draggedId, setDraggedId] = useState<string | null>(null);
|
||||
const dragOffset = useRef(new THREE.Vector3());
|
||||
const initialDepth = useRef(0);
|
||||
const ref = useRef<RapierRigidBody>(null);
|
||||
const [color, setColor] = useState(new THREE.Color('white'));
|
||||
const [objectsOnCollider, setObjectsOnCollider] = useState<Set<RapierRigidBody>>(new Set());
|
||||
|
||||
const getColorByType = (type: string) => {
|
||||
switch (type) {
|
||||
case 'Material 1':
|
||||
return new THREE.Color('blue');
|
||||
case 'Material 2':
|
||||
return new THREE.Color('green');
|
||||
case 'Material 3':
|
||||
return new THREE.Color('yellow');
|
||||
default:
|
||||
return new THREE.Color('white');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const current = colliders.find(c => c.id === id);
|
||||
if (current) {
|
||||
setColor(getColorByType(current.colliderType));
|
||||
}
|
||||
}, [colliders, id]);
|
||||
|
||||
|
||||
const handlePointerDown = (id: string) => {
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = false;
|
||||
}
|
||||
setDraggedId(id);
|
||||
|
||||
const collider = colliders.find(c => c.id === id);
|
||||
if (!collider || !ref.current) return;
|
||||
|
||||
const pos = ref.current.translation();
|
||||
const screenPos = new THREE.Vector3(pos.x, pos.y, pos.z).project(camera);
|
||||
|
||||
dragOffset.current = new THREE.Vector3(screenPos.x - pointer.x, 0, screenPos.y - pointer.y);
|
||||
initialDepth.current = new THREE.Vector3(pos.x, pos.y, pos.z).sub(camera.position).length();
|
||||
|
||||
ref.current.setGravityScale(0, true);
|
||||
ref.current.setLinearDamping(10);
|
||||
ref.current.setAngularDamping(10);
|
||||
};
|
||||
|
||||
const handlePointerMove = () => {
|
||||
if (!draggedId) return;
|
||||
|
||||
const collider = colliders.find(c => c.id === draggedId);
|
||||
if (!collider || !ref.current) return;
|
||||
|
||||
const screenTarget = new THREE.Vector3(
|
||||
pointer.x + dragOffset.current.x,
|
||||
0,
|
||||
pointer.y + dragOffset.current.z
|
||||
);
|
||||
|
||||
const worldTarget = new THREE.Vector3(screenTarget.x, screenTarget.z, 0.5).unproject(camera);
|
||||
const dir = worldTarget.clone().sub(camera.position).normalize();
|
||||
const finalPos = camera.position.clone().add(dir.multiplyScalar(initialDepth.current));
|
||||
|
||||
ref.current.setTranslation(
|
||||
{ x: finalPos.x, y: finalPos.y, z: finalPos.z },
|
||||
true
|
||||
);
|
||||
};
|
||||
|
||||
const handlePointerUp = () => {
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
if (!draggedId) return;
|
||||
|
||||
if (ref.current) {
|
||||
ref.current.setGravityScale(1, true);
|
||||
ref.current.setLinearDamping(0.5);
|
||||
ref.current.setAngularDamping(0.5);
|
||||
}
|
||||
|
||||
setDraggedId(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = gl.domElement;
|
||||
canvas.addEventListener('pointermove', handlePointerMove);
|
||||
canvas.addEventListener('pointerup', handlePointerUp);
|
||||
|
||||
return () => {
|
||||
canvas.removeEventListener('pointermove', handlePointerMove);
|
||||
canvas.removeEventListener('pointerup', handlePointerUp);
|
||||
};
|
||||
}, [colliders, draggedId, controls]);
|
||||
|
||||
const handleMaterialEnter = (e: CollisionPayload) => {
|
||||
setColor(new THREE.Color('pink'));
|
||||
const body = e.other.rigidBody;
|
||||
const current = colliders.find(c => c.id === id);
|
||||
if (current) {
|
||||
if (body && (body.userData as any)?.materialType === current.colliderType) {
|
||||
setObjectsOnCollider(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(body);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMaterialExit = (e: CollisionPayload) => {
|
||||
const current = colliders.find(c => c.id === id);
|
||||
if (current)
|
||||
setColor(getColorByType(current.colliderType));
|
||||
const body = e.other.rigidBody;
|
||||
if (body) {
|
||||
setObjectsOnCollider(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(body);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useFrame(() => {
|
||||
objectsOnCollider.forEach(rigidBody => {
|
||||
if (!rigidBody) return;
|
||||
|
||||
rigidBody.setLinvel({ x: 0, y: 0, z: 2 }, true);
|
||||
rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<RigidBody
|
||||
name='Sensor-Collider'
|
||||
key={id}
|
||||
ref={ref}
|
||||
type="fixed"
|
||||
sensor
|
||||
position={position}
|
||||
colliders="cuboid"
|
||||
includeInvisible
|
||||
gravityScale={0}
|
||||
onIntersectionEnter={handleMaterialEnter}
|
||||
onIntersectionExit={handleMaterialExit}
|
||||
>
|
||||
<mesh
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePointerDown(id);
|
||||
}}
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
setColliders(prev =>
|
||||
prev.map(c =>
|
||||
c.id === id
|
||||
? {
|
||||
...c,
|
||||
colliderType:
|
||||
c.colliderType === 'Default material'
|
||||
? 'Material 1'
|
||||
: c.colliderType === 'Material 1'
|
||||
? 'Material 2'
|
||||
: c.colliderType === 'Material 2'
|
||||
? 'Material 3'
|
||||
: 'Default material',
|
||||
}
|
||||
: c
|
||||
)
|
||||
);
|
||||
}}
|
||||
|
||||
>
|
||||
<boxGeometry args={[0.1, 1, 1]} />
|
||||
<meshStandardMaterial color={color} transparent opacity={0.3} />
|
||||
</mesh>
|
||||
</RigidBody>
|
||||
);
|
||||
}
|
||||
|
||||
export default ColliderInstance;
|
||||
Reference in New Issue
Block a user