2

所以,我今天整天都在与一个坦率地说现在令人愤怒的问题作斗争。

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

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

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

就我对解决方案的尝试而言,我已经......列出了我想出的方法,其中没有一个看起来很快或......特定于好的案例(太笼统)。

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

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

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

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

或2b:再次以某种循环方式对交叉点进行排序,然后根据greens函数计算面积。

不幸的是,据我所知,这仍然很复杂。为了找到交点,我可以开始进一步分解所有的情况,因为我知道正方形只有 0 和 1,这使得数学放弃了一些术语.. 但这并不一定很简单。

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

选项 4:一些带有重质阶跃函数的公式。这可能是我最想要的,我怀疑它会有点……大,但也许我乐观地认为这是可能的,一旦我有了公式,这将是最快的计算运行时间。

--- 总的来说,我知道可以使用一些高级库(例如 Clipper)来解决它。我还意识到,在使用各种数据结构(链表,然后对其进行排序)时,编写通用解决方案并不难。如果我只需要这样做几次,所有这些情况都可以。但是,由于我需要将其作为图像处理步骤运行,因此每张图像的顺序为 >9 * 1024*1024 次,并且我正在以 .. 的速度拍摄图像,比如说 1 fps(从技术上讲,我想提高这个速度尽可能快,但计算 900 万个三角形相交面积问题的下限是 1 秒)。这在 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

结尾

4

3 回答 3

7

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

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

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

接下来考虑您的三角形顶点AB并按C顺时针顺序排列。剪断线段ABBCCA靠在正方形上。它们要么被改变,要么位于广场内,要么被发现位于广场外,在这种情况下,它们可以被忽略。

您现在有一个最多包含三个线段的有序列表,这些线段定义了一些边相交多边形。很容易计算出如何从一条边遍历到另一条边以找到相交多边形的其他边。考虑一条线段 ( ) 的端点与下一条线段 ( )e的起点s

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

请注意,此遍历将按顺时针顺序进行,因此无需计算交集形状的顶点,将它们排序,然后计算面积。可以随时计算面积,而无需存储顶点。

考虑以下示例: 在此处输入图像描述

在第一种情况下:

  1. 我们剪裁线ABBCCA靠在正方形上,产生线段ab>baca>ac
  2. ab>ba形成相交多边形的第一条边
  3. bato遍历caba位于 上y=1,而ca没有,所以下一条边是ca>(1,1)
  4. (1,1)并且ca都躺在 上x=1,所以下一条边是(1,1)>ca
  5. 下一条边是我们已经拥有的线段,ca>ac
  6. ac并且ab是重合的,因此不需要遍历(在这些情况下,您可能只是计算退化边缘的区域并避免分支)

在第二种情况下,将三角形边缘剪裁到正方形会得到ab>ba和。这些线段之间的遍历是微不足道的,因为起点和终点位于相同的方形边缘上。bc>cbca>ac

在第三种情况下,从bato遍历ca经过两个正方形顶点,但比较它们所在的正方形边仍然很简单:

  1. ba位于y=1ca不,所以下一个顶点是(1,1)
  2. (1,1)位于x=1ca不,所以下一个顶点是(1,0)
  3. (1,0)y=0像一样,位于 上ca,所以下一个顶点是ca
于 2013-08-20T08:49:51.403 回答
0

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

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

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

于 2013-08-19T05:25:39.680 回答
0

我迟到了这个问题,但我想我已经按照 ryanm 的回答提出了一个更全面的解决方案。我将为其他试图至少在某种程度上有效地解决这个问题的人提供一个大纲。

首先,您需要检查两个微不足道的案例:

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

2)正方形完全位于三角形内(只需检查所有角是否都在三角形内

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

首先,使用 Cohen-Sutherland 或Liang-Barsky 算法将三角形的每条边剪裁到正方形。(链接的文章包含一些不错的代码,如果您使用 C,您基本上可以复制粘贴这些代码)。

给定三角形边,这些算法将输出剪裁边或表示该边完全位于正方形之外的标志。如果所有边都比正方形大,则三角形和正方形不相交。

否则,我们知道被剪裁边的端点至少构成了表示交点的多边形的一些顶点。

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

简单地说,相交多边形的顶点除了三角形内正方形的角外,还将是裁剪三角形边的(唯一)端点。

我们假设我们想以逆时针方式对这些顶点进行排序。由于相交多边形总是凸的,我们可以计算它的质心(所有顶点位置的平均值),它位于多边形内。

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

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

于 2020-05-12T17:16:21.683 回答