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

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
의 상대 좌표를 의미합니다. 이에 대해서는 본 블로그의 지난 포스트를 참고하시길 바랍니다.
읽어주셔서 감사합니다!
묻고 답하기
개인적인 판단에 의해 적절하다고 여겨지는 경우, 모두가 볼 수 있도록 이곳에 문답이 추가됩니다. 그렇지 않더라도 질문에 대한 답변은 별도로 이루어집니다.
Subscribe to my newsletter
Read articles from 고라니드로 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
