! Поэтому сначала убедитесь, что полигон прост!

раницы руководства дляXFillPolygon:

Еслиshape являетсяСложныйпуть может самопересекающийся. Обратите внимание, что смежные совпадающие точки на пути не рассматриваются как самопересечение.

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

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

У меня проблемы с заполнениемXFillPolygon и, как следует из справочной страницы, первый шаг, который я хочу сделать, - указать правильную форму многоугольника. Я сейчас пользуюсьСложный быть на безопасной стороне.

Существует ли эффективный алгоритм для определения того, является ли многоугольник (определяемый серией координат) выпуклым, невыпуклым или сложным?

 Will Ness16 янв. 2018 г., 14:56
К вашему сведению для Google:правильный ответ.
 Drew Noakes24 окт. 2010 г., 04:07
Смотрите этот вопрос для получения информации о проверке сложных / простых полигонов:stackoverflow.com/questions/4001745/...
 Discrete lizard26 мар. 2018 г., 12:10
К вашему сведению:Этот ответ это, после некоторых последних обновлений, также исправить, т!

Ответы на вопрос(10)

ющихся ребер), предполагая, что вершины упорядочены (по часовой стрелке или против).

Для массива вершин:

vertices = [(0,0),(1,0),(1,1),(0,1)]

Последующийpython реализация проверяет, является лиz составляющая всех перекрестных произведений имеет одинаковый знак

def zCrossProduct(a,b,c):
   return (a[0]-b[0])*(b[1]-c[1])-(a[1]-b[1])*(b[0]-c[0])

def isConvex(vertices):
    if len(vertices)<4:
        return True
    signs= [zCrossProduct(a,b,c)>0 for a,b,c in zip(vertices[2:],vertices[1:],vertices)]
    return all(signs) or not any(signs)
Решение Вопроса
ПРИМЕЧАНИЕ. Вычисление выпуклой оболочки набора точек совершенно не нужно, если вы просто хотите определить, представляет ли список точек, представляющих многоугольник, выпуклый многоугольник.

Алгоритм упаковки подарков:

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

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

 n.m.26 мар. 2018 г., 18:03
@Discretelizard не имеет смысла спрашивать, является ли конечный список точек выпуклым или сложным или что-то еще. Нужен многоугольник, чтобы ответить на этот вопрос. Неупорядоченный список точек порождает много разных многоугольников. OTOH упорядоченный список точек соответствует одному многоугольнику, поэтому не совсем понятно, как вход может дать один, а не другой.
 Discrete lizard25 мар. 2018 г., 18:51
Кажется, у этого есть список точек на входе, а не многоугольник. Похоже, это правильный ответ на другой вопрос.
 plasmacel29 июл. 2017 г., 20:15
Не говоря уже о том, что вычисление выпуклой оболочки в этом случае совершенно не нужно.
 plasmacel29 июл. 2017 г., 19:51
Этот ответ нуждается в дополнительном объяснении. В этой форме это не более, чем просто ответ.

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

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

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

 given p[k], p[k+1], p[k+2] each with coordinates x, y:
 dx1 = x[k+1]-x[k]
 dy1 = y[k+1]-y[k]
 dx2 = x[k+2]-x[k+1]
 dy2 = y[k+2]-y[k+1]
 zcrossproduct = dx1*dy2 - dy1*dx2

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

Если есть N точек, убедитесь, что вы рассчитываете N перекрестных произведений, например, обязательно используйте триплеты (p [N-2], p [N-1], p [0]) и (p [N-1], p [0], p [1]).

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

 Will Ness16 янв. 2018 г., 14:06
Удивительно неправильный ответ со всеми этими возражениями.самопересекающаяся петля передаст этот алгоритм с летающими цветами.
 zenna28 мая 2013 г., 07:39
Поправьте меня, если я ошибаюсь, но разве это не сработает для некоторых сложных полигонов? Например, [[1 3] [9 7] [7 9] [7 2] [9 6] [1 8]]]
 Jason S27 мар. 2018 г., 22:16
