Visibility graph и motion planning — различия между версиями
Igorjan94 (обсуждение | вклад) (→Visibility graph) |
м (rollbackEdits.php mass rollback) |
||
(не показано 39 промежуточных версий 5 участников) | |||
Строка 1: | Строка 1: | ||
− | == Нахождение пути между точками с препятствиями == | + | == Нахождение любого пути между точками с препятствиями == |
{|align="right" | {|align="right" | ||
|-valign="top" | |-valign="top" | ||
Строка 5: | Строка 5: | ||
|[[Файл:notShort.png|300px|thumb|right|Такой путь не самый короткий]] | |[[Файл:notShort.png|300px|thumb|right|Такой путь не самый короткий]] | ||
|} | |} | ||
− | + | Для начала рассмотрим движение материальной точки. Случай, когда размером и формой движимого объекта пренебречь нельзя, будет рассмотрен [[Visibility graph и motion planning#Motion planning|позднее]]. | |
− | + | Эту задачу можно решить с помощью [[Трапецоидная карта | трапецоидной карты]]. По ней строится граф, ребра которого соединяют центры трапедоидов, а также начальную и конечную вершины с серединами вертикальных сторон трапецоидов. В таком графе ищется путь между начальной и конечной вершинами. | |
− | + | Если точки лежат внутри одного трапецоида {{---}} ответ найден. Иначе идём из стартовой точки в центр её трапецоида, далее по построенным рёбрам ищем трапецоид содержащий финальную точку. Для этого можно использовать поиск в ширину или другой алгоритм нахождения кратчайшего пути в графе. В конечном итоге, соединяем середину последнего трапецоида с конечной вершиной. | |
− | + | Данный алгоритм работает за <tex> O(n \log n) </tex> и за линейное количество памяти и хорошо подходит для нахождения какого-нибудь пути между парой данных вершин. Но если нужно найти кратчайший путь, этот алгоритм не подходит, хоть и работает быстро. Однако, решения нахождения кратчайшего пути в лучшем случае работают за <tex> O(n^2) </tex> времени и памяти (здесь и далее <tex> n </tex> {{---}} количество всех вершин). | |
− | |||
− | Для простоты рассуждений вершины | + | == Нахождение кратчайшего пути между точками с препятствиями == |
+ | === Visibility graph === | ||
+ | Рассмотрим точное решение нахождения кратчайшего пути на плоскости между двумя точками с полигональными препятствиями с помощью построения графа видимости. После его построения, как и в случае с трапецоидной картой, кратчайший путь ищется любым стандартным алгоритмом поиска (например, алгоритмом [[Алгоритм Дейкстры|Дейкстры]] или [[Алгоритм A*|A*]]). | ||
+ | |||
+ | Для простоты рассуждений начальную и конечную вершины будем считать вершинами полигонов. | ||
{{Лемма | {{Лемма | ||
Строка 26: | Строка 29: | ||
{{Определение | {{Определение | ||
|definition = | |definition = | ||
− | Говорят, что вершина <tex> u </tex> | + | Говорят, что вершина <tex> u </tex> ''видна'' (англ. mutually visible) из <tex> v </tex>, если отрезок <tex> uv </tex> не пересекает ни одного препятствия. |
}} | }} | ||
{{Определение | {{Определение | ||
|definition = | |definition = | ||
− | '''visibility graph | + | ''Граф видимости'' (англ. visibility graph) {{---}} граф, вершины которого {{---}} вершины полигонов. Между вершинами <tex> u </tex> и <tex> v </tex> существует ребро, если из <tex> u </tex> видна <tex> v </tex>. |
}} | }} | ||
Строка 36: | Строка 39: | ||
{{Лемма | {{Лемма | ||
+ | |about=О неиспользуемых вершинах | ||
|statement= | |statement= | ||
[[Файл:edgeToDelete.png|150px|thumb|right|Удаляем <tex> BD </tex>]] | [[Файл:edgeToDelete.png|150px|thumb|right|Удаляем <tex> BD </tex>]] | ||
− | Если существуют вершины <tex> A, B, C </tex> одного препятствия и вершина <tex> D </tex> такая, что поворот <tex> DBA </tex> не совпадает с поворотом <tex> DBC </tex>, то ребро <tex> DB </tex> не принадлежит кратчайшему пути и его можно удалить из графа. (См. поясняющую картинку справа) | + | # Если существуют вершины <tex> A, B, C </tex> одного препятствия и вершина <tex> D </tex> такая, что поворот <tex> DBA </tex> не совпадает с поворотом <tex> DBC </tex>, то ребро <tex> DB </tex> не принадлежит кратчайшему пути и его можно удалить из графа. (См. поясняющую картинку справа) |
+ | # Все внутренние вершины, кроме вырожденного случая, (начальная/конечная точка лежит внутри выпуклой оболочки фигуры) можно игнорировать. | ||
|proof= | |proof= | ||
[[Файл:edgeNotToDelete.png|200px|thumb|right|Не удаляем <tex> BS </tex>]] | [[Файл:edgeNotToDelete.png|200px|thumb|right|Не удаляем <tex> BS </tex>]] | ||
− | Путь проходящий через ребро <tex> BD </tex> будет длиннее, чем через соседей точки <tex> B </tex>, так как по неравенству треугольника <tex> AB + BD | + | # Путь проходящий через ребро <tex> BD </tex> будет длиннее, чем через соседей точки <tex> B </tex>, так как по неравенству треугольника <tex> AB + BD > AD </tex> |
+ | # Если случай не вырожденный, значит заход внутрь фигуры только увеличит суммарный путь, так как по неравенству треугольника расстояние между соседними выпуклыми вершинами всегда меньше суммы расстояний с учётом внутренней. | ||
}} | }} | ||
− | По доказанным леммам любое ребро кратчайшего пути содержится в графе, | + | По доказанным леммам любое ребро кратчайшего пути содержится в графе. Таким образом, для нахождения кратчайшего пути осталось найти кратчайший путь в этом графе от начальной до конечной вершины. |
=== Построение visibility графа === | === Построение visibility графа === | ||
==== Наивный алгоритм. <tex> O(n ^ 3) </tex> ==== | ==== Наивный алгоритм. <tex> O(n ^ 3) </tex> ==== | ||
− | + | Для каждой пары вершин проверяем, можно ли добавить ребро между ними, то есть нет ли пересечений с полигонами. <tex> O(n^2) </tex> пар вершин и <tex> O(n) </tex> ребер, то есть <tex> O(n^3) </tex>. | |
==== Lee’s Algorithm. <tex> O(n ^ 2 \log n) </tex> ==== | ==== Lee’s Algorithm. <tex> O(n ^ 2 \log n) </tex> ==== | ||
Строка 54: | Строка 60: | ||
| | | | ||
[[Файл:Zam.png|300px|thumb|left|Заметание плоскости вращающимся лучом]] | [[Файл:Zam.png|300px|thumb|left|Заметание плоскости вращающимся лучом]] | ||
− | Однако можно это сделать за <tex> O(n ^ 2 \log n) </tex>. Идея алгоритма проста : для каждой вершины найдем видимые из нее вершины | + | Однако можно это сделать за <tex> O(n ^ 2 \log n) </tex>. Идея алгоритма проста: для каждой вершины найдем видимые из нее вершины. Если научиться делать это за <tex> O(n \log n) </tex>, задача решена, так как всего точек <tex> n </tex>. |
− | + | Для каждой вершины будем рассматривать только правую половину плоскости, так как ребра, которые должны идти в левую половину, будут исходить из вершин, для которых текущая вершина будет справа. | |
− | Переформулируем задачу : | + | Переформулируем задачу. Дано: точка <tex> v </tex> и множество отрезков {{---}} ребер препятствий. |
− | Найти : множество концов отрезков, видимых из <tex> v </tex>. | + | Найти: множество концов отрезков, видимых из <tex> v </tex>. |
Для решения этой задачи будем использовать заметающий луч с началом в точке <tex> v </tex>. Его статусом будут отрезки, которые его пересекают, упорядоченные по возрастанию расстояния от точки <tex> v </tex> до точки пересечения. Точками событий будут концы отрезков. | Для решения этой задачи будем использовать заметающий луч с началом в точке <tex> v </tex>. Его статусом будут отрезки, которые его пересекают, упорядоченные по возрастанию расстояния от точки <tex> v </tex> до точки пересечения. Точками событий будут концы отрезков. | ||
− | + | Пустим луч из рассматриваемой вершины <tex> v </tex> вертикально вверх и добавим в статус все отрезки, которые он пересекает, по увеличению расстояния до них. Теперь будем рассматривать точки <tex> w \in V </tex> в порядке сортировки по углу между <tex> v </tex> и вертикальной полуосью <tex> l </tex>. При таком обходе для проверки видимости вершины достаточно проверить пересечение с ближайшим к <tex> v </tex> отрезком, то есть первым в статусе(так как отрезки отсортированы по расстоянию до них). Действительно, если вершина <tex> w </tex> не видна, то отрезок <tex> vw </tex> пересекает несколько отрезков, лежащих перед <tex> w </tex>, а значит и ближайший. В противном случае все пересекаемые лучом отрезки лежат за вершиной <tex> w </tex> и пересечения отрезка <tex> vw </tex> с ближайшим отрезком не будет. Вне зависимости от видимости вершины, необходимо изменить статус заметающего луча. Для этого необходимо удалить из статуса все отрезки, которые заканчиваются вершине <tex> w </tex> (лежат слева от прямой <tex> vw </tex>) и добавить все отрезки, которые в ней начинаются (лежат справа от прямой <tex> vw </tex>). | |
===== Псевдокод ===== | ===== Псевдокод ===== | ||
− | + | graph buildVisibilityGraph(Set<Segment> segments) | |
− | graph buildVisibilityGraph(Set<Segment> segments) | + | vertices = getVertices(segments) <tex> \cup\ \{s,\ t\} </tex> |
− | + | graph = visibilityGraph(vertices) | |
− | + | '''for''' Vertex <tex>v</tex> '''in''' vertices | |
− | + | '''for''' Vertex <tex>w</tex> '''in''' getVisibleVertices(<tex>v</tex>, segments) | |
− | + | visibilityGraph.addEdge(<tex>v</tex>, <tex>w</tex>) | |
− | + | '''return''' visibilityGraph | |
− | + | Здесь функция getVisibleVertices(<tex> v </tex>) возвращает все видимые из <tex> v </tex> вершины и выглядит так: | |
− | + | Set<Vertex> getVisibleVertices(Vertex <tex>v</tex>, Set<Segment> segments) | |
− | Здесь функция getVisibleVertices(<tex> v </tex>) | + | Set<Vertex> answer |
− | + | '''for''' Segment <tex>s</tex> '''in''' segments | |
− | + | '''if''' intersect(<tex> s </tex>, <tex> l </tex>) | |
− | + | status.add(<tex>s</tex>) | |
− | + | '''for''' Point <tex>w</tex> '''in''' segments | |
− | + | '''if''' <tex>v.x \leqslant w.x</tex> | |
− | + | currentVertices.add(<tex>w</tex>) | |
− | + | sort(currentVertices) by angle | |
− | + | '''for''' Point <tex>w</tex> '''in''' currentVertices | |
− | + | '''if''' '''not''' intersect(<tex>vw</tex>, status.closest) | |
− | + | answer.add(<tex>w</tex>) | |
− | + | '''for''' Segment <tex>s</tex> ending in <tex>w</tex> | |
− | + | status.delete(<tex>s</tex>) | |
− | + | '''for''' Segment <tex>s</tex> beginning in <tex>w</tex> | |
− | + | status.add(<tex>s</tex>) | |
− | + | '''return''' answer | |
− | + | В качестве статуса нужно использовать структуру данных, позволяющую добавлять и удалять из нее отрезки за <tex> O(\log n) </tex> и извлекать минимум за <tex> O(1) </tex> или <tex> O(\log n) </tex>. В этом случае достигается асимптотика <tex> O(n^2 \log n) </tex>, так как для каждой из <tex> n </tex> точек выполняется сортировка за <tex> O(n \log n) </tex>, обновление статуса (суммарно <tex> O(n \log n) </tex>, так как каждый отрезок добавляется и удаляется из статуса не более одного раза) и запросы ближайшего отрезка (<tex> O(\log n) </tex> или <tex> O(1) </tex> на точку, то есть <tex> O(n \log n) </tex> или <tex> O(n) </tex>). | |
− | |||
− | |||
− | </ | ||
− | В качестве статуса нужно использовать структуру данных, позволяющую добавлять и удалять из нее отрезки за <tex> O(\log n) </tex> и извлекать минимум за <tex> O(1) </tex> или <tex> O(\log n) </tex>. В этом случае достигается асимптотика <tex> O(n^2 \log n) </tex>, так как для каждой из <tex> n </tex> точек выполняется сортировка | ||
| | | | ||
− | [[Файл:Zamrefr1.png|250px|thumb|right|Обновление статуса заметающего луча]] | + | [[Файл:Zamrefr1.png|250px|thumb|right|Обновление статуса заметающего луча: добавляем ребра <tex> w_1 w_5 </tex> и <tex> w_1 w_2 </tex> в статус]] |
− | [[Файл:Zamrefr2.png|250px|thumb|right| | + | [[Файл:Zamrefr2.png|250px|thumb|right|Добавляем ребра <tex> w_3 w_2 </tex> и <tex> w_3 w_4 </tex> в статус]] |
− | [[Файл:Zamrefr3.png|250px|thumb|right| | + | [[Файл:Zamrefr3.png|250px|thumb|right|Удаляем ребра <tex> w_3 w_2 </tex> и <tex> w_1 w_2 </tex> из статуса]] |
|} | |} | ||
− | == | + | == Motion planning == |
− | [ | + | [[Файл:mink.png|200px|thumb|left|Изменяем препятствия]] |
+ | [[Файл:mink2.png|400px|thumb|right|Ищем путь для точки]] | ||
+ | Рассмотрим задачу нахождения кратчайшего пути, когда движимый объект {{---}} это выпуклый полигон. Например, робот, которого надо доставить из начальной в конечную точку. | ||
− | + | Если полигон вращать нельзя, задачу сводится к движению точки так: выбирается точка на полигоне, которая принимается за начало координат. В такой системе координат для каждого препятствия считается [[Сумма Минковского (определение, вычисление)|сумма Минковского]] с полигоном. Получаются бОльшие препятствия, но теперь достаточно двигать выбранную точку, что было описано выше. | |
− | + | Если полигон можно вращать, задача нахождения ''кратчайшего'' пути становится достаточно ресурсоёмка, поэтому обычно рассматривают задачу нахождения какого-нибудь пути между конечными точками. | |
− | |||
− | |||
− | |||
− | + | Первый шаг решения этой задачи совпадает с предыдущим случаем: выберем точку и построим [[Сумма Минковского (определение, вычисление)|сумму Минковского]] препятствий с полигоном. Рассмотрим малый угол <tex> \epsilon </tex>. Представим, что поворот полигона на этот угол {{---}} это движение вверх-вниз между слоями, на каждом из которых посчитана сумма Минковского с полигоном, повернутым на этот угол. | |
− | + | На каждом слое построим трапецоидную карту и граф, как описано в [[Visibility graph и motion planning#Нахождение пути между точками с препятствиями|начале]]. Если [[Пересечение многоугольников (PSLG overlaying)|пересечь]] соседние слои и добавить между их графами ребра, получится один большой граф, в котором ищется кратчайший путь. | |
− | + | При таком подходе может возникнуть ошибка при пересечении слоев: на каждом слое состояния будут допустимые, а осуществить поворот физически будет невозможно. Обычно, эту проблему решают двумя способами: измельчением угла поворота и изначальным сглаживанием углов полигона. Первый способ повышает не только точность решения, но и вычислительную сложность задачи. Второй подход практически исключает возможность нахождения пути, когда его нет, но повышает вероятность "ненахождения" пути, когда он есть. | |
− | == Источники == | + | == Источники информации == |
− | * Mark de Berg, Otfried Cheong, Marc van Kreveld, Mark Overmars | + | * Mark de Berg, Otfried Cheong, Marc van Kreveld, Mark Overmars, Computational Geometry: Algorithms and Applications {{---}} Third edition {{---}} Springer, 2008. {{---}} Chapter 15. {{---}} ISBN 978-3-540-77973-5 |
− | * [http://www.academia.edu/2845047/3D_Visibility_Graph | + | * [http://www.academia.edu/2845047/3D_Visibility_Graph Academia.edu] {{---}} 3D Visibility Graph |
− | * [http://habrahabr.ru/post/199256/ | + | * [http://habrahabr.ru/post/199256/ Хабрахабр] {{---}} Motion planning: граф видимости, дорожные карты |
+ | * [http://igitur-archive.library.uu.nl/math/2006-1214-201604/overmars_88_new_methods.pdf igitur-archive.library.uu.nl] {{---}} Visibility graph при помощи rotation tree за <tex>O(n^2)</tex>. | ||
[[Категория: Вычислительная геометрия]] | [[Категория: Вычислительная геометрия]] | ||
== Ссылки == | == Ссылки == | ||
− | * | + | * [https://github.com/Igorjan94/cg/blob/master/include/cg/algorithms/visibilityGraph.h Github] {{---}} Реализация алгоритма за <tex> O(n^2 \log n) </tex> |
Текущая версия на 19:39, 4 сентября 2022
Содержание
Нахождение любого пути между точками с препятствиями
Для начала рассмотрим движение материальной точки. Случай, когда размером и формой движимого объекта пренебречь нельзя, будет рассмотрен позднее.
Эту задачу можно решить с помощью трапецоидной карты. По ней строится граф, ребра которого соединяют центры трапедоидов, а также начальную и конечную вершины с серединами вертикальных сторон трапецоидов. В таком графе ищется путь между начальной и конечной вершинами.
Если точки лежат внутри одного трапецоида — ответ найден. Иначе идём из стартовой точки в центр её трапецоида, далее по построенным рёбрам ищем трапецоид содержащий финальную точку. Для этого можно использовать поиск в ширину или другой алгоритм нахождения кратчайшего пути в графе. В конечном итоге, соединяем середину последнего трапецоида с конечной вершиной.
Данный алгоритм работает за
и за линейное количество памяти и хорошо подходит для нахождения какого-нибудь пути между парой данных вершин. Но если нужно найти кратчайший путь, этот алгоритм не подходит, хоть и работает быстро. Однако, решения нахождения кратчайшего пути в лучшем случае работают за времени и памяти (здесь и далее — количество всех вершин).Нахождение кратчайшего пути между точками с препятствиями
Visibility graph
Рассмотрим точное решение нахождения кратчайшего пути на плоскости между двумя точками с полигональными препятствиями с помощью построения графа видимости. После его построения, как и в случае с трапецоидной картой, кратчайший путь ищется любым стандартным алгоритмом поиска (например, алгоритмом Дейкстры или A*).
Для простоты рассуждений начальную и конечную вершины будем считать вершинами полигонов.
Лемма (О кратчайшем пути): |
Любой кратчайший путь между двумя вершинами с полигональными препятствиями представляет собой ломаную, вершины которой — вершины полигонов. |
Доказательство: |
Пусть кратчайший путь — не ломаная. В таком случае, на пути существует такая точка , которая не принадлежит ни одному прямому отрезку. Это означает, что существует -окрестность точки , в которую не попадает ни одно препятствие (случай, когда точка попала на ребро рассматривается аналогично). В таком случае, подпуть, который находится внутри -окрестности, по неравенству треугольника может быть сокращён по хорде, соединяющий точки пересечения границы -окрестности с путем. Раз часть пути может быть уменьшена, значит и весь путь может быть уменьшен, а значит исходное предположение некорректно. |
Определение: |
Говорят, что вершина | видна (англ. mutually visible) из , если отрезок не пересекает ни одного препятствия.
Определение: |
Граф видимости (англ. visibility graph) — граф, вершины которого — вершины полигонов. Между вершинами | и существует ребро, если из видна .
В худшем случае в таком графе может быть ребер. Однако по некоторым ребрам кратчайший путь точно не пройдет, и такие ребра из графа можно удалить.
Лемма (О неиспользуемых вершинах): |
|
Доказательство: |
|
По доказанным леммам любое ребро кратчайшего пути содержится в графе. Таким образом, для нахождения кратчайшего пути осталось найти кратчайший путь в этом графе от начальной до конечной вершины.
Построение visibility графа
Наивный алгоритм.
Для каждой пары вершин проверяем, можно ли добавить ребро между ними, то есть нет ли пересечений с полигонами.
пар вершин и ребер, то есть .Lee’s Algorithm.
Однако можно это сделать за . Идея алгоритма проста: для каждой вершины найдем видимые из нее вершины. Если научиться делать это за , задача решена, так как всего точек .Для каждой вершины будем рассматривать только правую половину плоскости, так как ребра, которые должны идти в левую половину, будут исходить из вершин, для которых текущая вершина будет справа. Переформулируем задачу. Дано: точка и множество отрезков — ребер препятствий. Найти: множество концов отрезков, видимых из .Для решения этой задачи будем использовать заметающий луч с началом в точке . Его статусом будут отрезки, которые его пересекают, упорядоченные по возрастанию расстояния от точки до точки пересечения. Точками событий будут концы отрезков.Пустим луч из рассматриваемой вершины вертикально вверх и добавим в статус все отрезки, которые он пересекает, по увеличению расстояния до них. Теперь будем рассматривать точки в порядке сортировки по углу между и вертикальной полуосью . При таком обходе для проверки видимости вершины достаточно проверить пересечение с ближайшим к отрезком, то есть первым в статусе(так как отрезки отсортированы по расстоянию до них). Действительно, если вершина не видна, то отрезок пересекает несколько отрезков, лежащих перед , а значит и ближайший. В противном случае все пересекаемые лучом отрезки лежат за вершиной и пересечения отрезка с ближайшим отрезком не будет. Вне зависимости от видимости вершины, необходимо изменить статус заметающего луча. Для этого необходимо удалить из статуса все отрезки, которые заканчиваются вершине (лежат слева от прямой ) и добавить все отрезки, которые в ней начинаются (лежат справа от прямой ).Псевдокодgraph buildVisibilityGraph(Set<Segment> segments) vertices = getVertices(segments)graph = visibilityGraph(vertices) for Vertex in vertices for Vertex in getVisibleVertices( , segments) visibilityGraph.addEdge( , ) return visibilityGraph Здесь функция getVisibleVertices( ) возвращает все видимые из вершины и выглядит так:Set<Vertex> getVisibleVertices(Vertex, Set<Segment> segments) Set<Vertex> answer for Segment in segments if intersect( , ) status.add( ) for Point in segments if currentVertices.add( ) sort(currentVertices) by angle for Point in currentVertices if not intersect( , status.closest) answer.add( ) for Segment ending in status.delete( ) for Segment beginning in status.add( ) return answer В качестве статуса нужно использовать структуру данных, позволяющую добавлять и удалять из нее отрезки за и извлекать минимум за или . В этом случае достигается асимптотика , так как для каждой из точек выполняется сортировка за , обновление статуса (суммарно , так как каждый отрезок добавляется и удаляется из статуса не более одного раза) и запросы ближайшего отрезка ( или на точку, то есть или ). |
Motion planning
Рассмотрим задачу нахождения кратчайшего пути, когда движимый объект — это выпуклый полигон. Например, робот, которого надо доставить из начальной в конечную точку.
Если полигон вращать нельзя, задачу сводится к движению точки так: выбирается точка на полигоне, которая принимается за начало координат. В такой системе координат для каждого препятствия считается сумма Минковского с полигоном. Получаются бОльшие препятствия, но теперь достаточно двигать выбранную точку, что было описано выше.
Если полигон можно вращать, задача нахождения кратчайшего пути становится достаточно ресурсоёмка, поэтому обычно рассматривают задачу нахождения какого-нибудь пути между конечными точками.
Первый шаг решения этой задачи совпадает с предыдущим случаем: выберем точку и построим сумму Минковского препятствий с полигоном. Рассмотрим малый угол . Представим, что поворот полигона на этот угол — это движение вверх-вниз между слоями, на каждом из которых посчитана сумма Минковского с полигоном, повернутым на этот угол.
На каждом слое построим трапецоидную карту и граф, как описано в начале. Если пересечь соседние слои и добавить между их графами ребра, получится один большой граф, в котором ищется кратчайший путь.
При таком подходе может возникнуть ошибка при пересечении слоев: на каждом слое состояния будут допустимые, а осуществить поворот физически будет невозможно. Обычно, эту проблему решают двумя способами: измельчением угла поворота и изначальным сглаживанием углов полигона. Первый способ повышает не только точность решения, но и вычислительную сложность задачи. Второй подход практически исключает возможность нахождения пути, когда его нет, но повышает вероятность "ненахождения" пути, когда он есть.
Источники информации
- Mark de Berg, Otfried Cheong, Marc van Kreveld, Mark Overmars, Computational Geometry: Algorithms and Applications — Third edition — Springer, 2008. — Chapter 15. — ISBN 978-3-540-77973-5
- Academia.edu — 3D Visibility Graph
- Хабрахабр — Motion planning: граф видимости, дорожные карты
- igitur-archive.library.uu.nl — Visibility graph при помощи rotation tree за .
Ссылки
- Github — Реализация алгоритма за