TransformControl 기능 넣기
이제 프로젝트에 transform control을 넣어서 분리된 mesh를 각각 이동시킬 수 있도록 해볼 계획이다.
일단 모드를 하나 추가하자.
export type Mode = "NONE" | "RAYCAST" | "REMOVE_VERTEX" | "REMOVE_EDGE" | "TRANSFORM"
transform이라는 모드를 선택할 수 있게 한다.
transform 모드에서는 현재 scene 안에있는 child mesh들을 클릭하는 경우, transform contols 객체에 attach 되도록 설정하면 쉽게 구현가능하지 않을까?
문제는 transform controls 객체는 three addons에 들어있다는 점이다.
three.js addons 사용하기
three.js 모듈을 받았다면 사실 addon은 이미 들어있다.
three/examples/jsm 파일이 바로 그 위치이다.
import { TransformControls } from 'three/examples/jsm/controls/TransformControls'
따라서 해당 위치의 controls/TransformControls을 경로로 잡아주면 된다.
이제 mouse input 이벤트를 활용하여 클릭시 오브젝트를 transform controler에 attach해서 테스트해보자.
transform controls
scene에 transform controls를 넣음으로써
이렇게 transform controls의 gizmo를 띄우는데 성공했다.
하지만 gizmo가 올바른 위치에 생성되는 것도, gizmo를 통해 object의 transform을 변경하는 것도 제대로 동작하지 않는 상태이다.
여기서 문제는 raycast에 transform gizmo object가 잡힌다는 것이다.
transform controls 자체적으로는 gizmo에 대한 것만 정의되어있고, 기능자체가 내재되어있는 시스템은 아닌가 싶다.
editor에서 코드를 보았을 때,
object transformation의 경우 translate, rotate, scale이 직접 구현되어있었다.
https://threejs.org/examples/#misc_controls_transform
그래서 위 기본 예제의 코드를 좀 읽어보고, raycast가 transform controls object를 attach하는 현상을 어떻게 적용하면 좋을지 고민해보자.
transform controls 예제 코드를 보면 control 자체적으로 transformation에 대한 기능이 내재되어있는 것으로 판명되었다.
딱히 translation이나 rotation 등에 대한 기능적인 구현 부분이 없었다.
그럼 raycast 이벤트에서 attach되는 object만 잘 처리하면 mesh를 transformation하는 데에는 큰 문제가 없을 것으로 예상된다.
editor에서는 transform controls의 gizmo object를 누른 건지 scene 상의 움직일 수 있는 object를 클릭한 건지 어떻게 구분하고 있을까.
editor 코드의 selector 스크립트를 보면, object를 intersect하여 찾았을 때 traverse 등으로 성질을 확인한다.
즉 역으로 생각하면, 내 코드의 경우 클릭한 object의 parent, ancestor 중에 Transform Controler가 있으면 선택하지 않도록 하면 되는 것이다.
또는 control 할 수 없는 object로 새로운 key를 설정하는 방법도 있는데.
일단 전자의 방법으로 해보자.
transform controls intersect 하지 않기
export function initTransformControls(transformControls: TransformControls, scene: Scene, camera: Camera){
window.addEventListener('mousedown', ()=>{
switch(mode.curMode){
case "TRANSFORM":
const object = getIntersectObject(scene, camera)
// search parent recursively is transform controls
let isTransformControls = false
let parent = object?.parent
while(parent){
if(parent.name == "TransformControls") isTransformControls = true
parent = parent.parent
}
if (object && isTransformControls === false) {
transformControls.attach(object);
console.log(object);
}
break;
}
}, false)
}
이렇게 parent 중에 transform controls인걸 체크해서 제외시키고 나니
이렇게 transform controls를 사용할 수 있게 되었는데, 상당히 제한적이다.
애초에 transform controls를 거르는 작업을 위 함수가 아니라. raycast의 intersects에서 실행해야할 것 같다.
또한 transformation의 문제는 위치를 이동시켜도 geometry의 절대적인 position 계산에는 반영되지 못해서
mesh를 옮겨서 충돌시키는 방법이 통하지 않을 수 있다.
즉 object translation 이후 translation matrix를 geometry에 적용해서 옮긴 위치를 geometry에 반영할 수 있도록 해야한다.
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
// search parent recursively is transform controls
let isTransformControls = false
let parent = intersect.object?.parent
while(parent){
if(parent.name == "TransformControls") isTransformControls = true
parent = parent.parent
}
if(isTransformControls) continue
return intersect.object as Mesh
}
}
return null
}
먼저 intersect object에서 transform controls를 선택하지 않도록 제외시킨다.
transformation geometry에 적용
그리고 transform controls를 이용해서 변환한 값을 geometry에 적용해보자.
위의 사진 처럼 옆으로 돌렸을 때 중력도 옆으로 적용되는 상태에서 변환된 행렬이 geometry에 적용되어서 다시 밑으로 중력이 작용하게 해야한다.
해당 부분은 cloth 객체의 physics object를 다시 읽어보며 어떻게 처리하면 좋을지 고민해보자.
아무래도 simulation start를 할 때, update mesh 과정에서 transformation을 반영하는게 어떨까.
mesh update에서 geometry 정보를 대입하기 전에 처리해야할 것 같다.
주의할 점
world matrix를 geometry에 적용하고 나면, 같은 행렬을 mesh에서 역행렬 연산해주어서 상쇄시켜야한다.
그렇지 않으면 geometry에도 적용되고 mesh에도 적용되어 두 번 적용되는 것이다.
흠... 뭔가 방법을 잘못 접근하고 있는 것 같다.
내가 원하는 방법은 transform 변환을 controler를 통해 하였을 때, geometry의 절대적인 값 위치에 행렬 연산을 적용하여 시뮬레이션을 시작하기 전에 geometry position의 위치를 변경하는 것이었다.
matrix 초기화 reset decompose
즉 matrix를 geometry에 적용하고 본래 mesh는 identity matrix로 만들면 될 줄 았았는데, 초기화가 제대로 안된다.
https://discourse.threejs.org/t/how-to-reset-matrix/36939
찾아보다가 이런 방법을 추천해주더라
mesh.matrix.identity().decompose(mesh.position, mesh.quaternion, mesh.scale)
matrix를 reset 하는 방법으로 decompose를 이용하는 방법이 있다고 하는데
identity matrix를 각 position, quaternion, scale에 적용시켜서 모두 초기화 방법인가보다.
그대로 적용해보니까
이게 웬걸? 너무 잘먹힌다.
방법은 조금 다르긴 하다. 내 경우 역행렬을 적용하려고 했지만
이건 완전히 단위행렬로 초기화시켜버리는 것이기 때문에 mesh가 자동으로 0,0,0으로 transform되는 걸 볼 수 있다.
하지만 프로젝트 내 기능상 simulation하기 전에 transform control을 하고,
simulation을 실행한 후에는 transformation을 적용하지 않는 것이 목적이므로
이 방법대로 적용해도 좋을 것 같다.
transform controls mesh 제거 버그
transform controls를 추가한 것 때문에 mesh remove에 많은 버그가 생겼다.
분명 intersect로 transform controls는 탐색하지 않도록 해놓았는데,
이렇게 cloth mesh를 찍으면 엉뚱한 곳이 삭제된다.
예상하는 바는 remove vertex 등에서 사용되는 intersect vertex에
intersect.point가 transform controls를 감지하는 문제로 예상된다.
function getIntersectVertex(scene: Scene, camera: Camera): number[]{
raycaster.setFromCamera(mouse, camera)
const intersects = raycaster.intersectObjects(scene.children)
let closestVertexIndices
if (intersects.length > 0) {
for(let i = 0; i < intersects.length; i++){
const intersect = intersects[i]
if(intersect.object === gizmoLine) continue
// search parent recursively is transform controls
let isTransformControls = false
let parent = intersect.object?.parent
while(parent){
if(parent.name == "TransformControls") isTransformControls = true
parent = parent.parent
}
if(isTransformControls) continue
closestVertexIndices = findClosestVertexIndex(intersect.point, intersect.object as Mesh)
break
}
}
return closestVertexIndices!
}
이 경우에도 똑같이 transform controls를 무시하는 방향으로 구현하자.
수정 후
remove vertex를 통해 cloth를 두 개로 나눈 후,
각각을 transform controls로 조종하는게 가능해졌다!
현재 mesh 분리로 인해 걱정되는 문제는
분리된 mesh 사이에는 collision이 적용되지 않는다는 점이다.
이건 어떻게 해결해야할까 추후 사람 모델을 하나 넣고 cloth fitting을 시킬 예정인데 말이다.
조금 고민해보면.. mesh 단일화를 시키는 방법이 있긴 하다.
merge buffer geometries
여러 방법 중 geometry buffer를 merge하는 방법도 있는데
BufferGeometryUtils.mergeBufferGeometries
이 기능을 사용하였을 때 geometry의 vertex들의 값이 합쳐진다면,
그대로 하나의 mesh를 시뮬레이션 돌리면 self collision이 가능할 수도 있다.
문제는 merge 하는 것은 cloth mesh만 가능하다.
사람 아바타를 추가하면 사람은 정적인 mesh이면서 simulation 되는 옷들과 충돌해야한다.
흠...