Векторная алгебра

Введение

Этот урок - короткое и практичное введение в линейную алгебру, применяемую в разработке игр. Линейная алгебра изучает векторы и их использование. Векторы могут применяться в 2D и 3D разработке и Godot использует их интенсивно. Разработчику игр требуется хорошее понимание векторной алгебры чтобы стать сильным в этой области.

Примечание

Этот урок не обычная книга по линейной алгебре. Мы рассмотрим только то что применяется в разработке игр. Для более широкого взгляда на математику, смотрите https://www.khanacademy.org/math/linear-algebra

Системы координат (2D)

В 2D пространстве, координаты определены использованием горизонтальной оси (x) и вертикальной оси (y). Определённая позиция в 2D пространстве записывается как пара значений, таких как (4, 3).

../../_images/vector_axis1.png

Примечание

Если вы новичок в компьютерной графике, это может показаться странным что положительная ось y указывает вниз вместо вверх, как вас учили в школе. Хотя, это распространено в большинстве компьютерных графических приложениях.

Любая позиция на 2D плоскости может быть определена парой чисел по этому определению. Хотя, вы можете принять что позиция (4, 3) это сдвиг от точки (0, 0), или начальной точки координат. Нарисуем стрелку от начальной точки до заданной точки:

../../_images/vector_xy1.png

Это вектор. Вектор представляет множество полезной информации. Помимо сообщения нам что точка расположена в (4, 3), мы можем также представить это в виде угла θ и длины(или величины(магнитуды)) m. В данном примере, стрелка это вектор позиции - он обозначает позицию в пространстве, относительно начала координат.

Очень важным пункт в понимании векторов, это то что они только представляют только относительные направление и магнитуду. Нет понятия векторной позиции. Два следующих вектора идентичны:

../../_images/vector_xy2.png

Оба вектора представляют точку в 4 пункта вправо и 3 пункта ниже стартовой точки. Не важно где на плоскости вы нарисуете вектор, он всегда представляет относительное направление и магнитуду.

Операции над векторами

Вы можете любой из методов (установка x и y координат или угла с длиной) для определения вектора, но обычно программисты используют установку координат. Для примера, в Godot начало координат это верхний-левый угол экрана, так что для перемещения 2D ноды с именем Node2D на 400 пикселей вправо и 300 вниз, используйте следующий код:

$Node2D.position = Vector2(400, 300)
var node2D = (Node2D) GetNode("Node2D");
node2D.Position = new Vector2(400, 300);

Godot поддерживает типы Vector2 и Vector3 для 2D и 3D соответственно. Математические правила, рассказанные в этой статье, применяются к обоим типам.

  • Доступ к полям

К отдельным компонентам вектора можно обращаться непосредственно по имени.

# create a vector with coordinates (2, 5)
var a = Vector2(2, 5)
# create a vector and assign x and y manually
var b = Vector2()
b.x = 3
b.y = 1
// create a vector with coordinates (2, 5)
var a = new Vector2(2, 5);
// create a vector and assign x and y manually
var b = new Vector2();
b.x = 3;
b.y = 1;
  • Сложение векторов

Когда два вектора складываются или вычитаются, складываются соответствующие компоненты :

var c = a + b  # (2, 5) + (3, 1) = (5, 6)
var c = a + b;  // (2, 5) + (3, 1) = (5, 6)

Мы также можем посмотреть визуально на добавление второго вектора к концу первого:

../../_images/vector_add1.png

Отметьте что сложение a + b даёт такой же результат что и b + a.

  • Скалярное перемножение

Примечание

Векторы представляют направление и длину(магнитуду). Значение представленное только длиной называется скаляром.

Вектор может быть умножен на скаляр:

var c = a * 2  # (2, 5) * 2 = (4, 10)
var d = b / 3  # (3, 6) / 3 = (1, 2)
var c = a * 2;  // (2, 5) * 2 = (4, 10)
var d = b / 3;  // (3, 6) / 3 = (1, 2)
../../_images/vector_mult1.png

