208 lines
8.6 KiB
TypeScript
208 lines
8.6 KiB
TypeScript
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<string | null>(null);
|
|
const dragOffset = useRef(new THREE.Vector3());
|
|
const initialDepth = useRef(0);
|
|
const ref = useRef<RapierRigidBody>(null);
|
|
const [objectsOnCollider, setObjectsOnCollider] = useState<Set<RapierRigidBody>>(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();
|
|
const q = new THREE.Quaternion(rot.x, rot.y, rot.z, rot.w);
|
|
const euler = new THREE.Euler().setFromQuaternion(q, 'XYZ');
|
|
|
|
updateCollider(draggedId, {
|
|
position: [pos.x, pos.y, pos.z],
|
|
rotation: [euler.x, euler.y, euler.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 (
|
|
<RigidBody
|
|
name='Sensor-Collider'
|
|
key={collider.id}
|
|
ref={ref}
|
|
type="fixed"
|
|
sensor
|
|
position={collider.position}
|
|
rotation={collider.rotation}
|
|
colliders="cuboid"
|
|
includeInvisible
|
|
gravityScale={0}
|
|
onIntersectionEnter={handleMaterialEnter}
|
|
onIntersectionExit={handleMaterialExit}
|
|
>
|
|
<mesh
|
|
onPointerDown={(e) => {
|
|
e.stopPropagation();
|
|
handlePointerDown(collider.id);
|
|
}}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setSelectedCollider(collider);
|
|
|
|
}}
|
|
>
|
|
<boxGeometry args={[0.1, 1, 1]} />
|
|
<meshStandardMaterial color={isSelected ? 'green' : 'white'} transparent opacity={0.3} />
|
|
</mesh>
|
|
{isSelected && collider.arrows.map((arrow) => (
|
|
<ColliderArrow
|
|
key={arrow.arrowId}
|
|
startPosition={collider.position}
|
|
endPosition={arrow.position}
|
|
colliderRotation={collider.rotation}
|
|
colliderId={collider.id}
|
|
arrowId={arrow.arrowId}
|
|
/>
|
|
))}
|
|
|
|
</RigidBody>
|
|
);
|
|
}
|
|
|
|
export default ColliderInstance;
|