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

Threejs Cloth Tailor 개발일지 - Typescript 함수 오버로딩, raycasting vertex debugger gui, vertex 중복과 face

728x90
반응형

 

raycast 로 찾은 vertex gui 표시하기

이전에 transformation을 고려하지 않고 vertex를 찾다가 고생좀 했다

geometry에는 transformation이 적용되지 않는다는 것을 고려하고 개발하는 것은 정말 중요한 점이다 

 

이래서 geometry vertex를 직접 건드리는 것은 컴퓨터그래픽스를 알지 못하면 불가능한 작업이다 

이제는 찾은 vertex를 gui에 띄우자 

Typescript 함수 오버로딩

이런 식으로 다른 인자를 갖는 같은 기능으로 오버로딩하고 싶었는데

찾아보니 TS에서 오버로딩은 지원하지 않는 것 같다

 

대신 인자의 형태를 여러 개 허용해서 함수 안에서 if문으로 분리하는 것.

불편하니 그냥 함수를 분리해야겠다 

 

raycast 로 찾은 vertex gui 표시하기

export function updatePositionGuiWithVector3(pos: Vector3){
  // + for type change string to number
  target.position.x = +pos.x.toFixed(4)
  target.position.y = +pos.y.toFixed(4)
  target.position.z = +pos.z.toFixed(4)

  gui.controllers.forEach(ctrl => {
    ctrl.updateDisplay()
  });
}

단순히 gui에 넣는 함수를 만들어서 구현하고

 

  if(mode.curMode === "NONE") gui.updatePositionGuiWithMesh(currentMesh)

기존에는 update마다 position을 gui에 반영해주었는데 raycast는 명확하게 인터렉션 타이밍이 있어서 이를 분리해야했다

 

vertex 제거하기

raycast로 찾은 vertex를 제거하는 기능을 구현할 것이다

 

일단 마우스를 가져다 댄 가장 가까운 vertex에 line을 띄우고,

마우스 클릭을 하면 해당 vertex가 geometry에서 사라지도록 해보자 

 

export function findClosestVertex(intersectPoint: THREE.Vector3, mesh: THREE.Mesh): THREE.Vector3{
  let closestVertex: THREE.Vector3 | null = null
  let closestDistance = Infinity

  const positionAttribute = mesh.geometry.attributes.position
  const vertex = new THREE.Vector3()
  const worldVertex = new THREE.Vector3();
  const worldMatrix = mesh.matrixWorld;
  
  for (let i = 0; i < positionAttribute.count; i++) {
      vertex.fromBufferAttribute(positionAttribute, i)

      // Transform vertex position to world space
      worldVertex.copy(vertex).applyMatrix4(worldMatrix);

      const distance = intersectPoint.distanceToSquared(worldVertex)
      if (distance < closestDistance) {
        closestDistance = distance
        closestVertex = worldVertex.clone()
      }
  }

  return closestVertex!
}

일단 vertex find의 기능을 보완해야하는데 

기존의 findClosestVertex의 기능은 clone된 vertex를 가져오는 것이었다

 

이제는 해당 vertex가 geometry의 position array에서 몇 번째 (xyz 총 3개) 인덱스인지를 반환하는 함수를 만들어야한다 

 

export function findClosestVertexIndex(intersectPoint: THREE.Vector3, mesh: THREE.Mesh): number{
  let closestVertexIndex: number
  let closestDistance = Infinity

  const positionAttribute = mesh.geometry.attributes.position
  const vertex = new THREE.Vector3()
  const worldVertex = new THREE.Vector3()
  const worldMatrix = mesh.matrixWorld
  
  for (let i = 0; i < positionAttribute.count; i++) {
      vertex.fromBufferAttribute(positionAttribute, i)

      worldVertex.copy(vertex).applyMatrix4(worldMatrix)

      const distance = intersectPoint.distanceToSquared(worldVertex)
      if (distance < closestDistance) {
        closestDistance = distance
        closestVertexIndex = i
      }
  }

  return closestVertexIndex!
}

이렇게 vertex의 인덱스를 반환하는 함수를 구현해서 테스트 해보자

 

테스트 하기 위한 plane 모델은 vertex 자체는 4개밖에 없지만, 

parsing해서 폴리곤으로 만드는 과정에서 분리된 버텍스가 있다

 