Примечание

Умножение вектора на скаляр не меняет направление, только длину. Это как бы увеличение вектора.

Практические применения

Давайте посмотрим на два обычных приёма использования для векторного сложения и вычитания.

  • Движение

Вектор может представлять любую длину и направление. Обычные примеры это: позиция, скорость, ускорение, и сила. На этом изображении, космический корабль на шаге 1 имеет вектор позиции (1,3) а вектор скорости (2,1). Вектор скорости представляет как далеко пойдёт корабль на каждом шаге. Вы можете найти его позицию на шаге 2 добавляя скорость к текущей позиции.

../../_images/vector_movement1.png

Совет

Скорость вычисляет изменение позиции за единицу времени. Новая позиция находится добавление скорости к предыдущей позиции.

  • Направление в сторону цели

В этом примере, вы, управляя танком, хотите направить дуло на робота. Вычитание позиции танка из позиции робота, даёт вектор направленный от танка на робота.

../../_images/vector_subtract2.png

Совет

Для нахождения вектора направления от A до B используйте B - A.

Единичные векторы

Вектор с длиной равной 1, называется единичным вектором. Они также иногда называются векторами направления или нормалями. Единичные векторы полезны когда вам нужно сохранить трэк направления.

Нормализация

Под нормализацией вектора подразумевается уменьшения его длины до 1 с сохранением его направления. Это может быть достигнуто делением каждого из его компонентов на длину:

var a = Vector2(2, 4)
var m = sqrt(a.x*a.x + a.y*a.y)  # get magnitude "m" using the Pythagorean theorem
a.x /= m
a.y /= m
var a = new Vector2(2, 4);
var m = Mathf.Sqrt(a.x*a.x + a.y*a.y);  // get magnitude "m" using the Pythagorean theorem
a.x /= m;
a.y /= m;

Поскольку это настолько обыденная операция, Vector2 и Vector3 имеют встроенный метод для нормализации:

a = a.normalized()
a = a.Normalized();

Предупреждение

Поскольку нормализация подразумевает деление на длину вектора, вы не можете нормализовать вектор с длиной равной 0. Попытка сделать это закончится ошибкой.

Отражение

Обычный пример использования единичных векторов это определение нормалей. Векторы нормалей - это единичные векторы направленные перпендикулярно поверхности, определяющей их направление. Они обычно используются в обработке света, коллизий, и других операциях с поверхностями.

Например, представьте что вы движете шар, который вы хотите отражать от стен или других объектов:

../../_images/vector_reflect1.png

Нормаль плоскости имеет значение (0, -1) поскольку это горизонтальная поверхность. Когда шар сталкивается , мы берём оставшийся момент движения (количество оставшихся шагов когда он ударяется об плоскость) и отражаем его используя нормаль. В Godot, класс Vector2 имеет метод bounce() для вычисления этого. Приведём пример диаграммы показанной выше на GDScript используя KinematicBody2D:

# object "collision" contains information about the collision
var collision = move_and_collide(velocity * delta)
if collision:
    var reflect = collision.remainder.bounce(collision.normal)
    velocity = velocity.bounce(collision.normal)
    move_and_collide(reflect)
// KinematicCollision2D contains information about the collision
KinematicCollision2D collision = MoveAndCollide(_velocity * delta);
if (collision != null)
{
    var reflect = collision.Remainder.Bounce(collision.Normal);
    _velocity = _velocity.Bounce(collision.Normal);
    MoveAndCollide(reflect);
}

Скалярное произведение

Результат скалярного произведения очень важный аспект в векторной алгебре, но его также часто плохо понимают. Скалярное произведение это операция над двумя векторами которая возвращает скаляр. В отличии от вектора, который содержит длину и направление, скаляр содержит только длину.

Формула скалярного произведения имеет две распространённых формы:

A \cdot B = \left \| A \right \|\left \| B \right \|\cos \Theta

и

A \cdot B = A_{x}B_{x} + A_{y}B_{y}

