Информация Страшное слово «кватернион» — это вообще что?

Vintik

Мечтатель
Автор темы
Проверенный
1,467
916
1711391442795.png

1711391451133.jpeg

Всем привет. Как всегда расскажут, что это бесполезная статья, как и про системы счисления, но всё же у меня остаётся надежда, что кто-то да задавался вопросом о кватернионах. И да, если нужно больше статьей по математике — пишите, могу разобрать всё, что вам нужно в рамках «адекватной» математики (например, тригонометрию могу разобрать для того, чтобы проще было вам связывать между собой углы и координаты, синусы-косинусы и прочее).

Значит, положение любого объекта в физике (и в компьютерных играх) задаётся неким вектором. В случае трехмерного пространства (3D) у нас трёхмерный вектор, то есть упорядоченный набор из 3 чисел. Пример: (2, 4.5, -82.11) — это пример координаты. Логика простая: у нас имеется три взаимно перпендикулярных оси (обычно XYZ), и на каждой оси отмечена «единица». Ну и мы стартуем в начале координат и двигаемся вдоль направлений оси на столько этих «единиц» (единичных отрезков), на сколько у нас указано в векторе. Просто для понимания воспринимать следующие направления осей: вверх-вниз, вправо-влево, вперёд-назад. И тогда ясно, что любое положение соответствует какому-либо вектору, а любой вектор соответствует какому-то положению. Более того, разные вектора (координаты) соответствуют разным положениям.

И вот это называется в математике «биекция», где мы можем между любой позицией и координатой поставить взаимно однозначное соответствие (то есть можем по координате определить позицию и по позиции определить координату). Именно этого эффекта учёные постарались достичь и при вращении объектов. Если вы задумаетесь над вопросом «а как можно с помощью чисел записать вращение объекта?» — то поймёте, что всё не так просто.
В голову приходит метод вращения вдоль осей (и записывать углы, на которые мы поворачиваем), типа вот так:
Rotating_gimbal-xyz.gif

Но вы тут поймёте достаточно быстро, что порядок вращения имеет значение (причём неважно — у вас внутренние оси вращаются с объектом или они закреплены на месте), давайте покажу:
1711392825962.png
1711393053084.png
В первом случае оси у нас «прикреплены» к объекту, во втором — оси остаются на месте. И, как вы можете видеть, что в том, что в другом случае мы получаем разные картинки. То есть поворачивать надо в чётко установленном порядке. Из-за этого возникает масса неудобств, например, если мы изменили ТОЛЬКО угол вращения по 1 оси — нам надо назад вернуть объект в исходное состояние, повернуть его на новый угол по 1 оси и только после этого повернуть на сохранённые углы по 2 и 3 оси. И это крайне неочевидно, типа, мы хотим повернуть, и совсем неочевидно куда нам надо повернуть угол по 1й оси, чтобы после поворотов по 2 и 3 оси он занял то положение, которое мы хотим. Тяжело понять как он поведёт после этих двух поворотов.
Еще одна проблема — если мы будем плавно вращать объект, то вполне возможна ситуация, что углы (это называется углы Эйлера) в какой-то момент резко скакнут, поэтому не выполняется непрерывность, что в математике обычно неприятно (матанализ вообще весь построен на принципе непрерывности). Из этого вытекает следующая проблема: если мы будем вращать объект в той плоскости, в которой мы захотим — непонятно как считать эти углы. С таким подходом мы можем разве что вращать объект в трёх осях (X, Y, Z), и то не в произвольном порядке, что сильно ограничивает нас и сужает круг возможностей (например, при разработке игры при ударе об кочку хотелось бы иметь возможность начать вращать машину так, чтобы задняя часть переворачивалась, но понятное дело, что угол, под которым мы будем подъезжать к кочке, всегда разный, поэтому такой вариант работы [через углы по осям] нам не подходит аж никак).

И спасение есть! Это кватернионы, которые использует GTA SA в том числе (в общем то это единственный известный мне метод, я не думаю, что кто-то использует что-то другое).

То, что это гиперкомплексные числа, вы прочитали, наверное 😀. Но это не помогает разобраться а как же с ними работать и что это такое? Если вкратце, то это число вида «комплексное от комплексного», оно имеет следующий вид:
a + bi + cj + dk, где i, j, k — мнимые единицы
Первое правило:
i² = j² = k² = ijk = -1, причём между собой они НЕ равны (это разные буквы и обозначают они разные числа!)
А связь между ними такая:
ij = k; jk = i; ki = j; ji = -k; kj = -i; ik = -j;
img-I8titH.png
Если идём по кругу по часовой стрелке (как указано стрелочками), то произведение двух «букв» (в том порядке, в котором они идут!) равно следующему встречающемуся после них. А если идти в обратном направлении, то добавляется знак минус. То есть ij = -ji, порядок умножения имеет значение!

