Как определить что точка внутри многоугольника
Реализации алгоритмов/Задача о принадлежности точки многоугольнику
В вычислительной геометрии известна задача об определении принадлежности точки многоугольнику. На плоскости даны многоугольник и точка. Требуется решить вопрос о принадлежности точки многоугольнику.
Многоугольник может быть как выпуклым, так и невыпуклым. Обычно предполагается, что многоугольник простой, т.е. без самопересечений, но задачу рассматривают и для не-простых многоугольников. В последнем случае разные способы определения принадлежности точки многоугольнику могут привести к разным результатам. Различают алгоритмы без предварительной обработки и алгоритмы с предварительной обработкой, в ходе которой создаются некоторые структуры данных, позволяющие в дальнейшем быстрее отвечать на множество запросов о принадлежности точек одному и тому же многоугольнику.
Алгоритм определяет точки границ многоугольника как точки, ему принадлежащие.
Содержание
Описание [ править ]
Для того чтобы все результаты вычислений в программе могли быть представлены целочисленными переменными (манипулирование данными целого типа повышает быстродействие программы и является естественным для приложений компьютерной графики), вычисления и сравнения площадей треугольников заменяются вычислениями и сравнениями их удвоенных площадей. Тем самым исключается погрешность округления при программной реализации всего алгоритма, в целом.
Аргументами функции, реализующей проверку принадлежности данной точки данному многоугольнику произвольного вида, являются
Функция возвращает 1, если точка принадлежит многоугольнику, иначе — 0.
Функция имеет следующий вид.
Очень быстрый алгоритм [ править ]
В основе алгоритма лежит идея подсчёта количества пересечений луча, исходящего из данной точки в направлении горизонтальной оси, со сторонами многоугольника. Если оно чётное, точка не принадлежит многоугольнику. В данном алгоритме луч направлен влево.
Замечание: Так как умножение быстрее деления, условие можно записать так:
Однако, стоит заметить, что данный алгоритм не эквивалентен предыдущему, поэтому его использование может привести к неправильным результатам.
Perl [ править ]
Delphi (Object Pascal) [ править ]
JavaScript [ править ]
Python 3 [ править ]
На Python программа несколько отличается от других языков в сторону компактности из-за особенностей адресации элементов массива. Не нужны дополнительные переменные. Не работает с многоугольниками вогнутого типа.
Быстрый алгоритм для случая, когда луч пересекает одну или несколько вершин [ править ]
Функция Cross определяет, пересекает ли луч j-ое ребро многоугольника:
Фрагмент основной программы:
Если переменная count примет нечетное значение, то точка лежит внутри многоугольника. В противном случает точка лежит вне заданого многоугольника.
Замечание: В данной реализации алгоритма луч направлен вниз.
Методы определения принадлежности точки многоугольнику
Недавно на хабре была статья, в которой описывалось как можно определить, где находится точка по отношению к многоугольнику: внутри или снаружи. Подобная проблема встречается в геометрическом моделировании и в компьютерной графике достаточно часто. А так как метод, описанный в статье, был несколько не оптимален, а в комментариях был небольшой хаос, возникла мысль написать эту статью. Итак, какие алгоритмы существуют в современной компьютерной графике, чтобы определить, принадлежит ли заданная точка многоугольнику или нет.
Прежде, чем начать, хочу сразу описать проблему. Хотя сама проблема проста: у нас задан набор точек и задан порядок, в котором эти точки соединяются. И задана точка, которую мы тестируем на принадлежность. Подразумевается, что у нас многоугольник замкнутый, и в общем случае ребра многоугольника не пересекаются друг с другом, то есть он избавлен от самопересечений. Ограничений на количество вершин нет, то есть легко может быть задан многоугольник с миллионом вершин. Мы надеемся, что пользователь не задаст нам непонятно что, но и гарантировать это тоже не можем. И еще один нюанс: так как мы работаем с компьютерной графикой, что означает, что мы используем арифметику с плавающей точкой, которая хотя и позволяет оперировать с числами достаточно точно, все равно не избавлена от ошибок.
Ну вроде определились с проблемой, давайте теперь посмотрим, какие методы решения существуют.
Метод 1. Трассировка лучей
Начну я с того, который считается наиболее популярным в мире графики и игр: трассировка лучей. Вкратце, алгоритм можно описать следующим образом:
Метод простой, но, к сожалению, в общем случае его лучше не применять. Причиной этого является случай, когда мы пересекаем лучом вершину многоугольника или ребро, которое частично совпадает с лучом. Иллюстрирую это на примере.
Допустим, у нас есть многоугольник, и есть точка. В самом начале мы договорились, что направление будет вдоль оси х. Выпускаем из точки луч в положительном направлении оси x и луч благополучно пересек многоугольник в вершине. Тут возникает вопрос, как именно мы проверяем такую ситуацию? Не забываем, что мы работаем с числами с плавающей точкой, и небольшие погрешности возможны. Перейдем в мир аналитической геометрии, чтобы можно было оперировать не просто геометрическими понятиями, а числами.
Посмотрим в другом направлении. Отправили луч в отрицательном направлении. Там тоже не очень хорошо – луч пересекает вершину внутри многоугольника. Тоже может оказаться что угодно. Вместо горизонтального направления взять вертикальное? Никто не гарантирует, что вы опять не пересечете вершину. В конкретно выбранном мной примере наверху точка подобрана таким образом, что пересечение ее с лучом, параллельным оси y и идущий сверху вниз тоже пересекает многоугольник в вершине.
Причем если вы думаете, что пересечение с вершиной – это плохо, смотрите что еще может произойти:
Здесь мы пересекаем луч с отрезком, который с этим лучом совпадает. Как быть в таком случае? А если не совпадает, а почти совпадает? А представьте себе, что в многоугольнике множество почти вырожденных ребер, как с таким пересекать?
Самое печальное во всей этой ситуации то, что нам вот кажется: «мне надо что-то очень простое для моих простых целей, меня такая ситуация не коснется». По закону Мерфи, к сожалению, именно такая ситуация возникает всякий раз когда ее совсем не ждешь. И поэтому я плавно перехожу ко второму методу.
Метод 2. Ближняя точка и ее нормаль
Вообще у этого метода есть страшное название angle weighted pseudo normals и связан он в понятием так называемых полей расстояний со знаком (signed distance fields). Но пугать лишний раз я никого не хочу, так что пусть будет просто ближняя точка и ее нормаль (то есть перпендикулярный вектор).
Алгоритм в данном случае такой:
Рассмотрим пример. Точка A1, ближайшая точка для нее находится на ребре. Если все делаем правильно, нормаль к ребру параллельна вектору от тестируемой точки до ближайшей. В случае точки A1, угол между векторами = 0. Или почти нуль, так как из-за операций с плавающей точкой все возможно. Меньше 90 градусов, тестируемая точка A1 – внутри. Протестируем точку A2. У нее ближайшая точка – вершина, нормаль к которой – усредненная нормаль ребер прилегающих к этой вершине. Считаем скалярное произведение двух векторов, должно быть отрицательным. Мы – снаружи.
Так, вроде бы с сутью метода разобрались. Что там с производительностью и проблемами, связанной с плавающей точкой?
Как и в случае трассировки точек, производительность – O(log n), если использовать деревья для хранения информации о ребрах. С вычислительной точки зрения метод, хотя и имеет подобную сложность, будет несколько помедленнее, чем трассировка. Прежде всего оттого, что расстояние между точкой и ребром чуть более дорогостоящая операция, чем пересечение двух линий. Неприятности, связанные с плавающей точкой, возникают в этом методе, как правило недалеко от ребер многоугольника. Причем чем мы ближе к ребру, тем больше вероятность неправильного определения знака. К счастью, чем мы ближе к ребру, тем меньше расстояние. То есть если мы, например, говорим, что если полученное расстояние меньше заранее заданного минимального (это может быть константа вроде DBL_EPSILON или FLT_EPSILON), то точка принадлежит ребру. А если она принадлежит ребру, то мы уже сами решаем, часть ли многоугольника его ребро или нет (как правило – часть).
Описывая предыдущий метод, достаточно много было сказано о недостатках. Пришло время назвать несколько недостатков и этого способа. Прежде всего, этот метод требует, чтобы все нормали к ребрам были направлены в правильную сторону. То есть до того, как определять, снаружи мы или внутри, надо провести некую работу по вычислению этих нормалей и правильное их ориентирование. Очень часто, особенно когда на входе большая свалка из вершин и ребер, этот процесс не всегда прост. Если надо определить только для одной точки, процесс ориентации нормалей может занять большую часть времени, которую можно было бы потратить на что-то еще. Также, этот метод очень не любит, когда на вход подается многоугольник с самопересечениями. В начале я сказал, что в нашей задаче такой случай не рассматривается, но если бы он рассматривался, то этот метод мог выдать совершенно неочевидные результаты.
Но в целом метод неплох, особенно если у нас на входе многоугольник с большим количеством вершин и ребер, а точек на принадлежность надо протестировать много. Если же точек мало, трассировка лучей нестабильна, а хочется чего-то более-менее надежного, то есть и третий способ.
Метод 3. Индекс точки относительно многоугольника
Этот метод известен довольно давно, но в основном остается теоретическим, по большей части потому, что он не так эффективен, как предыдущие два. Вот его суть «на пальцах». Возьмем единичную окружность с центром в тестируемой точке. Потом каждую вершину многоугольника спроецируем на эту окружность лучами, которые проходят через вершину и тестируемую точку. Как-то примерно так:
На рисунке точки P1, P2 и так далее – вершины многоугольника, а точки P1’, P2’ и так далее – их проекции на окружность. Теперь когда мы рассматриваем ребро многоугольника, по проекциям можно определить, происходит ли вращение против часовой стрелки или по часовой стрелке при переходе от одной вершины к другой. Вращение против часовой стрелки будем считать положительным поворотом, а вращение по часовой стрелке – отрицательным. Угол, который соответствует каждому ребру – это угол между сегментами окружности через проекции вершин этого ребра. Так как поворот у нас может быть положительный или отрицательный, то и угол может быть положительный или отрицательный.
Алгоритм в этом случае следующий:
Рассмотрим пример. Есть многоугольник, порядок которого установлен против часовой стрелки. Есть точка А, которую мы тестируем. Для тестирования сначала вычисляем угол между векторами AP1 и AP2. Векторное произведение этих же векторов смотрит на нас, значит прибавляем к сумме. Переходим дальше и считаем угол между AP2 и AP3. Векторное произведение смотрит на нас, полученный угол вычитаем. И так далее.
Для конкретно этого рисунка я все посчитал и вот что получилось:
(AP1, AP2)=74.13, (AP2, AP3)=51.58, (AP3, AP4)=89.99, (AP4, AP5)=126.47, (AP5, AP1)=120.99.
sum=74.13-51.58+89.99+126.47+120.99=360. 360/360=1 Точка – внутри.
(BP1, BP2)=44.78, (BP2, BP3)=89.11, (BP3, BP4)=130.93, (BP4, BP5)=52.97, (BP5, BP1)=33.63.
sum=-44.78+89.11-130.93+52.97+33.63=0. Точка – снаружи.
И традиционно опишем плюсы и минусы данного подхода. Начнем с минусов. Метод прост математически, но не так-то эффективен с точки зрения производительности. Во-первых, его алгоритмическая сложность O(n) и, как ни крути, а все ребра многоугольника придется перебрать. Во-вторых, для вычисления угла придётся воспользоваться операцией арккосинуса и двумя операциями взятия корня (формула скалярного произведения и связь его с углом тем в помощь, кто не понимает, почему). Эти операции очень недешевы с точки зрения скорости, и, к тому же, погрешности связанные с ними могут быть существенны. И в третьих, алгоритм напрямую не определяет точку, лежащую на ребре. Либо – снаружи, либо – внутри. Третьего не дано. Впрочем, последний недостаток легко определяется: если хотя бы один из углов равен (или почти равен) 180 градусам, это автоматически означает ребро.
Недостатки метода в чем-то компенсируются его достоинствами. Во-первых, это самый стабильный метод. Если многоугольник на вход подан корректный, то результат получается корректный для всех точек, за исключением разве что точек на ребрах, но о них смотри выше. Более того, метод позволяет частично бороться с некорректными входными данными. Многоугольник самопересекается? Не беда, метод скорее всего определит большинство точек правильно. Многоугольник не замкнут или вообще не многоугольник а малоосмысленный набор сегментов? Метод определит точки верно в большом количестве случаев. В общем, всем метод хорош, но медленный и требует вычислений арккосинусов.
Чем бы хотелось закончить этот обзор? А тем, что методов для решения проблемы определения принадлежности точки многоугольнику существует не один и даже не два. Они служат для разных целей и некоторые более подходят в случаях, когда важна скорость, другие – когда важно качество. Ну и не забываем о том, что у нас непредсказуемые входные данные и мы работаем с компьютером, у которого арифметика с плавающей точкой подвержена погрешностям. Если нужна скорость и качество совершенно неважно – трассировка лучей в помощь. В большинстве реальных приложений скорее всего поможет метод ближней точки и нормали. Если же на первом месте – точность определения при непонятных входных данных, метод индекса точки должен помочь.
Если будут какие-то вопросы, задавайте. Как человек, занимающийся геометрией и подобными проблемами связанными с графикой, буду рад помочь чем смогу.
Определить, что точка внутри фигуры или нет?
Добрый день, по какой формуле или алгоритме можно определить, что точка внутри этой фигуры?
Вот решение и формула, код.
Rsa97, это сработает для любого контура выпуклого многоугольника (как на картинке).
Другой вариант определить что все точки многоугольника находятся выше, ниже, правее или левее искомой точки. Значит искомая точка находится вне фигуры. Опять же это справедливо только для выпуклых многоугольников.
Если многоугольник не выпуклый его нужно сделать выпуклым (не помню как) выделив один большой многоугольник и несколько маленьких вычитаемых. Тогда задача решается в два этапа: 1. определяем лежит ли точка внутри большого многоугольника. Если да то 2. определяем лижит ли точка внутри вычитаемых многоугольников.
Опять же, упираемся в точность вычислений. Если получили 359.9999991231 градус, то что это значит?
Ещё можно взять интеграл по контуру относительно точки, но там сложно распознать краевые случаи из-за погрешности вычислений.
Как проверить, находится ли данная точка внутри или снаружи многоугольника?
По заданному многоугольнику и точке ‘p’ найдите, находится ли ‘p’ внутри многоугольника или нет. Точки, лежащие на границе, рассматриваются внутри.
Мы настоятельно рекомендуем сначала увидеть следующий пост.
Как проверить, пересекаются ли два заданных отрезка?
Ниже приводится простая идея проверить, находится ли точка внутри или снаружи.
Как обработать точку «g» на рисунке выше?
Обратите внимание, что мы должны вернуть true, если точка лежит на прямой или совпадает с одной из вершин данного многоугольника. Чтобы справиться с этим, после проверки, пересекается ли линия от ‘p’ до крайнего, мы проверяем, является ли ‘p’ коллинеарным с вершинами текущей линии многоугольника. Если это coliear, то мы проверяем, лежит ли точка ‘p’ на текущей стороне многоугольника, если она лежит, мы возвращаем true, иначе false.
Ниже приводится реализация вышеуказанной идеи.
// Программа на C ++ для проверки, находится ли заданная точка внутри заданного многоугольника
// См. Https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/amp/
// для объяснения функций onSegment (), direction () и doIntersect ()
#include
using namespace std;
// Определить бесконечность (использование INT_MAX вызвало проблемы переполнения)
#define INF 10000
// Учитывая три коллинеарных точки p, q, r, функция проверяет,
// точка q лежит на отрезке прямой ‘pr’
bool onSegment(Point p, Point q, Point r)
int orientation(Point p, Point q, Point r)
if (val == 0) return 0; // коллинеар
return (val > 0)? 1: 2; // часы или против часовой стрелки
// Функция, которая возвращает true, если отрезок линии ‘p1q1’
// и ‘p2q2’ пересекаются.
bool doIntersect(Point p1, Point q1, Point p2, Point q2)
// Находим четыре ориентации, необходимые для общего и
int o1 = orientation(p1, q1, p2);
int o2 = orientation(p1, q1, q2);
int o3 = orientation(p2, q2, p1);
int o4 = orientation(p2, q2, q1);
// p1, q1 и p2 коллинеарны, а p2 лежит на отрезке p1q1
// p1, q1 и p2 коллинеарны, а q2 лежит на отрезке p1q1
// p2, q2 и p1 коллинеарны, а p1 лежит на отрезке p2q2
// p2, q2 и q1 коллинеарны, а q1 лежит на отрезке p2q2
return false ; // Не попадает ни в один из вышеперечисленных случаев
// Возвращает true, если точка p лежит внутри многоугольника [] с n вершинами
bool isInside(Point polygon[], int n, Point p)
// В многоугольнике должно быть как минимум 3 вершины []
// Создать точку для отрезка от p до бесконечности
// Подсчитать пересечения вышеуказанной линии со сторонами многоугольника
// Проверяем, пересекается ли отрезок от ‘p’ до ‘extreme’
// с отрезком от ‘polygon [i]’ до ‘polygon [next]’
if (doIntersect(polygon[i], polygon[next], p, extreme))
// Если точка ‘p’ является коллинеарной с отрезком линии ‘i-next’,
// затем проверяем, лежит ли он на сегменте. Если это ложь, верните истину,
if (orientation(polygon[i], p, polygon[next]) == 0)
return onSegment(polygon[i], p, polygon[next]);
// Возвращаем true, если count нечетно, иначе false
return count&1; // То же, что (count% 2 == 1)
// Программа драйвера для проверки вышеуказанных функций
int n = sizeof (polygon1)/ sizeof (polygon1[0]);
isInside(polygon1, n, p)? cout «Yes \n» : cout «No \n» ;
isInside(polygon1, n, p)? cout «Yes \n» : cout «No \n» ;
n = sizeof (polygon2)/ sizeof (polygon2[0]);
isInside(polygon2, n, p)? cout «Yes \n» : cout «No \n» ;
isInside(polygon2, n, p)? cout «Yes \n» : cout «No \n» ;
isInside(polygon2, n, p)? cout «Yes \n» : cout «No \n» ;
n = sizeof (polygon3)/ sizeof (polygon3[0]);
isInside(polygon3, n, p)? cout «Yes \n» : cout «No \n» ;
// Java-программа для проверки, является ли данная точка
// лежит внутри заданного многоугольника
// См. Https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/amp/
// для объяснения функций onSegment (),
// Ориентация () и doIntersect ()
// Определить бесконечность (используя INT_MAX
// вызвал проблемы переполнения)
static int INF = 10000 ;
static class Point
public Point( int x, int y)
// Даны три коллинеарных точки p, q, r,
// функция проверяет, лежит ли точка q
// в линейном сегменте ‘pr’
static boolean onSegment(Point p, Point q, Point r)
// Найти ориентацию упорядоченного триплета (p, q, r).
// Функция возвращает следующие значения
static int orientation(Point p, Point q, Point r)
return 0 ; // коллинеар
// Функция, которая возвращает true, если
// отрезок «p1q1» и «p2q2» пересекаются.
static boolean doIntersect(Point p1, Point q1,
// Находим четыре ориентации, необходимые для
// общие и частные случаи
int o1 = orientation(p1, q1, p2);
int o2 = orientation(p1, q1, q2);
int o3 = orientation(p2, q2, p1);
int o4 = orientation(p2, q2, q1);
// p1, q1 и p2 являются коллинеарными и
// p2 лежит на сегменте p1q1
if (o1 == 0 && onSegment(p1, p2, q1))
// p1, q1 и p2 являются коллинеарными и
// q2 лежит на отрезке p1q1
if (o2 == 0 && onSegment(p1, q2, q1))
// p2, q2 и p1 являются коллинеарными и
// p1 лежит на сегменте p2q2
if (o3 == 0 && onSegment(p2, p1, q2))
// p2, q2 и q1 являются коллинеарными и
// q1 лежит на отрезке p2q2
if (o4 == 0 && onSegment(p2, q1, q2))
// Не попадает ни в один из вышеперечисленных случаев
// Возвращает true, если точка p лежит
// внутри многоугольника [] с n вершинами
static boolean isInside(Point polygon[], int n, Point p)
// В многоугольнике должно быть как минимум 3 вершины []
// Создать точку для отрезка от p до бесконечности
Point extreme = new Point(INF, p.y);
// Считаем пересечения вышеуказанной линии
// со сторонами многоугольника
int next = (i + 1 ) % n;
// Проверяем, является ли отрезок от ‘p’ до
// «крайний» пересекается с линией
// сегментируем от ‘polygon [i]’ до ‘polygon [next]’
if (doIntersect(polygon[i], polygon[next], p, extreme))
// Если точка ‘p’ является коллинеарной с линией
// сегментируем «i-next», затем проверяем, лежит ли он
// на сегменте. Если это ложь, верните true, иначе false
if (orientation(polygon[i], p, polygon[next]) == 0 )
return onSegment(polygon[i], p,
// Возвращаем true, если count нечетно, иначе false
return (count % 2 == 1 ); // То же, что (count% 2 == 1)
public static void main(String[] args)
int n = polygon1.length;
if (isInside(polygon1, n, p))
if (isInside(polygon1, n, p))
if (isInside(polygon2, n, p))
if (isInside(polygon2, n, p))
if (isInside(polygon2, n, p))
if (isInside(polygon3, n, p))
// Этот код предоставлен 29AjayKumar
// AC # программа для проверки наличия заданной точки
// лежит внутри заданного многоугольника
// См. Https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/amp/
// для объяснения функций onSegment (),
// Ориентация () и doIntersect ()
// Определить бесконечность (используя INT_MAX
// вызвал проблемы переполнения)
static int INF = 10000;
public Point( int x, int y)
// Даны три коллинеарных точки p, q, r,
// функция проверяет, лежит ли точка q
// в линейном сегменте ‘pr’
static bool onSegment(Point p, Point q, Point r)
// Найти ориентацию упорядоченного триплета (p, q, r).
// Функция возвращает следующие значения
static int orientation(Point p, Point q, Point r)
return 0; // коллинеар
// Функция, которая возвращает true, если
// отрезок «p1q1» и «p2q2» пересекаются.
static bool doIntersect(Point p1, Point q1,
// Находим четыре ориентации, необходимые для
// общие и частные случаи
int o1 = orientation(p1, q1, p2);
int o2 = orientation(p1, q1, q2);
int o3 = orientation(p2, q2, p1);
int o4 = orientation(p2, q2, q1);
// p1, q1 и p2 являются коллинеарными и
// p2 лежит на сегменте p1q1
if (o1 == 0 && onSegment(p1, p2, q1))
// p1, q1 и p2 являются коллинеарными и
// q2 лежит на отрезке p1q1
if (o2 == 0 && onSegment(p1, q2, q1))
// p2, q2 и p1 являются коллинеарными и
// p1 лежит на сегменте p2q2
if (o3 == 0 && onSegment(p2, p1, q2))
// p2, q2 и q1 являются коллинеарными и
// q1 лежит на отрезке p2q2
if (o4 == 0 && onSegment(p2, q1, q2))
// Не попадает ни в один из вышеперечисленных случаев
// Возвращает true, если точка p лежит
// внутри многоугольника [] с n вершинами
static bool isInside(Point []polygon, int n, Point p)
// В многоугольнике должно быть как минимум 3 вершины []
// Создать точку для отрезка от p до бесконечности
Point extreme = new Point(INF, p.y);
// Считаем пересечения вышеуказанной линии
// со сторонами многоугольника
// Проверяем, является ли отрезок от ‘p’ до
// «крайний» пересекается с линией
// сегментируем от ‘polygon [i]’ до ‘polygon [next]’
polygon[next], p, extreme))
// Если точка ‘p’ является коллинеарной с линией
// сегментируем «i-next», затем проверяем, лежит ли он
// на сегменте. Если это ложь, верните true, иначе false
if (orientation(polygon[i], p, polygon[next]) == 0)
return onSegment(polygon[i], p,
// Возвращаем true, если count нечетно, иначе false
return (count % 2 == 1); // То же, что (count% 2 == 1)