Я обновил этот ответ. Комментаторы правы, что это не относится к сложному случаю, но все же имеет значение.
 TurnipEntropy30 сент. 2018 г., 20:16
Это касается только части вопроса, это правда. Вот почему это не было принято. Другие люди, очевидно, нашли этот вопрос и смогли гарантировать, что у них нет сложного случая, поэтому нашли этот ответ полезным.

описанного вэтот ответ.

public boolean isConvex()
{
    if (_vertices.size() < 4)
        return true;

    boolean sign = false;
    int n = _vertices.size();

    for(int i = 0; i < n; i++)
    {
        double dx1 = _vertices.get((i + 2) % n).X - _vertices.get((i + 1) % n).X;
        double dy1 = _vertices.get((i + 2) % n).Y - _vertices.get((i + 1) % n).Y;
        double dx2 = _vertices.get(i).X - _vertices.get((i + 1) % n).X;
        double dy2 = _vertices.get(i).Y - _vertices.get((i + 1) % n).Y;
        double zcrossproduct = dx1 * dy2 - dy1 * dx2;

        if (i == 0)
            sign = zcrossproduct > 0;
        else if (sign != (zcrossproduct > 0))
            return false;
    }

    return true;
}

Алгоритм гарантированно будет работать до тех пор, пока вершины упорядочены (по часовой стрелке или против часовой стрелки), и у вас нет самопересекающихся ребер (т.е. он работает только дляпростые полигоны).

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

% M [ x1 x2 x3 ...
%     y1 y2 y3 ...]
% test if a polygon is convex

function ret = isConvex(M)
    N = size(M,2);
    if (N<4)
        ret = 1;
        return;
    end

    x0 = M(1, 1:end);
    x1 = [x0(2:end), x0(1)];
    x2 = [x0(3:end), x0(1:2)];
    y0 = M(2, 1:end);
    y1 = [y0(2:end), y0(1)];
    y2 = [y0(3:end), y0(1:2)];
    dx1 = x2 - x1;
    dy1 = y2 - y1;
    dx2 = x0 - x1;
    dy2 = y0 - y1;
    zcrossproduct = dx1 .* dy2 - dy1 .* dx2;

    % equality allows two consecutive edges to be parallel
    t1 = sum(zcrossproduct >= 0);  
    t2 = sum(zcrossproduct <= 0);  
    ret = t1 == N || t2 == N;

end

выпуклый.

Рассмотрим каждый набор из трех точек вдоль многоугольника. Если каждый угол составляет 180 градусов или меньше, у вас есть выпуклый многоугольник. Когда вы вычисляете каждый угол, также сохраняйте промежуточный итог (180 - угол). Для выпуклого многоугольника это составит 360.

Этот тест выполняется за время O (n).

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

один, опубликованный @UriGoren (с небольшим улучшением - только целочисленная математика) и один из @RoryDaulton, на Java. У меня были некоторые проблемы, потому что мой многоугольник замкнут, поэтому оба алгоритма считали второй вогнутым, когда он был выпуклым. Поэтому я изменил это, чтобы предотвратить такую ​​ситуацию. Мои методы также используют базовый индекс (который может быть или не 0).

Это мои тестовые вершины:

// concave
int []x = {0,100,200,200,100,0,0};
int []y = {50,0,50,200,50,200,50};

// convex
int []x = {0,100,200,100,0,0};
int []y = {50,0,50,200,200,50};

А теперь алгоритмы:

