Математика в Defold. Часть 2: Эйлеры, радианы и кватернионы. Вращение объектов в трёхмерном пространстве

В этой статье вы узнаете как поворачивать объекты в Defold. В программирования нет понятия градусов. Для поворотов в двухмерных играх используют радианы. Однако, Defold — это полностью трёхмерный движок. Даже когда вы создаёте 2д игру, то используется трёхмерное пространство. В трёхмерных играх, в основном, используются кватернионы и расширенная версия углов Эйлера. Рассмотрим сначала углы Эйлера, радианы, кватернионы по отдельности, а потом способы поворота объектов и их преобразования.

Углы Эйлера

Начнём с наиболее простой для понимания темы — углы Эйлера. Углы Эйлера описывают последовательные вращения объекта вокруг трех осей координат (x, y, z). По сути это те же самые градусы, но в трёхмерном пространстве с указанием оси. Например, указанная система координат (0, 0, 90) поворачивает объект по оси Z на 90 градусов. В действительности, то, что мы используем в игровых движках на сегодняшний день, это расширенная версия Эйлеровых углов. Сам Леонард Эйлер описывал вращение объекта по двум осям с использованием трех переменных. Часто такой метод поворота описывают визуализацией юлы


Расширенная же версия углов Эйлера описывает поворот вокруг трех осей с использованием трех переменных.
Повороты эйлеровых углов задаются конкретной последовательностью трех чисел. То есть присваивание поворота конкретному объекту по каждой оси должно протекать в определенном порядке. Если мы хотим изменить поворот какого-либо объекта сразу по трем осям, то присвоение нужного поворота не пройдет за одну операцию. Из-за этого часто возникает известная проблема под названием gimbal lock — потеря одной степени свободы. При использовании эйлеров могут возникать ситуации, когда две оси будут совпадать, отчего мы потеряем поворот по одной из осей. Например, при повороте объекта на 90 градусов вокруг оси x, вращая далее объект по осям y и z мы заметим, что они идентично поворачивают объект, к чему и приводит особенность Эйлеровых углов.

Gimbal Lock возникает, когда две оси выравниваются, что приводит к потере независимой оси вращения

Однако здесь есть один крайне важный ньюанс: так или иначе Defold всё равно преобразовывает углы Эйлера в кватернионы. Сделано это для того, чтобы как раз-таки избегать проблемы шарнирного замка. Иначе говоря, мы можем задавать повороты углами Эйлера, однако “под капотом” Defold преобразует все в кватернионы во избежание проблемы.

Для базовых задач достаточно использовать углы Эйлера. Например, их используют для функции плавного поворота через go.animate()

function init(self)
    -- поворот по часовой стрелке на один полный оборот за две секунды
    go.animate(".", "euler.z", go.PLAYBACK_LOOP_FORWARD, -360, go.EASING_LINEAR, 2)
end

Однако помимо проблем с углами Эйлера, они также требуют больше вычислительных мощностей и ресурсов, чем кватернионы. Поэтому для поворотов в трёхмерных пространствах часто используют кватернионы, хоть и они более сложные в понимании.

Радианы

Прежде чем начать работать с кватернионами, надо понять радианы.
Как вы знаете, окружность делится на 360 частей, которые называются градусами.


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


В чем отличие от градуса? А в том, что угол образован реальной дугой окружности, а не мнимым делением на 360 кусков общей окружности.

Радианы и градусы легко переводятся друг в друга. Есть простое соотношение:


В Defold есть функция math.rad(), которая превращает градусы в радианы

print(math.rad(180)) -- 3.1415926535898

Радианы используются для вычисления кватернионов.

Кватернионы

Кватернионы — четырёхмерный вектор, описывающий поворот вокруг оси на заданный угол. В Defold он выражается так: vmath.quat(x, y, z, w). Первые 3 компонента — это три оси координат в трёхмерном пространстве или же мнимые части, а четвёртый компонент w — компонент, описывающий поворот или же вещественная часть. Кватернионы рассчитываются по этой формуле:

x = RotationAxis.x * sin(RotationAngle / 2)
y = RotationAxis.y * sin(RotationAngle / 2)
z = RotationAxis.z * sin(RotationAngle / 2)
w = cos(RotationAngle / 2)

где RotationAxis — ось вращения (вектор направления), а RotationAngle — угол вращения. Не пытайтесь воссоздать функцию вычисления кватернионов в Defold, там, на удивление, нет векторного направления. Но есть функции, которые как раз рассчитывают кватернионы по нужной оси: vmath.quat_rotation_x(), vmath.quat_rotation_y(), vmath.quat_rotation_z(). Они принимают радианы и возвращают рассчитанные кватернионы.

Интересно, почему нет векторного направления, но есть кватернионы? Defold, я требую полную свободу действий с векторами!

Пример:

go.set_rotation(vmath.quat_rotation_z(3.14)) -- поворот на 180 градусов

Поворот по оси z используют в 2д играх

Способы вращения объектов в Defold

Примеры поворотов с использованием эйлеров:

go.set(".", "euler.z", 90)
	--задаёт свойство объекта "поворот по оси z" на значение 90

Под капотом эйлеры всё равно преобразуются в кватернионы, даже если мы не используем функцию vmath.euler_to_quat(x, y, z).

go.set_rotation(vmath.euler_to_quat(0, 0, 90)) 
	--сначала эйлеры преобразовываются в кватернионы, 
	--а потом объект поворачивается на указанные кватернионы

Снизу пример, в котором никто ни во что не превращаются. Указаны чистые кватернионы.

go.set_rotation(vmath.quat(0, 0, math.sqrt(0.5), math.sqrt(0.5)))
-- поворот на все те же 90 градусов по оси Z

Пример ниже преобразовывает радианы с указанной осью в кватернионы

go.set_rotation(vmath.quat_rotation_z(1.5707963267949))
-- поворот на 90 градусов по оси Z

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

go.set_rotation(vmath.quat_rotation_z(math.rad(90)))

И вот, бонусом держите пример, в котором объект поворачивается в сторону курсора мыши:

function init(self)
	msg.post(".", "acquire_input_focus")
end

function on_input(self, action_id, action)
	if not action_id then
		local cursor_pos = vmath.vector3(action.x, action.y, 0)
		local pos = go.get_position()
		local angle = math.atan2(pos.x-cursor_pos.x, cursor_pos.y-pos.y)
		go.set_rotation(vmath.quat_rotation_z(angle))
	end
end

Спрайт должен смотреть вверх, иначе он будет поворачиваться не куда надо

Сообщение тем, кто прочитал статью от начала до конца

Если Вы прочитали статью от начала до конца и всё поняли, то поздравляю! Вы теперь знаете немного высшей математики :tada:
Говорят, чтобы понять высшую математику, надо сойти с ума. Но я так не думаю, кстати, вы теперь умеете делать такие штуки:

Башня танка из моей игры, сделанной в 2026 году

3 лайка

Привет, а что с вёрсткой текста?

Может я и педант(в хорошем смысле), AmazBega думаю при написании и публикации статьи хорошим тоном будет и её вёрстка(реально польза будет всем).


Хотелось бы в коде статьи видеть <h2> - заголовки

То что скрывается под спойлер хорошо бы помечать заголовками 2-го уровня


Подсветка синтаксиса кода

Не башем единым живы… Когда код в статье оформлен - это :fire: круто!
А можно пойти и ещё дальше: явно указывать ЯП

:backhand_index_pointing_right: ```lua

print(math.rad(180)) -- 3.1415926535898

:backhand_index_pointing_right: ```terminal

$ ls -la

:smiling_face_with_sunglasses: Почему акцентирую на этом внимание?

+ Очень большое преимущество перед неподготовленной не оптимизированной статьёй особенно для SEO (поисковая оптимизация для сайта)
+ Улучшение чи-та-бель-нос-ти


От внесённой правки будет больше плюсов

  • Дело конечно авторское, но такого рода правка не так затратна.

Ещё раз, с уважением и успехов в гемдеве!

2 лайка