2025-07-23 14:03:00 +05:30
|
|
|
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 }: {
|
2025-07-23 14:03:00 +05:30
|
|
|
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' }[]>>;
|
2025-07-23 14:03:00 +05:30
|
|
|
position: [number, number, number];
|
2025-07-30 18:16:01 +05:30
|
|
|
rotation: [number, number, number];
|
2025-07-23 14:03:00 +05:30
|
|
|
}) {
|
|
|
|
|
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;
|