threejs editor
threejs editor 소스를 읽어보자.
threejs 공식 문서에 editor folder가 있는데,
이 example은 editor 기능에 대한 것들이 모여있을 것으로 예상된다.
아직 대규모 코드 베이스에 대한 경험이 부족하기도 하고,
라이브러리의 오픈소스를 제대로 study from scrach 해본 경험은 없어 시도해볼만 한 것 같다.
main flow 찾기
가장 먼저 해야할 것은.
어떤 스크립트가 main flow을 나타내냐는 것이다.
https://github.com/mrdoob/three.js/blob/dev/editor/index.html
아무것도 모르겠어서 일단 index.html을 보았다.
많은 module들이 import 된 것이 보인다.
보다시피 editor.js가 main flow가 될 것 같다.
코드를 읽어보니 editor에는 storage라는 개념과 signal이라는 개념이 있는 모양이다.
브라우저 이벤트에 대한 함수도 정의되어있는데
브라우저에도 dragover, drop등 이벤트가 정의되어 있다는걸 이제 알았다.
웹 개발자가 아니라서 몰랐는데, hash를 사용하여 상태를 처리하는 부분이다.
Editor.js
중심이 되는 스크립트는 editor.js 인것 같다. 무작정 읽어보자.
editor라는 함수에서는 signal을 상당히 많이 사용한다. 객체처럼말이다.
근데 객체? import 정보를 보면 signal이 안보인다.
new Signal이라는 생성자로 여러 변수를 초기화? 하는건가 문법을 모르겠네. : (colon) 으로 되어있다.
signals가 import되지 않았는데 사용하고 있다. 그래서 주석으로 eslint-disable-line no-undef라고 적어놓았는데,
이 의미가 정의되지 않은 객체에 대해서 undefine 규칙을 비활성화 하는 것이라고 한다.
주석이지만 특별히 성능이 있는 경우라고 한다.
이렇게 선언된 이유를 추정해보자면, signals라는 스크립트는 libs 폴더 안에 있는데,
https://millermedeiros.github.io/js-signals/
아무래도 이 이벤트 시그널 라이브러리를 사용하는 것 같다.
index.html에는 script가 선언되어있는데,
하여튼 이걸 사용하는 하나의 방법으로 적용되는 걸로 추정된다. 일단 그렇다.
signals 객체를 생성하고 나서부터는 이외 여러가지 객체들을 생성자를 통해서 선언한다.
constructor function
처음부터 의문을 가졌던 부분인데, 이게 C++에 익숙한 개발자가 보기에는 class가 아니라 function으로 Editor가 정의되어있는 것이 계속 의문이었다. 실제로 Editor는 class처럼 사용되고 있는데 왜 function일까?
그 이유는 이러한 형태가 생성자 함수(constructor function)라고 불리며, ES6 클래스가 도입되기 전 객체 지향 프로그래밍을 구현하는 전통적인 방법이라고 한다.
즉 class라고 생각하고 코드를 봐도 무방하는 뜻이다. ES6가 도입되기 전부터 개발되었던 프로젝트이기 때문에, ES6 문법을 사용하지 않은 것 같다.
editor funciton의 밑에는 editor prototype이 있는데 이는 함수를 정의하는 구간이라고 생각하면 쉽다.
this.config = new Config();
this.history = new _History( this );
this.selector = new Selector( this );
this.storage = new _Storage();
this.strings = new Strings( this.config );
this.loader = new Loader( this );
this.camera = _DEFAULT_CAMERA.clone();
this.scene = new THREE.Scene();
this.scene.name = 'Scene';
this.sceneHelpers = new THREE.Scene();
this.sceneHelpers.add( new THREE.HemisphereLight( 0xffffff, 0x888888, 2 ) );
this.object = {};
this.geometries = {};
this.materials = {};
this.textures = {};
this.scripts = {};
this.materialsRefCounter = new Map(); // tracks how often is a material used by a 3D object
this.mixer = new THREE.AnimationMixer( this.scene );
this.selected = null;
this.helpers = {};
this.cameras = {};
this.viewportCamera = this.camera;
this.viewportShading = 'default';
this.addCamera( this.camera );
signal 이후에 생성되는 객체들이다.
하나씩 어떤 역할을 수행하는지 간단히 보자.
Config.js
언어설정과 project, settings에 대한 설정이 저장되는 storage가 있다.
우측 창의 설정 부분들이다.
storage의 경우 브라우저 local storage에 저장되고, 불러올 때 파싱하여 적용한다.
이렇게 return 할 때 함수를 선언한 것들을 전달하는 방식도 현재는 흔치 않은데,
prototype을 사용하지 않고 config constructor function에서 함수 자체를 반환하여 이용한다.
History.js
재밌게도 history의 경우는 class로 선언되어있다.
history는 이름만 보아도 알 수 있듯. 사용자의 동작을 저장해놓았다가, ctrl+z 키 등으로 undo를 실행할 수 있게 하는 기능이다.
undo list와 redo list에 cmd를 저장하고 undo, redo 동작을 한다.
동작 하나하나를 실행할 때는 execute함수를 실행하여 undos list에 동작을 추가하는 방식이다.
이는 editor에 호출하는 함수가 있다.
from json은 어딘가에 json 형식으로 저장했다가 파싱하여 cmd list를 undo, redo에 가져오는 기능이다.
확실하게 어딘지는 아직 모른다. 어딘가라는게 local storage일 가능성도 높지만, json인 것으로 보아 외부로 export하는 것 같다.
go to state. 이건 뭘까
editor에서 state가 어떤 상태를 의미하지?
흐름상 parameter로 id를 받고, cmd.id가 있는 걸 보면. 추정할 수 있는게 있다.
editor에서 translation, rotation, scale 이나 여러 tool 적인 기능의 상태를 의미하는 것이다.
cmd 의 종류라고 보면 될 것 같다.
이게 JS로 구성되어있어서 아시운 점은 cmd라는 object가 어떤 type들을 담고 있는지 정해진 type define이 없다는 것이다.
이래서 TS가 편하다.
go to state에서는 특히 signal 중 scene graph changed, gistory changed 라는 두 개를 active 하여 dispatch한다.
dispatch가 아마 함수를 실행하는 역할을 할 것이다.
아마 scene graph라는 것은 hierarchy를 의미하지 않나 싶고, history는 cmd history일 거다.
그럼 변경되는 순간 go to state처리를 하는 모양이다.
이제 보니 history가 여기에 있었다.
마지막에는 enable serialization이 있는데,
설명을 읽어보면 toJSON으로 직렬화되지 않는 cmd들을 goto state로 직렬화 시켜서 undos, redos에 넣는 과정이라고 한다.
모든 코드를 볼 필요는 없다.
생각해보니 내가 필요한 부분은 scene hierarchy를 어떻게 editor에서 구현하였는지 이지
history 기능과 같은걸 지금 알려고 하는 게 아니다. 그럼 어떻게 하나. hierarchy와 관련된 코드만을 editor에서 찾아서
파헤쳐 보는 수밖에 없다.