다양한 물리엔진 라이브러리가 존재
1. cannon
2. rapier
3. p2
...
이중에서 우리는 cannon 으로 작업할 예정
cannon-es 는 가벼우면서 웹에서 사용하기 쉬운 3D 물리엔진
use-cannon 은 R3F 와 cannon-es 를 연결하여 쉽게 3D 물리 시뮬레이션을 만들 수 있게 해줌
동작원리
보이지 않는 가상의 3D 캐논 요소를 Mesh 요소에 대입함으로써
대입이 된 순간부터는 Mesh 가 position 혹은 rotation 과 같은 동작이
넣어준 값대로 변경되는것이 아닌 물리적인 상황이나 충돌에 따라서
3D 캐논 요소가 position 혹은 rotation 설정을 하도록 위임
★★ 그러므로 ref 를 넣어준 컴포넌트는 초기 가상 3D 캐논 요소를 따라가므로 해당 ref 를 넣어준 컴포넌트에서 position 과 rotation 을 줘도 동작하지않음. 그 자식에 선언해줘야 동작 ★★
적용방법
물리엔진을 적용할 Mesh 들을 Physics 컴포넌트로 감싸주면서 활성화 시킴
Box geometry 를 사용하는 경우 useBox 를 이용해 물리엔진을 적용할 Box ref 를 만들어주고, 해당 ref 를 Mesh 컴포넌트의 ref 로 할당해주면 useBox 로써 반환된 초기값에 따라 position 과 rotation 값이 물리적인 상황의 충돌에 따라 결정
주의점
두께가 있는 Mesh 를 사용해야 충돌이 정상적으로 발생함
Physics 의 요소들
gravity: x,y,z 축으로의 중력을 설정 (y축의 default값은 지구 중력인: -9.81)
y 축으로 끌어당기는 힘보다 x,z 의 끌어당기는 힘이 더 쌔면 마치 바람에 날아가는 사물처럼 보임
defaultContactMaterial: 별도로 특정 오브젝트끼리 충돌할 때 적용할 물리 속성을 선언하지 않으면 이 값이 적용됨
★ allowSleep: 물체가 일정 시간 동안 움직이지 않을 경우 "수면 상태(sleep state)"로 전환되어 물리 계산을 중지. 최적화 및 물체가 혼자서 조금씩 흔들거리는거 방지
Debug 컴포넌트
물리엔진의 충돌체 영역을 확인할 수 있음 ( wireframe 느낌? )
children 의 material 이 wireframe 인 경우 더 육안으로 확인하기 쉬움

물리엔진 본체를 만들기 위해 사용되는 훅
useBox useSphere useCylinder usePlane
두 가지 값을 받음
ref : 물리엔진 적용시킬 mesh 를 감싼 가상 3D 캐논 ref
api : 자연적인 힘(충돌)이 아닌 인위적인 힘을 줄때 사용
const [ref, api] = useBox(() => ({
// 여기서 선언되는 초기값은 mesh 를 감싼 가상 3D 캐논에 적용할 값들이다
args: [50, 1, 50],
// 물리적인 충돌이나 중력이 있어도 해당 ref 는 움직임이 발생하지 않음
type: "Static",
// 질량 ( 무거운 정도 )
mass: 1,
position: [0, 0, 0],
// mesh 의 material 이 아닌 cannon 의 material
material: {
// 탄성력
restitution: 1,
// 마찰력
friction: 0.5,
},
// 충돌 후 이벤트
onCollide: () => {
console.log("바닥에 충돌하였습니다");
},
}));
return (
<Box ref={ref} args={[50, 1, 50]}>
);
간단한 기하 모형은 useBox useSphere useCylinder usePlane 로 구현 가능
하지만 다음과 같이 복잡한 Mesh 에 물리엔진을 적용하기 위해서는 다른 충돌체 생성 훅 ( useTrimesh, useConvexPolyhedron ) 이 필요

useTrimesh
복잡한 형태의 mesh 에 적합
일반적으로 3D 모델의 표면을 삼각형으로 나누어 충돌체로 사용
성능적인 측면에서는 간단한 기하 모형보다는 높은 비용
geometry.attributes.position.array
해당 mesh 를 이루고 있는 모든 정점들의 위치(x, y, z)를 담고 있는 배열
geometry.index.array
삼각형을 정의하는 인덱스 배열
자세히 설명하자면, geometry.attributes.position.array 의 요소를 차례로 3개씩 묶은(하나의 정점) 새로운 배열을
현재 만들 삼각형을 구성하는데 필요한 인덱스를 불러와서 구현
e.g.)) [0, 1, 2, 2, 3, 0]
첫 번째 삼각형: 정점 0 (x1, y1, z1), 정점 1 (x2, y2, z2), 정점 2 (x3, y3, z3)
두 번째 삼각형: 정점 2 (x3, y3, z3), 정점 3 (x4, y4, z4), 정점 0 (x1, y1, z1)
const geometry = useMemo(() => new TorusGeometry(0.5, 0.2, 16, 100), []);
const [ref, api] = useTrimesh(() => ({
args: [geometry.attributes.position.array, geometry.index.array],
mass: 1,
rotation: [-Math.PI / 2, 0, 0],
...props,
}));
return (
<mesh
ref={ref}
geometry={geometry}
>
<meshBasicMaterial color="orange" />
</mesh>
);
useConvexPolyhedron
간단한 형태의 mesh에 적합함
볼록 다면체를 나타내는 충돌체를 생성하는데 사용
※ 볼록 다면체란? 모든 면이 외부로 향한 형태
※ 오목 다면체란? 모든 면이 내부로 들어간 형태

정점과 인덱스의 배열만이 아닌, 평면 정보와 법선 벡터 등 추가적인 정보도 필요하므로 CannonUtils 로 폼 변형시켜주어야함
※ CannonUtils 는 커스텀 클래스
const geometry = useMemo(() => new IcosahedronGeometry(0.5, 0), []);
const args = useMemo(() => CannonUtils.toConvexPolyhedronProps(geometry), []);
const [ref, api] = useConvexPolyhedron(() => ({
args,
mass: 1,
...props,
}));
return (
<mesh ref={ref} geometry={geometry}>
<meshBasicMaterial color="orange" />
</mesh>
);
사용자에게 GUI 를 제공하여 R3F 기반의 3D 애플리케이션에서 간단하게 씬의 속성을 조작하고 모니터링 할 수 있는 도구
코드 수정 없이 사용자가 실시간으로 매개변수를 조절하고 시각적으로 확인 가능
버그를 잡거나 수정하는데 시간을 단축시켜 필수적 요소
※ cannon 에는 적용 안됨
import { useControls } from "leva";
const bgValue = useControls({ bgColor: "#fff" });
const gravity = useControls("Gravity", {
x: {
value: 0,
min: -10,
max: 10,
step: 0.1,
},
y: { value: -9.81, min: -10, max: 10, step: 0.1 },
z: { value: 0, min: -10, max: 10, step: 0.1 },
});
return (
...
<color attach={"background"} args={[bgValue.bgColor]} />
<Physics gravity={[gravity.x, gravity.y, gravity.z]}>
...
</Physics>
);
