Поворот, масштаб и сдвиг
Три кита геометрических преобразований — поворот, масштаб и сдвиг — все записываются простыми матрицами.
Матрица поворота на угол $\theta$ вращает каждый вектор вокруг начала координат, сохраняя его длину.
Зная, что столбцы матрицы — образы осей, мы можем сконструировать матрицу под нужное действие. Достаточно понять, куда должны поехать $\vec{e}_1$ и $\vec{e}_2$ — и матрица готова. Разберём три классических преобразования.
Поворот
При повороте на угол $\theta$ ось $\vec{e}_1 = (1, 0)$ уходит в $(\cos\theta, \sin\theta)$, а $\vec{e}_2 = (0, 1)$ — в $(-\sin\theta, \cos\theta)$. Ставим эти образы столбцами:
$$R(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix}$$
Масштаб и сдвиг
Масштабирование растягивает оси независимо, а сдвиг (shear) «наклоняет» картинку, сдвигая верх относительно низа:
$$S = \begin{bmatrix} s_x & 0 \\ 0 & s_y \end{bmatrix}, \qquad H = \begin{bmatrix} 1 & k \\ 0 & 1 \end{bmatrix}$$
В матрице сдвига $H$ ось $\vec{e}_1$ остаётся на месте, а $\vec{e}_2 = (0, 1)$ уезжает в $(k, 1)$ — поэтому вертикальные линии наклоняются, как буквы курсива.
import math
def matvec(A, v):
return [sum(A[i][j] * v[j] for j in range(len(v)))
for i in range(len(A))]
def rot(theta):
c, s = math.cos(theta), math.sin(theta)
return [[c, -s], [s, c]]
R = rot(math.pi / 2) # поворот на 90 градусов
v = matvec(R, [1, 0])
print("e1 после поворота на 90:", [round(x, 3) for x in v])
H = [[1, 2], [0, 1]] # сдвиг
print("сдвиг точки (0,1):", matvec(H, [0, 1]))Вывод:
e1 после поворота на 90: [0.0, 1.0] сдвиг точки (0,1): [2, 1]
Проверка интуиции
Поворот на $90°$ отправил горизонтальную ось $(1, 0)$ строго вверх в $(0, 1)$ — ровно как и должно быть. Сдвиг с $k = 2$ оставил низ на месте, но верхнюю точку $(0, 1)$ толкнул вправо в $(2, 1)$. Эти преобразования — рабочие лошадки компьютерной графики.
Как работает под капотом
Поворот сохраняет длины и углы, потому что его столбцы $(\cos\theta, \sin\theta)$ и $(-\sin\theta, \cos\theta)$ единичны и перпендикулярны — это так называемая ортогональная матрица. Масштаб с разными $s_x, s_y$ длины меняет, а сдвиг искажает углы, но у всех троих общее: они линейны, оставляют ноль на месте и полностью определяются образами двух осей. Маленькое значение $\sin(90°)$, выводимое компьютером как $1{,}0$, на самом деле чуть-чуть отличается от единицы из-за округления чисел с плавающей точкой — но для геометрии это несущественно.
Частые ошибки
- Перепутать знак в матрице поворота: при $+\theta$ минус стоит у верхнего $\sin$, иначе получится поворот в обратную сторону.
- Подавать угол в градусах в
math.cos: функции тригонометрии в Python ждут радианы. Переводите черезmath.radians. - Думать, что сдвиг меняет площадь. На самом деле определитель матрицы сдвига равен 1 — площадь сохраняется (об этом в разделе про определитель).
Итог
- Поворот: $\begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix}$, сохраняет длины и углы.
- Масштаб: диагональная матрица, тянет оси независимо.
- Сдвиг (shear): наклоняет картинку, оставляя площадь неизменной.
- Чтобы построить матрицу, достаточно решить, куда поедут оси, и поставить образы столбцами.