PGA3D — демонстрация как она работает

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

Ссылка: Кратко про GA/PGA

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

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

Результат, который мы ходит увидеть

Результат, который мы ходит увидеть

Содержание:

Минимум теории

Для работы в 3D-пространстве будем использовать проективную геометрическую алгебру (коротко PGA3D).

Все объекты PGA3D (точки, прямые, плоскости, точки на бесконечности, трансформации и пр.) описываются мультивектором, состоящим из 16 чисел.

Здесь мы эти 16 чисел будем записывать в виде вложенного списка:

mv = {s,{nx,ny,nz,d},{vx,vy,vz,mx,my,mz},{px,py,pz,pw},ps}

Примеры мультивекторов:

Строим веер прямых

Построим прямые La и Lb, и веер из прямых L1..L9 между ними.

1) Строим прямую La

Возьмём в 3D-пространстве точку {0, -4, 2} и вектор {4, 1, 1}. В алегебру PGA3D они загоняются следующим образом:

p1 = {0, {0,0,0,0}, {0,0,0,0,0,0}, {0, -4, 2, 1}, 0}
v1 = {0, {0,0,0,0}, {0,0,0,0,0,0}, {4, 1, 1, 0}, 0}

В качестве прямой La зададим прямую, проходящую через p1 в направлении v1. Такая прямая находится по формуле:
$L_A = p_1 \lor v_1$
(здесь «V» — т.н. Regressive product, см. ниже)

Вычисленная прямая:
La = {0, {0,0,0,0}, {0, 8, -1, -12, 0, 0}, {0,0,0,0}, 0}

Например, в языке Haskell код для Regressive Product выглядит так:

Код для вычисления RP на языке Haskell

Код для вычисления RP на языке Haskell

Формулы для других преобразований далее не будут приводиться. Здесь просто показывается, как в целом выглядят формулы преобразований в PGA.

2) Строим прямую Lb

Возьмём две точки: {1, 2, 3} и {-1, 0, 5}

p2 = {0, {0,0,0,0}, {0,0,0,0,0,0}, {1, 2, 3, 1}, 0}
p3 = {0, {0,0,0,0}, {0,0,0,0,0,0}, {-1, 0, 5, 1}, 0}

и выберем Lb такой, чтобы она проходила через них. Такая Lb находится по той же формуле:
$L_B = p_2 \lor p_3$

Вычисленная прямая:
Lb = {0, {0,0,0,0}, {-2, -2, 2, 10, -8, 2}, {0,0,0,0}, 0}

Наши построенные прямые La и Lb (кстати, в мультивекторах прямых также хранится информация об их направлении, оно показано стрелочками):

Прямая La

Прямая La

Прямая Lb

Прямая Lb

3) Строим трансформацию, переводящую прямую La в прямую Lb

Трансформации (или моторы) — специальные мультивектора PGA, хранящие информацию о том, насколько и куда надо сдвинуть/повернуть что-либо.

$T_0 = \sqrt{L_A/L_B}$
(в GA можно делить мультивектор на мультивектор; также в GA из трансформаций можно извлекать корень — по сути это делает трансформацию ровно в 2 раза меньше)

Применив эту формулу, получим:

T0 = {0.42, {0,0,0,0}, {-0.59, -0.08, -0.67, -1.60, -0.77, 0.94}, {0,0,0,0}, 0.89}

4) Создадим из трансформации T0 отмасштабированные трансформации:

$T_1 = ScaleMotor[T_1, 0.1]$
$T_2 = ScaleMotor[T_1, 0.2]$
$T_3 = …$
$T_9 = ScaleMotor[T_1, 0.9]$

Например, для T1 получится:

T1 = {0.99, {0,0,0,0}, {-0.07, -0.01, -0.08, -0.23, -0.10, 0.08}, {0,0,0,0}, 0.01}

Полная формула для ScaleMotor (её обычно называют логарифмом от мотора) — сильно сложнее приводимой выше для Regressive Product.

Вычисление выглядит примерно так:

5) Строим веер прямых:

Чтобы построить первую прямую из веера — применяем к прямой La трансформацию T1.
Чтобы построить вторую прямую из веера — применяем к прямой La трансформацию T2.
И т.д.

Например, 5я прямая из веера находится применением Т5 к La по формуле:

$L_5 = T_5 * L_A * \tilde{T_5}$
(здесь под умножением подразумевается т.н. Geometric Product между 3-мя мультивекторами, а значок тильды означает операцию Reverse над мультивектором)

Применив эту формулу, в числах мы получим:

L5 = {0, {0,0,0,0}, {-5.52, 3.96, 4.33, 1.69, -13.69, 14.69}, {0,0,0,0}, 0}

Получив таким образом все прямые веера (от L1 до L9) и построив их в 3D-пространстве, мы и получаем картинку из начала статьи.

При использовании уже готового PGA движка пользователю не надо знать как численно высчитываются сами формулы (Regressive Product, Motor Scaling, Reverse и т.д.). Надо только знать, что «в таком-то случае используется Regressive Product, в таком-то Reverse» и т.п.

Исходные прямые La и Lb и веер прямых L1..L9 между ними

Исходные прямые La и Lb и веер прямых L1..L9 между ними

А теперь самое главное

Если мы захотим, например, построить такой же веер, но теперь не прямых, а плоскостей — формулы будут по сути те же самые!

Построение:

Прямая по 2м точкам: $p_1 \lor p_2$

Плоскость по 3м точкам: $p_1 \lor p_2 \lor p_3$

Трансформация:

Перевод прямой 1 в прямую 2:

$T = \sqrt{line_1/line_2}$

$line_2 = T * line_1 * \tilde{T}$

Перевод плоскости 1 в плоскость 2:

$T = \sqrt{plane_1/plane_2}$

$plane_2 = T * plane_1 * \tilde{T}$

Веер плоскостей<br><em>(стрелочки показывают ориентацию, т.е. направление нормалей плоскостей)</em>

Веер плоскостей
(стрелочки показывают ориентацию, т.е. направление нормалей плоскостей)

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

Обобщаем полученный Experience

Формулы всяких разных построений и трансформаций — одинаковы для плоскостей и прямых (кстати, для точек тоже).

В других аспектах картина схожая: