Изящный/Чистый (особый случай) Прямолинейный Алгоритм Обхода Сетки?

Я стираю старый мой проект. Одна из вещей, которые это должно было сделать, была - учитывая Декартову объединенную энергосистему и два квадрата на сетке, найдите список всех квадратов, через которые прошла бы строка, присоединяющаяся к центру тех двух квадратов.

Особый случай здесь - то, что все запускают, и конечные точки ограничены точным центром квадратов/ячеек.

Вот некоторые примеры - с парами демонстрационных начальных и конечных точек. Теневые квадраты - те, которые должны быть возвращены соответствующим вызовом функции

удаленная мертвая ссылка ImageShack - пример

Запуск и конечные точки упомянуты квадратами, в которых они находятся. В вышеупомянутом изображении, предполагая, что левая нижняя часть [1,1], строка на нижнем правом была бы определена как [6,2] кому: [9,5].

Таким образом, от (центр) квадрат на шестом столбце слева, на второй строке от нижней части до (центр) квадрат на девятом столбце слева, на пятой строке от нижней части

Который действительно не кажется сложным. Однако я так или иначе, казалось, нашел некоторый сложный алгоритм онлайн и реализовал его.

Я действительно вспоминаю, что это было очень, очень быстро. Как, optimized-for-a-hundreds-or-thousands-of-times-per-frames быстро.

В основном это спрыгнуло с границы к границе квадратов вдоль строки (точки, где строка пересекает линии сетки). Это знало, где следующая точка пересечения была путем наблюдения, какая точка пересечения была ближе - горизонтальная или вертикальная - и переместилась в ту следующую.

Который является видом хорошо в понятии, но фактическая реализация оказалась симпатичным not-so-pretty, и я боюсь, что уровень оптимизации мог бы быть слишком высоким для того, в чем я практически нуждаюсь (я называю этот пересекающийся алгоритм, возможно, пять или шесть раз в минуту).

Существует ли простой, легкий для понимания, прозрачный прямолинейный алгоритм обхода сетки?

В программных терминах:

def traverse(start_point,end_point)
  # returns a list of all squares that this line would pass through
end

где данные координаты определяют сами квадраты.

Некоторые примеры:

traverse([0,0],[0,4])
# => [0,0], [0,1], [0,2], [0,3], [0,4]
traverse([0,0],[3,2])
# => [0,0], [0,1], [1,1], [2,1], [2,2], [3,2]
traverse([0,0],[3,3])
# => [0,0], [1,1], [2,2], [3,3]

Обратите внимание, что строки, которые перемещаются непосредственно через углы, не должны включать квадраты в "крыло" строки.

(Старый добрый Bresenham мог бы работать здесь, но это немного назад, от какого я хочу. Насколько я знаю для использования его, я должен был бы в основном применить его к строке и затем просканировать каждый квадрат на сетке для TRUE или FALSE. Неосуществимый - или по крайней мере неэлегантный - для больших сеток)

(Я повторно изучаю Bresenham и находящиеся в Bresenham алгоритмы, из-за моего недоразумения),


Для разъяснения одно возможное приложение этого было бы, если бы я хранил все свои объекты в игре в зонах (сетка), и я имею луч и хочу видеть, каких объектов луч касается. Используя этот алгоритм, я мог протестировать луч только к объектам, которые являются в данных зонах вместо каждого объекта на карте.

Фактическое использование этого в моем приложении состоит в том, что каждая мозаика имеет эффект, связанный с ним, и объект перемещает посредством данного линейного сегмента каждый поворот. На каждом шагу необходимо проверить для наблюдения, который придает объекту квадратную форму, пересек через, и поэтому, который эффекты относиться к объекту.

Обратите внимание, что в этой точке текущая реализация, которую я имею, действительно работает. Этот вопрос главным образом для цели любопытства. Должен быть более простой путь... так или иначе... к такой простой проблеме.


Что я ищу точно? Что-то концептуально/аккуратный и чистый. Кроме того, я понял, что из-за того, что я точно указываю, все запускают, и конечные точки всегда будут в центре квадратов/ячеек; таким образом, возможно, что-то, что использует в своих интересах, который был бы аккуратен также.

22
задан SuperBiasedMan 27 August 2015 в 11:09
поделиться

3 ответа

Вам нужен частный случай суперпокрытия, которое представляет собой все пиксели, пересекаемые геометрическим объектом. Объект может быть линией или треугольником, и есть обобщения на более высокие измерения.

В любом случае, вот одна реализация для линейных сегментов . На этой странице также сравнивается суперпокрытие с результатом алгоритма Брезенхема - они разные. alt text
(источник: free.fr )

Я не знаю, считаете ли вы этот алгоритм элегантным / чистым, но он определенно кажется достаточно простым, чтобы адаптировать код и перейти к другому части вашего проекта.

Кстати, из вашего вопроса следует, что алгоритм Брезенхема неэффективен для больших сеток. Это неправда - он генерирует только пиксели на линии. Вам не нужно проводить проверку истинности / ложности для каждого пикселя сетки.

Обновление 1: Я заметил, что на картинке есть два «лишних» синих квадрата, через которые, как мне кажется, линия не проходит. Один из них находится рядом с буквой «h» в «этом алгоритме». Я не знаю, отражает ли это ошибку в алгоритме или диаграмме (но см. Комментарий @kikito ниже).

В общем, «трудные» случаи, вероятно, возникают, когда линия проходит точно через точку сетки. Я предполагаю, что если вы используете числа с плавающей запятой, эта ошибка с плавающей запятой в этих случаях может вас испортить. Другими словами, алгоритмы, вероятно, должны придерживаться целочисленной арифметики.

Обновление 2: Другая реализация .

17
ответ дан 29 November 2019 в 05:26
поделиться

Для вашей задачи существует очень простой алгоритм, который выполняется за линейное время:

  1. Даны две точки A и B, определите точки пересечения прямой (A, B) с каждой вертикальной линией вашей сетки, которая лежит в этом интервале.
  2. Вставьте две особые точки пересечения внутри ячеек, содержащих A и B, в начало и конец списка из пункта 1.
  3. Интерпретируйте каждые две последовательные точки пересечения как векторы min и max прямоугольника, выровненного по оси, и отметьте все ячейки сетки, которые лежат внутри этого прямоугольника (это очень просто (пересечение двух прямоугольников, выровненных по оси), особенно учитывая, что прямоугольник имеет ширину 1 и поэтому занимает только 1 столбец вашей сетки)
Пример:
+------+------+------+------+
|      |      |      |      |
|      |      | B    *      |
|      |      |/     |      |
+------+------*------+------+
|      |     /|      |      |
|      |    / |      |      |
|      |   /  |      |      |
+------+--/---+------+------+
|      | /    |      |      |
|      |/     |      |      |
|      *      |      |      |
+-----/+------+------+------+
|    / |      |      |      |
*   A  |      |      |      |
|      |      |      |      |
+------+------+------+------+ 

"A" и "B" - точки, заканчивающие линию, обозначенную "/". "*" обозначает точки пересечения линии с сеткой. Две специальные точки пересечения нужны для того, чтобы отметить ячейки, содержащие A и B, и для обработки особых случаев, таких как A.x == B.x

Оптимизированная реализация требует Θ(|B.x - A.x| + |B.y - A.y|) времени для линии (A, B). Далее, можно написать этот алгоритм для определения точек пересечения с горизонтальными линиями сетки, если это проще для исполнителя.

Обновление: пограничные случаи

Как правильно указывает brainjam в своем ответе, трудные случаи - это те, когда линия проходит точно через точку сетки. Предположим, что такой случай имеет место, и арифметические операции с плавающей точкой правильно возвращают точку пересечения с интегральными координатами. В этом случае предложенный алгоритм отмечает только правильные ячейки (как указано на изображении, предоставленном ОП).

Тем не менее, ошибки с плавающей точкой рано или поздно возникнут и дадут неверные результаты. На мой взгляд, даже использования double недостаточно и следует перейти на Decimal тип числа. Оптимизированная реализация будет выполнять Θ(|max.x - min.x|) сложений на этом типе данных, каждое из которых займет Θ(log max.y) времени. Это означает, что в худшем случае (линия ((0, 0), (N, N)) с огромным N (> 106) алгоритм деградирует до времени работы O(N log N) в худшем случае. Даже переключение между вертикальным/горизонтальным определением пересечения линий сетки в зависимости от наклона линии (A, B) не помогает в этом худшем случае, но точно помогает в среднем случае - я бы рассмотрел возможность реализации такого переключения только в том случае, если профайлер покажет, что десятичные операции являются "бутылочным горлышком".

Наконец, я могу представить, что некоторые умные люди смогут придумать O(N) решение, которое корректно справится с этим пограничным случаем. Все ваши предложения приветствуются!

Поправка

brainjam указал, что десятичный тип данных не является удовлетворительным, даже если он может представлять числа произвольной точности с плавающей точкой, поскольку, например, 1/3 не может быть представлен корректно. Поэтому следует использовать дробный тип данных, который должен уметь правильно обрабатывать пограничные случаи. Thx brainjam! :)

4
ответ дан 29 November 2019 в 05:26
поделиться

Документ по этой теме можно найти здесь . Это касается трассировки лучей, но в любом случае это кажется весьма актуальным для того, что вам нужно, и я предполагаю, что вы сможете немного поработать с этим.

Здесь также есть еще одна статья , посвященная чему-то похожему.

Обе эти статьи связаны с частью 4 отличных руководств Якко Биккера по трассировке лучей (которые также включают его исходный код, так что вы можете просмотрите / изучите его реализации).

6
ответ дан 29 November 2019 в 05:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: