3622
правки
Изменения
→Алгоритм с изпользованием SLAV
=== Выпуклый полигон ===
В случае выпуклого многоугольника возникают только <tex> edge\ event'</tex>ы по определению. Поэтому просто Объяснить алгоритм можно описать следующим простым образом: найдём точки пересечения биссектрис многоугольника для каждой вершины со всеми соседними вершинами, возьмём такую точку, в которой произойдёт самый первый <tex> edge\ event</tex>, добавим полученную вершину в <tex> \mathrm{straight}\ \mathrm{skeleton} </tex>, соеденим её с вершинами ребра, которое исчезло в процессе текущего <tex> edge\ event'</tex>а, а потом перестроим полигон, создав новую вершину и подвинув все остальные вдоль биссектрис на одинаковое расстояние. Будем продолжать этот процесс до тех пор, пока многоугольник не превратится в треугольник.
Теперь реализуем этот алгоритм более эффективно. Для этого мы будем использовать специальную структуру данных {{---}} <tex> \mathrm{SLAV}</tex> (set of circular lists of active vertices). Эта структура хранит цикл всех вершин для внешней грани, а так же цикл для каждой дыры многоугольника и для всех многоугольников, возникающих в процессе построения <tex> S(P) </tex>. В данном случае у нас будет просто <tex> \mathrm{LAV}</tex> {{---}} [[Список#Циклический список | циклический список]] всех вершин многоугольника.
[[Файл:skeleton_lav.jpg]]
В таком списке частично найденного <tex> \mathrm{straight}\ \mathrm{skeleton} </tex> вершины имеют указатели на следующую и предыдущую вершину в порядке обхода контура, а так же указатели на инцидентные рёбра. Если представить процесс стягивания многоугольника, как будто у нас уже построена для него крыша, а мы двигаем вверх некоторую заметающую плоскость, где пересечение крыши и плоскости будет обозначать текущий слой, то можно заметить, что область полигона разбивается на несколько частей. Каждой части будет соответствовать свой <tex> \mathrm{LAV}</tex>, отсюда нам и нужен <tex> \mathrm{SLAV}</tex>.
==== Алгоритм для выпуклых полигонов ====
Далее считаем, что полигон представлен рёбрами вдоль движения по контуру полигона против часовой стрелки.
'''Шаг 1.''' Инициализация:
:<tex>(a)</tex> Поместим все вершины многоугольника <tex> V_1, V_2 \dots V_n </tex> в двусвязный циклический список в порядке обхода вдоль контура. Все вершины в <tex> \mathrm{LAV}</tex> считаются активными сейчас.
:<tex>(b)</tex> Для каждой вершины <tex> V_i </tex> в <tex> \mathrm{LAV}</tex> добавим указатели на инцидентные рёбра <tex> e_{i-1} = V_{i-1}V_i</tex> и <tex> e_i = V_i V_{i+1}</tex>, а также найдём луч биссектрисы <tex> b_i </tex>.
:<tex>(c)</tex> Для каждой вершины <tex> V_i </tex> найдём ближайшее пересечение биссектрисы <tex> b_i </tex> с лучами биссектрисами <tex> b_{i-1} </tex> и <tex> b_{i+1} </tex>. Если это пересечение существует, то положим поместим его в [[Двоичная куча | приоритетную очередь]] согласно <tex> L(e_i) </tex> {{---}} расстоянию от точки пересечения до одного из рёбер, инцидентных вершине <tex> V_i </tex>. Для каждой точки пересечения <tex> I_i </tex> будем так же хранить два указателя на вершины <tex> V_a </tex> и <tex> V_b </tex> {{---}} начала лучей биссектрис, которые пересекаются в точке <tex> I_i </tex>. Эти указатели понадобятся в будущем, когда нужно будет определять соответствующие вершинам рёбра <tex> e_a, e_b </tex> (см. рисунок ниже).
'''Шаг 2.''' Следующие действия выполняются в цикле, пока приоритетная очередь не пустая:
:<tex>(a)</tex> Извлечём точку пересечения <tex> I </tex> из приоритетной очереди.
:<tex>(b)</tex> Если вершины <tex> V_a </tex> и <tex> V_b </tex>, соответствующие данной точке пересечения помечены как обработанные, то переходим к следующей итерации цикла шага 2. Это означает, что ребро между данными вершинами полностью стянулось (обработанные вершины и стянутые рёбра помечены крестом на рисунке ниже). Эту проверку необходимо делать из-за того, что мы могли поместить обработанные вершины в момент получения новых <tex>event'</tex>ов.
:<tex>(c)</tex> Если осталось всего три вершины <tex> V_a, V_b, V_c </tex>, то добавим в <tex> \mathrm{straight}\ \mathrm{skeleton} </tex> рёбра <tex> IV_a, IV_b, IV_c </tex>. В случае выпуклого многоугольника в этом месте можно завершить алгоритм. Но в общем случае нужно будет перейти к началу цикла снова.
:<tex>(d)</tex> Добавим в <tex> \mathrm{straight}\ \mathrm{skeleton} </tex> рёбра <tex> IV_a, IV_b </tex>.
:<tex>(f)</tex> Посчитаем дополнительные величины для вершины <tex> V </tex>:
::* луч биссектрисы <tex> b </tex> между рёбрами <tex> e_a </tex> и <tex> e_b </tex>,
::* точки пересечения луча биссектрисы <tex>b </tex> с соседями биссектрисами вершин, соседними к <tex> V </tex> в <tex> \mathrm{LAV}</tex>, как в шаге <tex> 1c </tex>,::* сохраним ближайшие точки пересечения в приоритетной очереди. Точку пересечения кладём с расстоянием до стянутого ребра <tex> L(e_i) </tex>.
[[Файл:skeleton_convex_example.png|600px]]
В этом случае асимптотика алгоритма составляет <tex> \mathcal{O}(n \log n)</tex>, так как на каждой итерации цикла нам нужно положить константное число элементов в очередь, а итераций цикла не больше <tex> n </tex>.
==== Частные случаи ====
Частным случаем в алгоритме может быть совпадение нескольких <tex> edge\ event'</tex>ов в одной точке. Эти совпадения добавляются в шагах <tex> 1c </tex> и <tex> 2f </tex>, так как точки пересечения добавляются только в них, но могут быть относительно легко обработаны в шаге <tex> 2b </tex>. Или мы можем считать, что между <tex> edge\ event'</tex>ами в одной точке будут рёбра нулевого веса в полученном <tex> S(P) </tex>, а затем можно просто избавиться от лишних вершин в итоговом результате.
Также может случиться, что какие-то рёбра не стянулись в итоге в одну вершину, а слились. Такое возможно, если какие-то стороны полигона были изначально параллельны (этот случай легко увидеть на прямоугольнике, не являющемся квадратом). С этим частным случаем можно разобраться в шаге <tex> 2c </tex>, проверив, не совпала ли одна из трёх вершин с другой. В выпуклом многоугольнике слияние двух рёбер может произойти только один раз (что неправда для невыпуклого многоугольника), поэтому здесь несложно разобраться с таким случаем.
=== Невыпуклый полигон ===
Основной принцип для невыпуклых полигонов такой же. Только с вершиной ещё хранится дополнительный атрибут, обозначающий событие, которое в ней произошло: <tex> edge\ event</tex> или <tex> split\ event</tex>.
Если представить процесс стягивания многоугольника, как будто у нас уже построена для него крыша, а мы двигаем вверх некоторую заметающую плоскость, где пересечение крыши и плоскости будет обозначать текущий слой, то можно заметить, что область полигона разбивается на несколько частей. Каждой части будет соответствовать свой <tex> \mathrm{LAV}</tex>, отсюда нам и нужен <tex> \mathrm{SLAV}</tex>.
[[Файл:skeleton_split_event_example.png|400px]]
[[Файл:skeleton_b_point_coord.png|500px]]
В простейшем случае точка <tex> B </tex> появляется, когда "волновой фронт" распространения движения рёбер от невыпуклой вершины натыкается на встречный фронт противолежащего ребра. В такой момент возникает <tex> split\ event</tex>. Поэтому точка <tex> B </tex> может быть изначально охарактеризована, как точка, находящаяся на одном расстоянии от противолежащего угла ребра и прямых, содержащих рёбра невыпуклой вершины. Задача состоит в том, чтобы найти это самое противолежащее ребро (случай <tex> a) </tex> на рисунке выше). Но как показывает случай <tex> b) </tex>, простой тест на пересечение ребра и биссектрисы невыпуклой вершины не может быть использован (в этом случае луч биссектрисы пересекает сразу два ребра, непонятно, с каким из них произойдёт <tex> split\ event</tex>). Поэтому необходимо ещё проверять, что точка <tex> B </tex> лежит между лучами <tex> b_i </tex> и <tex> b_{i+1} </tex>, идущих из вершин, инцидентных противолежащему ребру <tex> e_i </tex>.
'''Замечание:''' простой тест на пересечение биссектрисы вершины <tex> V </tex> и целой линии, содержащей ребро, отсекает случаи тех рёбер, которые лежат ''позади'' вершины <tex> V </tex>.
[[Файл:skeleton_lav_managing.png|600px]]
Когда происходит работа с точкой <tex> B\ split\ event'</tex>а, то необходимо разбить соответствующий полигон на две части, что соответствует разделению <tex> \mathrm{LAV} </tex> данного полигона на два списка. И в каждый новый список нужно вставить новую вершину копию вершины <tex> X V </tex>, образующуюся образующейся в точке пересечения <tex> B </tex>. Обе вершины <tex> X V_1 </tex> и <tex> V_2 </tex> указывают на разделяющее ребро <tex> e_i </tex> (см. рисунок выше).
==== Частный случай множественных split event'ов на одном ребре ====
[[Файл:skeleton_collide_edge.jpg]]
Уже должно было стать понятно, что алгоритм не строит промежуточного представления <tex> \mathrm{straight}\ \mathrm{skeleton}</tex>, а работает исключительно с рёбрами исходного полигона. Это приводит к ситуации (см. рисунок выше), когда одно ребро является общим для нескольких новых полигонов в промежуточном представлении (то есть одно ребро меняет свою топологию разбивается несколько раз), образовавшихся после разделения старого полигона. В случае, когда ребро уже разбито, и происходит следующий за ним <tex>event</tex>, необходимо правильно определить концы противолежащего ребра (то есть вершины/узлы, которые активный активны в текущем уровне конструирования крыши, как например вершины <tex> X </tex> и <tex> Y </tex> на рисунке ниже).
[[Файл:skeleton_multi_edge.png|600px]]
Например, в данном случае ребро <tex> SY </tex> является частью ребра <tex> e_i = ZY </tex>, которое стягивается и должно теперь указывать на вершину <tex> X </tex>. Когда произойдёт следующее событые в точке пересечения <tex> B </tex>, то нам необходимо правильно указать ребро новой вершине в этой точке в <tex> \mathrm{LAV} </tex>. Реальный конец ребра <tex> e_i </tex> {{---}} точка <tex> Z </tex>, но мы хотим указать на ребро <tex> XY </tex>. Это необходимо для поддержания корректности структуры <tex> \mathrm{SLAV} </tex>. Ниже будет представлено два способа решения этой проблемы.===== Первый способ =====Можно физически разделить исходное ребро на два, вставив новую точку <tex> S </tex>. Это решает проблему, так как никакое ребро не будет разделено дважды, а определение концов разделяемого ребра выполняется просто. Вставка вершины для точки <tex> B </tex> в <tex> \mathrm{LAV} </tex> тоже происходит относительно просто, потому что мы знаем точно, с каким ребром эта вершина связана. Но такой подход требует отдельно рассматривать вершины типа <tex> S </tex>, чтобы не добавить их случайно в <tex> \mathrm{straight}\ \mathrm{skeleton}</tex>.===== Второй способ (используемый в авторском решении) =====Идея заключается в том, чтобы хранить только вершины, которые реально будут в <tex> \mathrm{straight}\ \mathrm{skeleton}</tex>, а указатели на разделямое ребро хранятся во всех подполигонах, для которых это ребро является общим. Это приводит к множественным попаданиям <tex> split\ event'</tex>ов на одно ребро. Для примера, два полигона на рисунке выше разделяют общую ссылку на ребро <tex> e_i </tex>. Во время процесса обработки вершины <tex> B </tex> многоугольник <tex> XMVNY </tex> разбивается на две части <tex> XMB </tex> и <tex> BNY </tex>, а вершина <tex> V </tex> помечается как обработанная.
==== Алгоритм для невыпуклых полигонов ====
:<tex>(c)</tex> Для каждой биссектрисы выпуклой вершины найдём ближайшую точку пересечения с биссектрисой соседней вершины, а для невыпуклых вершин найдём также точки пересечения с противолежащими рёбрами (как это описывалось раньше), и положим в приоритетную очередь ближайшую точку пересечения <tex> I </tex>. Будем также с этой точкой хранить её тип {{---}} <tex> edge\ event</tex> или <tex> split\ event</tex>.
'''Шаг 2.''' Пока очередь не пуста:
:<tex>(a)</tex> Извлечём точку пересечения <tex> I </tex> из приоритетной очереди. Если она имеет тип <tex> edge\ event</tex>, то её надо обработать так же, как в шагах <tex> 2b-2f</tex> алгоритма для невыпуклых выпуклых полигонов. Иначе выполнять шаги ниже.
:<tex>(b)</tex> Если точка пересечения указывает на уже обработанные вершины, то продолжить со следующей итерации цикла шага 2, как в случае с выпуклым полигоном.
:<tex>(c)</tex> Нужно сделать примерно то же самое, что и шаге <tex>2c</tex> алгоритма для выпуклых многоугольников. Только на этом цикл не завершается, а продолжается с новой итерации, так как многоугольник мог разделиться на несколько частей, и, возможно, мы обработали лишь один подпалигон и не последний.
::* найдём для каждой вершины <tex> V_1 </tex> и <tex> V_2 </tex> противолежащее ребро в своём подпалигоне,
::* разделим <tex> \mathrm{LAV}</tex> с вершиной <tex> V </tex> на две части (как показано на рисунке выше), вставим в части вершины <tex> V_1 </tex> и <tex> V_2 </tex>, а затем обе части добавим в <tex> \mathrm{SLAV}</tex>. Вершина <tex> V_1 </tex> будет следующей для предыдующего к <tex> V </tex> узлу в <tex> \mathrm{LAV}</tex> и предыдущей для конца противолежащего ребра. Аналогично для вершины <tex> V_2 </tex>. Этот шаг в действительно разбивает полигон на две части,
::* удалим из ассоциативного массива пару вершин ребра <tex> e_i </tex> и поместим две новых пары, в одной из которых будет вершина <tex> V_1 </tex> и конец ребра <tex> e_i </tex>, а в другом {{---}} начало <tex> e_i </tex> и вершина <tex> V_2 </tex>,
::* Добавим указатели вершинам <tex> V_1 </tex> и <tex> V_2 </tex> на соответствующие рёбра.
:<tex>(f)</tex> Для обеих вершин <tex> V_1 </tex> и <tex> V_2 </tex>:
Обработка событий обоих типов выполняется с такой же асимптотикой, что и в алгоритме для выпуклых полигонов. Основной вклад в асимптотику вносит вычисление <tex> split\ event'</tex>ов, когда нам нужно пробежаться по всем рёбрам и найти противолежащее. Отсюда и получается итоговая асимптотика <tex> \mathcal{O}(n^2) </tex>.
==== Случай полигонов с дырами дырками ====Данный алгоритм может работать и с многоугольниками, содержащими дырыдырки, если они ориентированы по часовой стрелке, чтобы внутренняя область многоугольника лежала слева от рёбер. И в самом начале алгоритма каждый замкнутый контур помещается в свой <tex> \mathrm{LAV} </tex> в множестве <tex> \mathrm{SLAV} </tex>.
[[Файл:skeleton_hole_example.png|500px]]
[[Файл:skeleton_chain2.jpg]]
Мы можем также упорядочить сами цепи вокруг точки события, объединив эти цепи в один циклический список. Таким образом событие получается как бы окружено списком рёбер, которые участвуют в этом событии, и никакие другие рёбра не участвуют. Можно заметить (рисунки <tex> c,\ d,\ e</tex> выше), что соседние рёбра в списке из изначально разных цепей становятся потом соседними в <tex>\mathrm{LAV}</tex>. И эти цепочки рёбер на самом деле не хранятся отдельными цепочками. Эти цепи и есть <tex>\mathrm{LAV}</tex>.
Алгоритм обработки GIE следующий: