Algorithm 在基于DCEL/半边的图形中动态添加边?

Algorithm 在基于DCEL/半边的图形中动态添加边?,algorithm,graphics,graph-theory,Algorithm,Graphics,Graph Theory,我试图实现一个矢量图形“绘图”系统,本质上,用户可以在屏幕上画线,并与相交线创建的区域交互。我正在努力确定/评估这些区域是什么 我尝试了几种不同的解决方案,主要是保留一个边列表并运行BFS以找到最短的周期,但这带来了无数问题,BFS会以非法方式进行快捷操作,而孔和退化边导致的问题超出了我的计算范围,因此我转向DCEL,半边系统 我已经阅读了关于这个话题的所有文章,包括两篇经常被引用的文章:和。然而,这两种方法似乎都不能解决我在向图形动态添加边时遇到的问题 假设我从这一条边开始 半边在一个循环中相

我试图实现一个矢量图形“绘图”系统,本质上,用户可以在屏幕上画线,并与相交线创建的区域交互。我正在努力确定/评估这些区域是什么

我尝试了几种不同的解决方案,主要是保留一个边列表并运行BFS以找到最短的周期,但这带来了无数问题,BFS会以非法方式进行快捷操作,而孔和退化边导致的问题超出了我的计算范围,因此我转向DCEL,半边系统

我已经阅读了关于这个话题的所有文章,包括两篇经常被引用的文章:和。然而,这两种方法似乎都不能解决我在向图形动态添加边时遇到的问题

假设我从这一条边开始

半边在一个循环中相互连接,全局无边界“外表面”连接到其中一条半边。轻松,明白了

然后添加另一条边,附加到中心顶点:

新的半边工作正常,我们更新流入v1下一个指针的边,使其成为唯一可用的其他边,而不是它们的孪生边。再说一次,这对我来说很有意义

让我非常困惑的是,当我们将第三条边添加到中心顶点时,这里发生了什么:

我知道这就是它应该看起来的样子和链接,但我对如何通过编程实现这一点感到困惑,因为我不确定如何确定边(4,1)是指向边(1,2)还是指向边(1,3)(类似地,对于边应该指向什么(1,4))

当你看这张图片时,答案似乎很明显,但当你试图以一种稳健、严密的算法方式将其合理化时,我的大脑会融化,我无法理解它。我正在读的教科书(计算几何,马克·德伯格等人,第35页)只是说

“[测试边的位置]应按边的循环顺序 围绕顶点v”

hilvi.org文章中给出的用于查找要链接的传出边和传入边的算法似乎根本不起作用,因为它将取顶点1,并跟随其传出边的孪生边,直到找到一条“自由”边,在本例中,该边是(2,1),这是错误的。(除非我理解不正确,否则我可能对整个问题的理解都是错误的。)


所以我完全被难住了。我现在唯一的想法是为每个半边创建某种标题属性,在这里我测量了边创建的角度,并以这种方式选择边,也许这是对的,但这似乎与半边结构所支持的相反,至少在我阅读的文章中,似乎没有提到类似的内容。任何帮助都将不胜感激。这个问题我已经讨论了一个多星期了,似乎无法解决。

是的,所以我花了很多时间思考这个问题,说实话,我有点惊讶我找不到这个问题的直接答案。因此,如果将来有人遇到类似的问题,想要从头开始填充半边图,这里有一个可行的解决方案。我没有博客,所以我在这里写

我不知道这是否是最好的答案,但它在线性时间内工作,对我来说似乎很简单

我将处理与传统DCEL略有不同的以下对象/类:

class Vertex {
    x;
    y;

    edges = []; //A list of all Half Edges with their origin at this vertex.
                //Technically speaking this could be calculated as needed, 
                  and you could just keep a single outgoing edge, but I'm not 
                  in crucial need of space in my application so I'm just 
                  using an array of all of them.
}


class HalfEdge {
    origin; //The Vertex which this half-edge emanates from

    twin; // The half-edge pair to this half-edge

    face; // The region/face this half-edge is incident to

    next; // The half-edge that this half-edge points to
    prev; // The half-edge that points to this half-edge

    angle; //The number of degrees this hedge is CW from the segment (0, 0) -> (inf, 0)
}


class Face {
    outer_edge; //An arbitrary half-edge on the outer boundary defining this face.
    inner_edges = []; //A collection of arbitrary half-edges, each defining
                      //A hole in the face.

    global; //A boolean describing if the face is the global face or not.
            //This could also be done by having a single "global face" Face instance. 
            //This is simply how I did it.
}
用于在(x,y)处初始化顶点:

  • 验证具有给定(x,y)坐标的顶点是否不存在。如果是这样,您不必做任何事情(除非如果您立即使用它,可能会返回此现有顶点)

  • 如果没有,则为其分配空间,并使用相应的x、y值及其关联边空值创建一个新顶点

  • 用于初始化从顶点A到顶点B的边:

  • 与本主题的许多文章类似,我们创建了两个半边的新实例,一个从顶点A到B,一个从B到A。它们相互链接,因为我们将它们的twin、prev和next指针都设置为另一个半边(对冲)

  • 我们还设置了树篱的角度。角度是从正x轴顺时针计算的。下面是我实现的函数。这对于使这个数据结构正常工作是非常重要的,而我在文献中没有读到任何关于这一点的重要信息,这让我觉得必须有更好的方法,但我离题了

        setAngle(){
            const dx = this.destination().x - this.origin.x;
            const dy = this.destination().y - this.origin.y;
    
            const l = Math.sqrt(dx * dx + dy * dy);
            if (dy > 0) {
                this.angle = toDeg(Math.acos(dx / l));
            } else {
                this.angle = toDeg(Math.PI * 2 - Math.acos(dx / l));
            }
    
             function toDeg(rads) {
                 return 180 * rads / Math.PI;
             }
    
         }
    
  • 接下来,通过将顶点添加到顶点的边列表中,我们将顶点与其新边配对,然后根据树篱的角度从最小(0)到最大(359)对边列表进行排序

  • 然后,这是关键的一步,,为了正确地连接所有东西,我们抓住最接近我们试图按CCW顺序连接的新对冲的对冲。基本上,只要我们的新对冲最终出现在边缘列表中,就是
    index-1
    (如果
    index=0
    ,我们返回
    边缘[edges.length-1]
    )。以edge的孪生兄弟为例,这就成了我们在上面的hivli文章中描述的AIn
    BOut=AIn.next

  • 我们设置
    AIn.next=hedgeAB
    ,同样地,
    hedgeAB.prev=AIn
    ,然后
    hedgeBA.next=AOut
    AOut.prev=hedgeBA
    。对hedgeBA也执行步骤3-5,但在顶点B上运行CCW搜索除外

  • 然后,如果顶点A和B都是“旧”顶点,这意味着它们的边列表现在每个都至少有2个元素,则可能添加了一个新的面,我们需要找到它(边的情况是有两个独立的边,并将它们连接起来以创建一个无边界的桶形或帽形)

  • 要初始化面,请执行以下操作:

  • 我们需要在图中找到所有的圈。在我的第一次实现中,我重新计算了