Все статьи / Класс math::Transform2D


Класс math::Transform2D находится в libmath и представляет аффинную трансформацию, состоящую из масштабирования, ориентирующего поворота и перемещения объекта.

В динамичной программе матрица трансформации объекта меняется на каждом кадре. Если вы храните матрицу, то будет трудно изменить отдельный компонент, не затронув остальные. Например, если матрица содержит translate, то любой rotate будет выполняться относительно начала координат, а не центра фигуры.

Удобнее хранить и изменять исходные компоненты трансформации. Обычно хватает трёх компонентов:

  • масштабирующий scale
  • ориентирующий rotate
  • задающий позицию translate

Компоненты накапливаются по отдельности: если вы добавили к трансформации вращение (rotate), оно никак не повлияет на последующие добавления смещения позиции (translate), и наоборот.

Определение класса

#pragma once
#include <glm/fwd.hpp>
#include <glm/vec2.hpp>

namespace math
{
// Преобразует координаты из локальных в мировые в следующем порядке:
//  - сначала вершины масштабируются
//    например, единичный круг может превратиться в эллипс
//  - затем поворачиваются
//    т.е. фигуры ориентируются на плоскости
//  - затем переносятся
//    т.е. задаётся положение тела
// изменив порядок, мы изменили бы значение трансформаций.
struct Transform2D
{
public:
	// Позиция фигуры относительно центра мира.
	glm::vec2 position{ 0, 0 };

	// Угол собственного поворота фигуры в радианах.
	float orientation{ 0 };

	// Множители размера фигуры по двум осям.
	glm::vec2 size{ 1, 1 };

	// Добавляет вращение на заданное число радиан.
	void rotateBy(float radians);

	// Добавляет масштабирование с указанными множителями масштабирования для двух осей.
	void scaleBy(const glm::vec2& scale);

	// Добавляет масштабирование с указанным множителем масштабирования.
	void scaleBy(const float scale);

	// Добавляет перемещение на указанное расстояние.
	void moveBy(const glm::vec2& distance);

	// Создаёт матрицу 3x3 для выполнения аффинного преобразования, эквивалентного текущему состоянию класса.
	glm::mat3 toMat3() const;

	// Создаёт матрицу 4x4 для выполнения аффинного преобразования, эквивалентного текущему состоянию класса.
	glm::mat4 toMat4() const;
};
}

Реализация

Реализовать изменение трансформации очень просто:

void Transform2D::rotateBy(float radians)
{
	this->orientation += radians;
}

void Transform2D::scaleBy(const glm::vec2& scale)
{
	this->size *= scale;
}

void Transform2D::scaleBy(const float scale)
{
	this->size *= scale;
}

void Transform2D::moveBy(const glm::vec2& distance)
{
	this->position += distance;
}

Для превращения Transform2D в матрицу трансформации надо выполнить несколько умножений матриц.

Компоненты трансформации применяются в строго определённом порядке, при изменении которого компоненты потеряют свой текущий смысл — например, компонент поворота, применённый после компонента перемещения, перестанет быть ориентацией фигуры и станет поворотом вокруг центра системы координат.

Так выглядит преобразование класса в матрицу 3x3, удобную для трансформаций в 2D пространстве:

// Метод использует расширение GLM_GTX_matrix_transform_2d
// См. https://glm.g-truc.net/0.9.9/api/a00201.html
glm::mat3 Transform2D::toMat3() const
{
	glm::mat3 mat;
	mat = glm::translate(mat, position);
	mat = glm::rotate(mat, orientation);
	mat = glm::scale(mat, size);

	return mat;
}

Так выглядит преобразование класса в матрицу 3x3, удобную для трансформаций в 3D пространстве или для передачи в шейдеры на языке GLSL:

// Метод использует расширения GLM_GTC_matrix_transform
// См. https://glm.g-truc.net/0.9.9/api/a00157.html
glm::mat4 Transform2D::toMat4() const
{
	glm::mat4 mat;
	mat = glm::translate(mat, { position.x, position.y, 0 });
	mat = glm::rotate(mat, orientation, glm::vec3(0, 0, 1));
	mat = glm::scale(mat, { size.x, size.y, 1 });

	return mat;
}