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

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

by Cray Fall 2019. 7. 26.

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();

오늘은 여기서 맺습니다.

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

수고하셨습니다~