Implementation of analytic projective geometry for computer graphics

Cover Page

Cite item

Full Text

Abstract

In their research, the authors actively exploit different branches of geometry. For geometric constructions, computer algebra approaches and systems are used. Currently, we are interested in computer geometry, more specifically, the implementation of computer graphics. The use of the projective space and homogeneous coordinates has actually become a standard in modern computer graphics. In other words, the problem is reduced to the application of analytic projective geometry. The authors failed to find a computer algebra system that could implement projective geometry in its entirety. Therefore, it was decided to partially implement computer algebra for visualization of algebraic relations. For this purpose, the Asymptote system was employed.

Full Text

1. Введение

Проективная геометрия находит применение в разных областях компьютерных наук: в компьютерной графике, робототехнике, машинном зрении. Математическая теория служит основой для создания алгоритмов и их программной реализации. Авторы считают, что данный математический раздел имеет большие прикладные перспективы.

В проективной геометрии непропорционально большую роль играет визуализация геометрических структур. Авторам не удалось найти полной системы компьютерной алгебры, специализирующейся на методах проективной геометрии, что несомненно связано с ее спецификой. Ранее мы использовали методы проективной геометрии для обоснования применения гиперкомплексных чисел для описания движений в разных вариантах физических пространств [1–4]. В качестве следующего шага мы использовали подход геометрической алгебры (с использованием систем компьютерной алгебры) для исследования тех же объектов под другим углом зрения [5–7]. На следующем шаге мы решили вернуться к прямому применению теоретических основ к исследованиям. В качестве первого этапа данной научной программы предлагается частичная компьютеризация исследований. Алгебраические формулы реализованы в виде структур данных и наборов функций, связанных с геометрическими структурами. Затем с помощью языка Asymptote сделана программная реализация для визуализации полученных алгебраических структур. В данном случае к структурам относятся точки, прямые и плоскости.

Язык Asymptote имеет структуру системы компьютерной алгебры, но вместо символьного представления алгебраических объектов в результате работы Asymptote получается их визуальное представление.

Система Asymptote [8–12] — специализированный высокоуровневый язык программирования для создания векторных изображений научной направленности (геометрические чертежи, схемы, диаграммы и т.д.). Язык использует C-подобный синтаксис с некоторым числом дополнений и улучшений1, что по замыслу разработчиков делает его более удобным для освоения и использованием по сравнению с MetaPost [14] и PostScript.

Для наших целей крайне важно, что Asymptote поддерживает создание полноценных трехмерных изображений благодаря использованию OpenGL с автоматическим расчетом взаимного расположения геометрических тел, источников света и тени, что позволяет изображать пересечение поверхностей и кривых. Однако программный интерфейс для построения трехмерных изображений гораздо менее проработан и скупее документирован по сравнению с возможностями двумерной графики.

В данной статье мы не будем излагать основ языка Asymptote. Заинтересованный читатель может найти описание всех его возможностей в официальном руководстве на сайте [15], базовые примеры приведены в обучающих заметках [11], также множество примеров с комментариями на русском языке можно посмотреть в источниках [16–18]. Следует отметить, что большая часть примеров относится к двумерным изображениям. В статье акцент сделан на описании реализации посредством Asymptote структур данных, таких как проективные точки, прямые и плоскости для трехмерных графиков, приведены примеры исходного кода с пояснениями.

1.1. Структура статьи

В начале статьи приведена реализация структур данных, описывающих проективные точки (раздел 2), прямые (раздел 3) и плоскости (раздел 4). Изложение сопровождается примерами исходного кода с пояснением всех специфических конструкций, ключевых слов и функций языка Asymptote. В разделе 5 приводятся несколько примеров использования созданных структур, а также описываются некоторые технические тонкости создания именно трехмерных изображений, так как в официальном руководстве данный аспект освещен довольно скупо.

1.2. Обозначения и соглашения

Напомним основные используемые обозначения проективной геометрии. Формулы приведены в однородных координатах, обозначения взяты из монографии [19]:

  • {v|p×v}  — прямая, проходящая через точку P по направлению v;
  • {p2p1|p1×p2}  — прямая, проходящая через две точки P1 и P2;
  • {p|0} — прямая, проходящая через начало координат и точку P;
  • {w1p2w2p1|p1×p2} — прямая, проходящая через две точки p1=(p1|w1) и p2=(p2|w2);
  • {n1×n2|d1n2d2n1} — прямая линия пересечения двух плоскостей [n1|d1] и [n2|d2];
  • (m×n+dv|(n,v)) — точка пересечения плоскости [n|d] и прямой {v|m};
  • [m1×m2|(v2,m1)] — точка пересечения двух прямых {v1|m1} и {v2|m2};
  • (d1n3×n2+d2n1×n3+d3n2×n1|(n1,n2,n3)) — точка пересечения трех плоскостей [n1|d1], [n2|d2] и [n3|d3];
  • (v×m|(v,v)) — точка, ближайшая к началу координат на прямой {v|m};
  • (dn|(n,n)) — точка, ближайшая к началу координат на плоскости [n|d];
  • [v×u|(u,m)] — плоскость, содержащая прямую {v|m} и направление u;
  • [v×p+m|(p,m)] — плоскость, содержащая прямую {v|m} и точку (p|1);
  • [m|0] — плоскость, содержащая прямую {v|m} и начало координат;
  • [v×p+wm|(p,m)] — плоскость, содержащая прямую {v|m} и точку (p|w);
  • [v×u|(u,v,p)] — плоскость, содержащая точку (p|1) и направления v и u;
  • [m×v|(m,m)] — плоскость с прямой {v|m}, наиболее отдаленная от O;
  • [wp|(p,p)] — плоскость с точкой (p|w), наиболее отдаленная от O;
  • (v1,m2)+(v2,m1)v1×v2 — расстояние между прямыми {v1|m1} и {v2|m2};
  • v×p+mv — расстояние между прямой {v|m} и точкой (p|1);
  • m/v — расстояние от прямой {v|m} до начала координат;
  • (n,p)+dn — расстояние от плоскости [n|d] до точки (p|1);
  • d/n — расстояние от плоскости [n|d] до начала координат.

