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'; import { useSceneContext } from '../../../sceneContext'; import { ColliderArrow } from './colliderArrow'; function ColliderInstance({ collider }: { collider: Collider }) { const { camera, gl, pointer, controls } = useThree(); const { colliderStore } = useSceneContext(); const { colliders, selectedCollider, setSelectedCollider, updateCollider, getArrowByArrowId } = colliderStore(); const [draggedId, setDraggedId] = useState(null); const dragOffset = useRef(new THREE.Vector3()); const initialDepth = useRef(0); const ref = useRef(null); const [objectsOnCollider, setObjectsOnCollider] = useState>(new Set()); const isSelected = selectedCollider?.id === collider.id; const objectCounterRef = useRef(0); 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 = (e: PointerEvent) => { if (!draggedId) return; const collider = colliders.find(c => c.id === draggedId); if (!collider || !ref.current) return; if (e.altKey) { const rotation = ref.current.rotation(); const currentQuaternion = new THREE.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w); const deltaQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), 0.01); const newQuaternion = currentQuaternion.multiply(deltaQuaternion); ref.current.setRotation({ x: newQuaternion.x, y: newQuaternion.y, z: newQuaternion.z, w: newQuaternion.w }, true); 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); const pos = ref.current.translation(); const rot = ref.current.rotation(); updateCollider(draggedId, { position: [pos.x, pos.y, pos.z], rotation: [rot.x, rot.y, rot.z], }); } 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) => { const body = e.other.rigidBody; if (body && (body.userData as any)?.materialType) { setObjectsOnCollider(prev => { const newSet = new Set(prev); newSet.add(body); return newSet; }); if (collider.colliderCondition.conditionType === "count") { objectCounterRef.current += 1; } } }; const handleMaterialExit = (e: CollisionPayload) => { 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; if (collider.colliderCondition.conditionType === "material") { const bodyMaterial = (rigidBody as any).userData.materialType; if (!bodyMaterial) return; let matchedCondition = collider.colliderCondition.arrowCondition?.find((material) => material.materialType === bodyMaterial); if (!matchedCondition) { matchedCondition = collider.colliderCondition.arrowCondition?.find((material) => material.materialType === "Any"); } if (matchedCondition) { let arrowDetails = getArrowByArrowId(matchedCondition.arrowId); if (arrowDetails?.position && ref.current) { const arrowPos = new THREE.Vector3(...arrowDetails.position); const colliderPos = new THREE.Vector3(...(collider.position || [0, 0, 0])); const direction = new THREE.Vector3(); direction.subVectors(arrowPos, colliderPos).normalize(); rigidBody.setLinvel({ x: direction.x, y: direction.y, z: direction.z }, true); } } } else if (collider.colliderCondition.conditionType === "count") { let { count, arrowsOrder } = collider.colliderCondition; if (!arrowsOrder || arrowsOrder.length === 0) return; const totalProcessed = objectCounterRef.current; const arrowIndex = Math.floor(totalProcessed / count) % arrowsOrder.length; const arrowId = arrowsOrder[arrowIndex]; const arrowDetails = getArrowByArrowId(arrowId); if (arrowDetails?.position && ref.current) { const arrowPos = new THREE.Vector3(...arrowDetails.position); const colliderPos = new THREE.Vector3(...(collider.position || [0, 0, 0])); const direction = new THREE.Vector3(); direction.subVectors(arrowPos, colliderPos).normalize(); rigidBody.setLinvel({ x: direction.x, y: direction.y, z: direction.z }, true); } } }); }); return ( { e.stopPropagation(); handlePointerDown(collider.id); }} onClick={(e) => { e.stopPropagation(); setSelectedCollider(collider); }} > {isSelected && collider.arrows.map((arrow) => ( ))} ); } export default ColliderInstance;