본문 바로가기
Web Development/ThreeJS

[Three.js / Javascript] OBJ 파일 로드 후 오브젝트 원점 이동 구현(feat. 오브젝트 사이즈 구하기, 부모/자식 요소 분리)

by 감자맹고우 2022. 6. 13.
728x90
반응형

Three.js를 프로젝트에 적용하는 이유로는 직접 메쉬를 만들어서 구현하려는 이유도 있겠지만,

Blender나 여러 가지 툴을 통해서 생성된 파일을 로드해 3D 모델로 보여주는 이유도 클 것이다.

(개인적으로는 후자의 예가 더 많을 것이라 생각한다)

 

그러나 파일을 로드해보면 파일에 따라 기준점이 제각각이다.

이런 경우에는 카메라의 위치나 모델의 위치 등에 의해 화면에 출력되는 시점도 제각각이 되는데,

여러 가지 기능을 구현하려다보면 이 문제가 항상 걸림돌이 된다.

 

그렇기 때문에 나는 이를 해결하기 위해, 오브젝트를 로드할 때 원점으로 이동시키는 기능을 구현하고자 했다.

하지만, 구글링을 통해 많은 정보를 찾아보아도 제대로 된 정보를 얻을 수가 없었다.

 

그렇게 며칠간 고생한 끝에 다행히도 stackoverflow에서 옛 버전에서 적용할 수 있는 관련 정보를 얻고 최신 버전에서 적용하는데 성공하여, 구현하는데 성공했기에 다른 분들은 나처럼 헤매지 않았으면 하는 마음에 이를 공유하고자 한다.

 

반응형

 

[ 구현 방법 ]

 

간결한 설명을 위해, scene이나 camera 등의 Three JS 기본 설정은 생략하고, 관련 코드만 작성하였다.

 

/** 3d-loader.js **/
import * as THREE from 'three';
import {OBJLoader} from 'threejs/examples/jsm/loaders/OBJLoader';

const OBJ_FILE = '~~~.obj'; //로드할 obj 파일

window.onload = function() {
    ~~ 기본 설정 생략 ~~
    
    const loader = new OBJLoader();
    
    loadObj(loader, scene, controls, camera).then(obj => {
        obj.rotation.reorder('YXZ');
    });
}

//오브젝트 load(load 후 처리를 위해 async await 사용)
const loadObj = async (loader, scene, controls, camera) => {
    return new Promise( (resolve, reject) => {
        loader.load(
            OBJ_FILE,
            function(obj) {
                //로드된 오브젝트 scene에 추가
                scene.add(obj);
                //오브젝트 원점 이동
                resetCenter(obj, controls, camera, scene);
                
                resolve(obj);
            },
            function(xhr) {
                console.log( (xhr.loaded / xhr.total * 100) + '% loaded');
            },
            function(error) {
                console.log('An error was happened : ' + error);
                reject(error);
            }
        );
    });
}

//오브젝트를 원점으로 이동
const resetCenter = (object, controls, camera, scene) => {
    //Object를 감싸는 Box 생성
    const box = new THREE.Box3().setFromObject(object);
    //생성된 Box 속성을 통해 Object Size 구하기
    const size = {x: box.max.x - box.min.x, y: box.max.y - box.min.y, z: box.max.z - box.min.z};
    
    //Repositioning
    object.position.x = -box.min.x - size.x / 2;
    object.position.y = -box.min.y - size.y / 2;
    object.position.z = -box.min.z - size.z / 2;
    
    //카메라를 Object의 황금비 위치로 이동할 변수(카메라 황금비 = 1.618)
    const sceneRadiusForCamera = Math.max(
        size.y,
        size.z,
        size.x
    ) / 2 * 1.618;
    
    //object 위치 변경을 control에도 반영
    controls.reset();
    
    //카메라 위치 설정(현재는 Z축에서 타겟을 바라봄)
    camera.position.z = sceneRadiusForCamera;
    camera.position.y = 0;
    camera.position.x = 0;
    
    //카메라가 바라보는 지점 변경
    camera.lookAt(scene.position);
}

 

 

이렇게 파일을 로드하고 원점으로 이동시키면, 오브젝트의 규칙성을 발견할 수 있다.

규칙이 있으면, 로드 시 보기 좋게 오브젝트를 회전을 시켜 놓거나 카메라 위치를 재설정해주는 등의 작업이 가능하다.

또한, 황금비를 구해 오브젝트 전체를 출력해주기도 한다.

 

그 외에도 THREE JS에서 제공하는 Box3 를 통해 사이즈를 측정할 수 있는 방식도 여러 가지 기능 구현에 유용하므로 많은 도움이 되었으면 좋겠다!

 

===================================================================

 

2022.06.14 내용 추가

부모 / 자식 요소 분리(detach & attach)

 

Transform Controls를 적용하려는 과정에서 기존 Object의 그룹을 사용함에 따라 기준점이 그대로 유지되어 회전 오작동  현상 발견

 

(원인)

 

위의 코드에 따라, children(Mesh 들)은 World에서 position이 Vector3(0,0,0) 이지만, Parent(Group)은 children을 원점으로 이동시키기 위해, children이 있던 좌표만큼 World에서 이동하였다.

또한, Transform Controls은 load한 object인 Parent(Group)에 attach되면서 Parent 기준으로 control helper가 만들어지고 회전 기준점이 지정되었고, 그에 따라 helper가 이상하게 출력되거나 혹은 회전이 이상하게 이루어지는 것처럼 보이게 되었다.

 

 

(의식의 흐름)

 

=> 처음에는 Group을 새로 만들어서 원점(Origin)으로 이동시키고, children을 그룹에 추가하는 방식으로 해결하려고 했다.

 

=> 그러나 화면에 출력하기 위해 Parent(Group)이 가지고 있는 요소를 필요로 했고, clone()을 통해 복제를 하려고 했다.

 

=> 복제를 한 Group을 따로 빼둔 후, 기존 Parent(Group)을 원점으로 이동시키고 따로 빼서 저장해두었던 children을 attach로 추가하였다.

 

=> 하지만, children을 기존 그룹과 복제 그룹이 반반 나눠 가지는 이상 현상을 발견했고, 구글링 결과 ThreeJS에서 Group의 children을 다른 Group에서 동일하게 가질 수 없다는 것을 알 수 있었다. 자체적으로 remove가 수행된다고 하였다.

 

=> 그렇기에 복제 Group대신 신규 Array에 push하여 children(Mesh들)을 저장 후, 기존 그룹에 children을 넣도록 하였지만 화면에 출력되지 않으면서 정상동작하지 않았다. Group에 ThreeJS의 특별한 수행요소가 있는 듯 보였다. attach 대신 add를 사용하여 children을 추가해보기도 했지만 마찬가지로 동작하지 않았다.

 

=> 잠시 휴식 뒤에 깨달음을 얻을 수 있었다. 반복문으로 attach를 할 때 자체적으로 remove된다면, BufferedReader의 readLine()처럼 휘발성으로 생각해야 되지 않나?

 

=> 그래서 while로 복제 Group의 children 길이가 0 이하가 될 때까지 첫 번째 인덱스를 attach하도록 했더니, 원하던 것처럼 Parent와 children을 분리시켜서 각각 원점에 정렬할 수 있었다.

 

반응형

 

(해결 코드)

 

/** resetCenter 메소드 안에 적절히 추가 **/
//기존 object(load한 object)를 복제하여 temp 변수에 할당
const temp = object.clone();

//기존 object(parent group)의 전체 children 제거
object.clear();
//기존 object를 원점(origin)으로 position 설정
object.position.copy(new THREE.Vector3(0, 0, 0));

//복제 group의 children 길이가 0이하일 때까지(자동으로 remove 되므로),
//children을 기존 object에 attach로 children 추가
while(temp.children.length > 0){
    object.attach(temp.children[0]);
}

//scene에 object 추가(코드 위치 참고, position이 변경된 후 scene에 추가해주어야 함)
scene.add(object);

//TransformControl을 object에 attach
transformControls.attach(object);

//scene에 TransformControl 추가
scene.add(transformControls);

//TransformControl을 회전 컨트롤 모드로 설정
transformControls.setMode('rotate');

 

위의 코드를 주석을 참고하여 필요한 부분을 적절히 추가해주면, 로드한 오브젝트의 부모와 자식 요소 전체를 원점으로 정렬시킬 수 있다!

 

🤞 도움이 되셨기를 바랍니다. 한 번의 클릭과 댓글은 어딘가의 누군가에게 진실로 큰 힘이 됩니다. 🐱‍🏍

 

728x90
반응형

댓글