Что не так с этим псевдокодом для версии Форстера-Оверфельта алгоритма обрезки полигонов Грейнера-Хормана?

Проблема

Я пытаюсь понять и внедрить версию Forster-Overfelt алгоритм обрезки полигонов Грейнера-Хормана. Я прочитал другой пост Stackoverflow о разъяснении этого алгоритма, но я все еще не могу Кажется, это не работает.

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

subjpoly = [(0,0),(6,0),(6,6),(0,6),(0,0)]
clippoly = [(1,4),(3,8),(5,4),(5,10),(1,10),(1,4)]

который производит пересечение:

[ [(5.0, 6.0), (4.0, 6.0), (5, 4), (5.0, 6.0)], 
  [(1.0, 6.0), (2.0, 6.0), (4.0, 6.0)] ]

Визуально это выглядит так:

введите здесь описание изображения

Таким образом, этот вопрос касается не конкретного фрагмента кода или синтаксиса языка, а понимания алгоритма и воплощения его в псевдокоде. Заранее спасибо!

Алгоритм

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

Часть 1:

Начните с зацикливания subj и clip и пометьте расположение каждой вершины как «внутри», «вне» или «на» другом полигоне.

for s in subj.iter():
    s.loc = testLocation(s, clip)
for c in clip.iter():
    c.loc = testLocation(c, subj)

Часть 2:

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

for s in subj.iter():

    if s.intersect:

Подчасть 2.1:

Обработайте каждое пересечение subj, либо сняв пометку с него как пересечение, либо пометив его как вход или выход, и сделайте то же самое для соседней точки пересечения. ПРИМЕЧАНИЕ. Алгоритм, описанный в статье, объясняет только, как пометить основной многоугольник объекта, но никогда не говорит, как пометить соседний полигон, поэтому здесь я просто предполагаю, что оба полигона отмечены с использованием одного и того же набора правил.

        mark(s)
        mark(s.neighbour)

Где правила обработки mark() определены как:

        def mark(s):

            curlocs = (s.prev.loc,s.next.loc)
            neighlocs = (s.neighbour.prev.loc,s.neighbour.next.loc)

            # in in
            if curlocs == ("in","in"):
                if neighlocs == ("in","in")\
                   or neighlocs == ("out","out")\
                   or neighlocs == ("on","on"):
                    s.intersect = False
                else:
                    s.entry = True

            # out out
            elif curlocs == ("out","out"):
                if neighlocs == ("in","in")\
                   or neighlocs == ("out","out")\
                   or neighlocs == ("on","on"):
                    s.intersect = False
                else:
                   s.entry = False

            # on on
            elif curlocs == ("on","on"):
                if neighlocs == ("in","in")\
                   or neighlocs == ("out","out")\
                   or neighlocs == ("on","on"):
                    s.intersect = False
                else:
                    # label opposite of neighbour
                    # NOTE: this is not specified in the article,
                    # but one cannot take the opposite of the neighbour's entry flag
                    # if the neighbour hasn't been marked yet,
                    # thus the decision to mark the neighbour first
                    mark(s.neighbour)
                    s.entry = not s.neighbour

            # partial exit
            elif curlocs == ("in","on")\
                 or curlocs == ("on","out"):
                s.entry = False

            # partial entry
            elif curlocs == ("on","in")\
                 or curlocs == ("out","on"):
                s.entry = True

            # normal exit
            elif curlocs == ("in","out"):
                s.entry = False

            # normal entry
            elif curlocs == ("out","in"):
                s.entry = True

Подчасть 2.2:

Наконец, убедитесь, что curr и сосед не имеют одинаковых входных и выходных флагов; если они отключают свой флаг пересечения и меняют флаги местоположения.

        if s.entry and s.neighbour.entry:
            s.intersect = False
            s.neighbour.intersect = False
            s.loc = "in"
        elif not s.entry and not s.neighbour.entry:
            s.intersect = False
            s.neighbour.intersect = False
            s.loc = "out"

Бонусный вопрос

Бонусный вопрос заключается в том, как заставить этот алгоритм поддерживать как операции объединения, так и операции пересечения, поскольку поддержка объединения исходным алгоритмом Грейнера заключалась в простом инвертировании начального флага входа/выхода, но этот алгоритм Форстера не использует такой флаг?


person Karim Bahgat    schedule 05.08.2014    source источник
comment
Как именно он сломан? Чем результат отличается от того, что вы ожидаете?   -  person TheSoundDefense    schedule 06.08.2014
comment
Сократите это до минимального примера и предоставьте более четкое описание проблемы.   -  person jonrsharpe    schedule 06.08.2014
comment
Теперь я обновил сообщение о том, как я узнал, что он сломан, но нет четкой закономерности того, как он отличается от того, что я ожидаю, потому что это отличается для любого полигона, который я ему даю. Позже я постараюсь добавить минимальный пример, но важно, чтобы были включены все части псевдокода, чтобы его можно было проверить и проверить.   -  person Karim Bahgat    schedule 06.08.2014
comment
@jonrsharpe Я сделал еще одно редактирование, сделав весь пост более четким, и разбил алгоритм на разделы вместо одного блока кода, чтобы людям было легче просматривать и выявлять проблемы с различными частями алгоритма. Я не могу сделать его более минимальным, потому что я не знаю, какая часть неверна. Надеюсь, это поможет.   -  person Karim Bahgat    schedule 06.08.2014
comment
Изменение начального флага входа/выхода в Greiner-Horman эквивалентно изменению всех флагов. Делая это, можно проследить многоугольник, который находится снаружи другого многоугольника, и, таким образом, вы получите объединение. Мне нужно будет рассмотреть ваш вопрос более подробно и постараюсь дать хороший ответ позже.   -  person erichlf    schedule 06.08.2014
comment
Кроме того, алгоритм Фостера-Оверфельта несовершенен в некоторых основных случаях, когда нет вырождений, поэтому вместо этого я бы посоветовал обратиться к следующей статье sciencedirect.com/science/article/pii/S0010448510001478   -  person erichlf    schedule 06.08.2014


Ответы (1)


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

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

'''
  The following is an adaptation of the above Greiner-Hormann* algorithm to deal
  with degenerate cases. The adaptation was briefly described by Liu et al.**  
  *Greiner, G. and Hormann K., Efficient Clipping of Arbitrary Polygons, ACM
  Trans. on Graphics, 17(2), 1998, pp.71-83
  **Liu, Y. K., Wang X. Q., Bao S. Z., Gombosi M., and Zalik B, An Algorithm for
  Polygon Clipping and for Determining Polygon Intersections and Unions, Comp. &
  Geo, 33, pp. 589-598, 2007
'''
def clip(subject, constraint):
    subject, constraint = inside_outside(subject, constraint) #label vertices as inside or outside
    subject, constraint = poly_inters(subject, constraint) #find intersections
    subject, constraint = label(subject, constraint) #label intersections and entry or exit and possibly remove

    flag = True #loop flag

    #set our current location to the first point in subject
    current = subject.first
    #loop through our polygon until we have found the first intersection
    while flag:
        current = current.next
        #Either an intersection has been found or no intersections found
        if current.intersect or current.pt == subject.first.pt:
            flag = False

    #reset our flag for the new loop
    flag = True
    #If a point lies outside of c and there was an intersection clip s
    if current.intersect:
        append(clipped, current.pt) 
        While flag:
            #Entry
            if current.en:
                clipped, current = forward(clipped, current)
            #Exit
            else:
                clipped, current = backward(clipped, current)

            #Check to see if we have completed a polygon
            if current.pt == clipped.first.pt:
                #check if the polygon intersect at a point
                if clipped.num_vertices is not 1:
                    #remove the last vertex because it is also the first 
                    remove(clipped, clipped.last)
                #we have created our polygon so we can exit
                flag = .FALSE.

            #change to the neighbor polygon since we are at a new intersection
            current = current.neighbor

    #Check if one polygon is contained wholly within the other
    elif contained(subject, constraint):
        clipped = subject
    elif contained(subject, constraint):
        clipped = constraint

    return clipped

Теперь можно обсудить маркировку. Следующий код представляет собой цикл для маркировки пересечений как внутренних или внешних. Он не включает в себя логику определения внутри/снаружи и представляет собой только порядок операций.

  #label intersections as entry or exit
  def label(poly1, poly2):
      #cycle through first polygon and label intersections as en or ex
      current = poly1.first
      for i in range(0,poly1.num_vertices):
          if current.intersect:
              current = intersect_cases(current)
              #Make sure current is still an intersection
              if current.isect:
                  current.neighbor = intersect_cases(current.neighbor)
                  #if the intersection is en/en or ex/ex
                  if current.en == current.neighbor.en:
                      current = remove_inter(current)

          current = current.next #move to the next point

      return poly1, poly2

И, наконец, работа с различными случаями маркировки.

  #deal with the cases
  #on/on, on/out, on/in, out/on, out/out, out/in, in/on, in/out, in/in
  def intersect_cases(current):
      neighbor = current.neighbor
      #on/on
      if current.prev.intersect and current.next.intersect:
          #Determine what to do based on the neighbor
          #en tag is the opposite of the neighbor's en tag 
          if neighbor.prev.intersect and neighbor.next.intersect:
              current = remove_inter(current)
              current.en = True
              neighbor.en = True
          elif neighbor.prev.intersect and not neighbor.next.en:
              current.en = False
          elif neighbor.prev.intersect and neighbor.next.en:
              current.en = True
          elif not neighbor.prev.en and neighbor.next.intersect:
              current.en = False
          elif not (neighbor.prev.en or neighbor.next.en):
              current = remove_inter(current)
              current.en = True
              neighbor.en = False
          elif not neighbor.prev.en and neighbor.next.en:
              current.en = False
          elif neighbor.prev.en and neighbor.next.isect:
              current.en = True
          elif neighbor.prev.en and not neighbor.next.en:
              current.en = True
          elif neighbor.prev.en and neighbor.next.en:
              current = remove_inter(current)
              current.en = False
              neighbor.en = True
      #on/out
      elif current.prev.intersect and not current.next.en:
          current.en = False
      #on/in  
      elif current.prev.intersect and current.next.en:
          current.en = True
      #out/on  
      elif not current.prev.en and current.next.intersect:
          current.en = True
      #out/out  
      elif not (current.prev%en or current.next.en):
          if neighbor.prev%intersect and neighbor.next.intersect:
              current = remove_inter(current)
              neighbor.en = True
          elif neighbor.prev.en == neighbor.next.en:
              current = remove_inter(current)
          else:
              if neighbor.prev.en and not neighbor.next.en:
                  current.en = True
              else:
                  current.en = False
      #out/in  
      elif not current.prev.en and current.next.en:
          current.en = True
      #in/on
      elif current.prev.en and current.next.intersect:
          current.en = False
      #in/out
      elif current.prev.en and not current.next.en:
          current.en = False
      #in/in
      elif current.prev.en and current.next.en:
          if neighbor.prev.intersect and neighbor.next.intersect:
              current = remove_inter(current)
              neighbor.en = False
          elif neighbor.prev.en == neighbor.next.en:
              current = remove_inter(current)
          else:
              if neighbor.prev.en and not neighbor.next.en:
                  current.en = True
              else:
                  current.en = False

      return current

Приведенный выше код не тестировался и написан не для эффективности, а для удобочитаемости и понимания.

person erichlf    schedule 06.08.2014
comment
это идеально @lvleph! Четкий структурированный псевдокод того, как обращаться с дегенератами. Я бы дал вам больше кредитных баллов за ваш ответ, если бы я знал, как это сделать. Это алгоритм Лю и др. вместо алгоритма Форстера-Оберфельта, но важно то, что он выглядит так, как будто он работает. Опубликую, когда у меня будет время, чтобы попытаться реализовать и протестировать это :) - person Karim Bahgat; 06.08.2014
comment
На самом деле это Фостер-Оверфельт. Фостер-Оверфельт упоминает, что идея исходит от Лью и др., Но эта идея не была изложена. - person erichlf; 06.08.2014