728x90

 

아래와 같은 환경에서  언덕을 만들어주려고 합니다.

cannon-es 공식문서에서 간단하게 Trimesh를 이용해서 삼각형을 만드는 법을 알려주는데,  이를 기반으로 react-three/cannon에서 삼각형 모양의 기둥을 만들어주려고 합니다.

https://pmndrs.github.io/cannon-es/docs/classes/Trimesh.html

 

  // 꼭짓점 좌표
  const x = 5;
  const y = 3;
  const z = 10;
  const vertices = [
    0,
    0,
    0, // 꼭짓점 1
    x,
    y,
    0, // 꼭짓점 2
    x,
    0,
    0, // 꼭짓점 3
    0,
    0,
    -z, // 꼭짓점 4
    x,
    y,
    -z, // 꼭짓점 5
    x,
    0,
    -z, // 꼭짓점 6
  ];

  // 삼각형 인덱스
  const indices = [
    0,
    1,
    2, // 앞쪽 삼각형
    3,
    4,
    5, // 뒤쪽 삼각형
    0,
    1,
    4, // 왼쪽 사각형 위
    0,
    4,
    3, // 왼쪽 사각형 아래
    1,
    2,
    5, // 오른쪽 사각형 위
    1,
    5,
    4, // 오른쪽 사각형 아래
    0,
    2,
    5, // 아래쪽 사각형
    0,
    5,
    3, // 아래쪽 사각형
  ];

  const [ref] = useTrimesh(() => ({
    args: [vertices, indices],
    position: [0, 0, 0],
    rotation: [0, Math.PI, 0],
    mass: 100,
    type: 'Static',
    collisionFilterGroup: 3,
  }));

indices는 각각의 꼭지점을 잇는 것이고

해당 번호는 vertices에 있는 3개의 점이 각각 x,y,z축으로 하나의 꼭짓점이 됩니다.

 

하지만 위 사진을 보면 알 수 있듯이 문제점이 발생하였습니다. 시바 충돌체가 언덕안에 들어가 있느 것이죠.. 그런데 언덕이 정작 바닥 위에는 떠있는 상황입니다.

 

문제 상황

Trimesh가 중력에는 영향을 받고, 바닥과는 충돌을 하는데 시바랑은 전혀 충돌을 하고 있지 않습니다.

react-three/cannon 및 cannon-es에서 속성 등,, 공식 문서를 이것 저것 많이 찾고  고생을 했었는데, 아래와 같은 표를 확인하게 됩니다.

2024-06-04일 기준

https://pmndrs.github.io/cannon-es/docs/index.html

 

문제는 바닥과는 충돌하는데 시바랑은 충돌하지 않는 것이었쬬,

시바는 Box , 바닥은 Plane,...  react-three/cannon도 결국 cannon-es를 이용해서 만든 라이브러리이기에 마찬가지 이겠쬬,,,

 

해결 방안 모색

두 가지 해결 방법이 있다고 생각합니다. 

1. Sphere를 이용한다. 

그냥 sphere를 이용하면 시바가 데굴데굴 구르겠죠.. 하지만 언덕에서만 구르도록 박스 안에 구를 넣는 방식으로 구현을 한다면 언덕을 이용할 수 있을 겁니다..

 

2. 언덕을 벽이 아닌 Box로 만든 후 기울인다.

이 방법을 사용하면 결국 언덕과 유사한 효과를 만들 수 있겠군요.

 

3. 바퀴 형식을 도입하여, 움직인다.

바퀴를 sphere로 하고 회전 시키는 경우에 시바는 데굴데굴 구르지 않을 겁니다.

해당 방법을 도입하였지만,, 움직임이 부자연스러워서  기각,,

 

최종 방법은  언덕을 만들어서 기울이기로 하였습니다.

position 관리에 용이하게 하기 위해서 useCompoundBody를 사용하였고,

아래와 같이 값을 분리하여 사용하였습니다

  const [houseBody, _] = useCompoundBody(
    () => ({
      mass: 0,
      rotation: [0, 0, 0],
      collisionFilterGroup: 3,
      type: 'Static',
      shapes: [...HOUSE_GROUND, ...HOUSE_SHAPE, ...HOUSE_STAIR],
    }),
    useRef(null)
  );