Не то, чтобы в этом есть какой-то смысл, но Гамильтон, который их изобрёл, как я понимаю, просто подумал «ну вот а что будем, если мы сделаем такие интересные числа?» — и оказалось, что они полезны. Они обладают нужными математическими свойствами (не будем вдаваться в поля и прочее...)
Вот стандартная функция получения кватерниона т/с:
float x, float y, float z, float w = getVehicleQuaternion(Vehicle car)
Так вот x, y, z и w — это и есть кватернион w + x * i + y * j + z * k.

Теперь давайте поговорим о вращении и вообще что связывает эти страшные числа с положениями (поворотами) объектов?
Всегда надо рассматривать более простые варианты для понимания, поэтому посмотрим повороты в плоскости. (хотел написать я, а потом понял, что так не получается)
Во-первых, введём понятие вектор нормали.
1711403757758.png
Допустим, мы поворачиваем коричневый объект «в горизонтальной плоскости» (в жёлтой). Если мы проведём вектор, перпендикулярный данной плоскости (в нашем случае — красный), то при повороте он вообще не вращается (если мы проведём другой вектор, параллельный красному, то он будет менять положение, но при этом смотреть он будет в ту же сторону, т.е. его угол поворота меняться не будет). Представьте, что мы эту жёлтую подставку вместе с красным вектором приклеили к нашему коричневому объекту, и потом начали пальцем крутить красный вектор (ну как спицу крутят в руках). Коричневый объект повернётся так, как показано на рисунке.

Так вот вот этот перпендикулярный вектор называется «нормалью к плоскости». У любой плоскости перпендикулярный вектор называется нормалью, она (нормаль) всегда существует и, более того, её легко задать с помощью XYZ-координат вектора. Вращение в плоскости = вращение нормали к этой плоскости как спицы в руках, если совсем наглядно говорить (при таком вращении нормаль вообще не двигается). В дальнейшем плоскость, относительно которой мы вращаем, будем задавать именно этим вектором.

Следующий интересный факт, который надо осознать: любое вращение (переход из любого положения в любое) можно осуществить с помощью вращения всего в одной плоскости. В большинстве случаев это тяжело представить в голове, именно поэтому я начал говорить о нормалях и прочем. Вот простая картинка. Коричневый и зелёный вектора здесь просто для наглядности, чтобы вы понимали в какое положение переходит этот прямоугольник. А вот синий вектор (на картинке справа — вниз, влево, вперед; на картинке слева он же — влево, вперёд, вниз) при таком вращении, как вы можете заметить, не меняется. А это значит, что это нормаль к нашей плоскости! Саму плоскость я рисовать не стал, так как она под углом 45 градусов и крайне неудобно смотрелась бы...
1711404205280.png


Значит теперь, как вы понимаете, любое вращение можно задать просто задав вектор нормали и угол, на который мы поворачиваем. Ну, строго говоря, у нас еще есть начальное состояние (это так как модельку нарисовал моделлер), а дальше мы её уже крутим-вертим.
Так вот кватернион это и обозначает. Если мы вращаем объект на угол α «в плоскости», нормаль которой имеет координаты (x, y, z), то кватернион данного вращения будет иметь вид:
q = cos(α / 2) + sin(α / 2) * (x * i + y * j + z * k)

И в чём же прикол — спросите вы?

Ну, во-первых, вы можете перевернуть машину. Давайте попробуем... выбираем любую плоскость, параллельную вертикальной оси, и выписываем координаты вектора нормали:
1711405921847.png
Мне лично такая плоскость нравится. И угол поворота — 180 градусов, естественно, вверх ногами. Давайте выпишем кватернион. И да, этот вектор должен быть длины 1 для того, чтобы выполнялись математические преобразования, не буду вдаваться в их подробности. Разделим каждую координату вектора на его модуль: (1 / √2; -1 / √2; 0).
q = cos(90) + sin(90) * (x * i + y * j + z * k) = 0 + 1 * (x * i + y * j + z * k) = i / √2 - j / √2, а всё остальное сократится
Ну и в виде кода Lua:
Lua:
setVehicleQuaternion(car, 0.707, -0.707, 0, 0)
Примечание: 0.707 ~= 1 / √2
1711879724524.png

Во-вторых, мы можем вращать последовательно. Сначала повернули в одной плоскости, потом в другой, потом в третьей. Для этого нужно просто перемножать кватернионы.
Давайте перевёрнутую машину попробуем опять поставить на колёса, для этого повернём её же еще раз на 180 градусов в той же плоскости.
И так, мы имеем:
(i / √2 - j / √2) * (i / √2 - j / √2) = 1/2 * (i - j)² = 1/2 * (i² - ij - ji + j²) = (-1 - k - (-k) - 1) / 2 = -1 = -1 + 0i + 0j + 0k.
Так, итого надо в виде кода записать:
Lua:
setVehicleQuaternion(car, 0, 0, 0, -1)
Перевернули назад!
1711879698852.png

Поворот вектора на кватернион

Допустим, у нас есть вектор (x, y, z). Мы хотим повернуть его на кватернион q. Для этого нам надо использовать формулу: v' = q * v * q'. Здесь v — вектор, v' — вектор после поворота, q — кватернион (который описывает наш поворот), q' — сопряжённый кватернион.
Сопряжённый кватернион для кватерниона a + bi + cj + dk — это кватернион a - bi - cj - dk. Просто надо знаки перед i, j, k заменить на противоположные.
Давайте это всё на примере. У нас есть три оси: X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1). Давайте мы будем поворачивать их на кватернион автомобиля: картинка слева (первая) — без поворота, картинка справа — с поворотом на кватернион авто.
Новый проект.gif
Новый проект 2-min.gif

(если картинки не показываются, то вот первая, а вот вторая)
В виде кода Lua это выглядит вот так:
Функции:
function quaternion_multiply(a, b, c, d,    w, x, y, z)
    return a * w - b * x - c * y - d * z,    a * x + b * w + c * z - d * y,    a * y - b * z + c * w + d * x,    a * z + b * y - c * x + d * w
end

function rotate_vector_on_quaternion(x, y, z,    a, b, c, d)
    -- Вектор v переходит в v' по повороте на кватернион q вот так: v' = q * v * q'
    local a1, b1, c1, d1 = quaternion_multiply(a, b, c, d,    0, x, y, z)
    local a2, b2, c2, d2 = quaternion_multiply(a1, b1, c1, d1,    a, -b, -c, -d)
    return b2, c2, d2
end
Lua:
x, y, z, w = getVehicleQuaternion(car)
local ax, bx, cx = rotate_vector_on_quaternion(1, 0, 0,    w, x, y, z)
local ay, by, cy = rotate_vector_on_quaternion(0, 1, 0,    w, x, y, z)
local az, bz, cz = rotate_vector_on_quaternion(0, 0, 1,    w, x, y, z)

local carX, carY, carZ = getCarCoordinates(car)
local wposX1, wposY1 = convert3DCoordsToScreen(carX, carY, carZ)

local wposX2, wposY2 = convert3DCoordsToScreen(carX + ax, carY + bx, carZ + cx)
renderDrawLine(wposX1, wposY1, wposX2, wposY2, 5.0, 0xFFFFFFFF)
local wposX2, wposY2 = convert3DCoordsToScreen(carX + ay, carY + by, carZ + cy)
renderDrawLine(wposX1, wposY1, wposX2, wposY2, 5.0, 0xFFFFFFFF)
local wposX2, wposY2 = convert3DCoordsToScreen(carX + az, carY + bz, carZ + cz)
renderDrawLine(wposX1, wposY1, wposX2, wposY2, 5.0, 0xFFFFFFFF)

А теперь кто-то да сможет пофиксить этот долбанный редактор объектов самп, в котором крутишь оси, а нихера не происходит, потому что реализован по тупому)
Постараюсь сам заняться как будет мотивация 😀
И еще вот это моё сообщение причитайте. Там краткий итог статьи.
 
Последнее редактирование:

GrezeeBal

Известный
Проверенный
1,475
878
И да, если нужно больше статьей по математике — пишите, могу разобрать всё, что вам нужно в рамках «адекватной» математики (например, тригонометрию могу разобрать для того, чтобы проще было вам связывать между собой углы и координаты, синусы-косинусы и прочее).
давай. Было бы неплохо еще и привести конкретные примеры, где можно использовать такие знания
 
  • Нравится
Реакции: #Northn, Vintik и why ega

Vintik

Мечтатель
Автор темы
Проверенный
1,467
916
Было бы неплохо еще и привести конкретные примеры, где можно использовать такие знания
Конкретно здесь я привёл как минимум два примера:
например, при разработке игры при ударе об кочку хотелось бы иметь возможность начать вращать машину так, чтобы задняя часть переворачивалась, но понятное дело, что угол, под которым мы будем подъезжать к кочке, всегда разный, поэтому такой вариант работы [через углы по осям] нам не подходит аж никак
А теперь кто-то да сможет пофиксить этот долбанный редактор объектов самп, в котором крутишь оси, а нихера не происходит, потому что реализован по тупому)
Постараюсь сам заняться как будет мотивация 😀
Хотел как-то подводную лодку запилить. Ну и там тоже есть крен, рыскание, дифферент (в авиации — тангаж). Надо иметь возможность вращать субмарину во всех трёх осях так, чтобы выглядело реалистично. Тоже может быть крайне полезным.
Тут рассказ о том, что такое кватернион и где он используется в гта-шке — дальше дело фантазии скриптеров и разработчиков.
Как будет время — расскажу про тригонометрию основы, чтобы ребята могли сами считать расстояния, скорости, складывать вектора, умножать, самостоятельно написать аирбрэйк и понять куда надо добавлять координаты при таком-то угле поворота камеры. Но в ближайшем будущем вряд-ли, если честно. Работа)
И вот в этом скрипте если бы её использовали — он бы выглядел реалистично 😀
 
  • Нравится
