vertex connect 방법
cloth를 접합하기 위해서 mesh를 붙이는 방법으로는 위와 같은 방법을 채택할 것이다.
먼저 빨간 구역이 첫 번째 클릭하는 vertex가 포함된 곳, 파란 구역이 두 번째 클릭하는 vertex가 포함된 face이다.
첫번째 vertex는 건드리지 않고,
두 번째 vertex가 포함되어있던 face들의 vertex index를 첫 번째 vertex에 연결시킨다.
즉, 두 번째 vertex index가 포함된 face에 첫 번째 vertex index를 대체해서 넣기만 하면 구현할 수 있을 것 같다.
mesh vertex connect (attach vertex)
export type Mode = "NONE" | "RAYCAST" | "REMOVE_VERTEX" | "REMOVE_EDGE" | "TRANSFORM" | "ATTACH_VERTEX"
새로운 모드 attach vertex를 추가하고 작업을 시작해보자.
일단 raycast처럼 vertex에 마우스를 호버하였을 때, 어떤 vertex를 선택할지를 보여주는 gizmo point가 있어야한다.
이제보니 RAYCAST 모드에 transform controls를 detect 하지 않게 하는걸 빼먹었다.
고쳐주고,
첫 번째 vertex를 클릭하고, 두 번째 vertex를 클릭하는 이벤트를 만들어주었다.
이제 vertex 두 개를 눌렀을 때 line으로 연결하는 시각적 표시를 먼저 해주자.
import * as THREE from "three"
let vertexIndex1
let vertexIndex2
let attachLineList: THREE.Mesh[] = []
export function setVertexIndex1(number: number){
vertexIndex1 = number
}
export function setVertexIndex2(number: number){
vertexIndex2 = number
}
export function attachVertex(){
}
일단 mesh-attacher.ts 스크립트를 만들었다.
여기에 attachVertex라는 함수를 통해서 두 vertex를 하나로 합치는 역할을 수행할 것이다.
attach vertex 함수로 들어오는 index는.. 아니지 vertex index만 들어오면 안되고
첫 번째 vertex의 mesh와 index, 두 번째 vertex의 mesh와 index를 각각 받아야할 것 같다.
attach를 하는 과정에서 같은 mesh에서 이루어질 수도, 혹은 다른 mesh에서 이루어질 수도 있기 때문이다.
한 번 로직을 구현해보자.
merge buffer geometries
... geometry merge를 하는 기능이 three.js에 addon으로 mergeBufferGeometries라고 있다.
근데 merge되면 vertex index는 어떻게 변하지? 이 규칙을 알면 merge 하면서 vertex attach를 할 수 있을 것 같은데 말이다.
https://threejs.org/docs/#examples/en/utils/BufferGeometryUtils.mergeBufferGeometries
아니면 다른 방법으로, merge를 한 후 각 vertex1, vertex2 position과 가장 가까운 vertex를 찾는 방법으로
새로 합쳐진 mesh 안에서 vertex1, vertex2에 해당하는 vertex를 다시 찾아내는 방법이 있다.
인덱스(index) 속성을 사용하는 지오메트리를 합칠 때는 geom2의 인덱스를 geom1의 인덱스 뒤에 추가합니다. 이 경우 geom2의 인덱스는 geom1의 인덱스 크기만큼 오프셋(offset)이 적용되어 추가됩니다.
일단 merge geometry의 설명은 단순히 뒤에 이어붙이는 거라고 나온다.
그럼 merge geometry 함수를 이용해서 시도해보자.
export function mergeGeometries(geometries: BufferGeometry[], useGroups?: boolean): BufferGeometry;
일단 merge geometries의 함수는 geometry들을 받는데, useGroups 라는 bool 형태의 parameter가 있다.
반드시 없어도 되는 인자이긴 한데, 여러 geometry에서 각각 사용하는 material들을 병합하면서도 적용할 수 있도록 하기 위한 것이라고 한다.
현재 material을 동일하게 사용할 계획이므로 false로 설정하자.
merge geometry에서의 index face list
생각해보면 geometry의 index에는 index에 기반한 face 정보들이 3개 한 쌍으로 짝지어 들어있는 배열이 있다.
즉 merge되었을 때 face list가 어떻게 변경될지 예측이 안간다는 점이 문제이다.
utility로 지원하는 기능인 만큼 face list도 index가 변경된 것을 반영하여 수정되는건가?
간단히 큐브를 확인해보면 좋을 것 같다.
그리고 또 생각해보아야하는 것이. 한 vertex 위치에 두 개 이상의 index가 존재하는 경우도 있다.
즉 같은 위치에 두 개 이상의 vertex가 있는 경우 대응되는 모든 vertex를 찾아 처리해야한다.
다시 말하면 한 정점을 변경하기 위해서 하나의 vertex index만을 고려하면 안된다는 것이다.
이 부분은 mesh edge cutter에서 다루었던 문제인데 다시 확인해보자.
...확인 중
이제보니 현재 사용하고 있는 cloth mesh는 그런 경우가 없었던 것 같다.
한 정점 위치에는 하나의 인덱스만이 자리하고 있는 모양이다.
vertex index 확인하기
export function updateIndexGui(numbers: number[]){
// + for type change string to number
target.pickVertexIndex = ""
numbers.forEach((number, index)=>{
target.pickVertexIndex += number
if(index !== numbers.length - 1){
target.pickVertexIndex += ", "
}
})
gui.controllers.forEach(ctrl => {
ctrl.updateDisplay()
});
}
구현해놓았던 RAYCAST mode를 통해서
어떤 위치에 vertex index가 무엇인지, 여러 개 있는지 하나 있는지를 알아보기로 하자.
위 코드를 통해서 index를 확인하는 gui를 추가하였다.
만약 여러 index가 같은 vertex position에 있으면 1,2,3 이런식으로 출력하게 하였는데 그렇게 나오지 않는 것으로 보아,
모든 위치에는 하나의 index만이 위치한다는 것을 알았다.
그럼 attach vertex를 하는 경우, 연결하는 두 vertex index에 대해서만 생각하면 된다는 뜻이다.
같은 face에 또 다른 index의 vertex는 없다는 말이니까.
mesh attacher
다시 mesh attach 로직을 생각해보자.
merge 된 후 vertex index2의 위치는 vertex index2에서 geometry 1의 index count만큼 밀린 값이라고 예상한다.
그렇다면, merge 된 geometry의 face 정보에서
merged 된 후의 vertex index 2가 포함된 face들을 찾아내고
그 face들의 vertex index를 찾아서 vertex index 1로 대체해주는 map 함수를 돌면 될 것 같다는 생각이 든다.
이러한 과정이 edge cutter와 유사한데, 이때는 어떻게 처리했었는지 좀 보자.
edge cutter의 경우 새로운 vertex가 생기고, 기존 face들에 새로 추가된 vertex index로 반영하는 시스템이었다.
mesh attacher의 경우 기존 vertex의 다른 face의 vertex를 대체하는 경우이다.
비슷하게 face를 대체하는 로직을 짜야할 것 같은데,...
mesh attach 로직 구상
geometry 두 개가 merge되고, merged vertex index2를 구한 다음. face list에서 vertex index 2에 해당하는 부분을 vertex index 1로 수정한다.
이런 단순한 로직이면 충분한가?
스케치를 해보니 vertex index1, vertex index2 대응하는 한 단계씩 face list만 수정하는 것으로도 충분할 것 같다.
그럼 여기서 생각해야하는 것은 위 사진에서 없어진 vertex index 9에 대해서 vertex position list에서 제거해야하는가이다.
근데 제거한다는 것은 전반적인 vertex index 정보가 바뀐다는 뜻이고 곧 로직이 복잡해진다.
빠른 구현을 위해서는 제거된 vertex index 9에 대해서는 vertex list에 남겨두는 편으로 생각한다.
// case 1: both mesh is same
if(mesh1 === mesh2){
// 같은 경우 merge 하지 않고 사용
geom1.index.array = geom1.index.array.map(index=>{
if(index === vertexIndex2)
return vertexIndex1!
else
return index
})
// vertex index 2가 모두 대체되었는지 확인
return !geom1.index.array.includes(vertexIndex2)
}
// case 2: difference mesh -> merge geometry
else{
const mergedGeom = utils.mergeGeometries([geom1, geom2], false);
// get index2 after merge
const geom1IndexCnt: number = geom1.index.count
const mergedVertexIndex2 = vertexIndex2 + geom1IndexCnt;
// index face list에서 merged vertex index 2에 해당하는 인덱스를 vertex index 1으로 대체
if(!mergedGeom.index)
{
console.error("merged geometry index is none.");
return false;
}
mergedGeom.index.array = mergedGeom.index.array.map(index=>{
if(index === mergedVertexIndex2)
return vertexIndex1!
else
return index
})
// mergedGeom에 merged vertex index 2가 모두 대체되었는지 확인
return !mergedGeom.index.array.includes(mergedVertexIndex2)
}
이러한 방식으로 일단 로직을 구현해보았다.
정상 작동했다고 로그는 뜨지만,
시뮬레이션을 하면 attach되지 않았다.
디버깅해보자.
mesh attach debugging
디버깅을 하다가 문제를 찾았는데,
우선 sphere geometry를 잡았다는 것은 point가 raycast로 인해서 intersect 되었다는 뜻이다.
transform controls를 제외하고 point나 line 등 gizmo관련된 것들은 모두 raycast intersect 목록에서 제외시켜야한다.
해당 raycast 스크립트에서 사용되는 gizmo object들에는 isGizmo라는 bool 변수를 넣고 처리해보자.
threejs를 typescript에서 사용하면 임의로 key를 추가하지 못하기 때문에,
userData를 이용하여 그 안에서 custom 데이터를 다루어야한다.
// check is gizmo
if(intersect.object.userData.isGizmo) continue
위 코드를 intersect 함수들에 넣어서 gizmo들을 detect 범위에서 무시해버리자.
흠... 그래도 잘 작동하지 않는다. index가 이상하다.
4x3 크기의 cloth object로 테스트를 해보아야겠다.
mesh를 분리한 후 vertex index1 = 7, vertex index2 = 4로 지정해서 attach를 시도해본다.
이상하게 분리하였는데도 mesh1와 mesh2가 동일했다.
index array mapping을 돌린 후 vertex index 2가 vertex index1으로 대체된 형태이다.
그럼 위 상태에서 파란색이 vertex index2인데, 파란색이 없어지고
파란색이 포함된 face는 빨간색 point와 연결되어 face를 이루어야하는게 의도했던 로직이다.
시뮬레이션을 실행하면, 파란점이 있던 부분은 face가 늘어진다.
이는 face의 index가 제거되어서 constraint가 없어서 늘어지는 것이기 때문에 vertex가 없어졌다고 볼 수 있다.
하지만 기존 빨간점과 연결되어 face를 만들었는지는 현재 구분이 안된다.
혹시 wireframe으로 보더라도 똑같이 보인다.
이에 대한 가능성은 uv 혹은 normal을 재연산해야하나 싶다.
index를 변경하였는데 수정된 face 렌더링이 반영이 안된다면...?