2. Создание структур на примере однородных координат точки

Для создания пользовательских структур в языке Asymptote используется синтаксис, во многом похожий на соответствующий синтаксис языка Си, однако имеющий некоторую специфику, которую мы рассмотрим на примере структуры для однородных координат.

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

Язык Asymptote имеет встроенный тип triple, который используется для задания координат точек в трехмерном пространстве. Структура данных для однородных координат отсутствует, по крайней мере недоступна пользователям, поэтому следует ее создать.

Назовем структуру Vec4, по аналогии с соответствующим типом данных из GLSL (OpenGL Shading Language, Graphics Library Shader Language):

struct Vec4 {
real x; real y; real z; real w;
pair xy; triple xyz;
real[] xyzw;
}

Структура данных должна содержать всего 4 числа типа real, однако для удобства использования кроме полей x, y, z, w, соответствующих однородным координатам x:y:z:w, мы задаем поля, позволяющие получить только координаты xy в виде типа данных pair, трехмерные декартовы координаты xyz в виде типа данных triple и все координаты в виде массива xyzw. Кроме удобства при манипуляциях с указанными объектами такой подход также повторяет программный интерфейс языка GLSL.

После определения структуры Vec4 Asymptote автоматически создает одноименную функцию, обязательными аргументами которой являются все поля, перечисленные при определении структуры. Данная функция эквивалентна конструктору по умолчанию в терминах языка C++ или Java.

В нашем случае придется перечислить все семь полей, хотя они частично дублируют друг друга. Данная проблема решается заданием специализированных операторов инициализации (конструкторов), для чего следует перегрузить специальный оператор init. Внутри этого оператора можно использовать ключевое слово this, которое позволяет сослаться на экземпляр структуры.

Для нашего примера создадим три специализированных конструктора:

  • Конструктор Vec4 из набора чисел:

void operator init(real x, real y,
real z, real w) {
this.x = x;
this.y = y;
this.z = z;this.w = w;
this.xy = (x, y);
this.xyz = (x, y, z);
this.xyzw = new real[] {this.x,
this.y, this.z, this.w};
}

  • Конструктор Vec4 из декартовых координат точки и отдельного скаляра w:

void operator init(triple p, real w)
{
this.x = p.x;
this.y = p.y;
this.z = p.z;
this.w = w;
this.xy = (p.x, p.y);
this.xyz = (p.x, p.y, p.z);
this.xyzw = new real[] {this.x,
this.y, this.z, this.w};
}

  • Конструктор Vec4 из массива:

void operator init(real[] v) {
this.x = v[0];
this.y = v[1];
this.z = v[2];
this.w = v[3];
this.xy = (v[0], v[1]);
this.xyz = (v[0], v[1], v[2]);
this.xyzw = v[0:5];
}

Все перечисленные операторы следует размещать внутри пространства имен структуры (т.е. между открывающей и закрывающей фигурными скобками структуры Vec4).

Для удобства манипулирования данными можно также перегрузить оператор cast, который ответственен за неявное преобразование типов данных:

  • Преобразование типа Vec4 в массив:

real[] operator cast(Vec4 v) {
return v.xyzw;
}

  • Преобразование типа Vec4 в трехмерные декартовы координаты:

triple operator cast(Vec4 v) {
return v.xyz / v.w;
}

  • Преобразование массива в тип Vec4:

Vec4 operator cast(real[] v) {
return Vec4(v);
}

При преобразовании данных в тип triple осуществляется дополнительное деление на весовую координату w. В случае несобственной (идеальной) точки данное преобразование сработает некорректно, так как у идеальной точки координата w = 0 и произойдет деление на ноль. Однако проверку на равенство нулю координаты w всегда можно произвести отдельно.

3. Проективные прямые в виде структуры Line3D

3.1. Структура и конструкторы

Прямая в пространстве полностью определяется координатами Плюккера, которые задаются направляющим вектором v и вектором момента m. Для вычислений удобно, чтобы вектор v был единичными. На основе векторов v и m можно вычислить любые другие характеристики прямой, поэтому в качестве полей структуры достаточно использовать только эти два вектора. Однако для вычислений часто необходимо знать некоторую точку P прямой, поэтому удобно также включить ее в поля структуры. Также для большей общности можно допустить хранение неединичного вектора v. В итоге структура для задания проективной прямой будет иметь следующий вид:

struct Line3D {
// Направляющий вектор и момент:
triple V, m;
// Единичный направляющий вектор:
triple v;
// Произвольная точка прямой:
triple P;
}

Для полноценного использования созданной структуры в ее области имен необходимо задать несколько дополнительных операторов инициализации. Так, прямая, проходящая через две собственные точки, заданные в декартовых координатах, вычисляется по формуле:

m=p1×p2. (1)

Соответствующий конструктор будет выглядеть следующим образом:

void operator init (triple P2, triple
P1) {
this.V = P2 - P1;
this.v = unit(this.V);
this.m = cross(P1, P2) /
length(this.V);
this.P = P1;
}

Следует обратить внимание, что для вычисления единичного вектора v мы использовали встроенную в Asymptote функцию unit, для вычисления векторного произведения — функцию cross, а для вычисления нормы вектора — функцию length. Обратите внимание, что при вычислении m используется функция (1) в комбинации с соотношением 

