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

199 lines
7.0 KiB
TypeScript
Raw Normal View History

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';
2025-07-30 18:16:01 +05:30
function ColliderInstance({ id, colliders, setColliders, position, rotation }: {
id: string;
2025-07-30 18:16:01 +05:30
colliders: { id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[];
setColliders: React.Dispatch<React.SetStateAction<{ id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[]>>;
position: [number, number, number];
2025-07-30 18:16:01 +05:30
rotation: [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;