Files
Dwinzo_Demo/app/src/modules/scene/physics/colliders/colliderInstance/colliderInstance.tsx

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;