본문 바로가기
자바스크립트와 캔버스

3차원 웹, 클론? 복제! - 28번째 시간

반응형

three.js 버전이 변경되어 소스가 일부 수정되었습니다 ( 2020. 4. 12 )
작동 소스는 본문과 다소 상이할 수 있습니다.
http://dreamplan7.cafe24.com/canvas2/three016.html

 

세계적으로 생물의 복제는 사회적, 윤리적 문제를 유발하고 있습니다. 심지어 인간복제까지도 이미 실현화되어 실용화한 사례도 있다고 하니 놀라운 일이 아닐 수 없습니다.

https://www.youtube.com/watch?v=iDcLznQLPg8&t=329s

한국에도 한차례 복제열풍이 있었지요. 바로 '황우석 박사'의 논문 사기 논란이었습니다.

당시 논문은 조작으로 드러나 확실히 문제가 있었고 줄기세포 연구 자체도 사기로 판명이 났었지만 웬지 그 모든 것이 사기라고 하기에는 석연치 않은 부분이 있었지요.

단 하나 성공한 1번 줄기세포가 사실은 처녀혼 임신과 같은 이상한 발표를 하고 얼른 땜방해서 묻어버리려고 하는 분위기로 느껴지기도 했습니다.

최근 해외에서 연구 활동중인 '황우석 박사'의 국내 복귀설 뉴스를 유튜브에서 접해 들었습니다. 정말 사기꾼이었다면 이렇게도 롱런할 수 있을까? 하는 생각이 들기도 합니다.

https://youtu.be/ZmoJXvbBY9k - 서울경제 TV -

'신의 영역'과 '인간의 영역'의 경계는 어디까지 일까요?

'인간'을 창조하신 '신'께서 과연 '인간'이 '인간을 재창조'하는 것을 허용하실까 하는 생각입니다.

황우석 박사의 난자를 이용한 배아 줄기세포 연구는 장기 복제를 목적으로 했었지만 결국 인간복제의 밑기술이 된다고도 하다는 말이 있습니다.

'신의 영역'에 도전하는 '인간',

하지만 성공한다 해도 그 복제인간은 '영'을 가지고 있지는 못할 것으로 생각됩니다.

복제된 인간에게는 하나님께서 인류 아담을 창조하실 때 그 코에 부으신 '생령'이 없을 것이기 때문이지요.

"여호와 하나님이 땅의 흙으로 사람을 지으시고 생기를 그 코에 불어넣으시니 사람이 생령이 되니라"

- 성서 창세기 2:7절 말씀 -

크레이이 바램으로는 황우석 박사의 연구가 동물 복제 연구 정도로 그치는게 적당한 선이 되지 않을까 합니다. 인간이 재창조한 인간을 과연 누가 책임질 수 있을까요?

이런, 이야기가 너무 심각해 진것 같습니다 !

이제, 오늘의 본론으로 들어가볼까요?


오늘 보실 내용은 지난 주에 보셨던 3차원 가상 공간에서의 돌고래를

바로 복제하는 것입니다.

가상공간에서의 복제야 뭐 별 문제될 건 없습니다 :)

그 안에 생명이 없기 때문입니다.

재미있는 것은 3차원 오브젝트에서 복제기능을 영어로 clone [ 클론 ] 이라는 명령어를 사용한다는 것이지요.

돌고래를 4마리로 복제하여 각각 재주(?)를 넘게 하려는 목적인데요. 먼저 영상 보시죠 :)

https://youtu.be/W5DyO_hQ5aY

4마리의 돌고래들이 미니집 주위를 기점으로 멋진 쇼를 펼치는데요 :)

이러한 여러마리의 돌고래를 가상 세계에 띄우려면 2가지 방법중 1가지를 선택해야 합니다.

첫번째는 똑같은 모델을 4번 부르는 과정을 반복하는 방법이 있습니다.

시간도 많이 들고 트래픽도 많이 들겠지요.

두번째는 하나의 모델만 불러다 놓고 이걸 계속 복사해 쓰는 방법입니다.

이걸 클론, 즉 복제라고 하지요 :)

복제를 왜 해야 할까요? 복제에 대해서 자세히 알아볼텐데요.

