구현하고자 하는 결과는 아래의 그림처럼 노란색 경로가 있고 빨간색 직육면체가 이 경로를 따라 자연스럽게 이동하는 것입니다.
먼저 제가 사용하는 three.js의 구성 중 거의 변경되지 않는 HTML과 CSS를 살펴보겠습니다. HTML은 다음과 같습니다.
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="style.css"> <script type="module" src="app.js" defer> </head> <body> </body> </html>
CSS는 다음과 같구요.
* { outline: none; padding: 0; margin: 0; }
그리고 이제 app.js에 대한 코드를 살펴보겠습니다. 먼저 기초 코드입니다.
import * as THREE from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r126/three.module.min.js' class App { constructor() { this._initialize(); } _initialize() { this.domWebGL = document.createElement('div'); document.body.appendChild(this.domWebGL); let scene = new THREE.Scene(); let renderer = new THREE.WebGLRenderer(); renderer.setClearColor(0x000000, 1.0); this.domWebGL.appendChild(renderer.domElement); window.onresize = this.resize.bind(this); this.renderer = renderer; this.scene = scene; this._setupModel(); this._setupLights() this._setupCamera(); this.resize(); } _setupModel() { // 경로 및 직사각형 모델 구성 } update(time) { // 직사각형 모델을 경로에 따라 이동시킴 } _setupLights() { const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(30, 50, 20); this.scene.add(light); } _setupCamera() { const fov = 60; const aspect = 1; const zNear = 0.1; const zFar = 1000; let camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar); camera.position.set(40, 40, 40).multiplyScalar(0.3); camera.lookAt(0,-2,0); this.scene.add(camera); this.camera = camera; } render(time) { requestAnimationFrame(this.render.bind(this)); this.update(time); this.renderer.render(this.scene, this.camera); } resize() { let camera = this.camera; let renderer = this.renderer; renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); } } window.onload = function() { (new App()).render(0); }
위의 코드에서 경로와 정육면체 매쉬를 구성하는 _setupModel과 매쉬 모델을 움직이도록 속성값을 업데이트하는 update 함수는 아직 비어 있습니다.
모델을 구성하는 _setupModel 함수의 코드는 다음과 같습니다.
_setupModel() { const path = new THREE.SplineCurve( [ new THREE.Vector2( 10, 5 ), new THREE.Vector2( 5, 5 ), new THREE.Vector2( 5, 10 ), new THREE.Vector2( -5, 10 ), new THREE.Vector2( -5, 5 ), new THREE.Vector2( -10, 5 ), new THREE.Vector2( -10, -5 ), new THREE.Vector2( -5, -5 ), new THREE.Vector2( -5, -10 ), new THREE.Vector2( 5, -10 ), new THREE.Vector2( 5, -5 ), new THREE.Vector2( 10, -5 ), new THREE.Vector2( 10, 5 ), ] ); this.path = path; const points = path.getPoints( 100 ); const geometry = new THREE.BufferGeometry().setFromPoints( points ); const material = new THREE.LineBasicMaterial( { color : 0xffff00 } ); const pathLine = new THREE.Line( geometry, material ); pathLine.rotation.x = Math.PI * .5; this.scene.add(pathLine); const boxGeometry = new THREE.BoxGeometry(1, 1, 3); const boxMaterial = new THREE.MeshPhongMaterial({color: 0xff0000}); const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial); this.scene.add(boxMesh); this.boxMesh = boxMesh; }
그리고 update 함수는 다음과 같습니다.
update(time) { const boxTime = time * .0001; const boxPosition = new THREE.Vector3(); const boxNextPosition = new THREE.Vector2(); this.path.getPointAt(boxTime % 1, boxPosition); this.path.getPointAt((boxTime + 0.01) % 1, boxNextPosition); this.boxMesh.position.set(boxPosition.x, 0, boxPosition.y); this.boxMesh.lookAt(boxNextPosition.x, 0, boxNextPosition.y); }
위의 코드 중 7과 8번 라인의 getPointAt은 경로를 구성하는 좌표를 얻을 수 있는데, 이 함수의 첫번째 인자는 0에서 1사이의 값을 가질 수 있고 0일때 경로의 시작점 1일때 경로의 끝점을 얻을 수 있습니다.