private boolean isConvex1(int[] x, int[] y, int base, int n) // Rory Daulton
{
  final double TWO_PI = 2 * Math.PI;

  // points is 'strictly convex': points are valid, side lengths non-zero, interior angles are strictly between zero and a straight
  // angle, and the polygon does not intersect itself.
  // NOTES:  1.  Algorithm: the signed changes of the direction angles from one side to the next side must be all positive or
  // all negative, and their sum must equal plus-or-minus one full turn (2 pi radians). Also check for too few,
  // invalid, or repeated points.
  //      2.  No check is explicitly done for zero internal angles(180 degree direction-change angle) as this is covered
  // in other ways, including the `n < 3` check.

  // needed for any bad points or direction changes
  // Check for too few points
  if (n <= 3) return true;
  if (x[base] == x[n-1] && y[base] == y[n-1]) // if its a closed polygon, ignore last vertex
     n--;
  // Get starting information
  int old_x = x[n-2], old_y = y[n-2];
  int new_x = x[n-1], new_y = y[n-1];
  double new_direction = Math.atan2(new_y - old_y, new_x - old_x), old_direction;
  double angle_sum = 0.0, orientation=0;
  // Check each point (the side ending there, its angle) and accum. angles for ndx, newpoint in enumerate(polygon):
  for (int i = 0; i < n; i++)
  {
     // Update point coordinates and side directions, check side length
     old_x = new_x; old_y = new_y; old_direction = new_direction;
     int p = base++;
     new_x = x[p]; new_y = y[p];
     new_direction = Math.atan2(new_y - old_y, new_x - old_x);
     if (old_x == new_x && old_y == new_y)
        return false; // repeated consecutive points
     // Calculate & check the normalized direction-change angle
     double angle = new_direction - old_direction;
     if (angle <= -Math.PI)
        angle += TWO_PI;  // make it in half-open interval (-Pi, Pi]
     else if (angle > Math.PI)
        angle -= TWO_PI;
     if (i == 0)  // if first time through loop, initialize orientation
     {
        if (angle == 0.0) return false;
        orientation = angle > 0 ? 1 : -1;
     }
     else  // if other time through loop, check orientation is stable
     if (orientation * angle <= 0)  // not both pos. or both neg.
        return false;
     // Accumulate the direction-change angle
     angle_sum += angle;
     // Check that the total number of full turns is plus-or-minus 1
  }
  return Math.abs(Math.round(angle_sum / TWO_PI)) == 1;
}

А теперь от Ури Горена

private boolean isConvex2(int[] x, int[] y, int base, int n)
{
  if (n < 4)
     return true;
  boolean sign = false;
  if (x[base] == x[n-1] && y[base] == y[n-1]) // if its a closed polygon, ignore last vertex
     n--;
  for(int p=0; p < n; p++)
  {
     int i = base++;
     int i1 = i+1; if (i1 >= n) i1 = base + i1-n;
     int i2 = i+2; if (i2 >= n) i2 = base + i2-n;
     int dx1 = x[i1] - x[i];
     int dy1 = y[i1] - y[i];
     int dx2 = x[i2] - x[i1];
     int dy2 = y[i2] - y[i1];
     int crossproduct = dx1*dy2 - dy1*dx2;
     if (i == base)
        sign = crossproduct > 0;
     else
     if (sign != (crossproduct > 0))
        return false;
  }
  return true;
}

ответ @RoryDaulton мне кажется лучшим, но что если один из углов равен точно 0? Некоторые могут захотеть, чтобы такой крайний случай возвратил True, и в этом случае измените «<=» на «<» в строке:

Вот мои тесты, которые выдвигают на первый план проблему:

# A square    
assert is_convex_polygon( ((0,0), (1,0), (1,1), (0,1)) )

# This LOOKS like a square, but it has an extra point on one of the edges.
assert is_convex_polygon( ((0,0), (0.5,0), (1,0), (1,1), (0,1)) )

2-е утверждение неверно в исходном ответе. Должно ли это? В моем случае я бы предпочел, чтобы этого не было.

 Discrete lizard25 мар. 2018 г., 18:58
Ах, крайние случаи. Приятно видеть, что вы заботитесь о них! Алгоритмы исследователи имеют тенденцию игнорировать их (поскольку это действительно реализация). Общая проблема здесь в том, что большинство геометрических примитивов неточны, поэтому ожидается, что «<=» и «<» будут иметь одинаковое поведение! Однако правильно реализовать геометрические алгоритмы по этой причине очень сложно.

ске слова «определить выпуклый многоугольник». Однако ни один из ответов не является достаточно хорошим.

