개발 · 컴퓨터공학/three.js / / 2024. 7. 20. 17:31

Threejs Cloth Tailor 개발일지 - geometry attribute uv 사라짐, 분리 mesh simulation, collition 상호작용

728x90
반응형

 

이제 mesh 분리 후 시뮬레이션 정상화 작업을 해보자. 

 

uv attribute의 실종 

계속해서 vertex remover에서 에러가 발생한다. 

uv? 갑자기 attribute에서 uv가 사라졌나?

 

실제로 분리되어 cloth0과 cloth1로 쪼개진 것 중 

cloth0을 보면 attirbute에 normal과 position만 있고, uv가 사라져있는 것을 알 수 있다. 어째서? 

 

심지어는 mesh를 분리할 때 computeVertexNormals 함수를 사용하지 않으면 normal 조차 없다.

uv도 다시 생성하면 될 것 같기도 한데..  방법이 없나.

 

export function separateMesh(scene: THREE.Scene, mesh: THREE.Mesh){
  // BufferGeometry의 속성 추출
  const positionAttribute = mesh.geometry.getAttribute('position') as THREE.BufferAttribute;
  const uvAttribute = mesh.geometry.getAttribute('uv') as THREE.BufferAttribute;
  const indexAttribute = mesh.geometry.getIndex() as THREE.BufferAttribute;
  
  // vertex 좌표 추출
  const vertices: THREE.Vector3[] = [];
  for (let i = 0; i < positionAttribute.count; i++) {
    vertices.push(new THREE.Vector3(
      positionAttribute.getX(i),
      positionAttribute.getY(i),
      positionAttribute.getZ(i)
    ));
  }

  // uv 좌표 추출
  const uvs: THREE.Vector2[] = [];
  for (let i = 0; i < uvAttribute.count; i++) {
    uvs.push(new THREE.Vector2(
      uvAttribute.getX(i),
      uvAttribute.getY(i)
    ));
  }
  

  // face 인덱스 추출
  const faces: number[][] = [];
  for (let i = 0; i < indexAttribute.count; i += 3) {
      faces.push([
          indexAttribute.getX(i),
          indexAttribute.getX(i + 1),
          indexAttribute.getX(i + 2)
      ]);
  }

  // 각 vertex의 인접 face를 기록
  const adjacencyList = new Map();
  faces.forEach((face, index) => {
      face.forEach(vertexIndex => {
          if (!adjacencyList.has(vertexIndex)) {
              adjacencyList.set(vertexIndex, []);
          }
          adjacencyList.get(vertexIndex).push(index);
      });
  });

  // 방문한 face를 기록하기 위한 배열
  const visitedFaces = new Array(faces.length).fill(false);

  // 깊이 우선 탐색(DFS)을 이용하여 연결된 face 그룹 찾기
  function findConnectedFaces(faceIndex: number, group: number[]) {
      const stack = [faceIndex];
      while (stack.length > 0) {
          const currentFaceIndex: number = stack.pop()!;
          if (visitedFaces[currentFaceIndex]) continue;
          visitedFaces[currentFaceIndex] = true;
          group.push(currentFaceIndex);

          const face = faces[currentFaceIndex];
          face.forEach((vertexIndex: number) => {
            const adjacentFaces = adjacencyList.get(vertexIndex);
            if (adjacentFaces) {
                adjacentFaces.forEach((adjacentFaceIndex: number) => {
                    if (!visitedFaces[adjacentFaceIndex]) {
                        stack.push(adjacentFaceIndex);
                    }
                });
            }
        });
      }
  }

  const groups = [];
  for (let i = 0; i < faces.length; i++) {
      if (!visitedFaces[i]) {
          const group: number[] = [];
          findConnectedFaces(i, group);
          groups.push(group);
      }
  }

  console.log(`분리된 mesh 개수 : `, groups.length)
  // mesh가 분리되지 않으면 바로 return
  if(groups.length === 1) return;

  // 각 그룹을 새로운 BufferGeometry로 분리
  const separatedGeometries = groups.map(group => {
      const newGeometry = new THREE.BufferGeometry();
      const newVertices: number[] = [];
      const newUvs: number[] = [];
      const newIndices: number[] = [];
      const vertexMapping = new Map();
      let newIndex = 0;

      group.forEach(faceIndex => {
          const face = faces[faceIndex];
          face.forEach(vertexIndex => {
              if (!vertexMapping.has(vertexIndex)) {
                  vertexMapping.set(vertexIndex, newIndex++);
                  const vertex = vertices[vertexIndex];
                  const uv = uvs[vertexIndex];
                  newVertices.push(vertex.x, vertex.y, vertex.z);
                  newUvs.push(uv.x, uv.y);
              }
              newIndices.push(vertexMapping.get(vertexIndex));
          });
      });

      newGeometry.setAttribute('position', new THREE.Float32BufferAttribute(newVertices, 3));
      newGeometry.setAttribute('uv', new THREE.Float32BufferAttribute(newUvs, 2));
      newGeometry.setIndex(newIndices);
      newGeometry.computeVertexNormals();

      return newGeometry;
  });

  separatedGeometries.forEach((geometry, index) =>{
    const newMesh: THREE.Mesh = mesh.clone();
    newMesh.geometry = geometry;
    newMesh.name = mesh.name + index

    scene.add(newMesh)
  })

  scene.remove(mesh)
}