export const HOUSE_SHAPE: CompoundShape = [
  {
    //굴뚝
    args: [1.2, 10, 1.2],
    position: [-12.3, 11.9, 0.8],
    type: 'Box',
  },
  {
    //집
    args: [4.2, 5.5, 4],
    position: [-10.3, 9.5, 3],
    type: 'Box',
  },
  {
    args: [5, 5.5, 1],
    position: [-10.3, 12.8, 0.8],
    rotation: [Math.PI / 5, 0, 0],
    type: 'Box',
  },
  {
    args: [5, 5.5, 1],
    position: [-10.3, 12.8, 3.5],
    rotation: [-Math.PI / 5, 0, 0],
    type: 'Box',
  },
  {
    args: [8, 1.5, 5.5],
    position: [-11.3, 10.5, 2.5],
    type: 'Box',
  },
  {
    args: [4, 1.5, 4],
    position: [-13.5, 12.2, 2.5],
    rotation: [0, 0, Math.PI / 3.3],
    type: 'Box',
  },
];

export const HOUSE_STAIR: CompoundShape = [
  {
    args: [4, 1.5, 2],
    position: [-8, 1.9, -9],
    rotation: [0, 0, -Math.PI / 5.1],
    type: 'Box',
  },
  {
    args: [4, 1.5, 2],
    position: [-10, 3, -8.3],
    rotation: [0, Math.PI / 4, -Math.PI / 5.1],
    type: 'Box',
  },
  {
    args: [4, 1.5, 2.4],
    position: [-10.7, 5, -6],
    rotation: [0, Math.PI / 2, -Math.PI / 5.1],
    type: 'Box',
  },
  {
    args: [4, 6, 0.5],
    position: [-12, 3.5, -5.7],
    rotation: [Math.PI / 4, Math.PI / 2, -0.5],
    type: 'Box',
  },
  {
    args: [3, 2, 4],
    position: [-12, 1, -7.7],
    rotation: [-0.2, 0, 0.2],
    type: 'Box',
  },
];

 

힘들었던 점

 

이중에서 useConvexPolyhedron을 사용해봤다. 복잡한 다면체를 구현하기에 효과적인 방법 중 하난데 꼭지점과 꼭지점을 연결하여 면을 만들고, 해당 면의 수직축을 정하여서 충돌체를 만드는 방식이다.

 

다만 cannon-es 에 의해 공식 문서 스펙대로 구현하였으나 (vertices 꼭지점, faces 면, normals 면이바라보는 방향) 충돌이 올바르게 일어나지 않았다. ( 통과할 때도 있고 통과하지 않을때도 있고,, 불안정한 기능)

   const width = 4;
   const length = 17;
   const vertices: Triplet[] = [
     [9.5, 4.5, width],
     [9.5, 4.5, 0],
     [7, 4.5, width],
     [7, 4.5, 0],
     [0, 2.5, width],
     [0, 2.5, 0],
     [length, 2.5, width],
     [length, 2.5, 0],
     [0, 0, width],
     [0, 0, 0],
     [length, 0, width], //10
     [length, 0, 0], //11
     [10, 2.5, width],
     [10, 2.5, 0],
     [7, 2.5, width],
     [7, 2.5, 0],
   ];

   const faces = [
     [0, 1, 2, 3],
     [2, 3, 4, 5],
     [0, 1, 6, 7],
     [4, 5, 8, 9],
     [6, 7, 10, 11],
     [10, 11, 12, 13],
     [8, 9, 14, 15],
     [12, 13, 14, 15],
     [5, 9, 3, 15],
     [2, 4, 8, 14],
     [0, 2, 12, 14],
     [1, 3, 13, 15],
     [0, 12, 6, 10],
     [1, 7, 13, 11],
   ];

   function calculateNormal(v0, v1, v2) {
     const vector1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];

     const vector2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];

     const normal = [
       vector1[1] * vector2[2] - vector1[2] * vector2[1],
       vector1[2] * vector2[0] - vector1[0] * vector2[2],
       vector1[0] * vector2[1] - vector1[1] * vector2[0],
     ];

     const length = Math.sqrt(
       normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]
     );

     return [normal[0] / length, normal[1] / length, normal[2] / length];
   }

   const normals = faces.map((face) => {
     const [v0, v1, v2] = face.map((index) => vertices[index]);
     return calculateNormal(v0, v1, v2);
   });

   console.log(normals, 'normals');

 

따라서 차선으로 다리같은 복잡한 물체도 box의 조합으로 구현하였다.

+ Recent posts