triangle 하나당 3개가 있는데, 3번과 4번 vertex가 같은 vertex에서 서로 다른 polygon으로 사용되기 위해 분리되었다

이게 parsing 코드를 다시 봐야 왜 이렇게 분리되었는지 알 수 있다

vertex 중복되는 이유 

loader 스크립트를 보고 로그를 찍어보면서 확인해봤다

 

왼쪽이 face와 face의 인덱스를 각각 찍은 로그이고

오른쪽은 obj 파일의 face 정보이다

 

이거랑 비교해보면 face 개수가 달라졌다 

face의 중복을 제거했기 때문인데

 

v/vt/vn

어차피 중간값의 경우 현재 texture를 사용하지 않으므로 딱히 의미없다

 

이렇게 vt를 삭제해서 간추려도 normal만 잘 유지되면 잘 나온다 

여기서는 딱히 텍스처를 입힐 계획이 없으므로 이렇게 유지 하자 

 

중복된 vertex가 나온 이유는 결국 의미없는 vt값이 다른 face가 있었기 때문에 

parsing과정에서 face에 따라서 vertex가 분리된 것이다 

 

이게 왜 그런지 확인해보자 

 

const cache: Record<string, number> = {}

...

cache[faceString] = i
finalIndices.push(i)

// Need to convert strings to integers, and subtract by 1 to get to zero index.
const [vI, uvI, nI] = faceString
  .split("/")
  .map((s: string) => Number(s) - 1)

vI > -1 && finalPosition.push(...cachedVertices[vI])
uvI > -1 && finalUvs.push(...cachedUvs[uvI])
nI > -1 && finalNormals.push(...cachedNormals[nI])

코드를 보면 cache안에서 face값에 해당하는 인덱스를 찾아 넣는 구조이다

cache에는 face가 key값으로 들어가기 때문에 vertex가 같아도 face값이 다르면 중복해서 position에 들어갈 수 밖에 없는 구조이다

 

uv와 normal을 고려한다면 당연한 과정이지만,

현재는 오히려 중복 vertex를 만들어내서 시뮬레이션이나 geometry를 조작하는데 상당히 난잡하다.

 

parsing은 model을 올바르게 읽고 처리하므로 

결국 obj 파일의 face 구조를 잘 파악하고 대비하자 

 

vertex 제거하기

그러면 만약에 face값이 달라서 vertex가 중복되면 제거할 때 어떻게 할까

즉 여러 개의 점을 제거해야하기 때문에 

 

가장 가까운 점 한 개만을 잡을 수 없고

중복되는 값을 가진 vertex index 여러 개가 반환되어야한다 

 

  for (let i = 0; i < positionAttribute.count; i++) {
      vertex.fromBufferAttribute(positionAttribute, i)

      worldVertex.copy(vertex).applyMatrix4(worldMatrix)

      const distance = intersectPoint.distanceToSquared(worldVertex)
      if (distance < closestDistance) {
        closestDistance = distance
        closestVertexIndex = i
      }
  }

그러면 여기 distance가 이전까지 가장 가까웠던 거리와 비교하는 곳을 수정해서

만약 거리가 같은 경우 vertex index를 array에 추가할 수 있도록 해야한다 

 

export function findClosestVertexIndex(intersectPoint: THREE.Vector3, mesh: THREE.Mesh): number[]{
  let closestVertexIndex: number
  let closestVertexIndexArray: number[] = []
  let closestDistance = Infinity

  const positionAttribute = mesh.geometry.attributes.position
  const vertex = new THREE.Vector3()
  const worldVertex = new THREE.Vector3()
  const worldMatrix = mesh.matrixWorld
  
  for (let i = 0; i < positionAttribute.count; i++) {
      vertex.fromBufferAttribute(positionAttribute, i)

      worldVertex.copy(vertex).applyMatrix4(worldMatrix)

      const distance = intersectPoint.distanceToSquared(worldVertex)
      if(distance === closestDistance){
        closestVertexIndexArray.push(i)
      }
      else if (distance < closestDistance) {
        closestDistance = distance
        closestVertexIndex = i
        closestVertexIndexArray = [i]
      }
  }

  return closestVertexIndexArray!
}

같은 경우에는 추가해주고, 더 가까운 값이 나오면 바꾸면서 배열을 초기화한다 

 

물론 지금은 vertex가 중복되지 않는 모델을 이용할 것이지만 추후 어떤 모델이 들어올 지 모른다 

 

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