기존 separate 코드에 vertex index에 맞추어서 uv도 분리해서 넣어줄 수 있도록 수정했다.

 

 

그래도 에러가 뜨길래 확인해보니. 

확인해보니 separate 함수에 들어오는 mesh의 uv가 없다?

처음에는 uv가 있었을 텐데 어디서부터 uv가 빠진걸까 .

 

아.. 확인해보니 vertex remover에서 주석처리 깜박했다. 

 

분리된 mesh simulation 

마지막으로 분리된 mesh들을 cloth 객체화하여 simulation list에 넣어 실행해보자.

 

분리 이후 separate 함수로부터 return으로 분리된 mesh list를 뽑아준다.

 

그럼 raycast event 로 돌아와서 separateMesh 함수가 return 하는 mesh들을 cloth 객체로 생성해서 

simulation list에 넣어주어야한다.

 

그렇게 코드를 수정하고 넣어주려는데, 

 

하필 Cloth의 부모 class PhysicsObject에서 attributes가 없다는 에러가 난다.

왜지?

 

디버깅을 돌리는 와중 알았다.

mesh가 아니라 mesh array가 들어있다... 

 

실수다. input simul cloth list에는 단일 mesh만 넣는 거였는데. 

그냥 array로 고정해서 input simul cloth list 함수를 수정하자.

 

오 다시 시뮬레이션이 잘 된다. 

근데 잘린 조각의 vertex가 공중에 고정되어있다.

 

그 이유는 cloth 객체를 새로 만든 것이라서, cloth 객체의 vertex fix 기능이 들어가서 그렇다.

그리고 서로 mesh가 분리되면 충돌하지 않는다는 점도 문제이다.

 

외부 object와의 충돌 상호작용 

지금 구현된 PBD simulation은 외부 object와 상호작용하지 못한다.

여기서 문제, 그럼 어떻게 아바타에 입히지? 

 

지금 떠오르는 방법은

physics object 클래스에 정적 아바타 mesh를 넣으면 해당 mesh에 대해서 충돌하도록 물리로직을 짜는 것.

같은 아바타 mesh들이 여러 physics object에 반영되면 한 물체에 대해서 여러 시뮬레이션 충돌은 가능할 수 있다.

기본적으로 floor에 대해 충돌하는 로직이 있으므로 정적 mesh에 충돌하는 것은 어떻게든 구현 가능할 것 같다.

 

흠.. 아니면 PBD 물리 엔진에 여러 mesh object를 넣을 수 있나.

 

 

728x90
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유