принятый ответ @EugeneYokota работает, проверяя, можно ли превратить неупорядоченный набор точек в выпуклый многоугольник, но это не то, о чем просил ОП. Он попросил метод проверки, является ли данный многоугольник выпуклым или нет. («Полигон» в информатике обычно определяется [как вДокументация XFillPolygon] как упорядоченный массив двумерных точек с последовательными точками, соединенными стороной, а также последней точкой с первой.) Кроме того, алгоритм упаковки подарков в этом случае будет иметь сложность по времени:O(n^2) заn баллов - что гораздо больше, чем нужно для решения этой проблемы, в то время как вопрос требует эффективного алгоритма.

@ JasonS ответнаряду с другими ответами, которые следуют за его идеей, принимаетзвездные полигоны такой какпентаграмма или в комментарии @ zenna, но звездные многоугольники не считаются выпуклыми. Как отмечает @plasmacel в комментарии, это хороший подход к использованию, если у вас есть предварительные знания о том, что многоугольник не является самопересекающимся, но он может потерпеть неудачу, если у вас нет этих знаний.

@ Ответ Сехата правильно, но он также имеет сложность времениO(n^2) и, следовательно, неэффективно.

@ LorenPechtel добавил ответ после нее правка здесь лучшая, но она расплывчатая.

Правильный алгоритм с оптимальной сложностью

Алгоритм, который я здесь представляю, имеет временную сложностьO(n), правильно проверяет, является ли многоугольник выпуклым или нет, и проходит все тесты, которые я ему бросил. Идея состоит в том, чтобы пересечь стороны многоугольника, отмечая направление каждой стороны и подписанное изменение направления между последовательными сторонами. «Подпись» здесь означает, что левый положительный, а правый отрицательный (или обратный), а прямой - ноль. Эти углы нормализованы, чтобы быть между минус-пи (эксклюзив) и пи (включительно).Суммируя все эти углы изменения направления (a.k.aотклонение углы)все вместе приведет к плюс-минус один ход (т.е.360 градусов) для выпуклого многоугольника, тогда как звездообразный многоугольник (или самопересекающийся цикл) будет иметь другую сумму (n * 360 градусов, дляn повороты в целом, для многоугольников, где все углы отклонения имеют одинаковый знак). Таким образом, мы должны проверить, что сумма углов изменения направления плюс-минус один оборот. Мы также проверяем, что все углы изменения направления являются положительными или отрицательными, а не обратными (пи радиан), все точки являются фактическими 2D точками и что никакие последовательные вершины не являются идентичными. (Этот последний пункт является дискуссионным - вы можете разрешить повторные вершины, но я предпочитаю запрещать их.) Комбинация этих проверок ловит все выпуклые и невыпуклые многоугольники.

Вот код для Python 3, который реализует алгоритм и включает некоторые незначительные преимущества. Код выглядит длиннее, чем на самом деле, из-за строк комментариев и бухгалтерии, предотвращающих повторный доступ к точкам.

TWO_PI = 2 * pi

def is_convex_polygon(polygon):
    """Return True if the polynomial defined by the sequence of 2D
    points is 'strictly convex': points are valid, side lengths non-
    zero, interior angles are strictly between zero and a straight
    angle, and the polygon does not intersect itself.

    NOTES:  1.  Algorithm: the signed changes of the direction angles
                from one side to the next side must be all positive or
                all negative, and their sum must equal plus-or-minus
                one full turn (2 pi radians). Also check for too few,
                invalid, or repeated points.
            2.  No check is explicitly done for zero internal angles
                (180 degree direction-change angle) as this is covered
                in other ways, including the `n < 3` check.
    """
    try:  # needed for any bad points or direction changes
        # Check for too few points
        if len(polygon) < 3:
            return False
        # Get starting information
        old_x, old_y = polygon[-2]
        new_x, new_y = polygon[-1]
        new_direction = atan2(new_y - old_y, new_x - old_x)
        angle_sum = 0.0
        # Check each point (the side ending there, its angle) and accum. angles
        for ndx, newpoint in enumerate(polygon):
            # Update point coordinates and side directions, check side length
            old_x, old_y, old_direction = new_x, new_y, new_direction
            new_x, new_y = newpoint
            new_direction = atan2(new_y - old_y, new_x - old_x)
            if old_x == new_x and old_y == new_y:
                return False  # repeated consecutive points
            # Calculate & check the normalized direction-change angle
            angle = new_direction - old_direction
            if angle <= -pi:
                angle += TWO_PI  # make it in half-open interval (-Pi, Pi]
            elif angle > pi:
                angle -= TWO_PI
            if ndx == 0:  # if first time through loop, initialize orientation
                if angle == 0.0:
                    return False
                orientation = 1.0 if angle > 0.0 else -1.0
            else:  # if other time through loop, check orientation is stable
                if orientation * angle <= 0.0:  # not both pos. or both neg.
                    return False
            # Accumulate the direction-change angle
            angle_sum += angle
        # Check that the total number of full turns is plus-or-minus 1
        return abs(round(angle_sum / TWO_PI)) == 1
    except (ArithmeticError, TypeError, ValueError):
        return False  # any exception means not a proper convex polygon
 Rory Daulton29 июл. 2017 г., 21:53
@plasmacel: Хорошо, что подход JasonS хорош, если у вас есть предварительные знания, что многоугольник не является самопересекающимся. Я хотел сосредоточиться на "выпуклой" проблеме, на которой другие также сосредоточились. Я также хотел функцию, которая вообще не делает никаких предположений о многоугольнике - моя процедура даже проверяет, что «точки» в массиве на самом деле являются структурами, содержащими два значения, то есть координаты точки.
 plasmacel29 июл. 2017 г., 21:40
Звездные многоугольники не просто не выпуклые, но и самопересекающиеся. Ваш ответ может расширить тест для правильной обработки самопересекающихся многоугольников (хорошо иметь такое решение), однако, если рассматриваются только несамопересекающиеся простые многоугольники, то смешанный продукт (называемыйzcrossproduct @Jason) подход предпочтительнее.
 Nominal Animal26 мар. 2018 г., 22:38
@RoryDaulton: я автор вышеупомянутогоответ на другой вопрос, но пропустил заметки здесь! Я переписал этот ответ; пожалуйста, сравните с вашими. Чтобы учесть самопересекающиеся (например, бабочки или звездообразные) многоугольники, достаточно рассчитать количество смен знака (игнорируя ноль, как если бы он не имел знака) в векторах ребер 'x x и $ y $ компоненты; существует ровно два для каждого выпуклого многоугольника.atan2() медленный. При желании я также могу предоставить реализацию Python для сравнения.
 plasmacel29 июл. 2017 г., 20:35
Вот несколько связанный, но более простой подход без использования тригонометрических функций:math.stackexchange.com/questions/1743995/...
 Rory Daulton29 июл. 2017 г., 21:17
@plasmacel: Этот подход, как и ответ JasonS, принимает звездные полигоны, такие как пентаграмма или та, что в комментарии Зенны. Если звездные многоугольники приемлемы, это действительно лучше, чем мой подход, но звездные многоугольники обычно не считаются выпуклыми. Вот почему я нашел время, чтобы написать и протестировать эту функцию, которая отклоняет звездные полигоны. Кроме того, спасибо за ваши изменения - это улучшило мой ответ. Однако вы изменили значение одного предложения, поэтому я снова редактирую его - надеюсь, на этот раз оно станет более понятным.

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

Вот пример изображения:

 25 мар. 2018 г., 18:56
Это очень расплывчато Это не алгоритм. Не могли бы вы расширить и объяснить без смутных ссылок и просто отредактировать ответ?
 emory16 окт. 2015 г., 03:26
Я без понятия что это значит. Что означает, что точка находится на уровне, позади или перед линией?
 James Hill17 февр. 2016 г., 22:49
Это должно прояснить ситуацию немного:stackoverflow.com/questions/1560492/...

Ваш ответ на вопрос