Векторна математика

Вступ

Ця стаття — це короткий вступ до лінійної алгебри та опис способів її використання в розробці ігор. Лінійна алгебра — це наука про вектори та їх використання. Використовувати їх можна як в 2D, так і в 3D й Godot робить це дуже часто. Для того, щоб бути добрим програмістом, розуміти векторну математику просто необхідно.

Примітка

Ця стаття — це не підручник з лінійної алгебри. Ми лише розглянемо, як їх можна використовувати в прив'язці до розробки ігор. Для ширшого розуміння математики, погляньте на https://www.khanacademy.org/math/linear-algebra

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

У двовимірному просторі, координати описуються за допомогою горизонтальної (x) та вертикальної (y) осей. Певна точка в просторі описується як пара чисел. Наприклад: (4,3).

../../_images/vector_axis1.png

Примітка

Якщо ви раніше не працювали з комп'ютерною графікою, то вам може здатись дивним, що вісь y напрямлена вниз а не вгору, як зазвичай, в підручниках з математики. Проте, такий підхід поширений серед більшости графічних комп'ютерних програм.

Будь-яка точка на площині може бути визначена як пара чисел у такий спосіб. А ще, ми можемо уявляти точку (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)

Godot працює як з двовимірними, так і з тривимірними векторами. Математичні принципи, описані в цій статті підходять до обох типів.

Доступ до компонентів

Окремі компоненти вектора можна отримати напряму, по назві.

# 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

Додавання векторів

Коли додаються або віднімаються два вектори, додаються і їхні відповідні компоненти:

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)
../../_images/vector_mult1.png

Примітка

Множення на скаляр не змінює напрямку вектора — лише модуль. Таким чином ви можете відмасштабувати вектор.

Практичне застосування

Погляньмо на два найпоширеніші способи використання суми та різниці векторів.

Пересування

Вектор може позначати будь-що що має довжину та напрямок. Наприклад: положення, швидкість, прискорення й сила. На цьому зображенні, космічний корабель, на першому кроці, знаходиться в точці (1,3) та має вектор швидкості (2,1). Вектор швидкості показує, як сильно повинен зміститись корабель на кожному кроці. Ми можемо знайти його положення на другому кроці додавши вектор швидкості до його попереднього положення.

../../_images/vector_movement1.png

Порада

Швидкість визначає зміну положення за одиницю часу. Нове положення можна знайти додавши швидкість до старого положення.

Указування на ціль

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

../../_images/vector_subtract2.png

Порада

Щоб знайти вектор, який вказує від A до B використовуйте B - A.

Одиничні вектори

Вектор з довжиною 1 називається одиничним вектором. Їх ще іноді називають напрямними векторами чи нормалями. Одиничні вектори корисні, коли необхідно знати про напрямок без довжини.

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

Щоб нормалізувати вектор потрібно зменшити його величину до 1 але при цьому зберегти його напрямок. Зробити це можна поділивши кожен з його компонентів на його довжину. Так як це дуже поширена операція, і Vector2, і Vector3 мають метод для нормалізації:

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)

Скалярний добуток

Скалярний добуток — це одне з найважливіших понять у векторній математиці. Проте, його часто не розуміють. Скалярний добуток — це операція, яка бере два вектори, та повертає скаляр. На відміну від векторів, які мають модуль та напрямок, скаляр — це просто число, яке має лише модуль.

Є дві формули для обчислення скалярного добутку:

../../_images/vector_dot1.png

і

../../_images/vector_dot2.png

Хоча, найкращим рішенням буде використати вбудований метод. Запримітьте, що порядок векторів не має значення:

var c = a.dot(b)
var 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 та нормалізувавши його. До речі, Godot має допоміжний метод для цього, він називається direction_to. Якщо кут між цим вектором, і тим що вказує на напрямок зомбі менший ніж 90° — то зомбі бачить гравця.

У коді це виглядатиме ось так:

var AP = A.direction_to(P)
if AP.dot(fA) > 0:
    print("A sees P!")

Векторний добуток

Як і скалярний добуток, векторний добуток — це операція над двома векторами. Але результат векторного добутку — це ще один вектор, який направлений перпендикулярно до двох інших. Його довжина залежить від кута між ними. Якщо вектори паралельні — то результатом векторного добутку буде нульовий вектор.

../../_images/vector_cross1.png ../../_images/vector_cross2.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)

В Godot можна використати вбудований метод:

var c = a.cross(b)

Примітка

Для векторного добутку важливий порядок: a.cross(b) дасть інший результат аніж b.cross(a). Отримані вектори будуть дивитись у протилежні сторони.

Обчислення нормалей

Часто, векторний добуток використовують, щоб знайти нормаль площини чи поверхні у тривимірному просторі. Якщо у нас є трикутник ABC, то ми можемо використати різницю векторів щоб знайти два його ребра: AB і 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

Указування на ціль

У розділі про скалярний добуток ми побачили, як він може бути використаний для знаходження кута між векторами. Цього не достатньо для повороту в 3D. Нам також потрібно знати навколо якої осі обертатись. Ми можемо знайти її знайшовши векторний добуток між вектором який дивиться уперед і тим, що направлений на ціль. Отриманий перпендикуляр і буде віссю обертання.

Додаткова інформація

Дізнатись більше про використання векторів у Godot можна в наступних статтях: