본문 바로가기

「three.js」dat.GUI를 활용, 3D 오브젝트 삽입 및 제거하기

by Recstasy 2019. 4. 26.

 

 

웹3.0이 대두되고 있지만 웹의 강력한 힘은 여전히 '반응형'이다. 사용자의 동작에 따라 웹은 '반응'하고, 사용자가 원하는 결과(아웃풋)가 곧 (웹)기술의 혁신이다. 그런데 3D로 제작되는 웹은 사용자와 반응하는 수준에 그치지 않고, 가상 현실을 만들어낸다. 이 같은 가상현실에서 필수적인 기술은 '컨트롤'이다.

 

 

오브젝트를 생성하는 컨트롤러 만들기

사용자가 특정 동작을 웹에 지시하려면 'dat.GUI()'가 필요하다.  

 

이를 위해,  'controls 객체'를 생성한 후 dat.GUI()와 연결한다. controls객체에 매서드를 생성하는 부분이 '사용자 컨트롤'의 핵심이다.

 

1) controls 프로토타입 객체 생성하기

controls 객체에서 'addCube', 'removeCube'매서드를 추가한다. 프로퍼티(항목)로는 numberOfObjects를 지정한다.

 

let controls = new functioin(){
     this.rotationSpeed = 0.02;
     this.numberOfObjects = scene.children.length;

     //매서드 추가
     this.removeCube = function(){
         let allChildren = scene.children;
         let lastObject = allChildren[allChildren.length - 1];
         if(lastObject instanceof THREE.Mesh){
               scene.remove(lastObject);
               this.numberOfObjects = scene.children.length;
     }
  }
}

 

rotationSpeed 항목은 cube의 속도를 조절하며, numberOfObject는 scene의 모든 요소들의 개수를 기록한다. scene의 요소들의 개수를 알 수 있어야만 특정 오브젝트를 삭제 혹은 변형을 줄 수 있으므로 위의 코드에서 length는 중요한 부분이다. ('allChildren'은 배열)

 

또, lastObject는 마지막에 생성된 오브젝트를 의미한다. allChildren의 index값에 'allChildren.length - 1'을 넣으면, 마지막에 생성된 오브젝트를 선택할 수 있다. 그리고 if문을 통해 lastObject의 데이터를 instanceof로 검색하여, THREE.Mesh 특성을 갖는 항목을 scene.remove()매서드로 제거할 수 있다.

 

마지막으로 scene.children.length를 통해 numberOfObjects값을 현재 상태로 리셋하는 부분에 주의하자. 

 

 

 

 

2) addCube매서드 

let controls = new function(){
   this.rotationSpeed = 0.02;
               ..
            (중략)


   this.addCube = function(){
     let cubeSize = Math.ceil((Math.random()*3));
     let cubeGeometry = new THREE.BoxGeometry(cubeSize,cubeSize,cubeSize);
     let cubeMaterial = new THREE.MeshLambertMaterial({ color:0xffffff*Math.random() })
     let cube = new THREE.Mesh(cubeGeometry,cubeMaterial);
          cube.castShadow = true;
          cube.name = "cube-"+scene.children.length;
          cube.position.x = -10 + Math.round((Math.random()*planeGeometry.parameters.width))
          cube.position.y = Math.round((Math.random()*5));
          cube.position.z = -20 + Math.round((Math.random()*planeGeometry.parameters.height));
          scene.add(cube);
          this.numberOfObjects = scene.children.length;
  }
   this.outputObjects = function(){
       console.log('scene.children');
  }
}

 

addCube를 추가하는 방법은 cube를 처음부터 생성하는 three.js 문법과 거의 일치한다. 단, scene.children.length와 같은 three.js API와 관련된 부분과 Math.round, Math.ceil(올림) 등.... 랜덤 & 반올림과 같은 함수를 사용하여 무작위로 오브젝트를 생성하게끔 구현했다. 

 

 

 

 

3) gui 추가

controls 프로토타입 객체는 dat.gui()함수의 항목으로 넘겨준다. 이때 gui.add().listen()과 같은 방식으로 체인을 만들면, 아래와 같이 이벤트를 생성할 수 있다. .listen()이벤트는 numberOfObjects 데이터가 변할 때마다 자동으로 상태를 체크한다.

let gui = new dat.GUI();
    gui.add(controls,'rotationSpeed',0,0.5);
    gui.add(controls, 'addCube');
    gui.add(controls, 'removeCube');
    gui.add(controls, 'outputObjects');
    gui.add(controls, 'numberOfObjects').listen();

 

 

 

 

 

4) .traverse()매서드 활용

three.js라이브러리에서 제공하는 .traverse()매서드는 forEach()구문과 비슷하다. traverse()함수는 scene의 child항목들을 반복적으로 검사하는 기능을 수행한다. traverse()함수 내부에 THREE.Mesh나 THREE.SpotLight 등... three.js의 요소들을 넣으면 특정 항목에 영향값을 줄 수 있다.

 

function render(){

  trackballControls.update(clock.getDelta());
      
      //     ..
      // (중략)
  
  scene.traverse(function(e){
    if(e instanceof THREE.Mesh && e != plane){
         e.rotation.x += controls.rotationSpeed;
         e.rotation.y += controls.rotatioinSpeed;
         e.rotation.z += controls.rotationSpeed;
    }
 })
   requestAnimationFrame(render);
   renderer.render(scene,camera);
}

 

scene내의 요소들을 검사한 후, THREE.Mesh중에서 plane이 아닌 경우에는 회전을 하도록 구현한다. 최종 코드를 구현하면, 아래와 같다. (gui.dat 컨트롤러는 우측 상단에 위치)

 

 

 

 

 

//최종코드

(function () {
    init();
})()

function init() {
    var scene;
    var camera;
    var renderer;
	
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
    renderer = new THREE.WebGLRenderer();
	renderer.setClearColor(new THREE.Color(0x000000));
    renderer.setSize(window.innerWidth * 0.375, window.innerHeight * 0.375);
    renderer.shadowMap.enabled = true;
	
	
    var planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1);
    var planeMaterial = new THREE.MeshLambertMaterial({
        color: 0xffffff
    });
    var plane = new THREE.Mesh(planeGeometry, planeMaterial);
    plane.position.set(0, 0, 0);
    plane.rotation.x = -0.5 * Math.PI;
    plane.receiveShadow = true;
    plane.castShadow = true;
    scene.add(plane);
	
    var axes = new THREE.AxesHelper(5);
    axes.position.set(-5, 10, 5);
    scene.add(axes);
	
	
    var spotLight = new THREE.SpotLight(0xffffff);
    spotLight.position.set(-40, 60, -10);
    spotLight.castShadow = true;
    spotLight.shadow.mapSize = new THREE.Vector2(1024, 1024);
    scene.add(spotLight);
    camera.position.set(-30, 40, 30);
    camera.lookAt(scene.position);
    document.getElementById('webgl-slc').appendChild(renderer.domElement); 
	
	//사용자 반응  var step = 0;  
	
	var controls = new function () {

	    this.rotationSpeed = 0.02;
	    this.numberOfObjects = scene.children.length;
	    this.removeCube = function () {
	        var allChildren = scene.children;
	        var lastObject = allChildren[allChildren.length - 1]
			
			if (lastObject instanceof THREE.Mesh) {
	                scene.remove(lastObject);
	                this.numberOfObjects = scene.children.length;
	            }
	    };
	    this.addCube = function () {
	        var cubeSize = Math.ceil((Math.random() * 3));
	        var cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
	        var cubeMaterial = new THREE.MeshLambertMaterial({
	            color: Math.random() * 0xffffff
	        });
	        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
	        cube.castShadow = true;
	        cube.name = "cube-" + scene.children.length;
	        cube.position.x = -50 + Math.round((Math.random() * planeGeometry.parameters.width));
	        cube.position.y = Math.round((Math.random() * 5));
	        cube.position.z = -50 + Math.round((Math.random() * planeGeometry.parameters.height));
	        scene.add(cube);
	        this.numberOfObjects = scene.children.length;
	    }
	    this.outputObjects = function () {
	        console.log(scene.children);
	    }
	}
	var gui = new dat.GUI({
		autoPlace: false,
		
	});
	
	wdGUI.append( gui.domElement );
	gui.domElement.id = 'webdol-gui';
	
	gui.add(controls, 'rotationSpeed', 0, 0.5);
	gui.add(controls, 'addCube');
	gui.add(controls, 'removeCube');
	gui.add(controls, 'outputObjects');
	gui.add(controls, 'numberOfObjects').listen();
	
	render()
	
	function render() {
	    scene.traverse(function (e) {
	        if (e instanceof THREE.Mesh && e != plane) {
	            e.rotation.x += controls.rotationSpeed;
	            e.rotation.y += controls.rotationSpeed;
	            e.rotation.z += controls.rotationSpeed;
	        }
	    })
		
		requestAnimationFrame(render);
	    renderer.render(scene, camera);
	}
	
}

댓글

최신글 전체

이미지
제목
글쓴이
등록일