Как стать автором
Обновить

Комментарии 52

А какие ещё алгоритмы могут быть для решения этой задачи? Я от программирования человек далёкий, но первое, что пришло в голову — разбить полигон на непересекающиеся треугольники и последовательно перебирать, не принадлежит ли точка одному из них.
А еще можно залить полигон цветом неуловимо для глаза отличающимся от цвета фона и по цвету пикселя в точке клика определять внутри она, снаружи или на границе
решение с цветом очень интересное… элегантно и просто. вероятно не для всех технологий подойдёт, но всё же.

Блин, спасибо. Хорошая идея. Я как раз искал решение чтобы поменьше математики

Кстати там же есть ветка, что задача решена не полностью и требует доп. проверок: habrahabr.ru/post/125356/#comment_4130015

Там же в комментариях приводят решение этой проблемы: https://habrahabr.ru/post/125356/#comment_4125696
Кстати, оно уже применено автором: "(yp <= y and y < yp_prev) or (yp_prev <= y and y < yp)". Все отрезки считаются открытыми сверху и закрытыми снизу. Поэтому пересечения с концами отрезков отрабатываются правильно — как 0, 1 или 2 пересечения с правильной четностью.

НЛО прилетело и опубликовало эту надпись здесь
Это будет работать только с выпуклыми многоугольниками…
Можно разбить многоугольник на выпуклые.
НЛО прилетело и опубликовало эту надпись здесь
Я считал количество пересечений отрезка (от нашей точки до любой точки за пределами канваса) с ребрами полигона. Если количество парное или равно 0 — точка не в полигоне; иначе, в полигоне.
Именно этот метод и описан в статье:
Последовательно проверяются все грани полигона на пересечение с лучом, идущим из точки, куда кликнул пользователь. Четное количество пересечений или нет пересечений вовсе — точка за пределами полигона. Количество пересечений нечетное — точки внутри.
понял, не сразу распознал эту часть)

if (...):
in_polygon = not in_polygon

прошу прощения.

Чем построение выпуклой оболочки поможет определить вхождение точки в многоугольник?

Я, к сожалению, python не знаю, поэтому ламерский вопрос: а что произойдет, если один или несколько сторон полигона окажутся строго горизонтальными и при этом точка окажется на прямой, к которой эта горизонтальная сторона принадлежит?

Другими словами что будет, если yp_prev == yp && y == yp?
В данной реализации попадание в вершину или на грань (даже если грань лежит на луче) будет считаться вхождением в полигон.
(xp_prev — xp) * (y — yp) / (yp_prev — yp) а деление на 0 нормально отработает в данной реализации?
Не могу точно сказать проверяет ли интерпретатор python все условия или прекращает дальнейшую проверку если одна часть and конструкции является False, но скорей всего прекращает. А это значит что ((yp <= y and y < yp_prev) or (yp_prev <= y and y < yp)) будет False и до деления на 0 не дойдет. Исключение — случай когда одна из граней имеет одинаковые координаты на обоих концах, то есть точка.
Ну я правильно понимаю, что в этом случае вы пропускаете горизонтальные стороны?
Если ситуация будет как на картинке (извините за цвета, нарисовал быстро для примера), то ваша функция посчитает, что луч пересекает только зеленые грани, т.е. пересечений четное кол-во?
image
Да, Вы правы, признаю, грань, лежащая на луче не будет засчитана, а прилегающие к ней грани посчитаются 2 раза, что выльется в результат «вне полигона». Такая ситуация требует отдельной проверки.

Нет, не требует. Вы реализовали полностью правильный алгоритм. Грани соседствующие с горизонтальной на луче посчитаются 0, 1 или 2 раза в зависимости от из ориентации. У вас все отрезки закрыты снизу и открыты сверху, обратите внимание на строгие и нестрогие знаки: ((yp <= y and y < yp_prev) or (yp_prev <= y and y < yp)). Поэтому на картинке сверху будет одно пересечение, а если бы касание горизонтальной грани было сверху — то было бы 2 пересечения.

Поясните подробнее, пожалуйста. Не понял вашу мысль.
Сколько пересечений будет, если сложится ситуация как на моей картинке? По-хорошему должно быть 3 пересечения.
Но, судя по тому, что реализация автора проигнорирует горизонтальную грань (результат приведенных вами условий будет false), пересечений будет 2.

Будет 1 пересечение. Горизонтальная грань будет проигнорирована вообще. Верхняя грань зачтется за 1 пересечение по вершине. Нижняя грань (внешняя) не будет учтена за пересечение, потому что она лежит на луче верхней точкой. Условие в алгоритме всегда игнорирует верхнюю точку любого отрезка или горизонтальные отрезки целиком.


Этот метод нормально учитывает и если граница полигона касается горизонтального луча сверху или снизу в вершине или по горизонтальному отрезку. В этом случае будет 0 или 2 пересечения, если касание снизу или сверху соответственно. Что и нужно для корректной четности.

Да, действительно.
Упустил из виду этот момент, акцентируя внимание на игнорировании горизонтальных линий.
Забавно, что сам автор ввел в дополнительное заблуждение своим ответом.
Да, я сам запутался, wataru прав, я выполнял задание в январе — детали забываются, но достаточно посмотреть на код:

((yp <= y and y < yp_prev) or (yp_prev <= y and y < yp))

как становится понятно что если один конец грани лежит на луче, а второй выше (визуально, учитывая что точка отсчета координат в левом верхнем углу) — такая грань не будет считаться пересечением.
Прекращает проверку, это описано в стандарте: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
А что если перебирать все точки последовательно, обходя контур в каком либо направлении и суммировать углы поворота? Если суммарный угол в итоге будет равен 0, значит точка снаружи. Если 360 значит внутри.

Это тоже правильный метод. Надо только аккуратно обрабатывать точку на грани или совпадающую с вершиной многоугольника. Его недостаток — скорость. Медленные тригонометрические функции и невозможность сделать целиком в целых числах.


Метод пересечения с лучом можно реализовать без деления вообще в целых числах, если координаты всех вершин целые.

НЛО прилетело и опубликовало эту надпись здесь
import this
Задача неплохая, но ее основная сложность, я бы сказал процентов 95, в самом алгоритме.
Это начинающие Django + JQuery программисты, интересующиеся векторной графикой в браузере с использованием canvas

Вызывает то чувство, когда вводишь сложный вопрос в Гугл и получаешь 0 результатов.

В статье нет новизны. Обе темы рассматривались неоднократно. Кроме того, опубликованный код — не лучший пример для других. Во-первых, имя view.py намекает, что данный модуль отвечает за представление, а не логику. Во-вторых, алгоритм легко протестировать, но я не вижу тестов. И чтобы их написать, вам придётся разделить логику и код, который принимает запрос и отправляет ответ.
Это не отменяет того, что логику надо выносить в отдельный модуль.
При пересекающихся ребрах есть особенности.

Бывает «even-odd winding rule» и «non-zero winding rule».
even-odd non-zero

В первом случае красная точка считается за пределами полигона (как у автора), во-втором — внутри.

Первый способ определения полигонов является более логичным. Иначе одно и то же множество точек будет описываться бесконечным количеством полигонов.

Вот еще один пример, из вики.

А почему левая четырехугольная область тут считается внутри многоугольника? Чем она принципиально отличается от правой треугольной, которая помечена как снаружи? По методу луча она тоже будет снаружи.

Она только по правилу non-zero winding rule считается внутри. А по правилу even-odd winding rule она бы считалась снаружи.

Насчет отличия таких областей в вики есть удачная метафора:
Если бы P была гвоздём, а C была бы завязанным в кольцо куском нитки, вытягивание какой-то части нитки прочь от гвоздя приведёт либо к вытягиванию всей нитки, либо нитка окажется накрученной несколько раз на гвоздь
Как-то я не уловил алгоритм:
Последовательно проверяются все грани полигона на пересечение с лучом, идущим из точки, куда кликнул пользователь. Четное количество пересечений или нет пересечений вовсе — точка за пределами полигона. Количество пересечений нечетное — точка внутри.

Треугольник дает 3 пересечения, прямоугольник — 4. В кого из них точка не входит?

   image
Луч уходит от точки влево (в реализации автора). Соответственно он пересекает только левые стороны ваших фигур. Кол-во пересечений в обоих случаях = 1.
То есть как и предполагал — алгоритм описан неправильно. )
Надо добавить, что луч единственный и направлен влево.

Тем не менее есть контр-пример:
   image

Если считать, как написано в этом комменте, грани полуинтервалами, то левый прямоугольник даст 1 пересечение и точка будет засчитана внутри него. Если же считать грани отрезками, то правый прямоугольник даст 3 пересечения и точка «попадет» в него.

Смотрите код внимательно. Полуинтервалы, открытые сверху. Не с начала или с конца, а сверху всегда. В первом примере с касанием угла левая грань не будет считаться пересечением, потому что касание идет по верхней точке отрезка, которая исключена. Правая грань так же не зачтется, ибо эта точка верхняя и для второй грани. Если бы касание было нижней точки многоугольника то было бы зачтено 2 пересечения. Точно так же и со вторым примером, там еще есть горизонтальная грань, которая игнорируется всегда.

С такими дополнениями — да.
Просто ничего этого в алгоритме не было указано.

Да, автор эту деталь не описал, хотя в коде она присутствует.

Скажу честно, меня заинтриговал питоновский цикл «for in range». Поскольку я абсолютно не специалист в этом языке, то подскажите пожалуйста, чему в Вашем цикле, где определяется вхождение точки в полигон, будет равно значение xp_prev (а также yp_prev) при первом шаге цикла, когда i=0, а [i-1], соответственно, равно -1?
Отрицательные индексы в питоне валидны и означают отсчет с конца массива, то есть [-1] — это последний элемент, [-2] — предпоследний и так далее.
Благодарю
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории