Skip to content

Преобразования координат для 1-, 2-, 3-мерных объектов их их подпространств. Подходит для графических вычислений.

Notifications You must be signed in to change notification settings

optozorax/space_objects

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Space Objects - библиотека для работы с объектами, представляющими собой пространства или подпространства.

doc/logo.png

В этой библиотеке представлены такие объекты, как: вектора, линии, плоскости, 2-х и 3-х мерные пространства. Пространства ненулевой размерности (всё, кроме векторов) имеют координаты на этом пространстве и обладают методами для преобразования координат из абсолютных в относительные конкретного пространства.

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

Обзор библиотеки

Всё находится в пространстве имен spob:: (сокращённо от SPace OBjects).

Для работы всей библиотеки достаточно подключить <spob/spob.h>.

Краткий обзор классов:

  • vec2 - двумерный вектор.
  • vec3 - трехмерный вектор.
  • crd2 - двумерный система координат с центром, базовый класс для следующих двух объектов.
  • space2 - двумерное пространство.
  • line2 - прямая, одномерное подпространство space2.
  • crd3 - трехмерная система координат с центром, базовый класс для следующих трех объектов.
  • space3 - трехмерное пространство.
  • plane3 - плоскость, двумерное подпространство space3.
  • line3 - прямая, одномерное подпространство space3.

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

Так же в данной библиотеке под фразой "система координат" не понимаются полярные, сферические системы координат. Здешние координаты имеют только такие параметры как наклон, поворот и смещение. Пример систем координат можно увидеть на первой картинке. Они обозначены как два вектора красного и синего цвета. Причем под красным цветом подразуемается ось первого компонента вектора, а под синим ось второго.

Из-за того, что системы координат обладают смещением, не удалось обойтись просто методами преобразования из одной системы координат к другой, пришлось разделять методты для преобразования векторов положения и векторов направления. Методы, не имеющие постфикса Dir преобразуют координаты вектора положения, а методы, имеющие этот постфик, преобразуют координаты вектора направления. То есть для методов Dir по сути не играет роль, как смещена система координат, при использовании этих метода можно сказать что все системы координат находятся в одной точке.

Методы этих классов:

  • .to() - преобразование вектора положения из абсолютных координат к координатам объекта, от которого этот метод вызывается.
  • .toDir() - тоже самое, но преобразуется вектор направления, то есть положение координат никак не влияет на это преобразование.
  • .from() - преобразование вектора положение из координат текущего объекта к абсолютным координатам.
  • .fromDir() - аналогично toDir(), но только с from().

Основные функции для этих классов:

  • distance(a, b) - расстояние между объектами a и b, оно рассчитывает расстояние между: векторами, прямыми, плоскостями, прямыми с плоскостями, векторами с прямыми и т. д. Так же корректно считает расстояние между трехмерными прямыми, когда они скрещиваются и во всех остальных случаях.
  • rotate(c, around, angle) - вращает некоторый объект c относительно точки around (в двумерном случае), относительно прямой around (в трехмерном случае) на заданный угол angle.
  • intersect(a, b) - находит пересечение двух объектов.
  • toMas(c, mas), toDirMas(c, mas), fromMas(c, mas), fromDirMas(c, mas) - вышеописанные методы, адаптированные для массового преобразования массивов.
  • invert(crd) - возвращает такую систему координат icrd, что icrd.to(x) == crd.from(x) и icrd.from(x) == crd.to(x).
  • deg2rad(deg), rad2deg(rad) - функции для преобразования угла из градусов (deg) в радианы (rad) и наоборот.

Методы для проверки в этих классах:

  • .isRight() - является ли данная система координат правой.
  • .isOrthogonal() - является ли данная система координат ортогональной.
  • .isOrthonormal() - является ли данная система координат ортонормированной.

Основные функции проверки для объектов этих классов:

  • isPerpendicular(a, b) - перпендикулярны ли a и b.
  • isIntersect(a, b) - пересекаются ли a и b.
  • isContain(a, b) - содержит ли объект a в себе объект b.
  • isParallel(a, b) - параллельны ли a и b.
  • isSkew(a, b) - скрещиваются ли прямые a и b.

Создание линий, плоскостей

При создании прямой с помощью функции l = makeLine2(a, b) передаются лишь две точки, и возвращается прямая, проходящая через них. Эта прямая начинается в точке a (т. е. a = l.pos), а её направляющий вектор i такой, что b = a + l.i, то есть отрезок от точки a до точки b задается координатами на прямой [0, 1]. А вектор l.j получается при помощи функции cross(i), то есть этот вектор перпендикулярен i и равен ему по длине. Получается, при использовании этой функции всегда получается ортогональная система координат с равными длинами направляющих векторов, и она всегда правая. Поэтому можно очень легко строить систему координат, которая находится на каком-то отрезке при помощи этой функции. Далее, в примерах, очень часто будет использоваться эта функция.

С функцией makeLine3 всё аналогично, только там более сложный механизм построения векторов j, k. Но в итоге всё-равно получается ортогональная система координат с равной длиной направляющих векторов. Так как возможно бесконечное множество различных пар этих векторов, ничего более не гарантируется.

При создании плоскости с помощью функции makePlane3 вектор k = cross(i, j). Тут уже не гарантируется что длина этого вектора будет равна длине i и j, так как у них самих может быть различная длина. Так же не гарантируется, что i и j будут ортогональны, они будут направлены так, как заданы точки при создании плоскости.

А что с остальным?

Вполне можно догадаться о предназначении методов по их названию и коду.

Использование

Используем преобразование координат для рисования графика синуса

#include <spob/spob.h>

using namespace spob;
void draw_line(vec2 a, vec2 b); // Некая сторонняя функция, которая рисует линию из точки a в точку b

void draw_sin(const space2& tr) {
	// Рисуем оси координат
	draw_line(tr.from(vec2(-1, 0)), tr.from(vec2(1, 0)));
	draw_line(tr.from(vec2(0, -1)), tr.from(vec2(0, 1)));

	// Рисуем график функции sin(x) в пределах [-2, 2]
	vec2 last(-2, std::sin(-2));
	for (double x = -2; x <= 2; x += 0.01) {
		vec2 current(x, std::sin(x));
		draw_line(tr.from(last), tr.from(current));
		last = current;
	}
}

int main() {
	space2 tr = getStandardCrd2();
	tr.pos = vec2(150, 150); // Смещаем в центр экрана
	tr.j.negate(); // Делается это потому что на экране ось Y направлена вниз
	tr.i *= 60; // Увеличиваем ось X в 60 раз
	tr.j *= 60;

	draw_sin(tr);
}

Полный код программы находится по адресу: doc/coordinates_transformation.cpp.

Результат:

doc/coordinates_transformation.png

Рисуем стрелки

#include <spob/spob.h>

using namespace spob;
void draw_line(vec2 a, vec2 b); // Некая сторонняя функция, которая рисует линию из точки a в точку b

void draw_arrow(vec2 a, vec2 b, double angle, double size) { // Стрелка начинается в точке a и кончается в точке b, стрелка должна находиться в точке b
	draw_line(a, b);

	// Создаем систему координат, которая начинается в точке b, и её вектор i направлен к точке a
	line2 line = makeLine2(b, a);

	// Нормализуем эту систему координат, потому что вектор i равен длине вектора (b-a)
	line.normalize();

	// Создаем две точки краев стрелки
	vec2 first(size, 0);
	vec2 second(size, 0);

	// Один край вращаем по часовой стрелке, а другой против
	first = rotate(first, vec2(0), angle);
	second = rotate(second, vec2(0), -angle);

	// Преобразуем эти края из системы координат линии в абсолютные координаты и рисуем это
	space2 space_of_line(line);
	draw_line(b, space_of_line.from(first));
	draw_line(b, space_of_line.from(second));
}

int main() {
	vec2 center(150, 150);

	vec2 a(50, 0); a += center;
	vec2 b(130, 0); b += center;

	// Рисование стрелок по всем направлениям
	int count = 4 + 3 * 4;
	for (int i = 0; i < count; ++i) {
		double angle = deg2rad(360.0 / count * i);
		draw_arrow(rotate(a, center, angle), rotate(b, center, angle), deg2rad(30), 10);
	}
}

Полный код программы находится по адресу: doc/arrow.cpp.

Результат:

doc/arrow.png

Рисуем радиально симметричные изображения

#include <spob/spob.h>

using namespace spob;
void draw_line(vec2 a, vec2 b); // Некая сторонняя функция, которая рисует линию из точки a в точку b

void draw_anything(const space2& tr) {
	vec2 a(-1, -1), b(-1, 1), c(1, 1), d(1, -1);

	draw_line(tr.from(a), tr.from(b));
	draw_line(tr.from(b), tr.from(c));
	draw_line(tr.from(c), tr.from(d));
	draw_line(tr.from(d), tr.from(a));

	draw_line(tr.from(a), tr.from(c));
}

int main() {
	vec2 center(150, 150);

	space2 tr = getStandardCrd2();
	tr.pos = center + vec2(130, 0);
	tr.j.negate();
	tr.i *= 15;
	tr.j *= 15;

	int count = 4 + 2 * 4;
	for (int i = 0; i < count; ++i) {
		double angle = deg2rad(360.0 / count * i);
		draw_anything(rotate(tr, center, angle));
	}
}

Полный код программы находится по адресу: doc/symmetry.cpp.

Результат:

doc/symmetry.png

Рисуем дерево Пифагора

#include <spob/spob.h>

using namespace spob;
void draw_line(vec2 a, vec2 b); // Некая сторонняя функция, которая рисует линию из точки a в точку b

void draw_pythagoras_tree(const space2& space) {
	// Выходим из рекурсии, если одна из осей (аналогично и сторона квадрата) имеет длину меньше, чем 2
	if (space.i.length() < 2)
		return;

	// Задаем координаты квадрата
	vec2 a(0, 0), b(1, 0), c(1, 1), d(0, 1);

	// Высчитываем координаты прямоугольного треугольника, который лежит своей гипотенузой на оси X, с углом alpha при основании
	double alpha = deg2rad(45);
	vec2 tr_a(0, 0), tr_b(1, 0), tr_c(cos(alpha), 0);
	tr_c = rotate(tr_c, vec2(0), alpha);

	// Преобразуем квадрат из текущих координат к координатам переданного пространства
	a = space.from(a);
	b = space.from(b);
	c = space.from(c);
	d = space.from(d);

	// Рисуем квадрат
	draw_line(a, b);
	draw_line(b, c);
	draw_line(c, d);
	draw_line(d, a);
	
	// Строим пространство, которое находится на верхней стороне квадрата
	space2 tr_line = makeLine2(d, c);

	// Переводим координаты треугольника к этому пространству
	tr_a = tr_line.from(tr_a);
	tr_b = tr_line.from(tr_b);
	tr_c = tr_line.from(tr_c);

	// Строим пространства, которые находятся на обоих катетах этого треугольника
	space2 l1 = makeLine2(tr_a, tr_c);
	space2 l2 = makeLine2(tr_c, tr_b);

	// Рекурсивно строим дерево в этих пространствах
	draw_pythagoras_tree(l1);
	draw_pythagoras_tree(l2);
}

int main() {
	draw_pythagoras_tree(makeLine2(vec2(400, 410), vec2(300, 410)));
}

Полный код программы находится по адресу: doc/pythagoras_tree.cpp.

Результат:

doc/pythagoras_tree.png

Рисуем дерево Пифагора на произвольном основании правильного многоугольника

#include <spob/spob.h>

using namespace spob;
void draw_line(vec2 a, vec2 b); // Некая сторонняя функция, которая рисует линию из точки a в точку b

void draw_pythagoras_tree(const space2& space) {
	// Выходим из рекурсии, если одна из осей (аналогично и сторона квадрата) имеет длину меньше, чем 2
	if (space.i.length() < 2)
		return;

	// Считаем правильный многоугольник
	const int n = 5;
	const int m = 1;
	std::vector<vec2> poly;
	for (int i = 0; i < n+1; ++i) {
		double angle = deg2rad(360.0/n * i);
		poly.push_back(vec2(cos(angle), sin(angle)));
	}

	// Преобразуем координаты так, чтобы он своим первым ребром находится на оси X
	space2 poly_line = makeLine2(poly[0], poly[1]);
	poly = toMas(poly_line, poly);

	// Высчитываем координаты прямоугольного треугольника, который лежит своей гипотенузой на оси X, с углом alpha при основании
	double alpha = spob::deg2rad(45);
	std::vector<vec2> triangle = {
		vec2(0, 0), 
		vec2(1, 0), 
		rotate(vec2(cos(alpha), 0), vec2(0), alpha)
	};

	// Преобразуем многоугольник из текущих координат к координатам переданного пространства
	poly = fromMas(space, poly);

	// Рисуем многоугольник
	for (int i = 0; i < poly.size() - 1; i++)
		draw_line(poly[i], poly[i+1]);
	
	// Строим пространство, которая находится на m-й стороне многоугольника
	space2 tr_line = makeLine2(poly[m+1], poly[m]);

	// Переводим координаты треугольника к этому пространству
	triangle = fromMas(tr_line, triangle);

	// Строим пространства, которые находятся на обоих катетах этого треугольника
	space2 l1 = makeLine2(triangle[0], triangle[2]);
	space2 l2 = makeLine2(triangle[2], triangle[1]);

	// Рекурсивно строим дерево в этих пространствах
	draw_pythagoras_tree(l1);
	draw_pythagoras_tree(l2);
}

int main() {
	draw_pythagoras_tree(makeLine2(vec2(500, 235), vec2(350, 235)));
}

Полный код программы находится по адресу: doc/pythagoras_polygon_tree.cpp.

Результат:

doc/pythagoras_polygon_tree.png

Рисуем проекцию трехмерного куба на экран

#include <spob/spob.h>

using namespace spob;
void draw_line(vec2 a, vec2 b); // Некая сторонняя функция, которая рисует линию из точки a в точку b
void draw_3D_cube(const space2& logic_to_img) {
	space3 cam = getStandardCrd3();

	// Делаем так, чтобы камера смотрела осью k в точку (0, 0, 0)
	cam.i.negate();
	std::swap(cam.i, cam.k);
	std::swap(cam.i, cam.j);

	// Задаем положение камеры
	cam.pos = vec3(5, 0, 0);

	line3 vertical(vec3(0, 0, 1), vec3(0));
	line3 horizontal(vec3(0, 1, 0), vec3(0));
	double alpha = deg2rad(30);
	double beta = deg2rad(30);

	// Вращаем её сначала относительно горизонтальной оси, то есть поднимаем её немного вверх. Направление оси k в точку (0, 0, 0) по прежнему остается.
	cam = rotate(cam, horizontal, -beta);
	// Затем вращаем её относительно вертикальной оси, то есть поворачиваем её направо. Направление оси k так же сохраняется.
	cam = rotate(cam, vertical, alpha);

	// Фокусное расстояние
	double focus = 0.1;

	// Получаем положение точки фокуса
	vec3 focus_point = cam.from(vec3(0, 0, -focus)); // Берем точку фокуса отрицательной, чтобы она находилась по обратную сторону от объкта, на который направлена камера, чтобы изображение было не перевернуто, да и чтобы при приближении камеры к объекту на расстояние фокуса, он не искажался слишком сильно.

	// Точки вершин куба
	std::vector<vec3> cube_points = {
		vec3(-1, -1, -1), vec3(-1, -1, 1), vec3(-1, 1, 1), vec3(-1, 1, -1),
		vec3(1, -1, -1),  vec3(1, -1, 1),  vec3(1, 1, 1),  vec3(1, 1, -1),
	};	

	// Ребра куба, они задаются двумя номерами вершины.
	std::vector<std::pair<int, int>> cube_edges = {
		{0, 1}, {1, 2}, {2, 3}, {3, 0},
		{4, 5}, {5, 6}, {6, 7}, {7, 4},
		{0, 4}, {1, 5}, {2, 6}, {3, 7},
	};

	auto project = [&] (vec3 point) -> vec2 {
		// Проецирует point на плоскость камеры через фокус

		/** Прежде чем объяснять что тут происходит расскажу немного об устройстве камеры, глаза:
			1. Свет двигается по прямой. Из-за этого и говорят "лучи света".
			2. В глазу и камере стоит линза, которая фокусирует лучи света таким образом, чтобы они все проходили через одну точку - фокус. 
			3. Далее этот луч света падает на сетчатку. Остается только вычислить координаты падения луча света, чтобы нарисовать его на экране. Конечно, сетчатка глаза искривлена, но вот матрица камеры - нет. И мы моделируем именно камеру. Поэтому у нас луч тоже падает на плоскость.

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

			Ну и тут мы получаем прямую, проходящую через данную точку и точку фокуса. Далее находим пересечение этой точки и плоскости. Координаты на плоскости и будут искомыми координатами.
		*/
		line3 ray = makeLine3(focus_point, point);
		vec2 pos;
		double t;
		if (intersect(cam, ray, pos, t) == INTER_OVERLAPS)
			return pos;
		else
			return vec2(0);

		// Следующий код реализует ортогональную проекцию: 
		//vec3 pos = cam.to(point);
		//return vec2(pos.x, pos.y)/50;

		// Деление на 50 происходит потому что при проецировании через фокус изображение получается примерно в 50 раз меньше объекта. 50 == distance(cam.pos, vec3(0, 0, 0))/focus.
	};

	// Перебираем все ребра, проецируем их точки на плоскость камеры и рисуем линию ребра на экран
	for (auto& i : cube_edges) {
		vec3 a = cube_points[i.first];
		vec3 b = cube_points[i.second];

		// logic_to_img нужен чтобы преобразовать координаты из "логических" в координаты изображения. В данном примере тут просто имеется смещение и масштабирование
		draw_line(logic_to_img.from(project(a)), logic_to_img.from(project(b)));
	}
}

int main() {
	double mx = 5000;
	space2 logic_to_img = getStandardCrd2();
	logic_to_img.i *= mx;
	logic_to_img.j *= mx;
	logic_to_img.j.negate();
	logic_to_img.pos = vec2(200, 200);
	draw_3D_cube(logic_to_img);
}

Полный код программы находится по адресу: doc/3D_cube_perspective.cpp.

Результат:

doc/3D_cube_perspective.png

Ортогональная проекция:

doc/3D_cube_orthogonal.png

Компиляция

Предположим, у нас есть некоторый файл test.cpp, со следующим содержанием:

#include <spob/spob.h>

int main() {
	using namespace spob;
	vec2 a(1, 2);
	vec2 b(0, 1);
	auto c = a + b;
}

Тогда для его компиляции можно использовать CMakeLists.txt файл:

cmake_minimum_required(VERSION 3.10.2)

#-----------------------------------------------------------------------------
# Код включения библиотеки, можно копировать в другие проекты

# Установите здесь ваш путь до библиотеки
set(SPACE_OBJECTS_DIR ~/my/space_objects)

add_library(space_objects STATIC 
	${SPACE_OBJECTS_DIR}/src/coordinates2.cpp 
	${SPACE_OBJECTS_DIR}/src/coordinates3.cpp
)
include_directories(${SPACE_OBJECTS_DIR}/include)
link_libraries(space_objects)
#-----------------------------------------------------------------------------

project(test)
add_executable(main test.cpp)

About

Преобразования координат для 1-, 2-, 3-мерных объектов их их подпространств. Подходит для графических вычислений.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages