Поворот, масштаб и сдвиг

Три кита геометрических преобразований — поворот, масштаб и сдвиг — все записываются простыми матрицами.

Матрица поворота на угол $\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): наклоняет картинку, оставляя площадь неизменной.
  • Чтобы построить матрицу, достаточно решить, куда поедут оси, и поставить образы столбцами.
Проверьте себя
1. Куда матрица поворота на 90 градусов отправляет вектор (1, 0)?
A(0, 1)
B(-1, 0)
C(0, -1)
D(1, 0)
2. Какое свойство сохраняет матрица поворота?
Aтолько площадь, но не длины
Bдлины векторов и углы между ними
Cтолько направление
Dничего не сохраняет
3. Что нужно сделать с углом перед передачей в math.cos в Python?
Aничего, он принимает градусы
Bперевести в радианы через math.radians
Cвзять корень
Dумножить на 100