Также, в большинстве случаев лучше использовать встроенный метод. Обратите внимание, что порядок двух векторов не имеет значения:

var c = a.dot(b)
var d = b.dot(a)  # these are equivalent
float c = a.Dot(b);
float d = b.Dot(a);  // these are equivalent

Скалярное произведение очень полезно в использовании с единичными векторами, сокращая первую формулу до cosθ. Это означает что мы можем использовать скалярное произведение чтобы получить угол между двумя векторами:

../../_images/vector_dot3.png

Когда используются единичные вектора, результат будет всегда между -1 (180°) и``1`` (0°).

Направление взгляда

Мы можем использовать этот факт для обнаружения, что объект смотрит в направлении другого объекта. На диаграмме ниже, игрок P пытается избежать взгляда зомби A и B. Могут ли зомби его увидеть если их угол обзора равен 180° ?

../../_images/vector_facing2.png

Зелёные стрелки fA и fB это единичные векторы представляющие направление взгляда зомби, а синий полукруг представляет их поле обзора. Для зомби A, мы находим направление вектора AP направленного на игрока использующего P - A и нормализующего его. Если угол между этим вектором и вектором взгляда меньше 90°, то зомби увидит игрока.

В коде это бы выглядело как:

var AP = (P - A).normalized()
if AP.dot(fA) > 0:
    print("A sees P!")
var AP = (P - A).Normalized();
if (AP.Dot(fA) > 0)
{
    GD.Print("A sees P!");
}

Векторное произведение

Также как и скалярное произведение, векторное произведение это операция над двумя векторами. Но в результате векторного произведения вы получаете вектор с направлением перпендикулярным обоим исходным векторам. Его длина зависит от их относительного угла. Если два вектора параллельны, в результате вы получите нулевой вектор.

\left \|a \times b  \right \| = \left \| a \right \|\left \| b \right \|\ |\sin(a,b)|

../../_images/tutovec16.png

Векторное произведение вычисляется так:

var c = Vector3()
c.x = (a.y * b.z) - (a.z * b.y)
c.y = (a.z * b.x) - (a.x * b.z)
c.z = (a.x * b.y) - (a.y * b.x)
var c = new Vector3();
c.x = (a.y * b.z) - (a.z * b.y);
c.y = (a.z * b.x) - (a.x * b.z);
c.z = (a.x * b.y) - (a.y * b.x);

В Godot, вы можете использовать встроенный метод:

var c = a.cross(b)
var c = a.Cross(b);

Примечание

В векторном произведении, порядок аргументов важен. a.cross(b) не даёт такого же результата что и b.cross(a). Получаемые векторы имеют противоположные направления.

Расчитывание нормалей

Одно из применений для векторного произведения - это нахождение нормалей плоскости или поверхности в 3D пространстве. Если мы имеем треугольник ABC мы можем использовать векторное вычитание для нахождения граней AB и AC. Используя векторное произведение, AB x AC возвращает вектор перпендикулярный обоим векторам: нормаль поверхности.

Здесь показана функция для вычисления нормали треугольника:

func get_triangle_normal(a, b, c):
    # find the surface normal given 3 vertices
    var side1 = b - a
    var side2 = c - a
    var normal = side1.cross(side2)
    return normal
Vector3 GetTriangleNormal(Vector3 a, Vector3 b, Vector3 c)
{
    // find the surface normal given 3 vertices
    var side1 = b - a;
    var side2 = c - a;
    var normal = side1.Cross(side2);
    return normal;
}

Направление на цель

В скалярном произведении выше, мы видели как оно может использоваться для нахождения угла между двумя векторами. Однако для 3D этого недостаточно. Мы также должны знать вокруг какой оси нужно осуществлять вращение. Мы можем найти её выполняя векторное произведение текущего направления взгляда и направлением цели. В результате мы получим перпендикулярный вектор оси вращения.

Дополнительная информация

Для большей информации об использовании векторной алгебры в Godot, смотрите следующие статьи: