Visibility graph и motion planning
Visibility graph
Рассмотрим задачу нахождения кратчайшего пути от точки позднее.
до с препятствиями. Для начала рассмотрим движение материальной точки, случай произвольного полигона будетДля нахождения пути можно построить трапецоидную карту, соединить ребрами середины вертикальных сторон трапецоидов с их центрами и в этом графе любым алгоритмом поиска кратчайших путей (например, алгоритмом Дейкстры или A*) найти путь от до . Однако, этот путь не будет абсолютно кратчайшим.
Этот алгоритм поиска пути через трапецоидную карту обширно используется, так как дает хорошее приближение, работает за
и занимает линейное количество памяти, в то время как точные решения в лучшем случае работают за времени и памяти (здесь и далее — количество всех вершин).Теперь рассмотрим точное решение с помощью построения графа видимости. Для этого нам потребуется лемма:
Лемма (О кратчайшем пути): |
Любой кратчайший путь от до с полигональными препятствиями представляет собой ломаную, вершины которой — вершины полигонов. |
Доказательство: |
Пусть кратчайший путь — не ломаная. В таком случае, на пути существует такая точка , которая не принадлежит ни одному прямому отрезку. Это означает, что существует -окрестность точки , в которую не попадает ни одно препятствие (случай, когда точка попала на ребро рассматривается аналогично). В таком случае, подпуть, который находится внутри -окрестности, по неравенству треугольника может быть сокращён по хорде, соединяющий точки пересечения границы -окрестности с путем. Раз часть пути может быть уменьшена, значит и весь путь может быть уменьшен, а значит исходное предположение некорректно. |
Определение: |
visibility graph — граф, вершины которого — вершины полигонов, а также | и . Между вершинами и существует ребро, если из видна (mutually visible) .
Лемма: |
Доказательство: |
Путь проходящий через ребро будет длиннее, чем через соседей точки , так как по неравенству треугольника |
По доказанным леммам любое ребро кратчайшего пути содержится в графе, таким образом для нахождения кратчайшего пути осталось найти кратчайший путь в этом графе от
до .Построение visibility графа
Наивный алгоритм.
Если делать наивно, т. е. для каждой пары вершин проверять можно ли добавить ли такое ребро(нет ли пересечений с полигонами), будет
.Lee’s Algorithm.
Однако можно это сделать за
. Нам нужно решить следующую задачу: на плоскости дано множество отрезков (рёбер препятствий) и точка . Найти все концы отрезков, видимые из точки .Будем заметать плоскость вращающимся лучом с началом в точке
. Статусом заметающего луча будут отрезки, которые его пересекают, упорядоченные по возрастанию расстояния от точки до точки пересечения. Точками событий будут концы отрезков.Итак, первым делом вершины
сортируются по углу между лучом и вертикальной полуосью, проходящей через — луч (достаточно рассматривать только точки, которые лежат правее , так как все точки левее мы уже рассмотрели). Затем инициализируем статус отрезками, которые пересекают луч . Далее начинается заметание плоскости. В порядке сортировки вершин для каждой из них выполняется проверка: видна ли вершина из вершины . Для этого нам достаточно проверить только пересечение заметающего луча с ближайшим отрезком в статусе. Если вершина видима, необходимо добавить её в список видимых вершин. И, наконец, вне зависимости от видимости вершины, необходимо изменить статус заметающего луча. Для этого для текущей вершины необходимо удалить из статуса все рёбра (отрезки), которые заканчиваются в этой вершине (лежат слева от прямой ) и добавить все рёбра (отрезки), которые в ней начинаются (лежат справа от прямой ).Псевдокод
graph buildVisibilityGraph(Set<Segment> segments) Set<Vertex> vertices = getVertices(segments) //получаем все вершины препятствий vertices.add(s, t) //добавляем начальную и конечную вершину graph visibilityGraph(vertices) //изначально в графе только вершины for Vertex v in vertices //для каждой вершины for Vertex w in getVisibleVertices(v) // добавляем в граф все видимые из нее вершины visibilityGraph.addEdge(v, w) return visibilityGraph
Здесь функция getVisibleVertices(
) возвращет все видимые из вершины и выглядит так:vector<Vertex> getVisibleVertices(vertex v) vector<Vertex> answer, currentVertices currentVertices.add(all w in vertices : w.x >= v.x) //рассматриваем только вершины правее v sort vertexes by angle //в порядке увеличения угла с // вертикальной прямой, проходящей через v heap<Segment> status status.add(all e in Edges : intersection(e, l) != null) //инициализируем статус for w in currentVertices if intersection(vw, status.closest) == null //если отрезок vw не пересекает ближайший // отрезок в статусе answer.add(w) // добавляем в ответ delete from status all edges ending in w //обновляем статус add in status all edges beginning in w return answer
Вершин у нас
, для каждой вершины мы выполяеем следующие шаги: сортировка ( ), проверка пересечения ( ), обновление статуса (суммарно выполняется за , так как мы имеем ребер и каждое ребро мы добавляем и удаляем один раз (вставка происходит за , удаление можно сделать за , например, для каждой вершины сохраняя позицию входящих в нее отрезков)). Итого получаеем . Что и требовалось доказать.Overmars and Welzl’s Algorithm
visibility graph при помощи rotation tree
C помощью rotation tree можно достичь асимптотики .
Motion planning
Тут мы двигаем не точку, а произвольный выпуклый полигон. Если мы его не можем вращать, просто считаем configuration space, т. е. "обводим" препятствия нашим полигоном (делаем сумму Минковского препятствий и полигона, сдвинутого в начало координат какой-нибудь точкой) и получаем другие препятствия, но зато теперь мы двигаем точку. А это мы уже научились делать выше.
Теперь рассмотрим случай, когда мы можем вращать полигон. Для начала построим трапецоидную карту, как будто мы не можем вращать полигон. Теперь будем вращать полигон на малый угол, пока он не сделает полный оборот вокруг своей оси, и для каждого угла сделаем трапецоидную карту. Теперь разместим(мысленно) все карты друг над другом. Таким образом получится, что поворот на малый угол — это движение вверх/вниз между слоями. Осталось попересекать соседние слои и добавить между ними ребра (помним что первый и последний слои одинаковы) и уже в этом графе найти путь.
При такой реализации в некоторых случаях у нас может возникнуть ошибка в повороте, так как в одной плоскости мы все можем делать точно: положения на соседних слоях могут допускаться, а повернуть мы не сможем. Это лечится в основном двумя способами: измельчением угла поворота и изначальным сглаживанием углов полигона — повращать полигон на
и и объединить с исходным, получив новый полигон.Так как эта задача достаточно ресурсоемка, мы рассматриваем только наличие пути, а не нахождение кратчайшего.
Источники
- Mark de Berg, Otfried Cheong, Marc van Kreveld, Mark Overmars (2008), Computational Geometry: Algorithms and Applications (3rd edition), Springer-Verlag, ISBN 978-3-540-77973-5 Chapter 15 page 324-331
- статья про visibility graphs
- Хабр