Straight skeleton
Существует целый класс структур типа [1].
, которые описывают базовые топологические свойства объектов. Структура была придумала Oswin Aichholzer. Она используются в различных практических задачах (проектирование крыш для зданий) и для доказательства некоторых теоремТопологические свойства
Определение: |
Straight skeleton (Angular Bisector Network, ABN) полигона без самопересечений называется планарный граф, определяющий разбиение полигона на регионы, границами которых являются стороны полигона, биссектрисы углов и отрезки, соединяющие вершины straight skeleton, образовавшиеся в результате сжатия полигона. |
Опишем подробней, как получается такое разбиение. Мы можем представить, будто все стороны прямоугольника параллельно двигаются внутрь с одинаковой постоянной скоростью, то есть многоугольник как бы сжимается внутрь. Тогда вершины будут двигаться вдоль биссектрис , а точки пересечения биссектрис будут являться точками, в которых рёбра полностью сократились (выродились в точку). В каждый момент времени от начала движения рёбер мы получаем слоистую структуру (рис 1.). На рис. 2 синим цветом выделен — множество отрезков, образованных точками пересечения при движении сторон полигона. Чем-то структура похожа на строение крыши в домах (рис. 3). И для решения этой задачи как раз и может применяться: по стенам здания необходимо спроектировать его крышу.
Процесса стягивания многоугольника продолжается до тех пор, пока происходят его топологические изменения, то есть меняется число вершин в стянутом многоугольнике, и таким образом появляются новые вершины дерева
. Существуют два типа изменений, в ходе которых образуются новые вершины дерева:- — данное изменение происходит, когда сторона многоугольника полностью стягивается, делая соседние стороны инцидентными.
- — происходит, когда ребро разбивается на два новых ребра, исходящих из точки преломления старого. Такое событие происходит на биссектрисе вогнутой вершины многоугольника. И тогда стягиваемая многоугольником область может разбивться на две непересекающиеся многоугольные области.
На рисунке
ы изображены зелёным кругом, а ы — красным прямоугольником.Таким образом,
ы соответствуют внутренним вершинам , гранями являются области многоугольника, заметаемые сторонами многоугольника в процессе стягивания, дуги соединяют либо две внутренние вершины либо внутреннюю вершину с листом — вершиной многоугольника.Стоит также отметить, что в общем случае
ы могут быть нетривиальными. На рисунке ниже в случае в вершине совпали из вершины и ребра и ребра , а в случае совпали два а вершин и . Случаи и — простые и ы.Задача построения такого motion planning. Задача является более сложной, и здесь рассматриваться не будет.
является частным случаем задачи построения , где каждому ребру можно задавать вес, то есть скорость движения ребра. И эта скорость может быть даже отрицательной. Таким образом можно оффсетить полигоны и решать задачуСвойства Straight skeleton
Из процесса построения планарным графом. Ранее уже упоминалось, что он также является деревом. Будем обозначать простого полигона без самопересечений , в котором вершин, как . Тогда справедливы следующие леммы:
следует, что он являетсяЛемма (1): |
является деревом, содержит граней, не более внутренние вершины и не более рёбер. |
Доказательство: |
Каждая грань начинает образовываться во время стягивания ребра , и даже если на ребре произошёл , сама грань не могла разделиться. Построение грани завершается, когда ребро полностью стягивается. И это ребро дальше не может появиться снова, поэтому граней в столько, сколько сторон в многоугольнике, то есть ровно .То, что Внутренние вершины в является деревом, легко доказывается по индукции. База верна, когда внутренняя вершина всего одна. Тогда у листьями будут вершины многоугольника. Такой граф очевидным образом будет деревом. Если в внутренних вершин, то рассмотрим самый первый . Он закончился в какой-то внутренней вершине , у неё есть смежные листья — вершины, инцидентные этому ребру, — и из неё достижимы другие ы, с не более чем внутренними вершинами, и они являются деревьями по предположению индукцию. Тогда получаем, что для вершин тоже будет деревом. имеют степень не меньше — простой перебор всех случаев ов (степень будет больше, если в одной вершине совпало несколько событий). Так как имеет листьев, то внутренних вершин будет не больше , а так как является деревом, то рёбер у него будет не более . |
Замечание: если мы рассмотрим
в какой-то момент времени, то он вполне может содержать циклы (это видно на рисунке ниже). Однако его конечная структура будет деревом.Алгоритм с изпользованием SLAV
Далее будет описан алгоритм, придуманный Petr Felkel, который строит [2]. Однако этот алгоритм всё равно ещё достаточно медленный. В реальной жизни используют его модификации или более сложные алгоритмы.
за время , или просто , где — общее число вершин в полигоне, — число вогнутых вершин в полигоне. Немного модифицированный этот алгоритм используется в открытой библиотеке вычислительной геометрии CGALСначала алгоритм будет рассмотрен на простом случае — выпуклом многоугольнике, — а потом на невыпуклом многоугольнике.
Выпуклый полигон
В случае выпуклого многоугольника возникают только
ы по определению. Поэтому просто алгоритм можно описать следующим образом: найдём точки пересечения биссектрис многоугольника для каждой вершины со всеми соседними вершинами, возьмём такую точку, в которой произойдёт самый первый , добавим полученную вершину в , соеденим её с вершинами ребра, которое исчезло в процессе текущего а, а потом перестроим полигон, создав новую вершину и подвинув все остальные вдоль биссектрис на одинаковое расстояние. Будем продолжать этот процесс до тех пор, пока многоугольник не превратится в треугольник.Теперь реализуем этот алгоритм более эффективно. Для этого мы будем использовать специальную структуру данных — циклический список всех вершин многоугольника.
(set of circular lists of active vertices). Эта структура хранит цикл всех вершин для внешней грани, а так же цикл для каждой дыры многоугольника и для всех многоугольников, возникающих в процессе построения . В данном случае у нас будет просто —В таком списке частично найденного
вершины имеют указатели на следующую и предыдущую вершину в порядке обхода контура, а так же указатели на инцидентные рёбра. Если представить процесс стягивания многоугольника, как будто у нас уже построена для него крыша, а мы двигаем вверх некоторую заметающую плоскость, где пересечение крыши и плоскости будет обозначать текущий слой, то можно заметить, что область полигона разбивается на несколько частей. Каждой части будет соответствовать свой , отсюда нам и нужен .Алгоритм для выпуклых полигонов
Далее считаем, что полигон представлен рёбрами вдоль движения по контуру полигона против часовой стрелки.
Шаг 1. Инициализация:
- Поместим все вершины многоугольника в двусвязный циклический список в порядке обхода вдоль контура. Все вершины в считаются активными сейчас.
- Для каждой вершины в добавим указатели на инцидентные рёбра и , а также найдём луч биссектрисы .
- приоритетную очередь согласно — расстоянию от точки пересечения до одного из рёбер, инцидентных вершине . Для каждой точки пересечения будем так же хранить два указателя на вершины и — начала лучей биссектрис, которые пересекаются в точке . Эти указатели понадобятся в будущем, когда нужно будет определять соответствующие вершинам рёбра (см. рисунок ниже). Для каждой вершины найдём ближайшее пересечение биссектрисы с лучами и . Если это пересечение существует, то положим его в
Шаг 2. Следующие действия выполняются в цикле, пока приоритетная очередь не пустая:
- Извлечём точку пересечения из приоритетной очереди.
- Если вершины и , соответствующие данной точке пересечения помечены как обработанные, то переходим к следующей итерации цикла шага 2. Это означает, что ребро между данными вершинами полностью стянулось (обработанные вершины и стянутые рёбра помечены крестом на рисунке ниже).
- Если осталось всего три вершины , то добавим в рёбра . В случае выпуклого многоугольника в этом месте можно завершить алгоритм. Но в общем случае нужно будет перейти к началу цикла снова.
- Добавим в рёбра .
- пометим вершины и как обработанные (напомню, что они обозначаются крестом на рисунке к данному алгоритму),
- создадим новую вершину в точке пересечения (отмечена квадратиком на рисунке),
- добавим вершину в , то есть между предыдущем к и следующим к узлами,
- добавим вершине указатели на соответствующие рёбра и .
Теперь необходимо модифицировать (детали на рисунке ниже):
- луч биссектрисы между рёбрами и ,
- точки пересечения луча b с соседями в , как в шаге
- сохраним ближайшие точки пересечения в приоритетной очереди.
Посчитаем дополнительные величины для вершины :
Заметим, что нам не нужно пересчитывать все расстояния в очереди приоритетов. Если мы стянем полигон до первого события исчезания ребра, то относительный порядок остальных событий не изменится. Нам необходимо только вставить новые точки пересечения в правильные места. Это можно эффективно сделать, если использовать структуру данных типа skip list.
В этом случае асимптотика алгоритма составляет
, так как на каждой итерации цикла нам нужно положить константное число элементов в очередь, а итераций цикла не больше .Частные случаи
Частным случаем в алгоритме может быть совпадение нескольких
ов в одной точке. Эти совпадения добавляются в шагах и , но могут быть относительно легко обработаны в шаге .Также может случиться, что какие-то рёбра не стянулись в итоге в одну вершину, а слились. Такое возможно, если какие-то стороны полигона были изначально параллельны (этот случай легко увидеть на прямоугольнике, не являющемся квадратом). С этим частным случаем можно разобраться в шаге
, проверив, не совпала ли одна из трёх вершин с другой. В выпуклом многоугольнике слияние двух рёбер может произойти только один раз (что неправда для невыпуклого многоугольника), поэтому здесь несложно разобраться с таким случаем.Невыпуклый полигон
Основной принцип для невыпуклых полигонов такой же. Только с вершиной ещё хранится дополнительный атрибут, обозначающий событие, которое в ней произошло:
или .Наличие невыпуклой вершины может привести (а может и не привести) к разделению внутренней области. Невыпуклая вершина может так же участвовать в обычном
е (точка на рисунке выше). В таком случае ы обрабатываются так же, как и в алгоритме с выпуклым многоугольником.Посмотрим теперь, что делать с точкой
, в которой возникает .Нахождение координат точки B
В простейшем случае точка
появляется, когда "волновой фронт" распространения движения рёбер от невыпуклой вершины натыкается на встречный фронт противолежащего ребра. В такой момент возникает . Поэтому точка может быть изначально охарактеризована, как точка, находящаяся на одном расстоянии от противолежащего угла и прямых, содержащих рёбра невыпуклой вершины. Задача состоит в том, чтобы найти это самое противолежащее ребро (случай на рисунке выше). Но как показывает случай , простой тест на пересечение ребра и биссектрисы невыпуклой вершины не может быть использован (в этом случае луч биссектрисы пересекает сразу два ребра, непонятно, с каким из них произойдёт ). Поэтому необходимо ещё проверять, что точка лежит между лучами и , идущих из вершин, инцидентных противолежащему ребру .Замечание: простой тест на пересечение биссектрисы вершины
и целой линии, содержащей ребро, отсекает случаи тех рёбер, которые лежат позади вершины .Координаты возможной точки кандидата
вычисляются следующим образом: это точка пересечения биссектрисы вершины и биссектрисы угла, который образуется в точке пересечения прямой, содержащей одно из рёбер, инцидентных , и прямой, содержащей противолежащее ребро . Итоговая точка пересечения выбирается как ближайшая среди всех найденных точек .Частный случай: в этом месте также должна быть проверка на то, что противолежащее ребро не будет параллельно ни одному ребру вершины TODO: И что делать?(
. Тогда рёбра могут накладываться друг на друга.Работа с LAV в момент возникновения split event'a
Когда происходит работа с точкой
а, то необходимо разбить соответствующий полигон на две части, что соответствует разделению данного полигона на два списка. И в каждый новый список нужно вставить новую вершину , образующуюся в точке пересечения . Обе вершины указывают на разделяющее ребро (см. рисунок выше).Частный случай множественных split event'ов на одном ребре
Уже должно было стать понятно, что алгоритм не строит промежуточного представления
, а работает исключительно с рёбрами исходного полигона. Это приводит к ситуации (см. рисунок выше), когда одно ребро является общим для нескольких новых полигонов в промежуточном представлении (то есть одно ребро меняет свою топологию несколько раз), образовавшихся после разделения старого полигона. В случае, когда ребро уже разбито, и происходит следующий за ним , необходимо правильно определить концы противолежащего ребра (то есть вершины/узлы, которые активный в текущем уровне конструирования крыши, как например вершины и на рисунке ниже).Например, в данном случае ребро
является частью ребра , которое стягивается и должно теперь указывать на вершину . Когда произойдёт следующее событые в точке пересечения , то нам необходимо правильно указать ребро новой вершине в этой точке в . Реальный конец ребра — точка , но мы хотим указать на ребро . Это необходимо для поддержания корректности структуры . Ниже будет представлено два способа решения этой проблемы.Первый способ
Можно физически разделить исходное ребро на два, вставив новую точку
. Это решает проблему, так как никакое ребро не будет разделено дважды, а определение концов разделяемого ребра выполняется просто. Вставка вершины для точки в тоже происходит относительно просто, потому что мы знаем точно, с каким ребром эта вершина связана. Но такой подход требует отдельно рассматривать вершины типа , чтобы не добавить их случайно в .Второй способ (используемый в авторском решении)
Идея заключается в том, чтобы хранить только вершины, которые реально будут в
, а указатели на разделямое ребро хранятся во всех подполигонах, для которых это ребро является общим. Это приводит к множественным попаданиям ов на одно ребро.Для примера, два полигона на рисунке выше разделяют общую ссылку на ребро
. Во время процесса обработки вершины многоугольник разбивается на две части и , а вершина помечается как обработанная.Всё это нужно для того, чтобы правильно связать
с вершинами и , а не с и . Во время обхода выбирается правильная часть ребра (в данном случае она определяется вершинами и ). Определяется следующим образом: вершина лежит справа от биссектрисы посещённой вершины и слева от биссектрисы предка посещённой вершины .Алгоритм для невыпуклых полигонов
Шаг 1. Инициализация:
- Положим все вершины в , как это делается в алгоритме для выпуклых многоугольников, а потом поместим в .
- Найдём биссектрисы как в случае с выпуклым многоугольником.
- Для каждой биссектрисы выпуклой вершины найдём ближайшую точку пересечения с биссектрисой соседней вершины, а для невыпуклых вершин найдём также точки пересечения с противолежащими рёбрами (как это описывалось раньше), и положим в приоритетную очередь ближайшую точку пересечения . Будем также с этой точкой хранить её тип — или .
Шаг 2. Пока очередь не пуста:
- Извлечём точку пересечения из приоритетной очереди. Если она имеет тип , то её надо обработать так же, как в шагах алгоритма для невыпуклых полигонов. Иначе выполнять шаги ниже.
- Если точка пересечения указывает на уже обработанные вершины, то продолжить со следующей итерации цикла шага 2, как в случае с выпуклым полигоном.
- Нужно сделать примерно то же самое, что и шаге алгоритма для выпуклых многоугольников. Только на этом цикл не завершается, а продолжается с новой итерации, так как многоугольник мог разделиться на несколько частей, и, возможно, мы обработали лишь один подпалигон и не последний.
- Добавим в ребро , где точка указывает на вершину . Для ов точки пересечения указывают ровно на одну вершину в .
- пометим вершину как обработанную,
- создадим две новые вершины и с одинаковыми координатами точки пересечения ,
- найдём для каждой вершины и противолежащее ребро в своём подпалигоне,
- разделим с вершиной на две части (как показано на рисунке выше), вставим в части вершины и , а затем обе части добавим в . Вершина будет следующей для предыдующего к узлу в и предыдущей для конца противолежащего ребра. Аналогично для вершины . Этот шаг в действительно разбивает полигон на две части,
- Добавим указатели вершинам и на соответствующие рёбра.
Модифицируем теперь :
- найдём биссектрисы между рёбрами, на которые эти вершины слинковались в шаге ,
- найдём точки пересечения лучей с биссектрисами соседних вершин как в шаге (тут могут получиться точки пересечения обоих типов),
- сохраним ближайшие точки пересечения в приоритетной очереди.
Для обеих вершин и :
Обработка событий обоих типов выполняется с такой же асимптотикой, что и в алгоритме для выпуклых полигонов. Основной вклад в асимптотику вносит вычисление
ов, когда нам нужно пробежаться по всем рёбрам и найти противолежащее. Отсюда и получается итоговая асимптотика .Случай полигонов с дырами
Данный алгоритм может работать и с многоугольниками, содержащими дыры, если они ориентированы по часовой стрелке, чтобы внутренняя область многоугольника лежала слева от рёбер. И в самом начале алгоритма каждый замкнутый контур помещается в свой
в множестве .Пример не для слабонервных
Особенности реализации и другие частные случаи
TODO: Совпадение нескольких split event'ов в одной точке
TODO: Накладывание рёбер друг на друга
Алгоритм построения с помощью Motorcycle graph
Рассмотрим алгоритм построения мотографов.
на основеTODO: Алгоритм на мотографах
Другие алгоритмы
Существует простой в понимании и реализации алгоритм для построения триангуляции, который работает за время [3]. Aichholzer смог обобщить этот алгоритм для построения произвольного планарного графа[4]. Также автором в его оригинальной статье был представлен алгоритм построения данной структуры, базирующийся на понятии волнового фронта (англ. wavefront). Этот алгоритм может быть реализован за время с использованием памяти либо с использованием приоритетной очереди за время и памяти[5]. Известен алгоритм построения для монотонных полигонов за время с использованием памяти[6].
на основеВ данном конспект был (P.S. точнее, ещё будет) представлен алгоритм на основе мотографов, который придумали Stefan Huber и Martin Held. Они говорят, что даже смогли реализовать этот алгоритм, но код нигде не выкладывали.
См. также
Примечания
- ↑ Wikipedia — Fold-and-cut theorem
- ↑ Stepan Obdrazalek, "The Angular bisector network Implementation and the CGAL library"
- ↑ Stefan Huber, Martin Held, "Straight Skeletons and their Relation to Triangulations"
- ↑ Oswin Aichholzer, Franz Aurenhammera, "Straight Skeletons for General Polygonal Figures in the Plane"
- ↑ Oswin Aichholzer, Franz Aurenhammera, "A Novel Type of Skeleton for Polygons"
- ↑ Therese Biedl, Martin Held, Stefan Huber, Dominik Kaaser, Peter Palfrader, "Straight Skeletons of Monotone Polygons"
Источники информации
- Wikipedia — Straight skeleton
- Designing roofs and drawing phylogenetic trees
- Eric Berberich, "Straight Skeleton, Computational Geometry and Geometric Computing Seminar"
- Petr Felkel, Stepan Obdrazalek, "Straight Skeleton Implementation"
- Engineering a weighted straight skeleton algorithm
- Визуализатор алгоритма
- CGAL 4.5 — 2D Straight Skeleton and Polygon Offsetting