Векторная математика движения

За движением объектов стоит несложная векторная математика. Разберём её на живых примерах, которые можно запустить прямо здесь.

Вектор — это направление и длина. Нормализация даёт чистое направление (длина 1), а интерполяция — плавный переход между значениями.

Длина и нормализация

В Three.js векторы — это THREE.Vector3 с готовыми методами .length() и .normalize(). Но чтобы понять, что под капотом, посчитаем руками. Длина вектора — теорема Пифагора в 3D; нормализация — деление каждой компоненты на длину.

function length(v) {
  return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}

function normalize(v) {
  const len = length(v);
  return { x: v.x / len, y: v.y / len, z: v.z / len };
}

const v = { x: 3, y: 4, z: 0 };
const n = normalize(v);
console.log('Длина исходного:', length(v));
console.log('Нормализованный:', {
  x: +n.x.toFixed(2), y: +n.y.toFixed(2), z: n.z
});
console.log('Длина нормализованного:', +length(n).toFixed(4));

Вывод:

Длина исходного: 5
Нормализованный: { x: 0.6, y: 0.8, z: 0 }
Длина нормализованного: 1

Зачем это нужно: чтобы двигать объект с постоянной скоростью, берут нормализованный вектор направления и умножают на скорость. Иначе скорость зависела бы от случайной длины вектора.

Линейная интерполяция (lerp)

Чтобы объект плавно переехал из точки A в точку B, используют lerp — линейную интерполяцию. Формула проста: a + (b - a) * t, где t идёт от 0 (старт) до 1 (финиш).

function lerp(a, b, t) {
  return a + (b - a) * t;
}

const from = 0, to = 10;
for (const t of [0, 0.25, 0.5, 0.75, 1]) {
  console.log('t=' + t, '->', lerp(from, to, t));
}

Вывод:

t=0 -> 0
t=0.25 -> 2.5
t=0.5 -> 5
t=0.75 -> 7.5
t=1 -> 10

Тот же приём двигает камеру, плавно меняет цвет, подтягивает объект к цели. В Three.js это vectorA.lerp(vectorB, t).

Поворот точки

Чтобы заставить объект кружить по окружности, точку поворачивают вокруг оси. Поворот в плоскости XZ на угол a описывается синусом и косинусом.

function rotateXZ(x, z, angle) {
  const cos = Math.cos(angle);
  const sin = Math.sin(angle);
  return {
    x: +(x * cos - z * sin).toFixed(3),
    z: +(x * sin + z * cos).toFixed(3)
  };
}

const start = { x: 1, z: 0 };
console.log('0 град:', rotateXZ(start.x, start.z, 0));
console.log('90 град:', rotateXZ(start.x, start.z, Math.PI / 2));
console.log('180 град:', rotateXZ(start.x, start.z, Math.PI));

Вывод:

0 град: { x: 1, z: 0 }
90 град: { x: 0, z: 1 }
180 град: { x: -1, z: 0 }

Точка (1, 0) при повороте на 90° уезжает в (0, 1), а на 180° — в (-1, 0). Подставляя в угол текущее время, вы получаете объект, который вечно кружит по орбите.

Итог

  • Нормализация даёт единичный вектор направления — основа движения с постоянной скоростью.
  • lerp(a,b,t) — плавный переход; t от 0 до 1.
  • Поворот в плоскости — через sin/cos; так делают орбиты и круговое движение.
Проверьте себя
1. Что делает нормализация вектора?
AУдлиняет вектор вдвое
BПриводит длину к 1, сохраняя направление — получается единичный вектор направления
CМеняет направление на противоположное
DОбнуляет вектор
2. Зачем нормализуют вектор направления перед движением?
AЧтобы объект двигался с постоянной скоростью независимо от исходной длины вектора
BЧтобы изменить цвет
CЭто требование синтаксиса
DЧтобы ускорить рендер
3. Что вычисляет линейная интерполяция lerp(a, b, t) при t = 0.5?
AЗначение a
BЗначение b
CСреднее между a и b (точку ровно посередине)
DСлучайное число
Поддержать проект