개발 · 컴퓨터공학/three.js / / 2024. 6. 28. 09:02

Threejs Cloth Tailor 개발일지 - cloth simulation 함수 시점 분리하기, vertex 제거 후 시뮬레이션 이슈 해결

728x90
반응형

 

 

remove vertex의 문제점 

이전에 vertex를 없애는 것이 가능했었는데 

위와 같은 모양으로 없앴을 때, mesh가 잘렸다. 라고 보고 분리되어야하지만

분리되지 않고 한 mesh처럼 동작한다

 

export function removeVertex(mesh: THREE.Mesh, rmIdx: number){
  const meshAttribute = mesh.geometry.attributes
  const meshIndex = mesh.geometry.index

  const posArr = meshAttribute.position.array
  const normArr = meshAttribute.normal.array
  const uvArr = meshAttribute.uv.array
  const idxArr = meshIndex?.array
  let newIndices

  if (!idxArr) {
    throw new Error('Geometry must have an index.')
  }

  console.log(idxArr)
  const faceList = []
  for(let i=0; i<idxArr.length; i+=3){
    faceList.push([idxArr[i], idxArr[i+1], idxArr[i+2]]) // face 하나에 index 3개
  } 
  const newFaceList = []
  for(let i=0; i<faceList.length; i++){ // rmIdx가 포함된 face를 찾아 없애기
    if(faceList[i].includes(rmIdx) === false){
      newFaceList.push(faceList[i])
    }
  }
  console.log(`new : `)
  console.log(newFaceList)

  // convert number[][] to float32array
  newIndices = newFaceList.reduce((acc, val) => acc.concat(val), [])
  console.log(newIndices)
  const geometry = new THREE.BufferGeometry()
  geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(posArr), 3))
  geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(normArr), 3))
  geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvArr), 2))
  geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(newIndices), 1))

  console.log(mesh.geometry)
  console.log(geometry)

  mesh.geometry = geometry
}

이 함수는 vertex를 제거한다고 되어있지만

실제로는 face에서 해당 vertex가 포함된 정보를 지우기만 할 뿐 실제로 vertex는 position array에 존재한다

 

그러므로 함수 이름을 remove vertex가 아니라 remove face가 적합할 것 같다

 

그럼 face만 지우는 현 상황에서 변경된 face list에 따라서 사용되지 않는 vertex는 지우고

결정적으로 PBD 시뮬레이션에도 반영해서 vertex가 없어졌음을 물리적으로도 반영해야한다 

 

메쉬 분리 & PBD에 반영

어떤 것이 먼저 진행되어야할까

 

cloth simulation 물리 시뮬레이션에 제거된 vertex 반영

역시 remove 된 정점들이 시뮬레이션에 반영이 되는 절차가 먼저이다

 

scene의 init 함수 부분인데, cloth class 안에 currentMesh를 넣고 나서 

 

function physicsSimulation(){
  gravity[2] = Math.cos(Date.now() / 2000) * 15.5
  cloth.preIntegration(sdt)
  for (let i = 0; i < steps; i++) {
    cloth.preSolve(sdt, gravity)
    cloth.solve(sdt)
    cloth.postSolve(sdt)
  }

  cloth.updateVertexNormals()

  // apply vertex position
  currentMesh.geometry.setAttribute('position', new BufferAttribute(new Float32Array(cloth.positions), 3))
  currentMesh.geometry.setAttribute('normal', new BufferAttribute(new Float32Array(cloth.normals), 3))

}

cloth로부터는 current mesh에 geometry 정보들을 일방적으로 받아서 담기만 한다

 

즉 cloth class로 제거된 정점에 대한 mesh 정보를 전달하지 않는다는 것이다 

 

현재로써는 face만 제거하고 있지 실제로 vertex를 제거하지는 않긴 한데, 

일단 cloth에 정점이 제거된 mesh정보를 적용해보고 vertex 정보에서까지 제거해야하는지 알아보자. 

 

cloth = new Cloth(currentMesh, thickness, false)

cloth 객체에 mesh가 들어가는 시점이다 

 

거슬러 올라가면 mesh는 cloth객체의 부모 physics object class의 생성자에서 초기화된다 

constraint factory에 index가 들어가고, 결국 시뮬레이션에 vertex가 적용되려면 

constraint를 update해야한다 

 

흠.. 근데 생각해보면 자르는 것이랑 시뮬레이션이랑 굳이 섞을 필요가 있나 싶기도 하다.

어차피 mesh를 분리해서 각각 cloth 객체에 만들어서 넣으면 될텐데.

 

그렇다. 시뮬레이션 start를 따로 분리해서, 자르고 분리된 객체를 나누어서 cloth 객체에 담고 초기화하면 되는 것이다

그럼 해야할 일은 시뮬레이션을 space bar를 누르면 초기화되고 실행되도록 분리하는 것

cloth simulation trigger 분리하기