dOO=OO==v×mv=vmsinπ2v2=mv,

т.е. она делится на исходную длину вектора v, вычисленного как p2p1. В этом случае длина m имеет геометрический смысл расстояния от начала координат до прямой.

Следующий конструктор инициализирует структуру по заданным векторам v и m:

void operator init (triple keyword V,
triple keyword m) {
this.V = V;
this.v = unit(this.V);
this.m = m / length(this.V);
this.P = cross(this.v, this.m);
}

Вектор v может быть произвольной длины, его исходное значение записывается в поле V структуры, а его нормированное значение — в поле v. Здесь вектор m нормализован путем деления на собственную длину ненормированного касательного вектора ||v||.

Инструкция keyword указывает, что данный оператор инициализации можно вызывать только явно указав все аргументы, т.е. следующим образом:

triple V = (1,1,1); triple m = (1, 1,
1);
Line3D L = Line3D(V=V, m=m);

Если осуществить вызов через Line3D L = Line(V, m), то будет вызван предыдущий конструктор по двум точкам, заданным в декартовых координатах.

В реализации конструктора, который инициализирует прямую, проходящую через две точки, заданные в проективных координатах, используется следующая формула:

{w1p2w2p1|p1×p2}. (2)

Соответствующий конструктор будет выглядеть следующим образом:

void operator init (Vec4 P2, Vec4 P1) {
this.V = P1.w * P2.xyz - P2.w *
P1.xyz;
this.v = this.V / length(this.V);
this.m = cross(P1.xyz, P2.xyz) /
length(this.V);
this.P = P1.xyz / P1.w;
}

Мы неявно подразумеваем, что по крайней мере P1 — собственная точка, т.е. ее координата w не равна нулю. Подчеркнем, что формула (3) — универсальна и работает для всех возможных вариантов точек. Например, если p1=(x,y,z,w) — собственная точка, а p2=(v|0)=(vx,vy,vz,0) — несобственная точка, то координаты Плюккера прямой вычисляются как l={wv|p×v}.

Произведя нормализацию путем деления на w|v|, получим формулу:

{v|m}={vx,vy,vz|mx,my,mz}={v|p×v}. (3)

Формуле (3) соответствует следующий конструктор (прямая, проходящая через точку по направляющему вектору):

void operator init (Vec4 P, triple V) {
this.V = V;
this.v = this.V / length(this.V);
this.m = cross(P.xyz, this.V) /
length(this.V);
this.P = P.xyz;
}

Если же обе точки p1=(v1|0) и p2=(v2|0) являются несобственными, то получаем несобственную прямую {0|v1×v2}. Несобственные прямые могут встречаться при рассмотрении взаимного расположения плоскостей. Если плоскости параллельны, то они будут пересекаться как раз по несобственным прямым. Пример формулы (3) иллюстрирует преимущество проективного подхода, который позволяет одной формулой охватить сразу все возможные варианты.

Осталось создать еще два конструктора, которые реализуют формулу {v|0}.

Соответствующие конструкторы имеют вид:

  • Прямая, проходящая через начало координат и точку P (в декартовых координатах):

void operator init (Vec4 P) {
this.V = P.xyz;
this.v = unit(this.V);
this.m = (0, 0, 0);
this.P = P.xyz;
}

  • Прямая, проходящая через начало координат и точку P:

void operator init (triple P) {
this.V = P;
this.v = unit(this.V);
this.m = (0, 0, 0);
this.P = P;
}

Для визуализации прямой необходимо знать хотя бы две ее точки. Для реализации параметрического уравнения прямой определим в пространстве имен структуры Line3D следующую функцию:

triple get_point(real t) {
return this.P + this.v * t;
}

Удобно также иметь возможность вычислить сразу массив точек прямой, что крайне просто делается с помощью поэлементного цикла for:

triple[] get_points(real[] T) {
triple[] res;
for (real t : T) {
res.push(get_point(t));
}
return res;
}

Данный вариант цикла for похож на аналогичный цикл из языка Python, позволяет перебирать элементы из массива и проводить с ними манипуляции.

3.2. Взаимное расположение прямых

В предыдущем пункте мы задали поля структуры, набор конструкторов и несколько методов. Теперь зададим несколько функций, аргументами которых будут экземпляры структуры Line3D. Данные функции разместим в том же файле, что и структура Line3D, но вне ее пространства имен (за закрывающей фигурной скобкой).

В первую очередь реализуем функцию вычисления взаимного момента двух прямых l1 и l2 по простой формуле M=(v1,m2)+(v2,m1).

Реализация соответствующей функции имеет вид:

real reciprocal_moment(Line3D line01,
Line3D line02) {
return dot(line01.m, line02.v) +
dot(line01.v, line02.m);
}

Здесь мы использовали встроенную функцию dot, которая вычисляет скалярное произведение двух векторов.

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

  • если M = 0, то прямые лежат в одной плоскости;
  • если M > 0, то переход от l1 к l2 осуществляется правым поворотом;
  • если M < 0, то переход от l1 к l2 получается левым поворотом.

Заметим, что делать проверку на равенство нулю следует с учетом погрешности представления вещественных чисел числами с плавающей точкой.

Далее определим функцию, реализующую формулу

d=d2d1=(v1,m2)+(v2,m1)v1×v2. (4)

Соотношение (4) позволяет вычислить длину d взаимного перпендикуляра между двумя скрещивающимися прямыми. Так как у нас уже есть функция вычисления взаимного момента, то вычисления длины перпендикуляра будет осуществляться также в одну строчку:

real reciprocal_perp_length(Line3D line01,
Line3D line02) {
return reciprocal_moment(line01, line02)
/ length(cross(line01.v, line02.v));
}

Предыдущую функцию можно было бы сделать более общей, добавив проверку скрещиваемости прямых, и в случае, если прямые лежат в одной плоскости, применить формулу

dQQ=m+v×qv=(pq)×vv

в виде

d=v×p2+m1v1,

где  l1={v1|m1} — координаты Плюккера первой прямой, p2=(p2|1) — однородные координаты произвольной точки второй прямой. Однако мы не стали усложнять функцию дополнительными внутренними проверками. Также реализуем формулу

[m1×m2|(v2,m1)]=[m2×m1|(v1,m2)]. (5)

При этом сделаем проверку на равенство нулю взаимного момента, так как в этом случае две прямые будут лежать в одной плоскости (будут компланарны):

// line-line-meet
Vec4 intersection(Line3D line01, Line3D
line02) {
real M = reciprocal_moment(line01,
line02);
assert( abs(M) < 1e-8, "Прямые не
компланарны" );
return Vec4( cross(line01.m, line02.m),
dot(line02.v, line01.m));
// return Vec4( cross(line02.m,
line01.m), dot(line01.v, line02.m));
}

Как указывалось выше, проверку на равенство нулю следует сделать с некоторой погрешностью. В нашей реализации мы допускаем довольно большую погрешность, так как основной целью является визуализация полученных объектов, где малые погрешности не критичны.

Для вычисления координат Плюккера прямой, содержащей взаимный перпендикуляр, воспользуемся довольно-таки громоздкой формулой:

v=v1×v2,m=m1×l2m2×v1++  (v1,m2)+(v2,m1)v1×v2(v1,v2)v1×v2.

Компьютерная реализация имеет вид:

Line3D reciprocal_perp_line(Line3D line01,
Line3D line02) {
triple V = cross(line01.v, line02.v);
triple m = cross(line01.m, line02.v) -
cross(line02.m, line01.v) +
 reciprocal_perp_length(line01,
 line02) * dot(line01.v, line02.v) *
 cross(line01.v, line02.v) /
 length(cross(line01.v, line02.v));
return Line3D(V=V, m=m);
}

4. Проективные плоскости в виде структуры Plane3D

4.1. Структура и конструкторы

Теперь рассмотрим структуру, реализующую задание плоскости в однородном виде. Для полного описания плоскости достаточно вектора π=[n|d], где n — единичный вектор нормали к плоскости, а d — ориентированное расстояние от начала координат до плоскости.

В модуле three языка Asymptote существует функция plane со следующей сигнатурой:

path3 plane(triple u, triple v,
  tripleO=O);

Здесь u и v — направляющие векторы плоскости, а 0 — точка их приложения. Данная функция возвращает замкнутый прямоугольный контур 0 -- 0+u -- 0+u+v -- 0+v -- cycle, который затем можно отобразить в виде плоскости с помощью функции surface. Поэтому в структуре Plane3D полезно хранить два направляющих вектора v и u, а также произвольную точку P. Полностью поля структуры будут выглядеть следующим образом:

struct Plane3D {
// Вектор нормали (ненормированный):
triple N;
// Единичный вектор нормали:
triple n;
// Направляющие векторы (касательные
векторы):
triple U, V;
// Единичные направляющие векторы:

triple u, v;
// Ориентированное расстояние от
начала координат до прямой:
real d;
// Произвольная точка плоскости:
triple P;
}

Зададим несколько сравнительно тривиальных операторов инициализации. Первый из них создает плоскость, содержащую прямую l={v|n} и направляющий вектор v:

void operator init (Line3D line, triple
U) {
this.U = U;
this.V = line.V;
this.u = unit(U);
this.v = line.v;
this.N = cross(this.v, this.u);
this.n = unit(this.N);
this.d = -dot(this.u, line.m);
this.P = -this.d * this.n;
}

В качестве точки плоскости берется ближайшая к началу координат. Вычисление проводится по формуле [v×u|(u,m)].

Второй конструктор инициализирует плоскость, проходящую через точку и прямую. Точка может быть задана однородными координатами, тогда работает формула [v×p+wm|(p,m)].

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

[v×p+m|(p,m)].

Так как случай однородных координат более общий, то мы ограничимся только им в реализации конструктора: 

void operator init (Line3D line, Vec4 P)
{
this.U = line.P - P.xyz;
this.V = line.V;

this.u = unit(U);
this.v = line.v;
this.N = cross(line.v, P.xyz) + P.w *
line.m;
this.n = unit(this.N);
this.d = -dot(P.xyz, line.m);
this.P = P.xyz;
}

Третий конструктор инициализирует плоскость, проходящую через прямую и начало координат:

void operator init (Line3D line) {
this.U = line.P;
this.V = line.V;
this.u = unit(U);
this.v = line.v;
this.N = line.m;
this.n = unit(this.N);
this.d = 0;
this.P = line.P;
}

Вычисления проводятся по следующей формуле: [m|0].

Четвертый конструктор создает плоскость, проходящую через заданную точку и два направляющих вектора. Это эквивалентно записи параметрического уравнения плоскости. Вычисления проводятся по формуле (8). В Asymptote нет смешанного произведения, но оно естественно заменяется на комбинацию скалярного и векторного произведений:

void operator init (triple P, triple U,
triple V) {
this.U = U;
this.V = V;
this.u = unit(U);
this.v = unit(V);
this.N = cross(U, V);
this.n = unit(this.N);
this.d = dot(V, cross(U, P)) /
length(this.N);
this.P = P;
}

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

void operator init (real A, real B, real
C, real D) {
this.N = (A, B, C);
this.n = unit(this.N);
triple w;
if ( A <= B && A <= C) {
w = this.n + (1, 0, 0);
} else if (B <= A && B <= C) {
w = this.n + (0, 1, 0);
} else {
w = this.n + (0, 0, 1);
}
this.u = unit(cross(w, this.n));
this.v = cross(this.n, this.u);
this.U = u;
this.V = v;
this.d = D / length(this.N);
this.P = -this.d * this.n;
}