자바스크립트는 기본이 참조 복사입니다.

참조복사란 원본이 바뀌면 사본도 함께 바뀌는 것인데요.

실제 코드로 설명드리도록 하겠습니다.

아래와 같은 스크립트가 있다고 칩시다.

b에 a를 대입하는 순간 어떤 일이 일어날까요?

var a=new Object();
var b=a;

b에 a를 대입하는 순간 a와 똑같은 b가 복사되는게 아닙니다.

b는 a 와 이름만 다르지 똑같은 녀석이 됩니다. b 자체에 다른 값을 배정하기 전까지는요.

만일 아래와 같은 2줄의 코드를 실행하면, 어떤 일이 일어날까요?

a.like = '사과';
b.like = '바나나';

a.like 가 '바나나'로 바뀌어 버립니다.

분명 a.like 에는 '바나나'를 대입한 적이 없는데요.

그렇다면 어떻게 해야 할까요?

애시당초 b 에 a를 직접 대입해서는 안됩니다.

새로운 오브젝트를 만들고 a 의 속성을 하나 하나 옮겨적어야지요.

만일 a가 아래와 같이 정의되었다면.

var a=new Object();
a.like = '사과';
a.hate = '브로커리';

아래와 같이 b도 정의하고 a의 값을 하나씩 옮기는 겁니다.

var b=new Object();
b.like = a.like;
b.hate = a.hate;

그리고 나서 b의 속성을 바꾸는 거야 별 문제가 없습니다.

b.like = '바나나';

a의 속성을 하나 하나 옮기는 것을 함수 하나로 만들수도 있는데요.

아래와 같은 함수를 정의한다면,

function clone(obj) {
  var newobj = new Object();
  for (key in obj) newobj[key] = obj[key];
	return newobj;
}

a 오브젝트를 단순히 함수 하나로 복제할 수 있습니다.

다만 이 기능은 배열 요소가 단순한 경우 사용 가능합니다.

var a=new Object();
a.like = '사과';
a.hate = '브로커리';

var b=clone(a);

이렇게 복제한 경우 b의 like 속성을 추가하든 뭐하든 a에는 영향을 주지 않습니다.

a와는 애당초 다른 new Object()를 통해 생성한 변수이기 때문이지요.


복제에 대한 이론적인 설명은 끝났습니다만 사실 3차원 모델 OBJLoader 로 불려진 모델은 애당초 복제 기능을 가지고 있습니다. 이제부터 알아보도록 하죠.

우선 지난 소스를 유지하기 위해 three015.html 파일은 three016.html 파일로,

js/three015.js 파일은 js/three016.js 파일로 복사해주세요.

그리고 three016.html 파일의 아래 소스 부분을

<script src="js/three015.js"></script>

이와 같이 변경해 주시기 바랍니다.

<script src="js/three016.js"></script>

이제 three016.js 파일만 수정하면 됩니다.

우선 돌고래 4마리 오브젝트를 선언하는데, new THREE.Group() 명령으로 선언해 줍니다.

태양위치 선언한 아랫 부분에,

parameters.azimuth+=0.2;

아래 소스를 추가해 주세요.

var dolphin1 = new THREE.Group();
var dolphin2 = new THREE.Group();
var dolphin3 = new THREE.Group();
var dolphin4 = new THREE.Group();

THREE.Group 는 그룹을 만들고 그 안에 오브젝트를 배치하기 위해 사용하는 방법입니다.

이렇게 할 경우 그룹 안에 있는 오브젝트의 회전과 그룹의 회전을 각각 따로 지정할 수 있어 매우 유용하지요.

그 바로 아랫 부분이 돌고래 모델을 부르는 부분일텐데요.

아래 소스로 바꿔치기 합니다.

// 돌고래
var dolphin = new Object();
	mtlLoader.load('dolphin/10014_dolphin_v2_max2011_it2.mtl', function(materials) {
		materials.preload();
		loaderOBJ.setMaterials(materials);
		loaderOBJ.load(
			'dolphin/10014_dolphin_v2_max2011_it2.obj',
			function ( obj ){
				obj.scale.set(0.1,0.1,0.1);
				dolphin.obj = obj;

				dolphin1.add(dolphin.obj.clone());
				dolphin2.add(dolphin.obj.clone());
				dolphin3.add(dolphin.obj.clone());
				dolphin4.add(dolphin.obj.clone());

				dolphin1.rotation.set(0, 90 * PI_PER_180, 0); 
				dolphin2.rotation.set(0, 0 * PI_PER_180, 0); 
				dolphin3.rotation.set(0, -90 * PI_PER_180, 0); 
				dolphin4.rotation.set(0, 180 * PI_PER_180, 0); 

				dolphin1.position.set(10, 0, 10);
				dolphin2.position.set(-10, 0, 10);
				dolphin3.position.set(-10, 0, -10);
				dolphin4.position.set(10, 0, -10);

				scene.add(dolphin1);
				scene.add(dolphin2);
				scene.add(dolphin3);
				scene.add(dolphin4);

			});
	});

붙여넣으신 부분에게 아래와 같은 부분이 보이시지요?

이 부분이 바로 복제입니다.

dolphin.obj 가 3차원 오브젝트 모델 파일인데 자체에 clone() 이라는 복제 기능 함수를 가지고 있어서 이 기능을 사용하는 것이지요 :)

dolphin1.add(dolphin.obj.clone());
dolphin2.add(dolphin.obj.clone());
dolphin3.add(dolphin.obj.clone());
dolphin4.add(dolphin.obj.clone());

그 외에 바뀐 부분을 설명드리자면,

그리고 4마리의 돌고래 모두 y축을 기준으로 각기 다른 방향을 바라보게 합니다.

동서남북을 말이죠

dolphin1.rotation.set(0, 90 * PI_PER_180, 0); 
dolphin2.rotation.set(0, 0 * PI_PER_180, 0); 
dolphin3.rotation.set(0, -90 * PI_PER_180, 0); 
dolphin4.rotation.set(0, 180 * PI_PER_180, 0); 

 

각각의 기준 위치도 서로 떨어져 보이게 잡습니다.

 

dolphin1.position.set(10, 0, 10);
dolphin2.position.set(-10, 0, 10);
dolphin3.position.set(-10, 0, -10);
dolphin4.position.set(10, 0, -10);

 

그리고 장면에 추가하는 부분도 각각 따로 4번씩 수행해줍니다.

 

scene.add(dolphin1);
scene.add(dolphin2);
scene.add(dolphin3);
scene.add(dolphin4);

 

이렇게 하면 4마리의 돌고래가 복제되어 가상세계에 출현합니다

또 고칠 부분이 있을까요?

있습니다 :) 애니메이션 작동 부분인데요.

animate 함수의 controls.update(); 바로 아랫 부분의 돌고래를 움직이는 한줄의 코드를

 

dolphin.obj.rotation.x+=0.1;

 

다음과 같이 수정합니다.

 

dolphin1.children[0].rotation.x+=0.1;
dolphin2.children[0].rotation.x+=0.1;
dolphin3.children[0].rotation.x+=0.1;
dolphin4.children[0].rotation.x+=0.1;

 

수정된 three016.js 파일은 아래와 같습니다.

점점 양이 늘어나는군요. 다음번에 한번 더 정리해야 할듯 합니다.

// 돌고래 추가 소스
var framesPerSecond=30;

// 태양 초기 위치
parameters.azimuth+=0.2;

var dolphin1 = new THREE.Group();
var dolphin2 = new THREE.Group();
var dolphin3 = new THREE.Group();
var dolphin4 = new THREE.Group();

// 돌고래
var dolphin = new Object();
	mtlLoader.load('dolphin/10014_dolphin_v2_max2011_it2.mtl', function(materials) {
		materials.preload();
		loaderOBJ.setMaterials(materials);
		loaderOBJ.load(
			'dolphin/10014_dolphin_v2_max2011_it2.obj',
			function ( obj ){
				obj.scale.set(0.1,0.1,0.1);
				dolphin.obj = obj;

				dolphin1.add(dolphin.obj.clone());
				dolphin2.add(dolphin.obj.clone());
				dolphin3.add(dolphin.obj.clone());
				dolphin4.add(dolphin.obj.clone());

				dolphin1.rotation.set(0, 90 * PI_PER_180, 0); 
				dolphin2.rotation.set(0, 0 * PI_PER_180, 0); 
				dolphin3.rotation.set(0, -90 * PI_PER_180, 0); 
				dolphin4.rotation.set(0, 180 * PI_PER_180, 0); 

				dolphin1.position.set(10, 0, 10);
				dolphin2.position.set(-10, 0, 10);
				dolphin3.position.set(-10, 0, -10);
				dolphin4.position.set(10, 0, -10);

				scene.add(dolphin1);
				scene.add(dolphin2);
				scene.add(dolphin3);
				scene.add(dolphin4);

			});
	});



