如何计算任意三角形与正方形的交点面积?

时间:2013-08-15 11:02:32

标签: geometry computational-geometry polygons

所以,我今天整天都在努力解决一个令人生气的问题。

给定平面上三角形的一组顶点(只有3个点,6个自由参数),我需要计算这个三角形与{0,0}和{1定义的单位平方的交点区域。 1}。 (我之所以选择这个,是因为2D中的任何方形都可以转换为此方,同一个转换可以移动3个顶点)。

所以,现在问题简化为只有6个参数,3个点......我认为这个参数足够短,我愿意编写完整的解决方案/找到完整的解决方案。

(如果可能的话,我想在GPU上运行超过200万个三角形,每次<0.5秒。至于需要简化/没有数据结构/库)

就我尝试解决方案而言,我已经找到了一系列我想出的方法,其中没有一个看起来很快或者......特定于这个好的案例(也是一般)。

选项1:找到封闭的多边形,它可以是从三角形到6-gon的任何东西。通过在我发现的O(n)时间算法中使用一些凸多边形的交叉来做到这一点。然后我会按CW或CCw顺序对这些交点(新顶点,最多7个O(n log n))进行排序,这样我就可以在点上运行一个简单的区域算法(基于格林函数)( O(n)再次)。对于与另一个m-gon相交的任意凸n-gon,这是我能提供的最快速度。但是......我的问题绝对不是那么复杂,它是一个特例,所以它应该有一个更好的解决方案......

选项2: 因为我知道它是一个三角形和单位正方形,所以我可以简单地以更强力的方式找到交叉点列表(而不是使用一些算法......坦率地说实施起来有点令人沮丧,如上所列)

只有19个点需要检查。 4个点是三角形内的正方形角。正方形内有三角三角形。然后对于三角形的每条线,每条线将与正方形相交4条线(例如,y = 0,y = 1,x = 0,x = 1条线)。那是另外12点。所以,12 + 3 + 4 = 19点检查。 一旦我有了这个交叉点的最多6个,最少3个点,我可以跟进我能想到的两种方法中的一种。

2a:通过增加x值对它们进行排序,然后简单地将形状分解为其子三角形/ 4-gon形状,每个形状都有一个基于限制顶部和底部线的简单公式。总结这些领域。

或2b:再以某种循环方式对交点进行排序,然后根据绿色函数计算区域。

不幸的是,就我所知,这仍然是最复杂的。为了找到交点,我可以开始分解所有的情况,因为我知道它的正方形只有0和1,这使得数学失去了一些条件......但它不一定简单。

选项3:根据各种条件开始分离问题。例如。正方形内的0,1,2或3个三角形点。然后针对每种情况,遍历所有可能数量的交叉点,然后针对每个多边形形状的情况,唯一地记下区域解决方案。

选项4:一些具有重载步骤功能的配方。这是我最想要的那个,我怀疑它会有点......很大,但也许我乐观地认为它是可能的,并且它将是我计算运行时间最快的一次有公式。

---总的来说,我知道可以使用一些高级库(例如clipper)来解决它。我也意识到,在使用各种数据结构(链表,然后对其进行排序)时,编写通用解决方案并不是那么难。如果我只需要这样做几次,所有这些情况都可以。但是,因为我需要将其作为图像处理步骤运行,每张图像大约为> 9 * 1024 * 1024次,并且我在...处拍摄图像...让我说1 fps(技术上我会想要尽可能快地推动这个速度,但是下限是1秒来计算这些三角形交叉区域问题中的900万个)。这可能在CPU上是不可能的,这很好,我可能最终会在Cuda中实现它,但我确实希望在这个问题上突破速度限制。

编辑:所以,我最终选择了2b。由于可能只有19个交叉点,其中最多6个将定义形状,我首先找到3到6个顶点。然后我按循环(CCW)顺序对它们进行排序。然后我通过计算该多边形的面积来找到该区域。

这是我为此编写的测试代码(它适用于Igor,但应该可以作为伪代码阅读)不幸的是它有点啰嗦,但是......我认为除了我糟糕的排序算法(虽然不应该超过20次交换,因此写入更好的排序不需要太多开销)......除了排序之外,我不认为我可以更快地完成任务。尽管如此,我对选择此选项时可能有的任何建议或疏忽持开放态度。

function calculateAreaUnitSquare(xPos, yPos)
wave xPos
wave yPos

// First, make array of destination. Only 7 possible results at most for this geometry. 
Make/o/N=(7) outputVertexX  = NaN
Make/o/N=(7) outputVertexY  = NaN

variable pointsfound = 0

// Check 4 corners of square
// Do this by checking each corner against the parameterized plane described by basis vectors p2-p0 and p1-p0. 
//   (eg. project onto point - p0 onto p2-p0 and  onto p1-p0. Using appropriate parameterization scaling (not unit). 
// Once we have the parameterizations, then it's possible to check if it is inside the triangle, by checking that u and v are bounded by u>0, v>0 1-u-v > 0

variable denom =  yPos[0]*xPos[1]-xPos[0]*yPos[1]-yPos[0]*xPos[2]+yPos[1]*xPos[2]+xPos[0]*yPos[2]-xPos[1]*yPos[2]

//variable u00 = yPos[0]*xPos[1]-xPos[0]*yPos[1]-yPos[0]*Xx+yPos[1]*Xx+xPos[0]*Yx-xPos[1]*Yx
//variable v00 = -yPos[2]*Xx+yPos[0]*(Xx-xPos[2])+xPos[0]*(yPos[2]-Yx)+yPos[2]*Yx

variable u00 = (yPos[0]*xPos[1]-xPos[0]*yPos[1])/denom
variable v00 = (yPos[0]*(-xPos[2])+xPos[0]*(yPos[2]))/denom

variable u01 =(yPos[0]*xPos[1]-xPos[0]*yPos[1]+xPos[0]-xPos[1])/denom
variable v01 =(yPos[0]*(-xPos[2])+xPos[0]*(yPos[2]-1)+xPos[2])/denom

variable u11 = (yPos[0]*xPos[1]-xPos[0]*yPos[1]-yPos[0]+yPos[1]+xPos[0]-xPos[1])/denom
variable v11 = (-yPos[2]+yPos[0]*(1-xPos[2])+xPos[0]*(yPos[2]-1)+xPos[2])/denom

variable u10 = (yPos[0]*xPos[1]-xPos[0]*yPos[1]-yPos[0]+yPos[1])/denom
variable v10 = (-yPos[2]+yPos[0]*(1-xPos[2])+xPos[0]*(yPos[2]))/denom

if(u00 >= 0 && v00 >=0 && (1-u00-v00) >=0)
        outputVertexX[pointsfound] = 0
        outputVertexY[pointsfound] = 0
        pointsfound+=1
endif

if(u01 >= 0 && v01 >=0 && (1-u01-v01) >=0)
        outputVertexX[pointsfound] = 0
        outputVertexY[pointsfound] = 1
        pointsfound+=1
endif

if(u10 >= 0 && v10 >=0 && (1-u10-v10) >=0)
        outputVertexX[pointsfound] = 1
        outputVertexY[pointsfound] = 0
        pointsfound+=1
endif

if(u11 >= 0 && v11 >=0 && (1-u11-v11) >=0)
        outputVertexX[pointsfound] = 1
        outputVertexY[pointsfound] = 1
        pointsfound+=1
endif

// Check 3 points for triangle. This is easy, just see if its bounded in the unit square. if it is, add it. 

variable i = 0

for(i=0; i<3; i+=1)
    if(xPos[i] >= 0 && xPos[i] <= 1 ) 
        if(yPos[i] >=0 && yPos[i] <=1)
            if(!((xPos[i] == 0 || xPos[i] == 1) && (yPos[i] == 0 || yPos[i] == 1) ))
                outputVertexX[pointsfound] = xPos[i]
                outputVertexY[pointsfound] = yPos[i]
                pointsfound+=1
            endif
        endif
    endif
endfor


// Check intersections.
//    Procedure is: loop over 3 lines of triangle. 
        //   For each line
            // Check if vertical
                // If not vertical, find y intercept with x=0 and x=1 lines.
                // if y intercept is between 0 and 1, then add the point
            // Check if horizontal
                // if not horizontal, find x intercept with y=0 and y=1 lines
                // if x intercept is between 0 and 1, then add the point

for(i=0; i<3; i+=1)
    variable iN = mod(i+1,3)

    if(xPos[i] != xPos[iN])
        variable tx0 = xPos[i]/(xPos[i] - xPos[iN]) 
        variable tx1 = (xPos[i]-1)/(xPos[i] - xPos[iN]) 

        if(tx0 >0 && tx0 < 1)
            variable yInt = (yPos[iN]-yPos[i])*tx0+yPos[i]
            if(yInt > 0 && yInt <1)
                outputVertexX[pointsfound] = 0
                outputVertexY[pointsfound] = yInt
                pointsfound+=1
            endif
        endif

        if(tx1 >0 && tx1 < 1)
            yInt = (yPos[iN]-yPos[i])*tx1+yPos[i]
            if(yInt > 0 && yInt <1)
                outputVertexX[pointsfound] = 1
                outputVertexY[pointsfound] = yInt
                pointsfound+=1
            endif
        endif
    endif


    if(yPos[i] != yPos[iN])
        variable ty0 = yPos[i]/(yPos[i] - yPos[iN]) 
        variable ty1 = (yPos[i]-1)/(yPos[i] - yPos[iN]) 


        if(ty0 >0 && ty0 < 1)
            variable xInt = (xPos[iN]-xPos[i])*ty0+xPos[i]

            if(xInt > 0 && xInt <1)
                outputVertexX[pointsfound] = xInt
                outputVertexY[pointsfound] = 0
                pointsfound+=1
            endif
        endif

        if(ty1 >0 && ty1 < 1)
            xInt = (xPos[iN]-xPos[i])*ty1+xPos[i]
            if(xInt > 0 && xInt <1)
                outputVertexX[pointsfound] = xInt
                outputVertexY[pointsfound] = 1
                pointsfound+=1
            endif
        endif
    endif
endfor

// Now we have all 6 verticies that we need. Next step: find the lowest y point of the verticies
// if there are multiple with same low y point, find lowest X of these. 
// swap this vertex to be first vertex. 

variable lowY = 1
variable lowX = 1
variable m = 0;
for (i=0; i<pointsfound ; i+=1)
    if (outputVertexY[i] < lowY)
        m=i
        lowY = outputVertexY[i]
        lowX = outputVertexX[i]
    elseif(outputVertexY[i] == lowY)
        if(outputVertexX[i] < lowX)
            m=i
            lowY = outputVertexY[i]
            lowX = outputVertexX[i]
        endif
    endif
endfor

outputVertexX[m] = outputVertexX[0]
outputVertexY[m] = outputVertexY[0]

outputVertexX[0] = lowX
outputVertexY[0] = lowY

// now we have the bottom left corner point, (bottom prefered). 
//  calculate the cos(theta) of unit x hat vector to the other verticies

make/o/N=(pointsfound) angles = (p!=0)?( (outputVertexX[p]-lowX) / sqrt( (outputVertexX[p]-lowX)^2+(outputVertexY[p]-lowY)^2) ) : 0

// Now sort the remaining verticies based on this angle offset. This will orient the points for a convex polygon in its maximal size / ccw orientation
//  (This sort is crappy, but there will be in theory, at most 25 swaps. Which in the grand sceme of operations, isn't so bad. 
variable j
for(i=1; i<pointsfound; i+=1)
    for(j=i+1; j<pointsfound; j+=1)
        if( angles[j] > angles[i] )
            variable tempX = outputVertexX[j]
            variable tempY = outputVertexY[j]
            outputVertexX[j] = outputVertexX[i] 
            outputVertexY[j] =outputVertexY[i]
            outputVertexX[i] = tempX
            outputVertexY[i] = tempY
            variable tempA = angles[j]
            angles[j] = angles[i]
            angles[i] = tempA
        endif
    endfor
endfor

// Now the list is ordered! 
// now calculate the area given a list of CCW oriented points on a convex polygon. 
// has a simple and easy math formula : http://www.mathwords.com/a/area_convex_polygon.htm
variable totA = 0

for(i = 0; i<pointsfound; i+=1)
    totA += outputVertexX[i]*outputVertexY[mod(i+1,pointsfound)] - outputVertexY[i]*outputVertexX[mod(i+1,pointsfound)]
endfor

totA /= 2

return totA

3 个答案:

答案 0 :(得分:6)

我认为Cohen-Sutherland线裁剪算法是你的朋友。

首先检查三角形的边界框与正方形,以捕捉琐碎的情况(正方形内的三角形,正方形外的三角形)。

接下来检查方块完全位于三角形内的情况。

接下来,按顺时针顺序考虑您的三角形顶点ABC。将线段ABBCCA对齐方块。它们要么被改变,要么它们位于广场内,要么被发现在外面,在这种情况下它们可以被忽略。

现在,您有一个最多三个线段的有序列表,用于定义一些边缘交叉点多边形。很容易弄清楚如何从一个边到另一个边遍历以找到交叉多边形的其他边。考虑一个线段(e)的端点与下一个(s)的开头

  • 如果es重合,就像三角形顶点位于正方形内的情况一样,则不需要遍历。
  • 如果es不同,那么我们需要绕着广场的边界顺时针移动。

请注意,此遍历将按顺时针顺序排列,因此无需计算交点形状的顶点,将它们按顺序排序,然后计算区域。无需存储顶点即可随时计算区域。

考虑以下示例: enter image description here

在第一种情况下:

  1. 我们会在方块上剪切ABBCCA行,从而生成线段ab>baca>ac
  2. ab>ba形成交叉点多边形的第一条边
  3. 要从ba遍历到caba位于y=1,而ca则没有,因此下一个边缘是ca>(1,1)
  4. (1,1)ca都位于x=1,因此下一个边缘是(1,1)>ca
  5. 下一个边缘是我们已经拥有的线段,ca>ac
  6. acab是一致的,因此不需要遍历(您可能只是计算退化边缘的区域并避免在这些情况下使用分支)
  7. 在第二种情况下,将三角形边缘剪切到正方形会给我们ab>babc>cbca>ac。这些段之间的遍历是微不足道的,因为起点和终点位于相同的方形边缘上。

    在第三种情况下,从baca的遍历经过两个方形顶点,但仍然可以比较它们所在的方形边缘:

    1. ba位于y=1ca没有,因此下一个顶点为(1,1)
    2. (1,1)位于x=1ca没有,因此下一个顶点为(1,0)
    3. (1,0)位于y=0ca也是如此,因此下一个顶点为ca

答案 1 :(得分:0)

考虑到大量的三角形,我建议使用扫描线算法:将所有点1和X排序,然后按Y排序,然后沿X方向进行“扫描线”,保持所有线的Y排序交叉点堆那条线。这种方法已被广泛用于大型多边形集合的布尔运算:AND,OR,XOR,INSIDE,OUTSIDE等运算都采用O(n * log(n))。

增强布尔AND操作应该相当简单,使用扫描线算法实现,以找到您需要的区域。复杂性将保持为三角形数量的O(n * log(n))。该算法还适用于具有任意多边形任意集合的交叉点,以防您需要扩展到该交叉点。

在第二个想法中,如果除了三角形区域之外你不需要任何东西,你可以在O(n)中做到这一点,而扫描线可能是一种矫枉过正。

答案 2 :(得分:0)

我这个问题来得很晚,但是我认为我已经提出了一种更加完全冲洗的解决方案。我将为尝试至少有效地解决此问题的其他人提供一个概述。

首先,您要检查两个简单的情况:

1)三角形完全位于正方形内

2)正方形完全位于三角形内(只需检查所有拐角are inside the triangle

如果都不是真的,那么事情就会变得有趣。

首先,使用Cohen-Sutherland或Liang-Barsky algorithm将三角形的每个边缘裁剪为正方形。 (链接的文章包含了很多代码,如果您使用的是C,则基本上可以将其粘贴粘贴)。

给出三角形的边缘,这些算法将输出修剪的边缘或标记,表明该边缘完全位于正方形的外部。如果所有边缘都超过正方形,则三角形和正方形不相交。

否则,我们知道修剪边缘的端点至少构成了表示相交的多边形的某些顶点。

通过简单的观察,我们可以避免繁琐的个案处理。 相交多边形的所有其他顶点(如果有)将是位于三角形内部的正方形的角。

简单地说,除了三角形内部正方形的角以外,交集多边形的顶点将是修剪的三角形边缘的(唯一)端点。

我们假设我们想按逆时针方向排列这些顶点。由于交集多边形将始终是凸的,因此我们可以计算其质心(所有顶点位置的均值),该质心位于多边形内部。

然后,我们可以使用atan2函数为每个顶点分配一个角度,其中输入是通过从顶点位置减去质心而获得的矢量的y和x坐标(即,矢量从质心到顶点)。

最后,可以基于分配的角度值以递增的顺序对顶点进行排序,这构成了逆时针的顺序。连续的顶点对对应于多边形边。