Space Objects
- библиотека для работы с объектами, представляющими собой пространства или подпространства.
В этой библиотеке представлены такие объекты, как: вектора, линии, плоскости, 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
.
Результат:
#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
.
Результат:
#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
.
Результат:
#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
.
Результат:
#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
.
Результат:
#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
.
Результат:
Ортогональная проекция:
Предположим, у нас есть некоторый файл 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)