[TypeScript] 제스처로 대화하기. #17 - 로테이트 편(with. 기준점)

고라니드로고라니드로
3 min read

Table of contents

이번 편은 이전 편으로부터 이어집니다.

핀치 때와 마찬가지로 기준점에 대해 한번 생각해 봅시다. 마찬가지로 고정된 포인터를 기준점으로 생각할 수 있습니다. 이를 수행하기 위한 변환 행렬은 아래와 같습니다.

$$\begin{aligned} \begin{pmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{pmatrix} = \begin{pmatrix} \cos\theta & -\sin\theta & t_x \\ \sin\theta & \cos\theta & t_y \\ 0 & 0 & 1 \end{pmatrix} \end{aligned}$$

여기까지 따라오신 분이라면 예상하실 수 있겠지만 기준점이 고정될 수 있도록 각 축에 대해 일정 수치만큼 이동해야 할 필요가 있습니다. 그것을 의미하는 것이 \(t_x\)와 \(t_y\)이지요. 고정 포인터의 좌표를 \((P_x, P_y)\)라 하면 로테이트 후 새로운 좌표는 아래와 같습니다.

$$\begin{aligned} \begin{pmatrix} P_x' \\ P_y' \\ 1 \end{pmatrix} & = \begin{pmatrix} \cos\theta & -\sin\theta & t_x \\ \sin\theta & \cos\theta & t_y \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} P_x \\ P_y \\ 1 \end{pmatrix} \\ & = \begin{pmatrix} P_x\cos\theta - P_y\sin\theta + t_x \\ P_x\sin\theta + P_y\cos\theta + t_y \\ 1 \end{pmatrix} \end{aligned}$$

따라서

$$\begin{aligned} & x = P_x\cos\theta - P_y\sin\theta + t_x \\ & \begin{aligned} t_x & = P_x - P_x\cos\theta + P_y\sin\theta \\ & = P_x(1 - \cos\theta) + P_y\sin\theta \end{aligned} \\ \\ & y = P_x\sin\theta + P_y\cos\theta + t_y \\ & \begin{aligned} t_y & = P_y - P_x\sin\theta - P_y\cos\theta \\ & = -P_x\sin\theta + P_y(1 - \cos\theta) \end{aligned} \end{aligned}$$

결국 기본 로테이트 행렬은 아래와 같습니다.

$$\begin{aligned} \begin{pmatrix} \cos\theta & -\sin\theta & t_x \\ \sin\theta & \cos\theta & t_y \\ 0 & 0 & 1 \end{pmatrix} = \begin{pmatrix} \cos\theta & -\sin\theta & P_x(1 - \cos\theta) + P_y\sin\theta \\ \sin\theta & \cos\theta & -P_x\sin\theta + P_y(1 - \cos\theta) \\ 0 & 0 & 1 \end{pmatrix} \end{aligned}$$

로테이트를 수행하기 전 기존 변환 행렬은 아래와 같습니다.

$$\begin{aligned} \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{pmatrix} \end{aligned}$$

예상하셨겠지만 핀치 때와 마찬가지로 위치 변환 행렬을 분해해야 합니다. 이에 대해서는 본 블로그의 지난 포스트에 설명되어 있으니 참고하시길 바랍니다.

$$\begin{aligned} \begin{pmatrix} 1 & 0 & e \\ 0 & 1 & f \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} a & c & 0 \\ b & d & 0 \\ 0 & 0 & 1 \end{pmatrix} \end{aligned}$$

이제 이 사이에 구성한 로테이트 행렬을 끼워 넣으면 됩니다.

$$\begin{aligned} M & = \begin{pmatrix} 1 & 0 & e \\ 0 & 1 & f \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} \cos\theta & -\sin\theta & P_x(1 - \cos\theta) + P_y\sin\theta \\ \sin\theta & \cos\theta & -P_x\sin\theta + P_y(1 - \cos\theta) \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} a & c & 0 \\ b & d & 0 \\ 0 & 0 & 1 \end{pmatrix} \\ & = \begin{pmatrix} \cos\theta & -\sin\theta & e + P_x(1 - \cos\theta) + P_y\sin\theta \\ \sin\theta & \cos\theta & f - P_x\sin\theta + P_y(1 - \cos\theta) \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} a & c & 0 \\ b & d & 0 \\ 0 & 0 & 1 \end{pmatrix} \end{aligned}$$

코드로 나타내면 아래와 같습니다.

const p = {
    x: origin.x - transformOrigin.x,
    y: origin.y - transformOrigin.y
};

const sinTheta = Math.sin(rotate);
const cosTheta = Math.cos(rotate);

box.style.transform = new DOMMatrix([
    cosTheta,
    sinTheta,
    -sinTheta,
    cosTheta,
    e + p.x * (1 - cosTheta) + p.y * sinTheta,
    f - p.x * sinTheta + p.y * (1 - cosTheta)
])
.multiply(new DOMMatrix([ a, b, c, d, 0, 0 ]))
.toString();

위 코드에서 한 가지 주의할 점은 회전 각도를 의미하는 rotate는 라디안 표기법에 따라야 한다는 것입니다. 추가로 transformOrigin은 뷰포트에 대한 transform-origin의 상대 좌표를 의미합니다. 이에 대해서는 본 블로그의 지난 포스트를 참고하시길 바랍니다.

읽어주셔서 감사합니다!

묻고 답하기

개인적인 판단에 의해 적절하다고 여겨지는 경우, 모두가 볼 수 있도록 이곳에 문답이 추가됩니다. 그렇지 않더라도 질문에 대한 답변은 별도로 이루어집니다.

0
Subscribe to my newsletter

Read articles from 고라니드로 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

고라니드로
고라니드로