scene.ts
function simulationStart(){
  cloth = new Cloth(currentMesh, thickness, false)

  cloth.registerDistanceConstraint(0.0)
  cloth.registerPerformantBendingConstraint(1.0)
  cloth.registerSelfCollision()
  // cloth.registerIsometricBendingConstraint(10.0)

  // set floor height
  cloth.setFloorHeight(floorHeight)
}
async function init() {
  // ===== Managers =====
  initInputEvents(simulationStart)
  
 ...
input-manager.ts
export function initInputEvents(_eventSpaceBar: Function){
  eventSpaceBar(_eventSpaceBar)
  eventL()
}

function eventSpaceBar(_eventSpaceBar: Function){
  document.addEventListener("keydown", function(event){
    if(event.key == ' ') {
      stopState()
      _eventSpaceBar()
    }
  },false)
}

이렇게 스페이스바를 눌렀을 시에 시뮬레이션이 되도록 분리하였다

 

그러면 스페이스바 이벤트를 눌렀을 때 cloth 객체가 새로히 초기화되도록 해놓았는데

테스트 해보자

 

vertex를 지우고 시뮬레이션을 하면 physics object에서 constraint를 만들면서 에러가 발생한다 

mesh를 이루는 정보가 온전하지 않아서 생기는 문제 같다 

 

잘 자르면 이렇게 분리되는 경우도 있다

보면 밑부분을 자르는 경우에는 잘 잘리는 것 같기도한데.

 

이렇게 오른쪽 끝이 edge하나 정도만 잘려있으면 오류가 생기다가 

 

이렇게 끝 vertex 하나를 더 지우면 시뮬레이션이 되고, 두 오브젝트가 분리된다

(실제로는 하나의 mesh이다)

 

뭐 때문에 생기는 문제인지 알아보자

vertex 제거 후 특정 상황에서 simulation issue 

경험상 vertex를 제거했을때
edge가 한 칸 차이인 경우 발생하는 것 같다
  private findTriNeighbors(): Float32Array {
    const edges = [];
    const numTris = this.indices.length / 3;

    for (let i = 0; i < numTris; i++) {
      for (let j = 0; j < 3; j++) {
        const id0 = this.indices[3 * i + j];
        const id1 = this.indices[3 * i + ((j + 1) % 3)];
        edges.push({
          id0: Math.min(id0, id1), // particle 1
          id1: Math.max(id0, id1), // particle 2
          edgeNr: 3 * i + j, // global edge number
        });
      }
    }
    // sort so common edges are next to each other
    edges.sort((a, b) =>
      a.id0 < b.id0 || (a.id0 == b.id0 && a.id1 < b.id1) ? -1 : 1
    );

    // find matching edges
    const neighbors = new Float32Array(3 * numTris);
    neighbors.fill(-1); // -1 means open edge, as in no neighbors

    let i = 0;
    while (i < edges.length) {
      const e0 = edges[i];
      const e1 = edges[i + 1];

      // If the particles share the same edge, update the neighbors list
      // with their neighbors corresponding global edge number
      if (e0.id0 === e1.id0 && e0.id1 === e1.id1) {
        neighbors[e0.edgeNr] = e1.edgeNr;
        neighbors[e1.edgeNr] = e0.edgeNr;
      }
      i += 2;
    }

    return neighbors;
  }

문제가 발생한 것은 이 함수에서인데

 

id0와 id1은 삼각형의 각각의 edge의 양쪽 점을 나타낸다

각각의 edge마다 global 번호를 매기고 같은 global edge를 공유하면 neighbor라고 보는 함수인데

 

이때 두 edge의 양쪽 끝 vertex가 둘 다 일치해서 같은 edge를 의미하는지 확인하는 구간에서 에러가 난 것이다 

 

에러나는 곳을 확인해보면 e0는 있는데 e1는 undefined이다

e1은 제거된 모양이다 

 

이슈 원인 발견 : 홀수 개의 edge 

어?

i값은 9272

edges 배열에서 마지막 인덱스가 9272이다

...그럼 당연히 i+1 번째는 없다

 

문제는 삭제된 후 edge가 홀수개여서 에러가 발생한 것이다

 

edge는 언제든지 홀수개가 될 수 있다

 

에러가 발생하는 다른 경우도 역시나 edge가 홀수개라서 생긴 문제였다

 

가장자리부분을 삭제하다 보면 edge가 홀수개가 되는 경우에 문제가 발생했다

처리해놓자 

    let i = 0;
    while (i < edges.length) {
      const e0 = edges[i];
      const e1 = edges[i + 1];

      // catch exception: if edges length is odd
      if(edges.length <= i+1) {
        i+=2
        continue
      }
      
      // If the particles share the same edge, update the neighbors list
      // with their neighbors corresponding global edge number
      if (e0.id0 === e1.id0 && e0.id1 === e1.id1) {
        neighbors[e0.edgeNr] = e1.edgeNr;
        neighbors[e1.edgeNr] = e0.edgeNr;
      }
      i += 2;
    }

이렇게 edge의 길이가 홀수개인 경우

i+1이 마지막 인덱스보다 크면 넘기도록 처리해놓아서 잘 해결되었다 

 

이제 에러없이 잘 잘린다 

 

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