如何计算三角形(指定为三(X,Y)对)和圆(X,Y,R)之间的交叉区域?我做了一些搜索无济于事 . 这是为了工作,而不是学校 . :)
它在C#中看起来像这样:
struct { PointF vert[3]; } Triangle;
struct { PointF center; float radius; } Circle;
// returns the area of intersection, e.g.:
// if the circle contains the triangle, return area of triangle
// if the triangle contains the circle, return area of circle
// if partial intersection, figure that out
// if no intersection, return 0
double AreaOfIntersection(Triangle t, Circle c)
{
...
}
11 回答
如果您拥有GPU,则可以使用this技术获取交叉点的像素数 .
如果你想要一个精确的解决方案(或者至少与使用浮点运算一样精确),那么这将涉及大量的工作,因为有很多情况需要考虑 .
我计算了九种不同的情况(在下图中按圆圈内三角形的顶点数量分类,以及圆圈中相交或包含的三角形边缘数量):
(然而,众所周知,这种几何情况的枚举是棘手的,如果我错过了一两个,它就不会让我感到惊讶!)
所以方法是:
确定三角形的每个顶点是否在圆内 . 我假设你知道怎么做 .
确定三角形的每个边缘是否与圆相交 . (我写了一个方法here,或者看到任何计算几何图书 . )你需要计算在步骤4中使用的一个或多个交点(如果有的话) .
确定您拥有的九个案例中的哪一个 .
计算交叉口的面积 . 案例1,2和9很容易 . 在其余六种情况下,我绘制了虚线,以显示如何根据三角形的原始顶点以及在步骤2中计算的交点将交叉区域划分为三角形和circular segments .
这个算法会相当精细并且容易出错,只会影响其中一个案例,因此请确保您拥有涵盖所有九个案例的测试用例(我建议也可以置换测试三角形的顶点) . 特别注意三角形的一个顶点位于圆的边缘的情况 .
如果你不需要一个精确的解决方案,那么光栅化数字和计算交集中的像素(正如其他几个受访者所建议的那样)似乎是一种更容易的代码方法,相应地也不容易出错 .
首先,我将提醒我们如何找到多边形的区域 . 完成此操作后,找到多边形和圆形之间交点的算法应该很容易理解 .
如何查找多边形的区域
设's look at the case of a triangle, because all the essential logic appears there. Let'假设我们在逆时针绕三角形时有一个带顶点(x1,y1),(x2,y2)和(x3,y3)的三角形,如下图所示:
然后,您可以通过公式计算面积
A =(x1 y2 x2 y3 x3 y1 - x2y1-x3 y2 - x1y3)/ 2 .
要了解为什么这个公式有效,让我们重新排列它以便它在形式中
A =(x1 y2-x2 y1)/ 2(x2 y3-x3 y2)/ 2(x3 y1-x1y3)/ 2 .
现在第一个术语是以下区域,在我们的案例中是积极的:
如果不清楚绿色区域的面积确实是(x1 y2 - x2 y1)/ 2,则读取this .
第二个任期是这个区域,这也是积极的:
第三个区域如下图所示 . 这次该地区是负面的
添加这三个,我们得到以下图片
我们看到三角形外面的绿色区域被红色区域取消,因此净面积就是三角形的面积,这就说明了为什么我们的公式在这种情况下是正确的 .
我上面所说的是关于为什么区域公式是正确的直观解释 . 更严格的解释是观察到当我们从边缘计算面积时,我们得到的面积与积分r ^2dθ/ 2的面积相同,因此我们有效地将r ^2dθ/ 2积分在边界附近通过多边形和斯托克斯定理,这给出了与在多边形有界的区域上积分rdrdθ相同的结果 . 由于将rdrdθ集成在由多边形限定的区域上给出了区域,我们得出结论,我们的过程必须正确地给出该区域 .
圆与多边形的交点区域
现在让我们讨论如何找到半径为R的圆与多边形的交点区域,如下图所示:
我们有兴趣找到绿色区域 . 正如在单个多边形的情况下,我们可以将计算分解为为多边形的每一边找到一个区域,然后向上添加这些区域 .
我们的第一个区域将如下所示:
第二个区域看起来像
第三个区域将是
同样,前两个区域在我们的情况下是积极的,而第三个区域将是负面的 . 希望取消将有效,以便净面积确实是我们感兴趣的区域 . 让我们看看 .
实际上,这些区域的总和将是我们感兴趣的区域 .
同样,我们可以更加严格地解释其原因 . 让我成为交集定义的区域,让P为多边形 . 然后从前面的讨论中,我们知道我们想要计算围绕I边界的r ^2dθ/ 2的积分 . 但是,这很难做到因为它需要找到交点 .
相反,我们在多边形上做了一个整数 . 我们在多边形的边界上积分了max(r,R)^2dθ/ 2 . 为了找出为什么这给出正确的答案,让我们定义一个函数π,它将极坐标(r,θ)中的一个点作为点(max(r,R),θ) . 参考π(r)= max(r,R)和π(θ)=θ的坐标函数应该不会引起混淆 . 然后我们做的是将π(r)^2dθ/ 2整合到多边形的边界上 .
另一方面,由于π(θ)=θ,这与在多边形的边界上积分π(r)^2dπ(θ)/ 2相同 .
现在做变量的变化,我们发现如果我们在π(P)的边界上积分r ^2dθ/ 2我们会得到相同的答案,其中π(P)是π下的P的图像 .
再次使用斯托克斯定理,我们知道在π(P)的边界上积分r ^2dθ/ 2给出了π(P)的面积 . 换句话说,它给出了与将dxdy积分超过π(P)相同的答案 .
再次使用变量变量,我们知道将dxdy积分超过π(P)与将Jdxdy积分在P上相同,其中J是π的雅可比 .
现在我们可以将Jdxdy的积分分成两个区域:圆圈中的部分和圆圈外的部分 . 现在π仅在圆中留下点,所以J = 1,所以P的这部分的贡献是位于圆中的P部分的面积,即交点的面积 . 第二个区域是圆圈外的区域 . 由于π将该部分折叠到圆的边界,因此J = 0 .
因此,我们计算的确实是交叉区域 .
现在我们相对肯定我们在概念上知道如何找到该区域,让我们更具体地讨论如何计算单个区段的贡献 . 让我们首先看一下我将称之为“标准几何”的片段 . 如下所示 .
在标准几何体中,边缘从左向右水平移动 . 它由三个数字描述:xi,边缘开始的x坐标,xf,边缘结束的x坐标,y,边缘的y坐标 .
现在我们看到如果| y | <R,如图中所示,则边缘将在点(-xint,y)和(xint,y)处与圆相交,其中xint =(R ^ 2-y ^ 2)^(1/2) . 然后我们需要计算的区域被分解为图中标记的三个部分 . 为了得到区域1和区域3,我们可以使用arctan得到各个点的角度,然后将区域等于R ^2Δθ/ 2 . 因此,例如,我们将θi= atan2(y,xi)和θl= atan2(y,-xint)设置 . 然后区域1的区域是R ^ 2(θl-θi)/ 2 . 我们可以类似地获得区域3的面积 .
区域2的区域只是三角形的区域 . 但是,我们必须小心标志 . 我们希望显示的区域为正,因此我们将该区域称为 - (xint - ( - xint))y / 2 .
另外要记住的是,通常,xi不必小于-xint,xf不必大于xint .
另一种需要考虑的情况是| y | > R.这种情况比较简单,因为只有一个部分类似于图中的区域1 .
既然我们知道如何从标准几何中的边缘计算面积,那么剩下要做的就是描述如何将任何边缘转换为标准几何体 .
但这只是一个简单的坐标变化 . 给定一些具有初始顶点vi和最终顶点vf的新x单位向量将是从vi指向vf的单位向量 . 那么xi就是vi从点缀成x的圆心的位移,而xf只是xi加上vi和vf之间的距离 . 同时y由x的楔积给出,其中vi从圆心位移 .
代码
这完成了对算法的描述,现在是时候编写一些代码了 . 我会用java .
首先,由于我们正在与圈子合作,我们应该有一个圆圈类
对于多边形,我将使用java的
Shape
类 .Shape
有一个PathIterator
,我可以用来迭代多边形的边缘 .现在为实际工作 . 我将分离迭代遍历边缘的逻辑,将边缘放在标准几何等中,一旦完成,就从计算区域的逻辑中分离出来 . 这样做的原因是,您可能在将来想要计算除该区域之外的其他内容,并且您希望能够重用必须处理迭代边缘的代码 .
所以我有一个泛型类,它计算类
T
关于我们的多边形圆交集的一些属性 .public abstract class CircleShapeIntersectionFinder<T> {
它有三个静态方法,可以帮助计算几何:
有两个实例字段,一个只保留圆的副本的
Circle
和保留方形半径副本的currentSquareRadius
. 这可能看起来很奇怪,但我使用的类实际上是为了找到整个圆形 - 多边形交叉集合的区域 . 这就是为什么我将其中一个圆圈称为"current" .接下来是计算我们想要计算的内容的方法:
initialize()
和getValue()
是抽象的 .initialize()
将设置保持总面积为零的变量,getValue()
将返回该区域 .processCircleShape
的定义是我们花一点时间快速查看
initializeForNewCirclePrivate
. 此方法仅设置实例字段,并允许派生类存储圆的任何属性 . 它的定义是initializeForNewCircle
是抽象的,一个实现将是它存储圆半径以避免必须做平方根 . 无论如何回到processCircleShape
. 在调用initializeForNewCirclePrivate
之后,我们检查多边形是否为null
(我将其解释为空多边形),如果它是null
则返回 . 在这种情况下,我们的计算区域将为零 . 如果多边形不是null
,那么我们得到多边形的PathIterator
. 我调用的getPathIterator
方法的参数是可以应用于路径的仿射变换 . 我不想申请一个,所以我只是通过null
.接下来,我声明将跟踪顶点的
double[]
. 我必须记住第一个顶点,因为PathIterator
只给我每个顶点一次,所以我必须在它给我最后一个顶点后返回,并形成一个带有最后一个顶点和第一个顶点的边 .下一行的
currentSegment
方法将下一个顶点放在其参数中 . 它返回一个代码,告诉你它何时不在顶点 . 这就是我的while循环的控制表达式就是这样的原因 .此方法的其余大部分代码都是与迭代顶点相关的无趣逻辑 . 重要的是,每次迭代while循环时,我调用
processSegment
然后在方法结束时再次调用processSegment
来处理将最后一个顶点连接到第一个顶点的边 .我们来看看
processSegment
的代码:在这种方法中,我实现了将边缘转换为标准几何的步骤,如上所述 . 首先,我计算
segmentDisplacement
,从初始顶点到最终顶点的位移 . 这定义了标准几何的x轴 . 如果这个位移为零,我会提前返回 .接下来我计算位移的长度,因为这是获得x单位向量所必需的 . 获得此信息后,我计算从圆心到初始顶点的位移 . 这个与
segmentDisplacement
的点积给了我leftX
,我一直在调用xi . 然后rightX
,我一直在调用xf,只是leftX + segmentLength
. 最后我按照上面的描述做了楔形产品y
.现在我已经将问题转换为标准几何体,它将很容易处理 . 这就是
processSegmentStandardGeometry
方法的作用 . 我们来看看代码吧第一个
if
区分y
足够小以使边可以与圆相交的情况 . 如果y
很大并且没有交叉的可能性,那么我调用该方法来处理这种情况 . 否则我处理可能交叉的情况 .如果可以交叉,我计算交点的x坐标
intersectionX
,并将边缘分成三个部分,分别对应于标准几何图形的区域1,2和3以上 . 首先我处理区域1 .为了处理区域1,我检查
leftX
是否确实小于-intersectionX
,否则将没有区域1.如果有区域1,那么我需要知道它何时结束 . 它以rightX
和-intersectionX
的最小值结束 . 在找到这些x坐标后,我处理了这个非交叉区域 .处理区域3我做了类似的事情 .
对于区域2,我必须做一些逻辑来检查
leftX
和rightX
实际上是否在-intersectionX
和intersectionX
之间包含了一些区域 . 找到区域后,我只需要区域的长度和y
,所以我将这两个数字传递给处理区域2的抽象方法 .现在让我们来看看
processNonIntersectingRegion
的代码我只需使用
atan2
来计算leftX
和rightX
之间的角度差 . 然后我添加代码来处理atan2
中的不连续性,但这可能是不必要的,因为不连续发生在180度或0度 . 然后我将角度差异传递给抽象方法 . 最后我们只有抽象方法和getter:现在让我们来看看扩展类,
CircleAreaFinder
}
它有一个字段
area
来跟踪该区域 .initialize
按预期将区域设置为零 . 当我们处理非交叉边时,我们将面积增加R ^2Δθ/ 2,如上所述 . 对于交叉边,我们将面积减少y*length/2
. 这是因为我们决定应该将y
的负值对应于正面区域 .现在好的一点是,如果我们想要跟踪周边,我们就没有必要做更多的工作 . 我定义了一个
AreaPerimeter
类:现在我们只需要使用
AreaPerimeter
作为类型再次扩展我们的抽象类 .我们有一个变量
perimeter
来跟踪周长,我们记得radius
的值,以避免必须多次调用Math.sqrt
,并且我们将区域的计算委托给我们的CircleAreaFinder
. 我们可以看到周边的公式很简单 .这里是参考
CircleShapeIntersectionFinder
的完整代码无论如何,这是我对算法的描述 . 我认为这很好,因为它是确切的,并没有那么多的情况要检查 .
我差不多一年半了,但我想也许人们会对我写的code here感兴趣,我认为这样做是正确的 . 查看靠近底部的功能IntersectionArea . 一般的方法是挑选由圆圈限定的凸多边形,然后处理小圆形帽 .
假设你说的是整数像素,而不是真实的,那么天真的实现就是遍历三角形的每个像素,并检查圆心的中心与半径之间的距离 .
这不是一个可爱的公式,或者特别快,但它确实完成了工作 .
试试computational geometry
注意:这不是一个小问题,我希望它不是功课;-)
我认为你不应该将圆形近似为一组三角形,而不是用多边形逼近它的形状 . 天真算法看起来像:
将圆转换为具有所需顶点数的多边形 .
计算两个多边形的交集(转换后的圆和三角形) .
计算该交点的平方 .
您可以通过将步骤2和步骤3组合到单个函数中来优化此算法 .
阅读此链接:
Area of convex polygon
Intersection of convex polygons
由于您的形状是凸的,您可以使用蒙特卡罗面积估计 .
在圆圈和三角形周围画一个方框 .
在框中选择随机点,并计算圆圈中落下的数量,以及圆圈和三角形中落入的数量 .
交叉区域circle圆形区域*#以圆圈和三角形为点/以圆圈为圆点
当估计区域在一定数量的回合中没有变化超过一定量时停止选择点,或者仅根据框的区域选择固定数量的点 . 区域估计应该快速收敛,除非你的一个形状的面积非常小 .
注意:以下是确定点是否在三角形中的方法:Barycentric coordinates
你需要多少精确?如果您可以使用更简单的形状来近似圆形,则可以简化问题 . 例如,将圆形建模为在中心相交的一组非常窄的三角形并不困难 .
如果只有一个三角形的线段与圆相交,那么纯数学解决方案就不会太难 . 一旦知道两个交点何时出现,就可以使用距离公式来找到弦长 .
根据these equations:
其中c是弦长,r是半径,θ是通过中心的角度,A是面积 . 请注意,如果切掉一半以上的圆圈,此解决方案就会中断 .
如果你只需要一个近似值,这可能是不值得的,因为它对实际的交叉点看起来有几个假设 .
我的第一直觉是变换所有东西,使圆圈以原点为中心,将三角形转换为极坐标,并求解三角形与圆的交点(或包围) . 我实际上并没有在纸上完成它,但这只是一种预感 .