개발자, 트렌드를 버리다 — 2년후 소감

벌써 2년

어느새 [개발자 트렌트를 버리다] 라는 글을 적은 후 2년의 세월이 흘렀습니다. 이 번글은 [1년후 소감]에 이어 또 다시 1년 동안 어떤 일이 일어났는지에 대한 이야기와 그 동안 에디터를 만들면서 알게된 나름의 팁들을 같이 정리해볼까 합니다.

코드

1년전 코드와 다시 한번 비교해보겠습니다.

릴리즈

v0.9.47 ~ v0.10.4 까지 총 9개의 릴리즈를 만들었습니다.

  • Matrix
  • FR translation v0.2

기능

다시 1년이라는 시간이 올 줄 몰랐기 때문에 처음부터 어떻게 해야겠다는 목표를 잡진 않았습니다.

  • 멀티 아트보드 구현
  • Layer 를 움직일 때 아트보드 변경 구현
  • 공간 제약 없는 Viewport 구현
  • Panzoom 구현 (25% ~ 2500% 확대 가능)
  • Ruler 구현
  • 드래그로 Layer 복제 기능 구현
  • 아트보드 Export 기능 구현
  • Layer Group/Ungroup 기능 구현
  • Rotate 된 Layer 의 resize 기능 구현
  • History 기능 적용
  • SVG Path 의 BBox 구현
  • Video Layer 추가
  • 비동기(async) LOAD () 함수 구현 (컴포넌트 내부 변경되는 html 을 비동기로 적용)
  • Viewport/Panzoom 구현하기
  • 회전 이후의 드래그 영역에서 Selection 적용하기
  • 회전 이후의 크기변경 적용하기
  • 회전 이후의 Path 에디팅 적용하기
  • 멀티 Selection 이후 회전 적용하기
  • Layer 의 부모가 바뀔 때 상대 경로 찾아가기

Viewport, Panzoom and Ruler

Single Selection

Selection 을 하는 구조가 완전히 바뀌었습니다. 기존에는 left, top, width, height 을 기준으로만 생각 했기 때문에 단순하게 구현했는데요. 현재는 matrix 를 기준으로 구현해야하기 때문에 아예 구현 방법이 달라졌습니다.

Resize after rotating

Single Selection 을 구현한 이후에 가장 힘들었던 부분이 Layer 를 Resize 하는 부분이었습니다. 기존에는 개념이 width, height 밖에 없었기 때문에 width, height 를 늘리거나 줄이면 됐는데요. Rotate 된 상태에서 크기를 줄이거나 늘리면 사실 left, top 이 전혀 다른 좌표로 구성이 되어야 하는 문제가 있습니다.

[부모 Matrix] * [자식 Matrix] 
[부모] * [자식 translate] * [자식 transform origin] * [자식 transform] * [자식 tranform origin * -1] 
[부모] * [자식 New Translate] * [자식 New Transform origin] * [자식 transform] * [자식 New Tranform origin * -1]

Group Selection

single selection 을 해결한 이후 좀 더 골치가 아픈 영역을 만났습니다. group selection 은 전혀 다른 컨셉으로 맞춰야 했습니다.

  1. Group 의 Transform Origin 은 전체 영역기준으로 다시 정의됩니다.
  2. Group 을 Rotate 하게 되면 개별 Layer 의 Origin 과 상관 없는 영역에서 연산이 일어납니다.
  3. Selection 을 하는 기준은 Layer 의 부모가 Selection 에 포함된 경우 자식은 Group Selection 에 포함하지 않습니다.

Path Editing

Matrix 를 적용하기 이전부터 가장 큰 고민거리가 Path 편집하는 방법이었습니다. Layer 가 Rotate 된 상태에서 Path 를 어떻게 나열해야할지 몰라서 Path 를 편집하는 툴에 백그라운드를 하얗게 만들었습니다.

BBox for Path

Path 에디팅을 할 때 Matrix 는 해결이 되었는데요. 문제가 하나 더 있습니다. Path 가 있는 svg 의 실제 영역은 변화가 없다는 것이죠. 그래서 영역을 벗어난 Path 가 됩니다.

History

사용자가 수행한 모든 Action 에 대해서 히스토리를 남깁니다. 설치형 어플리케이션이나 웹에서 단독으로 수행하는 형태는 괜찮았습니다. 하지만 요즘 유행하는 협업 소프트웨어에는 맞지 않았습니다. 내가 하지 않은 행위들도 더 큰 히스토리 개념 안에서 돌아가야 하기 때문인데요.

  1. 내가 만든 뒤로가기는 로컬에서 편집한 상태로 한정 짓는다.
  2. 다른 유저가 변경 한 것 까지는 뒤로 돌리지 않는다.

마무리

작년에 1년동안 한 것보다 양은 많진 않지만 훨씬 유익한 1년이 된 것 같습니다. 지금부터는 툴이 더 발전할 수 있는 밑바탕을 만들어 놓을 수 있게 되었습니다.

TIP & TIP & TIP

여기서 부터는 에디터를 만들면서 적용했던 부분들을 설명을 해볼까 합니다.

Matrix 다루기

에디터에서는 CSS 의 모든 기능에 대응하기 위해 3D 연산도 같이 포함합니다. 그렇기 때문에 모든 연산은 4x4 Matrix 를 사용합니다.

Vec3

4x4 Matrix 를 연산하기 위해서 3차원 벡터를 지정합니다.

Layer 위치 구하기

기본적으로 Layer 는 top, left, width, height 의 영역을 가집니다. 이때 position: absolute; 를 설정하게 되면 부모의 위치부터 상대 위치를 가집니다.

중첩된 Layer 들의 위치 구하기

ArtBoard -> Layer 1 -> Layer 2 -> Layer 3 형태의 중첩된 Layer 들이 있다면 Layer3 의 위치는 어떻게 될까요?

Layer 이동하기

이제 실제로 Layer 를 움직여 보겠습니다. Matrix 를 사용하지 않은 상태에서는 보통은 x, y 에 특정 움직인 간격을 더해주는 걸로 해도 됩니다.

Layer 회전하기

Layer 를 움직여 봤으니 Layer 를 현재 상태에서 Rotate(회전) 시켜 보도록 하겠습니다.

vec3.lerp([], start, end, 1 + pointDist/vec3.dist(start, end))

Layer 크기(Scale) 변경하기

처음에는 Matrix 를 사용하지 않았기 때문에 크기 변경은 width, height 를 변경하는 것에서 끝났습니다. 그러다 보니 회전한 상태로는 크기 변경을 하면 전혀 다른 형태로 , 다른 위치로 변형이 되고 있었습니다. 이것을 그냥 넘어가고 있었던 것이죠.

[parentMatrix] * [translate] * [transformOrigin] * [itemTransform] * [transformOrigin * -1] 
[parentMatrix] * [newTranslate] * [newTransformOrigin] * [itemTransform] * [newTransformOrigin * -1] 
TransformOrigin.scale(origin,width, height);
[newTransformOrigin] = mat4.translate(view, view, originVector) 
[parentMatrix] * [translate] * [transformOrigin] * [itemTransform] * [transformOrigin * -1] = [parentMatrix] * [newTranslate] * [newTransformOrigin] * [itemTransform] * [newTransformOrigin * -1]
[LayerMatrix] = [parentMatrix] * [newTranslate] * [newTransformOrigin] * [itemTransform] * [newTransformOrigin * -1] 
[parentMatrix * -1] *[LayerMatrix] = [newTranslate] * [newTransformOrigin] * [itemTransform] * [newTransformOrigin * -1]
--->
[parentMatrix * -1] *[LayerMatrix]* [newTransformOrigin] = [newTranslate] * [newTransformOrigin] * [itemTransform]
--->
[parentMatrix * -1] *[LayerMatrix]* [newTransformOrigin]* [itemTransform * -1] = [newTranslate] * [newTransformOrigin]
--->
[parentMatrix * -1] *[LayerMatrix]* [newTransformOrigin]* [itemTransform * -1] * [newTransformOrigin * -1] = [newTranslate]

Layer 의 부모 교체하기

지금까지는 Layer 하나를 가지고 translate, rotate, scale 등등이 실제로 마우스로 움직였을 때 어떻게 움직이는지 살펴보았습니다. 이번에 살펴볼 내용은 Layer 의 부모를 교체 했을 때, Layer 각 유저의 눈에는 그 위치 그대로 올 수 있는가에 대한 이야기를 해보겠습니다.

[newParentMatrix] * [newLocalMatrix] = [oldParentMatrix] * [oldLocalMatrix]
[newParentMatrix] * [newLocalMatrix] = [oldParentMatrix] * [oldLocalMatrix]
---->
[newLocalMatrix] = [newParentMatrix * -1] * [oldParentMatrix] * [oldLocalMatrix]
[newLocalMatrix] = [newTranslate] * [itemOrigin] * [newItemTransform] * [itemOrigin * -1] 
[newLocalMatrix] = [newTranslate] * [itemOrigin] * [newItemTransform] * [itemOrigin * -1]
---->
[newLocalMatrix] * [itemOrigin]= [newTranslate] * [itemOrigin] * [newItemTransform]
---->
[newLocalMatrix] * [itemOrigin] * [newItemTransform * -1] = [newTranslate] * [itemOrigin]
---->
[newLocalMatrix] * [itemOrigin] * [newItemTransform * -1] * [itemOrigin * -1]= [newTranslate]

Viewport 설계하기

이제 실제로 Layer 가 화면에 표시될 때 사용할 수 있는 좌표를 가지고 있는 Viewport 를 만들어 보도록 하겠습니다. Viewport 는 Editor 에서 Canvas 영역 중에서도 실제로 보이는 부분을 지정한다고 보시면 될 것 같습니다. 어떠한 Layer가 놓여있더라도 현재 Viewport 기준하에서 표시된다고 보시면 됩니다.

  • panning: viewport 의 중심을 이동합니다. (스크롤 한다고 보시면 됩니다.)
  • zoomming: Viewport 의 중심을 기준으로 확대/축소 하는 것을 말합니다.
this.verties = vertiesMap(this.cachedViewport, this.matrixInverse);
  1. selection 영역 표시
  2. ruler 표시
  3. path editor 좌표 변환
  4. Guide Line 표시
  5. Mouse Point 을 Viewport 좌표로 변환

Canvas 크기가 바뀔 때 Viewport 재정렬하기

Viewport 는 여러모로 상당히 유용한 기능입니다. 하지만 이 Viewport 를 계속 제대로 활용하기 위해서는 Canvas 크기가 바뀔 때 마다 모든 속성을 초기화 해줘야 하는 문제가 있습니다.

  1. Viewport 의 width, height 가 바뀌는 것에 scale 은 영향이 없다.
  2. 새로운 Viewport 의 width ,height 기준으로 TransformOrigin 을 다시 설정해야한다.
  3. TransformOrigin 을 다시 설정하기 때문에 Translate 도 다시 설정해야한다.
  4. 이 부분이 제일 중요한데요. 크기가 변경되더라도 viewport 의 left, top 은 변함이 없다. 즉, 시작지점은 그대로고 bottom, right 영역만 바뀌는 것이죠.
[oldTranslate] * [oldTranslateOrigin] * [scale] * [oldTranslateOrigin * -1] = [newTranslate?] * [newTransformOrigin] * [scale] * [newTransformOrigin * -1] 

Viewport 에서 Zooming 할 때 Origin 유지 하기

브라우저를 비롯한 panzoom 을 지원하는 모든 툴들은 특정 마우스 포인터 기준으로 zooming 을 할 때 해당 뷰가 유지가 됩니다. 마우스 위치 기준으로 크기가 변경이 되는 것이죠.

  1. zooming 을 시작하는 마우스 포인트를 등록한다.
  2. 마우스 포인트는 어떤 시점이든 현재의 transform origin 과의 거리가 있다.
  3. 확대를 하면 마우스 포인트와 transform origin 의 거리가 좁혀지고 축소를 하면 멀어진다.

Selection 영역 만들기

Viewport 는 모든 좌표 연산에 영향을 주지는 않지만, 보는 관점의 좌표에는 영향을 줍니다. 화면에 어떤 것이 보인다 하면 모두다 Viewport 의 영향에 있다고 보시면 됩니다. 그 중에 자주 사용하는 기능이 Selection 영역을 만드는 기능입니다.

this.$viewport.applyVerties(verties);
vec3.lerp([], pointers[0], pointers[1], 0.5)
const diff = vec3.subtract([], vec3.lerp([], pointers[0], pointers[1], 0.5), pointers[4]);const rotate = Length.deg(calculateAngle360(diff[0], diff[1]) + 90).round(1000);

Group Selection

여러개의 Layer 를 동시에 Selection 하는건 기본적으로 Single Selection 과 비슷할 수 있는데요. 몇가지 제한이 있습니다.

  1. 부모(최초 윗세대) 와 자식을 동시에 Selection 할 수가 없습니다.
  2. 회전을 하게 되면 마지막 시점에 Selection 영역이 바뀝니다.
  1. translate 할 때는 개별 부모 기준으로 Layer 이동하기를 구현해야합니다.
  2. scale 을 할 때는 Group 영역을 기준으로 변경된 크기를 비율 형태로 개별 Layer 에 다시 적용해야합니다. (이때 찌그러짐이 발생하죠)
  3. rotate 할 때는 transform origin 이 개별 Layer 가 아니고 Group 의 transform origin 으로 이루어져야 합니다.
  1. 선택된 각 Layer 들의 matrix 구합니다.
  2. GroupLayer 의 소속으로 바꿉니다.
  3. GroupLayer 의 Transform(translate, scale, rotate) 을 적용합니다.
  4. GroupLayer 에 속한 개별 Layer 들의 Matrix 를 다시 구합니다.
  5. 개별 Layer 들의 부모를 기준으로 다시 소속을 바꿉니다.

Collision(충돌)

사실 에디터를 만들면서 이런 것까지 할지는 몰랐는데요. Matrix 로 모든 좌표가 변경이 되고 회전을 한 상태에서도 정확한 Selection 을 해야하다보니 충돌상태를 구해야 했었습니다.

--

--

걸작을 만드는 사람.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store