По известному единичному нормальному вектору плоскости n можно вычислить направляющие векторы плоскости u и v. Для этого можно использовать простую процедуру, указанную в [20, с. 29]. Возьмем произвольный вектор w, не коллинеарный n. Чтобы найти такой вектор, можно взять наименьшую компоненту вектора n и увеличить ее на 1, например w=(nx,ny,nz+1), если nz < nx и nz < ny. После этого можно вычислить вектор u

u=w×nw×n.

Из свойств векторного произведения следует, что . При вычислениях с помощью компьютера полезно также удостовериться, что значение ||w × n|| не слишком велико или мало, чтобы избежать лишних погрешностей, связанных с машинной точностью.

На последнем шаге находим v как v = n × u. Так как ||n|| = ||u|| = 1, то и v — единичный вектор. Таким образом мы получили ортонормированную тройку векторов n,u,v.

Точку плоскости можно найти как проекцию начала координат на плоскость OO=dn при условии, что n — единичный вектор: (dn|n2).

В области имен структуры зададим еще две функции. Первая из них фактически является параметрическим уравнением плоскости: 

triple get_point(real s, real t) {
return this.P + this.u * s + this.v *
t;
}

в виде прямоугольника:

guide3 get_contour() {
triple P = this.P - 0.5(this.u +
this.v);
return P -- P + this.u -- P + this.u
+ this.v -- P +this.v -- cycle;
}

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

4.2. Взаимное расположение плоскостей

Имея в наличии структуру Plane3D, можно перейти к решению задачи определения взаимного положения двух и трех плоскостей.

В проективном пространстве две плоскости пересекаются всегда, так как параллельные плоскости считаются пересекающимися в бесконечности и прямая, по которой они пересекаются, является несобственной. Прямая пересечения вычисляется по формуле:

{v|m}={n1×n2|d1n2d2n1}. (6)

Возможны следующие варианты:

  • если v0, то прямая собственная;
  • если v = 0, то прямая несобственная;

- если d1 = d2, то плоскости совпадают;

- если d1d2, то плоскости параллельны.

Отметим также, что если две плоскости совпадают, то необязательно n1 = n2, так как возможен вариант, когда n1 = –n2. В этом случае лицевая и внутренняя стороны плоскостей отличаются.

В случае трех плоскостей вариантов может быть больше. Все они изображены на рис. 1 и 2.

 

Рис. 1. Варианты непараллельных плоскостей.

 

Рис. 2. Варианты параллельного расположения плоскостей. Вариант (в) — предельный случай варианта (б).

 

Чтобы выписать формулы для вычислений, введем следующие обозначения. Плоскости π1, π2 и π3 запишем в координатах Плюккера в следующем виде: π1 = [n1 | d1], π2 = [n2 | d2] и π3 = [n3 | d3]. Каждая пара этих трех плоскостей пересекается по некоторой прямой lij, где i, j = 1, 2, 3 и i < j.

Тогда по формуле (14) запишем

lij={ni×nj|dinjdjni}.

То есть получается следующее соотношение: vij=ni×nj и mij=d1n2d2n1.

Все возможные варианты расположения трех плоскостей можно определить по следующей схеме:

  • точка пересечения является собственной, т.е. (n1,n2,n3)=0;
  • точка пересечения является несобственной, т.е. (n1,n2,n3)=0:

- все прямые являются собственными, параллельными и различными (не совпадают), т.е. v12×v13=v13×v23=v23×v12=0 и m12m23m13;

- все прямые являются собственными, параллельными и совпадают, т.е. v12×v13=v13×v23=v23×v12=0 и m12 = m23 = m13;

- две прямые являются собственными, параллельными и различными (не совпадают), т.е. v13 × v23 = 0 и m13m23, а одна прямая является несобственной, т.е. v12 = 0;

- все прямые являются несобственными, т.е. v12=v23=v13=0, а плоскости не совпадают, т.е. d1d2d3;

- все прямые являются несобственными, т.е. v12=v23=v13=0, а плоскости совпадают, т.е. d1 = d2 = d3.

Однородные координаты точки пересечения трех плоскостей вычисляются по формуле:

p=d1n3×n2+d2n1×n3++d3n2×n1|n1,n2,n3==Δx,Δy,Δz|Δ. (15)

Обозначения Δx, Δy, Δz представляют собой определители решения системы уравнений  методом Краммера:

n1xx+n1yy+n1zz=d1,n2xx+n2yy+n2zz=d2,n3xx+n3yy+n3zz=d3.

Δ=n1xn1yn1zn2xn2yn2zn3xn3yn3z,Δx=d1n1yn1zd2n2yn2zd3n3yn3z,Δy=n1xd1n1zn2xd2n2zn3xd3n3z,Δz=n1xn1yd1n2xn2yd2n3xn3yd3.

Обозначение (Δx, Δy, Δz | Δ) задает точку в однородных координатах:

p=ΔxΔ,ΔyΔ,ΔzΔ1=:Δx,Δy,Δz|Δ.

Рассмотрим теперь реализацию указанных выше формул в виде кода. Можно встроить непосредственно в функции проверки того, являются ли получаемые точки и прямые несобственными. Однако если оперировать только проективными объектами, то такая проверка излишняя и ее можно проводить непосредственно в момент визуализации, извлекая всю информацию из однородного представления точек, прямых и плоскостей.

Вычисление общей прямой, по которой пересекаются две плоскости, можно реализовать следующим образом:

Line3D intersection(Plane3D plane01,
Plane3D plane02) {
Line3D line;
line.m = plane01.d * plane02.n -
plane02.d * plane01.n;
line.V = cross(plane01.n, plane02.n);
// Если плоскости пересекаются по
несобственной прямой, то v = 0
if (abs(line.V) < 1e-8) {
line.v = (0, 0, 0);
} else {
line.v = unit(line.V);
// проведем нормировку, разделив на
длину направляющего вектора
line.m = line.m / length(line.V);
line.P = cross(line.v, line.m) /
dot(line.v, line.v);
}
return line;
}

Необходимые проверки были встроены непосредственно в код. Функция abs вычисляет норму вектора (можно заменить на функцию length). Еще раз следует заметить, что в случае использования чисел с плавающей запятой необходимо учитывать машинную погрешность и использовать не строгое равенство, а проверять, что модуль числа не превосходит некоторое малое число.

Код для вычисления точки пересечения трех плоскостей будет выглядеть следующим образом:

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

Еще одна функция реализует формулу для вычисления однородных координат точки пересечения плоскости [n|d] и прямой {v|m}:

m×n+dvn,v1=m×n+dv|n,v==m×n+dv|n,v. (16)

Здесь также можно поступить двояко: вернуть тип triple и встроить все проверки внутрь функции, или же вернуть тип Vec4 и возложить ответственность за проверки на пользователя функции. Здесь мы приводим первый вариант функции:

triple intersection(Plane3D plane, Line3D
line) {
assert( dot(line.v, plane.n) > 1e-8,
"Плоскость и прямая не
пересекаются!" );
return -(cross(line.m, plane.n) +
plane.d * line.v) / dot(line.v,
plane.n);
}

5. Пример визуализации

В данном разделе рассмотрим пример использования созданных структур и встроенных в Asymptote средств для визуализации комплексного изображения пересечения плоскостей и прямых. Приведем полный код программы и прокомментируем все его части.

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

 

Рис. 3. Пересечение трех плоскостей.

 

Третья плоскость специально выбрана так, чтобы ее нормальный вектор совпадал с направляющим вектором прямой пересечения первой и второй плоскостей, т.е. n3=n1×n2=v12. Это изображение может служить иллюстрацией к доказательству формулы нахождения прямой, по которой пересекаются две плоскости.

Файл с исходным кодом Asymptote имеет расширение .asy. Для его запуска будем использовать команду asy. Вначале следует импортировать все используемые модули как встроенные, так и реализованные нами.

import settings;
settings.outformat = "pdf" ;
settings.render = 15;

include "Vec4.asy" ;
include "Line3D.asy" ;
include "Plane3D.asy" ;
import three;
import graph3;
unitsize(3cm);

Модуль settings позволяет настроить некоторые параметры работы Asymptote. В частности мы указываем, что следует создавать изображение в формате pdf, а также выставляем качество созданного трехмерного изображения. Значения больше нуля приведут к тому, что изображение получится растровым. В случае трехмерных чертежей мы вынуждены использовать растровый формат, так как иначе некорректно рассчитываются наложения поверхностей друг на друга. Качество растра следует подбирать опытным путем.

Далее импортируем три созданные нами структуры модуль three для поддержки трехмерных векторов и модуль graph3 для визуализации трехмерных поверхностей и кривых. Также выставляем единицу измерения по умолчанию в 3 сантиметра — такое значение было подобрано опытным путем исходя из критерия читаемости изображения при его публикации в статье или презентации.

В модуле three определена функция plane, которая принимает в качестве аргументов два направляющих вектора плоскости и точку их приложения, а в качестве результата возвращает контур плоскости в виде прямоугольника. Точка приложения векторов будет служить правым верхним углом данного прямоугольника. Далее этот контур можно использовать для отображения плоскости с помощью функции surface.

В первую очередь задаем направляющие векторы:

triple u_01 = X;
triple v_01 = Y;
triple u_02 = rotate(30, Y) * u_01;
triple v_02 = v_01;
triple u_03 = u_01;
triple v_03 = rotate(-90, X) * v_01;

Для первой плоскости в качестве направляющих выберем орты осей Ox и Oy, используя заданные в модуле graph3 векторы X, Y.

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

// Произвольная точка, к которой будем
 привязывать направляющие векторы
triple P_0 = (0, -0.5, 1);
// Точка плоскости, от которой будут
откладываться направляющие векторы при
 рисовании
triple P_01 = P_0 - u_01;
triple P_02 = P_0 - u_02;
triple P_03 = P_0 - X + 0.5Y + 0.5Z;

Далее можно создать контуры и поверхности плоскостей:

// Создаем контуры плоскостей
path3 pl_01 = plane(u=2u_01, v=v_01,
O=P_01);
path3 pl_02 = plane(u=2u_02, v=v_02,
O=P_02);
path3 pl_03 = plane(u=2u_03, v=v_03,
O=P_03);
surface pls_01 = surface(pl_01);
surface pls_02 = surface(pl_02);
surface pls_03 = surface(pl_03);

Чтобы созданные объекты были отрисованы, необходимо воспользоваться функцией draw. Покажем как отрисовать первую плоскость:

draw(
s=pls_01,
nu=1, nv=1,
surfacepen=lightgrey+opacity(.8),
meshpen=black+thin(),
light=nolight
);

Разберем передаваемые в данную функцию параметры:

  • в параметр s передается объект surface;
  • параметры nu и nv задают количество точек сетки сплайновой поверхности;
  • параметр surfacepen позволяет настроить перо для рисования поверхности;
  • параметр meshpen отдельно настраивает перо для сетки, в нашем случае от сетки остается только контур плоскости;
  • с помощью параметра light настраивается свет.

Мы хотим сделать подписи к элементам рисунка, в частности подписать обозначения для плоскостей. Для этого отображаем контуры плоскости отдельно:

draw(pl_01, p=0.25bp+black,
L=Label("$\pi_1$" ,
 position=Relative(0.80)));
draw(pl_02, p=0.25bp+black,

 L=Label("$\pi_2$" ));
draw(pl_03, p=0.25bp+black,
 L=Label("$\pi_3$" ,
 position=Relative(0.3)));

Здесь параметр p задает перо, параметр L — подпись к изображаемому объекту. Для создания подписей можно использовать нотацию LaTeX, что делает Asymptote удобным для создания иллюстраций к научным текстам.

Далее перейдем к использованию созданных нами структур. В первую очередь зададим все три плоскости:

Plane3D plane01 = Plane3D(P_01, u_01,
v_01);
Plane3D plane02 = Plane3D(P_02, u_02,
v_02);
Plane3D plane03 = Plane3D(P_03, u_03,
v_03);

Затем найдем прямые пересечения данных плоскостей:

Line3D line12 = intersection(plane01,
plane02);
Line3D line13 = intersection(plane01,
plane03);
Line3D line23 = intersection(plane02,
plane03);

Прямые отображаются в виде отрезков, поэтому следует знать две точки каждой прямой. Подбираются точки эмпирически, а чтобы получить координаты точек используем метод get_point. Например, для первой прямой:

triple l12_sp = line12.get_point(-1.6);
triple l12_ep = line12.get_point(+0.9);

Далее отображаем прямые. Так, для первой прямой:

draw(l12_sp--l12_ep, p=black,
 L=Label("$l_{12}$" ,
 position=BeginPoint));

Теперь находим точку пересечения и отображаем ее:

triple intersection_point =
 intersection(plane01, plane02,
 plane03);
dot(intersection_point);

Наконец, откладываем нормальные векторы всех трех плоскостей от точки их пересечения:

draw(
intersection_point -- intersection_point
  + plane01.n,
arrow=Arrow3(size=4),
p=black,
L=Label(s="$\vb{n}_1$" , align=E,
  position=Relative(0.9))
);

6. Заключение

Мы рассмотрели реализацию аналитической проективной геометрии на языке Asymptote. Были реализованы однородные координаты, координаты Плюккера прямой и однородные координаты плоскости. Подробно описаны три созданные нами структуры, инициализирующие операторы и функции. Фундаментальным преимуществом по сравнению с классической аналитической геометрией является существенное упрощение вычислений, так как большинство функций просто повторяют проективные формулы и вычислительная часть зачастую умещается в несколько строк.

Следует особо отметить, что так как все функции Asymptote предназначены для работы с объектами трехмерного декартова пространства, то для окончательной визуализации полученных объектов мы все же вынуждены делать проверки на равенство нулю тех или иных величин, а также учитывать невозможность однозначно визуализировать несобственные точки, прямые и плоскости. Однако это уже ограничения технического характера.

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

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

Источники финансирования

Публикация выполнена в рамках проекта № 021934-0-000 Системы грантовой поддержки научных проектов РУДН (Геворкян М.Н., Королькова А.В., Севастьянов Л.А.) и при поддержке Программы стратегического академического лидерства РУДН (Кулябов Д.С.).

 

1 Сам язык в своем синтаксисе скорее ориентируется на C++ [13]. Например, переменные в Asymptote можно объявлять в любом месте программы и инициализировать сразу при объявлении, а функции могут принимать параметры по умолчанию.

×

About the authors

M. N. Gevorkyan

RUDN University

Author for correspondence.
Email: gevorkyan-mn@rudn.ru
Russian Federation, 6 Miklukho-Maklaya St, Moscow, 117198

A. V. Korol’kova

Joint Institute for Nuclear Research

Email: korolkova-av@rudn.ru
Russian Federation, 6 ul. Zholio-Kyuri 6, Dubna, Moscow oblast, 141980

D. S. Kulyabov

RUDN University; Joint Institute for Nuclear Research

Email: kulyabov-ds@rudn.ru
Russian Federation, 6 Miklukho-Maklaya St, Moscow, 117198; 6 ul. Zholio-Kyuri 6, Dubna, Moscow oblast, 141980

L. A. Sevast’yanov

RUDN University; Joint Institute for Nuclear Research

Email: sevastianov-la@rudn.ru
Russian Federation, 6 Miklukho-Maklaya St, Moscow, 117198; 6 ul. Zholio-Kyuri 6, Dubna, Moscow oblast, 141980

References

  1. Korolkova A.V., Gevorkyan M.N., Kulyabov D.S. Implementation of hyperboliccomplex numbers in Julia language, Discrete Contin. Models Appl. Comput. Sci., 2022, vol. 30, no. 4, pp. 318–329.
  2. Kulyabov D.S., Korolkova A.V., Sevastianov L.A. Complex numbers for relativistic operations, 2021.
  3. Kulyabov D.S., Korolkova A.V., Gevorkyan M.N. Hyperbolic numbers as Einstein numbers, J Phys.: Conf. Ser., 2020, vol. 1557, p. 012027.
  4. Gevorkyan M.N., Korolkova A.V., Kulyabov D.S. Approaches to the implementation of generalized complex numbers in the Julia language, Workshop on Information Technology and Scientific Computing in the framework of the X Int. Conf. Information and Telecommunication Technologies and Mathematical Modeling of High-Tech Systems (ITTMM), Kulyabov, D.S., Samouylov, K.E., and Sevastianov, L.A., Eds., 2020, vol. 2639, pp. 141–157.
  5. Gevorkyan M.N., Korol’kova A.V., Kulyabov D.S. Implementation of geometric algebra in symbolic computation systems, Programmirovanie, 2023, no. 1, pp. 48–55.
  6. Korol’kova A.V., Gevorkyan M.N., Kulyabov D.S., Sevast’yanov L.A. Computer algebra tools for geometrization of Maxwell’s equations, Program. Comput. Software, 2023, vol. 49, pp. 336–371.
  7. Velieva T.R., Gevorkyan M.N., Demidova A.V., Korol’kova A.V., Kulyabov D.S. Geometric algebra and quaternion techniques in computer algebra systems for describing rotations in Eucledean space, Comput. Math. Math. Phys., 2023, vol. 63, pp. 29–39.
  8. Bowman J.C. Hammerlindl A. Asymptote: A vector graphics language, 2008, vol. 29, no. 2, pp. 288–294.
  9. Bowman J.C. Asymptote: Interactive TEX-aware 3D vector graphics, 2010, vol. 31, no. 2, pp. 203–205.
  10. Shardt O., Bowman J.C. Surface parameterization of nonsimply connected planar Bzier regions, Comput.-Aided Des., 2012, vol. 44, no. 5, pp. 484.e1–484.e10.
  11. Bowman, J.C. Asymptote: The vector graphics language, 2023. https://asymptote.sourceforge.io.
  12. Gevorkyan M.N., Korolkova A.V., Kulyabov D.S. Asymptote-based scientific animation, Discrete Contin. Models Appl. Comput. Sci., 2023, vol. 31, no. 2, pp. 139–149.
  13. Stroustrup B. Programming: Principles and Practice Using C++, Addison-Wesley Professional, 2014, 2nd ed.
  14. Hobby J., Knuth D. MetaPost on the web. https://www.tug.org/metapost.html.
  15. Staats C. An Asymptote tutorial, 2022. https://asymptote.sourceforge.io/asymptote_tutorial.pdf.
  16. Kryachkov Yu.G. Asymptote for beginners. http://mif.vspu.ru/books/ASYfb.pdf.
  17. Volchenko Yu.M. Scientific graphics in the Asymptote language. http://www.math.volchenko.com/AsyMan.pdf.
  18. Ivaldi, Ph., Euclidean Geometry with ASYMPTOTE, 2011.
  19. Lengyel E. Foundations of game engine development, Terathon Software LLC, vol. 1. http://foundationsofgameenginedev.com.
  20. Marschner S., Shirley P. Fundamentals of Computer Graphics, CRC Press, 5 ed.

Supplementary files

Supplementary Files
Action
1. JATS XML
2. Fig. 1. Variants of non-parallel planes.

Download (100KB)
3. Fig. 2. Options for parallel arrangement of planes. Option (c) is the limiting case of option (b).

Download (78KB)
4. Fig. 3. The intersection of three planes.

Download (79KB)

Copyright (c) 2024 Russian Academy of Sciences

Согласие на обработку персональных данных с помощью сервиса «Яндекс.Метрика»

1. Я (далее – «Пользователь» или «Субъект персональных данных»), осуществляя использование сайта https://journals.rcsi.science/ (далее – «Сайт»), подтверждая свою полную дееспособность даю согласие на обработку персональных данных с использованием средств автоматизации Оператору - федеральному государственному бюджетному учреждению «Российский центр научной информации» (РЦНИ), далее – «Оператор», расположенному по адресу: 119991, г. Москва, Ленинский просп., д.32А, со следующими условиями.

2. Категории обрабатываемых данных: файлы «cookies» (куки-файлы). Файлы «cookie» – это небольшой текстовый файл, который веб-сервер может хранить в браузере Пользователя. Данные файлы веб-сервер загружает на устройство Пользователя при посещении им Сайта. При каждом следующем посещении Пользователем Сайта «cookie» файлы отправляются на Сайт Оператора. Данные файлы позволяют Сайту распознавать устройство Пользователя. Содержимое такого файла может как относиться, так и не относиться к персональным данным, в зависимости от того, содержит ли такой файл персональные данные или содержит обезличенные технические данные.

3. Цель обработки персональных данных: анализ пользовательской активности с помощью сервиса «Яндекс.Метрика».

4. Категории субъектов персональных данных: все Пользователи Сайта, которые дали согласие на обработку файлов «cookie».

5. Способы обработки: сбор, запись, систематизация, накопление, хранение, уточнение (обновление, изменение), извлечение, использование, передача (доступ, предоставление), блокирование, удаление, уничтожение персональных данных.

6. Срок обработки и хранения: до получения от Субъекта персональных данных требования о прекращении обработки/отзыва согласия.

7. Способ отзыва: заявление об отзыве в письменном виде путём его направления на адрес электронной почты Оператора: info@rcsi.science или путем письменного обращения по юридическому адресу: 119991, г. Москва, Ленинский просп., д.32А

8. Субъект персональных данных вправе запретить своему оборудованию прием этих данных или ограничить прием этих данных. При отказе от получения таких данных или при ограничении приема данных некоторые функции Сайта могут работать некорректно. Субъект персональных данных обязуется сам настроить свое оборудование таким способом, чтобы оно обеспечивало адекватный его желаниям режим работы и уровень защиты данных файлов «cookie», Оператор не предоставляет технологических и правовых консультаций на темы подобного характера.

9. Порядок уничтожения персональных данных при достижении цели их обработки или при наступлении иных законных оснований определяется Оператором в соответствии с законодательством Российской Федерации.

10. Я согласен/согласна квалифицировать в качестве своей простой электронной подписи под настоящим Согласием и под Политикой обработки персональных данных выполнение мною следующего действия на сайте: https://journals.rcsi.science/ нажатие мною на интерфейсе с текстом: «Сайт использует сервис «Яндекс.Метрика» (который использует файлы «cookie») на элемент с текстом «Принять и продолжить».