Реакции: kyrtion и GrezeeBal

Vintik

Мечтатель
Автор темы
Проверенный
1,467
916
менее страшным оно не стало
Понимаю, всё достаточно страшно. Вкратце:
1. «Углы Эйлера» (углы по осям) очень непрактичны, нужно использовать другое решение.
2. Оказывается, вращение из любого положения в любое можно сделать, используя всего одно вращение вокруг оси (нормали к плоскости).
3. Кватернион хранит в себе информацию об оси и об угле вращения вокруг данной оси (в каком направлении отсчитывается этот угол – это сложно).
Если мы вращаем объект на угол α «в плоскости», нормаль которой имеет координаты (x, y, z), то кватернион данного вращения будет иметь вид:
q = cos(α / 2) + sin(α / 2) * (x * i + y * j + z * k)
4. Если мы хотим повернуть вокруг одной оси, а потом вокруг другой и так далее – нам надо просто умножить соответствующие кватернионы. Например:
I. Вращение вокруг оси X на 90 градусов: q₁ = 1/√2 + 1/√2 * (1 * i + 0 * j + 0 * k) = 1/√2 + 1/√2 * i
II. Вращение вокруг оси Y на 90 градусов: q₂ = 1/√2 + 1/√2 * (0 * i + 1 * j + 0 * k) = 1/√2 + 1/√2 * j
=> Вращение сначала вокруг оси X на 90 градусов, а потом вокруг оси Y на 90 градусов – это просто произведение двух кватернионов (в математике это называется композицией, то есть последовательным применением двух функций, в нашем случае — вращение уже повёрнутого объекта):
q₃ = q₁ * q₂ = (1/√2 + 1/√2 * i) * (1/√2 + 1/√2 * j) = 1/2 * (1 + i) * (1 + j) = 1/2 * (1 + j + i + ij) = 1/2 + 1/2 * i + 1/2 * j + 1/2 * k
То есть мы сначала повернули вокруг одной оси на 90 градусов, потом вокруг другой тоже на 90 градусов – а на самом деле этот же результат можно было получить с помощью всего одного поворота вокруг какой-то оси на какой-то угол. И вот этот посчитанный нами кватернион q₃ и хранит информацию об этой оси и об этом угле поворота.
5. На Lua поворот на кватернион реализовывается с помощью функций (тут x, y, z – это числа у i, j, k соответственно, w – вещественная часть, «без буквочки»):
Lua:
float x, float y, float z, float w = getCharQuaternion(Ped ped)
float x, float y, float z, float w = getObjectQuaternion(Object object)
float x, float y, float z, float w = getVehicleQuaternion(Vehicle car)
Lua:
setCharQuaternion(Ped ped, float x, float y, float z, float w)
setObjectQuaternion(Object object, float x, float y, float z, float w)
setVehicleQuaternion(Vehicle car, float x, float y, float z, float w)
6. Это можно использовать как тебе будет угодно. Одно из применений — пофиксить стандартный редактор объектов SA-MP, который реализован через углы Эйлера и поэтому работает некорректно.

Если есть более конкретные вопросы — пиши.
 
Последнее редактирование:

MrCreepTon

وНеизвестный
Всефорумный модератор
2,127
4,713
Спасибо за статью, было интересно почитать. Жду ещё чего-нибудь интересного)

На мой взгляд стоит вынести тему из "общение", ибо я её вообще случайно нашёл попутно пытаясь найти что-то другое
 

Vintik

Мечтатель
Автор темы
Проверенный
1,467
916
На мой взгляд стоит вынести тему из "общение", ибо я её вообще случайно нашёл попутно пытаясь найти что-то другое
Я не против. Перемести тему в тот раздел, где будет легче её найти. Я просто не знал где создать)
 

kyrtion

Известный
643
238
думал что кватернион, используются для радиуса. но когда начал понимать, что третье значение ну зачем оно нам нужен))
в раньше что я считал:
x, y, w. первое и второе повороты, а третье это уже размер (мб радиус?)
вышло не тот результат который я хотел ожидать.

после статьи теперь начинаю понимать как этот кватернион работает. ну ты бро, спасибо!
 
  • Нравится
Реакции: Vintik