function moveCam(eye_x, eye_y, eye_z, target_x, target_y, target_z)
{
	createjs.Tween.removeAllTweens();
	createjs.Tween.get(camera.position)
		.to({
			x: eye_x, 
			y: eye_y, 
			z: eye_z
		}, 900, 
		createjs.Ease.sineInOut);

	createjs.Tween.get(controls.target)
		.to({
			x: target_x, 
			y: target_y, 
			z: target_z
		}, 900, 
		createjs.Ease.sineInOut);
}

function aroundCam()
{
	createjs.Tween.removeAllTweens();
	createjs.Tween.get(camera.position, {loop: true})
		.to({
			x: 10, y: 15, z: 12
		}, 5000, 
		createjs.Ease.sineInOut)

		.wait(500)

		.to({
			x: -100, y: 17, z: 42
		}, 2000, 
		createjs.Ease.quartIn)

		.wait(500)

		.to({
			x: 126, y: 23, z: 93
		}, 6000, 
		createjs.Ease.bounceOut)

		.wait(500)

		.to({
			x: 0, y: 38, z: 227
		}, 2000,				
		createjs.Ease.cubicIn)

		.wait(500)

		.to({
			x: 36, y: 12, z: -32
		}, 15000,				
		createjs.Ease.elasticIn)

		.wait(500)

		.to({
			x: 36, y: 115, z: -2
		}, 3000,				
		createjs.Ease.backOut)

		.wait(500)

		.to({
			x: 36, y: 10, z: -2
		}, 12000,				
		createjs.Ease.bounceInOut)

		.wait(500)

		.to({
			x: 10, y: 11, z: 12
		}, 5000,				
		createjs.Ease.elasticIn)

		.wait(1500);
}

// 에니메이션 효과를 자동으로 주기 위한 보조 기능입니다.
var animate = function () {
	// 프레임 처리
	setTimeout(function() {
		 requestAnimationFrame(animate); 
	}, 1000 / framesPerSecond);

	if(home_mesh.obj != undefined)
	{
		home_mesh.obj.rotation.set(-90 * PI_PER_180, 0, -45 * PI_PER_180); 
	}

	water.material.uniforms[ 'time' ].value += 1.0 / 60.0;

	parameters.azimuth+=0.00001;

	phi = 2 * Math.PI * ( parameters.azimuth - 0.5 );

	light_sun.position.x = parameters.distance * Math.cos( phi );
	light_sun.position.y = parameters.distance * Math.sin( phi ) * Math.sin( theta );
	light_sun.position.z = parameters.distance * Math.sin( phi ) * Math.cos( theta );

	sky.material.uniforms['sunPosition'].value = light_sun.position.copy( light_sun.position );
	water.material.uniforms['sunDirection'].value.copy( light_sun.position ).normalize();

	cubeCamera.update( renderer, sky );
	controls.update();

	dolphin1.children[0].rotation.x+=0.1;
	dolphin2.children[0].rotation.x+=0.1;
	dolphin3.children[0].rotation.x+=0.1;
	dolphin4.children[0].rotation.x+=0.1;

	// 랜더링을 수행합니다.
	renderer.render( scene, camera );

	document.getElementById('monitor').innerText = 
		camera.position.x.toFixed(2) + ", " + 
		camera.position.y.toFixed(2) + ", " + 
		camera.position.z.toFixed(2);
};

// animate()함수를 최초에 한번은 수행해주어야 합니다.
animate();

오늘은 여기서 맺습니다.

여기까지 시청해주셔서 감사합니다 :)

수고하셨습니다~

반응형