다음 구현 목적
천을 자를 때 primitive 차원에서 다양한 방법이 있다
vertex를 없애면서 관련된 face들도 다 없애는 방법
edge만 없애고 vertex를 분리하는 방법
자를 때 테셀레이션을 사용해서 submesh들을 생성하면서 자연스럽게 만드는 방법
최종적으로는 테셀레이션을 사용해야할 것 같은데
먼저 edge 방법으로 천을 자르고 mesh를 분리하는 방법을 구현해보자
음.. 그 전에 remove mode에서 vertex를 삭제하는 테스트를 빨리 좀 해보자
remove vertex
function getIntersectObject(scene: Scene, camera: Camera): Mesh | null{
raycaster.setFromCamera(mouse, camera)
const intersects = raycaster.intersectObjects(scene.children)
if (intersects.length > 0) {
for(let i = 0; i < intersects.length; i++){
const intersect = intersects[i]
if(intersect.object === gizmoLine) continue
return intersect.object as Mesh
}
}
return null
}
raycast로 오브젝트 mesh를 가져오고
// remove clicked vertex
const clickMesh: Mesh = getIntersectObject(scene, camera)!
removeVertex(clickMesh, getIntersectVertex(scene, camera))
이 mesh에서 클릭한 vertex를 삭제하는 함수를 실행한다
removeVertex의 로직을 짜보자
참고로 geometry attribute의 position등 array는 JS의 array와는 달리 TypedArray라는 타입이다
이를 생각하면 평범하게 array의 함수는 사용하지 못해서 array에서 vertex를 삭제할때 주의해야한다
export function removeVertex(mesh: THREE.Mesh, indexArray: number[]){
const meshAttribute = mesh.geometry.attributes
for(let i=0; i<indexArray.length; i++){
const posArr = meshAttribute.position.array
const newArray = new Float32Array(posArr.length - 3)
console.log(posArr )
// subarray front of vertex x's index
newArray.set(posArr.subarray(0, indexArray[i]))
// subarray back of vertex z's index
newArray.set(posArr.subarray((indexArray[i] + 2) + 1), indexArray[i])
console.log(newArray)
}
}
정말 단순히 vertex만 지우는 코드이다
이렇게 만든 new array를 position, uv, normal에 넣고 mesh를 수정해보자
for(let i=0; i<indexArray.length; i++){
const posArr = meshAttribute.position.array
const normArr = meshAttribute.normal.array
const uvArr = meshAttribute.uv.array
const newPosArray = new Float32Array(posArr.length - 3)
const newNormArray = new Float32Array(normArr.length - 3)
const newUvArray = new Float32Array(uvArr.length - 2)
// subarray front of vertex x's index
newPosArray.set(posArr.subarray(0, indexArray[i]))
// subarray back of vertex z's index
newPosArray.set(posArr.subarray((indexArray[i] + 2) + 1), indexArray[i])
newNormArray.set(normArr.subarray(0, indexArray[i]))
newNormArray.set(normArr.subarray((indexArray[i] + 2) + 1), indexArray[i])
newUvArray.set(uvArr.subarray(0, indexArray[i]))
newUvArray.set(uvArr.subarray((indexArray[i] + 1) + 1), indexArray[i])
mesh.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(newPosArray), 3))
mesh.geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(newNormArray), 3))
mesh.geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(newUvArray), 2))
}
position, normal, uv에 모두 반영하려고 했는데
uv 길이가 0이다?
v 1.000000 0.000000 -1.000000
v 1.000000 0.000000 1.000000
v -1.000000 0.000000 -1.000000
v -1.000000 0.000000 1.000000
vn -0.0000 -1.0000 -0.0000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.375000 0.750000
vt 0.125000 0.750000
s 0
f 1//1 2//1 3//1
f 4//1 3//1 2//1
생각해보니 obj 파일 구조에서 vt는 있긴한데
아 face에서 vt 값을 모두 비워놔서 없구나
uv가 없는 경우를 생각하지 못했다..
일반적으로는 uv값은 모두 있으니까 obj 파일에 아무 uv 값이나 넣어서 수정하자
vertex 를 제거하는 것은 되지만, 제거 후에 mesh가 꼬여버리는 현상이 생긴다
이제 array에서 지우는 방식으로 하기 때문에 꼬일 가능성은 많았다
vertex마다 객체로 linked되어있는 형식이었으면 삭제가 편했겠지만 array에 때려박는 형식은 어렵다
vertex를 삭제하고 왜 저렇게 되는지 보자
...생각해보니 index는 수정 안했다.
원래 index가 position array에 암묵적으로 포함되지 않고 index를 따로 설정해주면 index에 따라 face를 만들어주는데
geometry attribute에 index도 넣었어야했다
const meshIndex = mesh.geometry.index
index도 vertex 삭제에 따라 잘 수정해주면 될 것 같다
만들고 나면 천으로 테스트해보자
remove verex index array 수정하기
index를 뽑아보면 결국 face의 내용이 그대로 담겨있는 것이다
근데 문제는 vertex를 지우면, face를 이루는 vertex가 없어지므로 face는 무용지물이 되고
face 하나를 다 없애야한다
index를 지우지 않고 position만 지우니까, vertex 개수는 하나 줄었는데
즉 4개 정점에서 지우고 3개가 되었지만, index는 그대로 0~3번까지 가리키고
position의 vertex는 하나가 없어졌으니 밀려서 마지막 인덱스인 3번이 비게 된다
그럼 사진처럼 3번은 (0,0,0)을 가리키게 되는 것 같다
이런 식으로 array로 처리하니까 삭제하면 밀려서 생기는 문제가 발생하는데
이걸 index단에서 잘 처리해주어야 한다
그래서 자료구조를 만들지 않고 array로 하면 처리하기 힘든데...
아니면 자료구조를 만들고 mesh를 만들 때만 array로 바꿔주는 로직도 생각할 법하다
일단 index 고쳐보자
index 즉 face가 기준이 되어야하므로 index와 vertex를 mapping해서 처리해보자
for (let i = 0; i < idxArr.length; i++) {
if (idxArr[i] !== index) { // || idxArr[i] !== index+1 || idxArr[i] !== index+2) {
newIndices.push(vertexMap.get(idxArr[i]))
}
}
vertex를 mapping한 데이터에 vertex 삭제 이후 index가 어떻게 변경되어야하는지 정보가 담겨있다
이걸 참고로 원래의 index가 담긴 idxArr에서 삭제한 vertex index에 해당하는 부분은 넘어가고
나머지 index는 새로운 new indices로 만든다
디버깅을 안하고 코드만 보면 정말 헷갈린다
정점이 4개인 plane에서 하나가 사라지면 face는 두 개에서 하나만 남아야하니까
vertex에 mapping된 index의 길이는 6개에서 face 하나만큼 3개가 줄은 3개만 남아야한다
하지만 0번 vertex를 지웠을 때 index가 5개라서 애매해진다
이렇게되면 지금은 face가 하나만 남아서 괜찮지만
face에 대한 index들이 더 있는 상황이라면 3개씩 짝지어진 상태에서
5개가 되어버리므로 face가 vertex 한 칸 씩 모두 밀려서 모델이 망가진다
이렇게 하나만 지워도 모든 모델이 망가졌다
그럼 어떻게 하냐, 위 그림처럼 0번 vertex를 제거하면
0번이 포함된 모든 face를 vertex 3개 한쌍으로 모두 지우고
지운 후 나머지 index들을 0번이 제거되었으므로 0번 이후 인덱스를 씩 앞당긴다
근데 생각해보면 굳이 인덱스를 앞당겨야되나?
그냥 0번 vertex가 없는 상태로 놔두어도 괜찮지 않을까 싶기도 하다
그러면 정리해서 특정 index의 vertex를 제거하면
인덱스가 앞당겨지지않고 해당 정점이 포함된 face만 제거하는 방향으로 다시 짜보자
vertex mapping을 하지 말자
position list는 변경하지 않고, index만 변경하도록 짜본다
geometry index 수정하기 오류
index를 변경하는데 setIndex함수를 사용하면서 자꾸 에러가 나길래 봤는데
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 Float32Array(newIndices), 1))
index의 경우 다른 attribute와는 달리 들어가야할 값이 int이기 때문에
buffer에 들어가는 array가 float이 아니라서 그런것 같다
Uint32Array로 typed array를 바꾸니까 index 수정이 잘 되고
index만 수정해도 예상대로 vertex가 잘 삭제된다
굳이 attribute를 변경하지 않고 face를 지워서 index를 수정하는 방식으로 remove 기능을 구현하는 것이 안정적일 것 같다
vertex remove 구현 완료
이렇게 구현하니 그림처럼 정점을 삭제했을 때
한쪽에만 포함되면 한쪽 삼각형만 제거되고
둘 다 포함되면 모두 사라진다
다른 모델에도 적용해보자
제거한 vertex부분만 구멍나게 동작하는 걸 보면
index가 밀리지 않고 잘 제거되었다
다만, 한 번 클릭하면 육각형 모양이 통째로 사라지는 건 뭘까
딱 정 중앙에 있는 vertex를 제거하니까 연결된 주변 6개의 삼각형이 사라진 것이다
wireframe으로 보면 잘 이해된다
정점을 지운다면 이렇게 될 것을 예상하고는 있었기에 일단 성공이다