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, rotation }: { id: string; colliders: { id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[]; setColliders: React.Dispatch>; position: [number, number, number]; rotation: [number, number, number]; }) { const { camera, gl, pointer, controls } = useThree(); const [draggedId, setDraggedId] = useState(null); const dragOffset = useRef(new THREE.Vector3()); const initialDepth = useRef(0); const ref = useRef(null); const [color, setColor] = useState(new THREE.Color('white')); const [objectsOnCollider, setObjectsOnCollider] = useState>(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 ( { 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 ) ); }} > ); } export default ColliderInstance;