这个 Forster-Overfelt 版本的 Greiner-Horman 多边形裁剪算法的伪代码有啥问题?

Posted

技术标签:

【中文标题】这个 Forster-Overfelt 版本的 Greiner-Horman 多边形裁剪算法的伪代码有啥问题?【英文标题】:What's wrong with this pseudocode for Forster-Overfelt's version of the Greiner-Horman polygon clipping algorithm?这个 Forster-Overfelt 版本的 Greiner-Horman 多边形裁剪算法的伪代码有什么问题? 【发布时间】:2014-09-28 15:13:44 【问题描述】:

问题

我正在尝试理解和实现 Greiner-Horman 多边形裁剪算法的 Forster-Overfelt version。我已经阅读了the other *** post 关于澄清这个算法的内容,但我似乎仍然无法让它发挥作用。

我知道有问题,因为它会产生两个多边形的错误交集,即使对于一个没有退化的简单示例也是如此:

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)] ]

可视化如下:

所以这个问题不是关于特定的代码或语言语法,而是关于理解算法并将其放入伪代码。提前致谢!

算法

这里介绍的算法直接基于并且应该模仿 Forster-Oberfelt 论文中第 4.2 节中描述的算法,但显然我遗漏了一些东西,这给了我错误的结果。

第 1 部分:

首先循环 subj 和 clip 并将每个顶点位置标记为“in”、“out”或“on”另一个多边形。

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:

通过取消将它们标记为交叉点或将它们标记为入口或出口来处理每个子交叉点,并对相邻交叉点执行相同的操作。注意:文章中解释的算法只解释了如何标记主要主题多边形,但从未说明如何标记邻居,所以在这里我只是假设两者都使用相同的规则集进行标记。

        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 和neighbor 没有相同的进入或退出标志;如果他们确实禁用了交叉口标志并更改了位置标志。

        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"

奖金问题

一个额外的问题是如何使这个算法同时支持联合和交叉操作,因为原始的 Greiner 算法对联合的支持是通过简单地反转初始进入/退出标志,但是这个 Forster 算法不使用这样的标志?

【问题讨论】:

到底是怎么破的?输出与您的预期有何不同? 请将其简化为minimal example,并提供更清晰的问题描述。 我现在已经更新了关于我如何知道它已损坏的帖子,但没有明确的模式说明它与我的预期有何不同,因为我给它的任何多边形都不同。稍后我将尝试使用“最小示例”进行更新,但必须包含伪代码的所有部分,以便对其进行检查和验证。 反转 Greiner-Horman 中的初始进入/退出标志相当于反转所有标志。通过这样做,将跟踪另一个多边形外部的多边形,从而得到一个联合。我将不得不更全面地考虑您的问题,稍后会尝试给出一个好的答案。 另外,Foster-Overfelt 算法在某些没有退化的基本情况下存在缺陷,因此我建议您查看以下文章而不是 sciencedirect.com/science/article/pii/S0010448510001478 【参考方案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

以上代码未经测试,不是为了效率而写的,而是为了可读性和理解性而写的。

【讨论】:

这是完美的@lvleph!一个清晰的结构化伪代码,用于处理退化。如果我知道怎么做,我会给你更多的积分。这是 Liu 等人的算法,而不是 Forster-Oberfelt,但重要的是它看起来很有效。当我有时间尝试实施和测试时会回复:) 其实是Foster-Overfelt。 Foster-Overfelt 提到这个想法来自 Lieu 等人,但没有详细说明这个想法。

以上是关于这个 Forster-Overfelt 版本的 Greiner-Horman 多边形裁剪算法的伪代码有啥问题?的主要内容,如果未能解决你的问题,请参考以下文章

oracle 11g express 版本的使用问题

使用 g++ 中的版本脚本导出 c++ 构造函数

关于 oracle 11g 第1版 即 oracle 11.1.0.6 版本 下载地址?

ubuntu17.10安装lnmp安装包的核心问题-gcc版本g++版本

错误:npm install -g @angular/cli

CMake基础教程cmake生成debug和release两个版本程序(如何编译-g版本)