Векторы на кортежах: сложение, длина, нормировка
Весь движок построен на векторах, а вектор в 2D — это всего лишь пара чисел и горстка функций над ней.
Вектор — величина, имеющая длину и направление; в 2D он задаётся парой компонент $(x, y)$.
Почему векторы — язык физики
Скорость, ускорение, сила, импульс — всё это векторы: у них есть и величина, и направление. Записывать каждую как две отдельные переменные ($v_x$, $v_y$) утомительно и чревато ошибками. Гораздо чище завести вектор как сущность и операции над ним. В Python для этого даже не нужны библиотеки: вектор — это кортеж (x, y), а операции — обычные функции. Такой подход прозрачен, не тянет зависимостей и идеально подходит для учебных движков.
Базовые операции
Определим сложение, вычитание, умножение на число, длину и нормировку. Длина вектора — это теорема Пифагора:
$$|\vec a| = \sqrt{a_x^2 + a_y^2}.$$
Нормировка превращает вектор в единичный (длины $1$), сохраняя направление: $\hat a = \frac{\vec a}{|\vec a|}$. Единичные векторы нужны повсюду — чтобы задать «куда», отделив направление от величины.
import math
def add(a, b): return (a[0]+b[0], a[1]+b[1])
def sub(a, b): return (a[0]-b[0], a[1]-b[1])
def mul(a, s): return (a[0]*s, a[1]*s)
def length(a): return math.hypot(a[0], a[1])
def normalize(a):
L = length(a)
return (a[0]/L, a[1]/L)
a = (3, 4)
b = (1, 2)
print("a + b =", add(a, b))
print("a - b =", sub(a, b))
print("a * 2 =", mul(a, 2))
print("|a| =", length(a))
print("normalize a=", normalize(a))
print("|norm a| =", round(length(normalize(a)), 6))
Вывод:
a + b = (4, 6) a - b = (2, 2) a * 2 = (6, 8) |a| = 5.0 normalize a= (0.6, 0.8) |norm a| = 1.0
Вектор $(3, 4)$ имеет длину ровно $5$ — это знаменитый египетский треугольник. После нормировки получаем $(0.6, 0.8)$, и его длина равна $1$, как и положено единичному вектору. Эти пять функций — фундамент: на них мы построим скорость, силы и столкновения.
Вектор скорости и движение
С векторными операциями шаг симуляции записывается компактно: pos = add(pos, mul(vel, dt)), vel = add(vel, mul(acc, dt)). Никаких отдельных $x$ и $y$ — одна строка обновляет обе оси. Это не только короче, но и надёжнее: невозможно случайно обновить одну ось и забыть другую.
Как работает под капотом
Функция math.hypot(x, y) вычисляет $\sqrt{x^2 + y^2}$ устойчиво — без промежуточного переполнения для очень больших чисел, в отличие от наивного math.sqrt(x*x + y*y). Нормировка опасна одним: если длина вектора равна нулю (нулевой вектор не имеет направления), деление на неё даст ошибку. В движках это частый источник падений — например, когда две точки совпали и вектор между ними нулевой. Поэтому в боевом коде нормировку всегда защищают проверкой на нулевую длину.
Частые ошибки
- Нормировка нулевого вектора. Деление на нулевую длину — деление на ноль; защищайте проверкой.
- Путать длину и квадрат длины. Для сравнения расстояний часто хватает $|\vec a|^2$ без корня — это быстрее и точнее.
- Менять кортеж «на месте». Кортежи неизменяемы; операции возвращают новый кортеж, а не правят старый.
Итог
- Вектор в 2D — пара чисел; операции реализуются обычными функциями без библиотек.
- Длина: $|\vec a|=\sqrt{a_x^2+a_y^2}$; нормировка даёт единичный вектор направления.
- Векторная запись шага короче и надёжнее покомпонентной.
- Нормировку нулевого вектора надо защищать — частая причина падений.