[英]Compute the area of intersection between a circle and a triangle?
如何計算三角形(指定為三(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)
{
...
}
首先,我將提醒我們如何找到多邊形的區域。 完成此操作后,找到多邊形和圓形之間交點的算法應該很容易理解。
讓我們看一下三角形的情況,因為那里出現了所有必要的邏輯。 假設我們在逆時針繞三角形時有一個帶頂點(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,那么請閱讀此內容 。
第二個任期是這個區域,這也是積極的:
第三個區域如下圖所示。 這次該地區是負面的
添加這三個,我們得到以下圖片
我們看到三角形外面的綠色區域被紅色區域取消,因此凈面積就是三角形的面積,這就說明了為什么我們的公式在這種情況下是正確的。
我上面所說的是關於為什么區域公式是正確的直觀解釋。 更嚴格的解釋是觀察到當我們從邊緣計算面積時,我們得到的面積與積分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。
首先,由於我們正在與圈子合作,我們應該有一個圓圈類
public class Circle {
final Point2D center;
final double radius;
public Circle(double x, double y, double radius) {
center = new Point2D.Double(x, y);
this.radius = radius;
}
public Circle(Point2D.Double center, double radius) {
this(center.getX(), center.getY(), radius);
}
public Point2D getCenter() {
return new Point2D.Double(getCenterX(), getCenterY());
}
public double getCenterX() {
return center.getX();
}
public double getCenterY() {
return center.getY();
}
public double getRadius() {
return radius;
}
}
對於多邊形,我將使用java的Shape
類。 Shape
有一個PathIterator
,可以用來遍歷多邊形的邊緣。
現在為實際工作。 我將分離迭代遍歷邊緣的邏輯,將邊緣放在標准幾何等中,一旦完成,就從計算區域的邏輯中分離出來。 這樣做的原因是,您可能在將來想要計算除該區域之外的其他內容,並且您希望能夠重用必須處理迭代邊緣的代碼。
所以我有一個泛型類,它計算了關於我們的多邊形圓交集的類T
一些屬性。
public abstract class CircleShapeIntersectionFinder<T> {
它有三個靜態方法,可以幫助計算幾何:
private static double[] displacment2D(final double[] initialPoint, final double[] finalPoint) {
return new double[]{finalPoint[0] - initialPoint[0], finalPoint[1] - initialPoint[1]};
}
private static double wedgeProduct2D(final double[] firstFactor, final double[] secondFactor) {
return firstFactor[0] * secondFactor[1] - firstFactor[1] * secondFactor[0];
}
static private double dotProduct2D(final double[] firstFactor, final double[] secondFactor) {
return firstFactor[0] * secondFactor[0] + firstFactor[1] * secondFactor[1];
}
有兩個實例字段,一個Circle
這只是不斷的圈子的副本,以及currentSquareRadius
,這使半徑平方的副本。 這可能看起來很奇怪,但我使用的類實際上是為了找到整個圓形 - 多邊形交叉集合的區域。 這就是為什么我將其中一個圈稱為“當前”。
private Circle currentCircle;
private double currentSquareRadius;
接下來是計算我們想要計算的內容的方法:
public final T computeValue(Circle circle, Shape shape) {
initialize();
processCircleShape(circle, shape);
return getValue();
}
initialize()
和getValue()
是抽象的。 initialize()
將設置保持總面積為零的變量, getValue()
將返回該區域。 processCircleShape
的定義是
private void processCircleShape(Circle circle, final Shape cellBoundaryPolygon) {
initializeForNewCirclePrivate(circle);
if (cellBoundaryPolygon == null) {
return;
}
PathIterator boundaryPathIterator = cellBoundaryPolygon.getPathIterator(null);
double[] firstVertex = new double[2];
double[] oldVertex = new double[2];
double[] newVertex = new double[2];
int segmentType = boundaryPathIterator.currentSegment(firstVertex);
if (segmentType != PathIterator.SEG_MOVETO) {
throw new AssertionError();
}
System.arraycopy(firstVertex, 0, newVertex, 0, 2);
boundaryPathIterator.next();
System.arraycopy(newVertex, 0, oldVertex, 0, 2);
segmentType = boundaryPathIterator.currentSegment(newVertex);
while (segmentType != PathIterator.SEG_CLOSE) {
processSegment(oldVertex, newVertex);
boundaryPathIterator.next();
System.arraycopy(newVertex, 0, oldVertex, 0, 2);
segmentType = boundaryPathIterator.currentSegment(newVertex);
}
processSegment(newVertex, firstVertex);
}
我們花一點時間快速查看initializeForNewCirclePrivate
。 此方法僅設置實例字段,並允許派生類存儲圓的任何屬性。 它的定義是
private void initializeForNewCirclePrivate(Circle circle) {
currentCircle = circle;
currentSquareRadius = currentCircle.getRadius() * currentCircle.getRadius();
initializeForNewCircle(circle);
}
initializeForNewCircle
是抽象的,一個實現是為了存儲圓半徑以避免必須做平方根。 無論如何回到processCircleShape
。 在調用initializeForNewCirclePrivate
,我們檢查多邊形是否為null
(我將其解釋為空多邊形),如果它為null
返回。 在這種情況下,我們的計算區域將為零。 如果多邊形不為null
那么我們得到多邊形的PathIterator
。 我調用的getPathIterator
方法的參數是可以應用於路徑的仿射變換。 我不想申請一個,所以我只是傳遞null
。
接下來,我聲明double[]
s將跟蹤頂點。 我必須記住第一個頂點,因為PathIterator
只給我每個頂點一次,所以我必須在它給我最后一個頂點后返回,並形成一個帶有最后一個頂點和第一個頂點的邊。
下一行的currentSegment
方法將下一個頂點放在其參數中。 它返回一個代碼,告訴你它何時不在頂點。 這就是為什么我的while循環的控件表達式就是這樣的原因。
此方法的其余大部分代碼都是與迭代頂點相關的無趣邏輯。 重要的是,每次迭代while循環時,我調用processSegment
,然后在方法結束時再次調用processSegment
來處理將最后一個頂點連接到第一個頂點的邊。
我們來看看processSegment
的代碼:
private void processSegment(double[] initialVertex, double[] finalVertex) {
double[] segmentDisplacement = displacment2D(initialVertex, finalVertex);
if (segmentDisplacement[0] == 0 && segmentDisplacement[1] == 0) {
return;
}
double segmentLength = Math.sqrt(dotProduct2D(segmentDisplacement, segmentDisplacement));
double[] centerToInitialDisplacement = new double[]{initialVertex[0] - getCurrentCircle().getCenterX(), initialVertex[1] - getCurrentCircle().getCenterY()};
final double leftX = dotProduct2D(centerToInitialDisplacement, segmentDisplacement) / segmentLength;
final double rightX = leftX + segmentLength;
final double y = wedgeProduct2D(segmentDisplacement, centerToInitialDisplacement) / segmentLength;
processSegmentStandardGeometry(leftX, rightX, y);
}
在這種方法中,我實現了將邊緣轉換為標准幾何的步驟,如上所述。 首先,我計算segmentDisplacement
,即從初始頂點到最終頂點的位移。 這定義了標准幾何的x軸。 如果這個位移為零,我會提前返回。
接下來我計算位移的長度,因為這是獲得x單位向量所必需的。 獲得此信息后,我計算從圓心到初始頂點的位移。 這個與segmentDisplacement
的點積給了我leftX
,我一直在調用xi。 然后rightX
,這是我一直在呼吁XF,只是leftX + segmentLength
。 最后,我做楔形產品獲得y
如上所述。
現在我已經將問題轉換為標准幾何體,它將很容易處理。 這就是processSegmentStandardGeometry
方法所做的。 我們來看看代碼吧
private void processSegmentStandardGeometry(double leftX, double rightX, double y) {
if (y * y > getCurrentSquareRadius()) {
processNonIntersectingRegion(leftX, rightX, y);
} else {
final double intersectionX = Math.sqrt(getCurrentSquareRadius() - y * y);
if (leftX < -intersectionX) {
final double leftRegionRightEndpoint = Math.min(-intersectionX, rightX);
processNonIntersectingRegion(leftX, leftRegionRightEndpoint, y);
}
if (intersectionX < rightX) {
final double rightRegionLeftEndpoint = Math.max(intersectionX, leftX);
processNonIntersectingRegion(rightRegionLeftEndpoint, rightX, y);
}
final double middleRegionLeftEndpoint = Math.max(-intersectionX, leftX);
final double middleRegionRightEndpoint = Math.min(intersectionX, rightX);
final double middleRegionLength = Math.max(middleRegionRightEndpoint - middleRegionLeftEndpoint, 0);
processIntersectingRegion(middleRegionLength, y);
}
}
第一個if
區分y
小到足以使邊與圓相交的情況。 如果y
很大並且沒有交叉的可能性,那么我調用該方法來處理該情況。 否則我處理可能交叉的情況。
如果可以交叉,我計算交點的x坐標,交叉intersectionX
X,並且我將邊緣分成三個部分,這三個部分對應於上面標准幾何圖形的區域1,2和3。 首先我處理區域1。
為了處理區域1,我檢查leftX
是否確實小於-intersectionX
,否則將沒有區域1.如果有區域1,那么我需要知道它何時結束。 它以rightX
和-intersectionX
的最小值結束。 在找到這些x坐標后,我處理了這個非交叉區域。
處理區域3我做了類似的事情。
對於區域2,我必須做一些邏輯來檢查leftX
和rightX
是否實際上在-intersectionX
和intersectionX
之間-intersectionX
一些區域。 找到區域后,我只需要區域和y
的長度,所以我將這兩個數字傳遞給處理區域2的抽象方法。
現在讓我們看一下processNonIntersectingRegion
的代碼
private void processNonIntersectingRegion(double leftX, double rightX, double y) {
final double initialTheta = Math.atan2(y, leftX);
final double finalTheta = Math.atan2(y, rightX);
double deltaTheta = finalTheta - initialTheta;
if (deltaTheta < -Math.PI) {
deltaTheta += 2 * Math.PI;
} else if (deltaTheta > Math.PI) {
deltaTheta -= 2 * Math.PI;
}
processNonIntersectingRegion(deltaTheta);
}
我只是用atan2
來計算leftX
和rightX
之間的角度rightX
。 然后我添加代碼來處理atan2
的不連續性,但這可能是不必要的,因為不連續性發生在180度或0度。 然后我將角度差異傳遞給抽象方法。 最后我們只有抽象方法和getter:
protected abstract void initialize();
protected abstract void initializeForNewCircle(Circle circle);
protected abstract void processNonIntersectingRegion(double deltaTheta);
protected abstract void processIntersectingRegion(double length, double y);
protected abstract T getValue();
protected final Circle getCurrentCircle() {
return currentCircle;
}
protected final double getCurrentSquareRadius() {
return currentSquareRadius;
}
}
現在讓我們看一下擴展類CircleAreaFinder
public class CircleAreaFinder extends CircleShapeIntersectionFinder<Double> {
public static double findAreaOfCircle(Circle circle, Shape shape) {
CircleAreaFinder circleAreaFinder = new CircleAreaFinder();
return circleAreaFinder.computeValue(circle, shape);
}
double area;
@Override
protected void initialize() {
area = 0;
}
@Override
protected void processNonIntersectingRegion(double deltaTheta) {
area += getCurrentSquareRadius() * deltaTheta / 2;
}
@Override
protected void processIntersectingRegion(double length, double y) {
area -= length * y / 2;
}
@Override
protected Double getValue() {
return area;
}
@Override
protected void initializeForNewCircle(Circle circle) {
}
}
它有一個現場area
來跟蹤該區域。 如預期的那樣, initialize
將區域設置為零。 當我們處理非交叉邊時,我們將面積增加R ^2Δθ/ 2,如上所述。 對於交叉邊,我們將面積減去y*length/2
。 這是因為y
負值對應於正面區域,正如我們決定的那樣。
現在好的一點是,如果我們想要跟蹤周邊,我們就沒有必要做更多的工作。 我定義了一個AreaPerimeter
類:
public class AreaPerimeter {
final double area;
final double perimeter;
public AreaPerimeter(double area, double perimeter) {
this.area = area;
this.perimeter = perimeter;
}
public double getArea() {
return area;
}
public double getPerimeter() {
return perimeter;
}
}
現在我們只需要使用AreaPerimeter
作為類型再次擴展我們的抽象類。
public class CircleAreaPerimeterFinder extends CircleShapeIntersectionFinder<AreaPerimeter> {
public static AreaPerimeter findAreaPerimeterOfCircle(Circle circle, Shape shape) {
CircleAreaPerimeterFinder circleAreaPerimeterFinder = new CircleAreaPerimeterFinder();
return circleAreaPerimeterFinder.computeValue(circle, shape);
}
double perimeter;
double radius;
CircleAreaFinder circleAreaFinder;
@Override
protected void initialize() {
perimeter = 0;
circleAreaFinder = new CircleAreaFinder();
}
@Override
protected void initializeForNewCircle(Circle circle) {
radius = Math.sqrt(getCurrentSquareRadius());
}
@Override
protected void processNonIntersectingRegion(double deltaTheta) {
perimeter += deltaTheta * radius;
circleAreaFinder.processNonIntersectingRegion(deltaTheta);
}
@Override
protected void processIntersectingRegion(double length, double y) {
perimeter += Math.abs(length);
circleAreaFinder.processIntersectingRegion(length, y);
}
@Override
protected AreaPerimeter getValue() {
return new AreaPerimeter(circleAreaFinder.getValue(), perimeter);
}
}
我們有一個可變的perimeter
來跟蹤周長,我們記住radius
的值以避免必須經常調用Math.sqrt
,並且我們將區域的計算委托給我們的CircleAreaFinder
。 我們可以看到周邊的公式很簡單。
這里參考的是CircleShapeIntersectionFinder
的完整代碼
private static double[] displacment2D(final double[] initialPoint, final double[] finalPoint) {
return new double[]{finalPoint[0] - initialPoint[0], finalPoint[1] - initialPoint[1]};
}
private static double wedgeProduct2D(final double[] firstFactor, final double[] secondFactor) {
return firstFactor[0] * secondFactor[1] - firstFactor[1] * secondFactor[0];
}
static private double dotProduct2D(final double[] firstFactor, final double[] secondFactor) {
return firstFactor[0] * secondFactor[0] + firstFactor[1] * secondFactor[1];
}
private Circle currentCircle;
private double currentSquareRadius;
public final T computeValue(Circle circle, Shape shape) {
initialize();
processCircleShape(circle, shape);
return getValue();
}
private void processCircleShape(Circle circle, final Shape cellBoundaryPolygon) {
initializeForNewCirclePrivate(circle);
if (cellBoundaryPolygon == null) {
return;
}
PathIterator boundaryPathIterator = cellBoundaryPolygon.getPathIterator(null);
double[] firstVertex = new double[2];
double[] oldVertex = new double[2];
double[] newVertex = new double[2];
int segmentType = boundaryPathIterator.currentSegment(firstVertex);
if (segmentType != PathIterator.SEG_MOVETO) {
throw new AssertionError();
}
System.arraycopy(firstVertex, 0, newVertex, 0, 2);
boundaryPathIterator.next();
System.arraycopy(newVertex, 0, oldVertex, 0, 2);
segmentType = boundaryPathIterator.currentSegment(newVertex);
while (segmentType != PathIterator.SEG_CLOSE) {
processSegment(oldVertex, newVertex);
boundaryPathIterator.next();
System.arraycopy(newVertex, 0, oldVertex, 0, 2);
segmentType = boundaryPathIterator.currentSegment(newVertex);
}
processSegment(newVertex, firstVertex);
}
private void initializeForNewCirclePrivate(Circle circle) {
currentCircle = circle;
currentSquareRadius = currentCircle.getRadius() * currentCircle.getRadius();
initializeForNewCircle(circle);
}
private void processSegment(double[] initialVertex, double[] finalVertex) {
double[] segmentDisplacement = displacment2D(initialVertex, finalVertex);
if (segmentDisplacement[0] == 0 && segmentDisplacement[1] == 0) {
return;
}
double segmentLength = Math.sqrt(dotProduct2D(segmentDisplacement, segmentDisplacement));
double[] centerToInitialDisplacement = new double[]{initialVertex[0] - getCurrentCircle().getCenterX(), initialVertex[1] - getCurrentCircle().getCenterY()};
final double leftX = dotProduct2D(centerToInitialDisplacement, segmentDisplacement) / segmentLength;
final double rightX = leftX + segmentLength;
final double y = wedgeProduct2D(segmentDisplacement, centerToInitialDisplacement) / segmentLength;
processSegmentStandardGeometry(leftX, rightX, y);
}
private void processSegmentStandardGeometry(double leftX, double rightX, double y) {
if (y * y > getCurrentSquareRadius()) {
processNonIntersectingRegion(leftX, rightX, y);
} else {
final double intersectionX = Math.sqrt(getCurrentSquareRadius() - y * y);
if (leftX < -intersectionX) {
final double leftRegionRightEndpoint = Math.min(-intersectionX, rightX);
processNonIntersectingRegion(leftX, leftRegionRightEndpoint, y);
}
if (intersectionX < rightX) {
final double rightRegionLeftEndpoint = Math.max(intersectionX, leftX);
processNonIntersectingRegion(rightRegionLeftEndpoint, rightX, y);
}
final double middleRegionLeftEndpoint = Math.max(-intersectionX, leftX);
final double middleRegionRightEndpoint = Math.min(intersectionX, rightX);
final double middleRegionLength = Math.max(middleRegionRightEndpoint - middleRegionLeftEndpoint, 0);
processIntersectingRegion(middleRegionLength, y);
}
}
private void processNonIntersectingRegion(double leftX, double rightX, double y) {
final double initialTheta = Math.atan2(y, leftX);
final double finalTheta = Math.atan2(y, rightX);
double deltaTheta = finalTheta - initialTheta;
if (deltaTheta < -Math.PI) {
deltaTheta += 2 * Math.PI;
} else if (deltaTheta > Math.PI) {
deltaTheta -= 2 * Math.PI;
}
processNonIntersectingRegion(deltaTheta);
}
protected abstract void initialize();
protected abstract void initializeForNewCircle(Circle circle);
protected abstract void processNonIntersectingRegion(double deltaTheta);
protected abstract void processIntersectingRegion(double length, double y);
protected abstract T getValue();
protected final Circle getCurrentCircle() {
return currentCircle;
}
protected final double getCurrentSquareRadius() {
return currentSquareRadius;
}
無論如何,這是我對算法的描述。 我認為這很好,因為它是確切的,並沒有那么多的情況要檢查。
如果你想要一個精確的解決方案(或者至少與使用浮點運算一樣精確),那么這將涉及大量的工作,因為有很多情況需要考慮。
我計算了九種不同的情況(在下圖中按圓圈內三角形的頂點數量分類,以及圓圈中相交或包含的三角形邊緣數量):
(然而,眾所周知,這種幾何情況的枚舉是棘手的,如果我錯過了一兩個,它就不會讓我感到驚訝!)
所以方法是:
確定三角形的每個頂點是否在圓內。 我假設你知道怎么做。
確定三角形的每個邊緣是否與圓相交。 (我在這里寫了一個方法,或者看到任何計算幾何圖書。)你需要計算在步驟4中使用的一個或多個交點(如果有的話)。
確定您擁有的九個案例中的哪一個。
計算交叉口的面積。 案例1,2和9很容易。 在其余六種情況下,我繪制了虛線,以顯示如何根據三角形的原始頂點以及在步驟2中計算的交點將交叉區域划分為三角形和圓形線段 。
這個算法會相當精細並且容易出錯,只會影響其中一個案例,因此請確保您擁有涵蓋所有九個案例的測試用例(我建議也可以置換測試三角形的頂點)。 特別注意三角形的一個頂點位於圓的邊緣的情況。
如果你不需要一個精確的解決方案,那么光柵化數字和計算交集中的像素(正如其他幾個受訪者所建議的那樣)似乎是一種更容易的代碼方法,相應地也不容易出錯。
我差不多一年半了,但我想也許人們會對我寫的代碼感興趣,我認為這樣做是正確的。 查看靠近底部的功能IntersectionArea。 一般的方法是挑選由圓圈限定的凸多邊形,然后處理小圓形帽。
假設你說的是整數像素,而不是真實的,那么天真的實現就是遍歷三角形的每個像素,並檢查圓心的中心與半徑之間的距離。
這不是一個可愛的公式,或者特別快,但它確實完成了工作。
嘗試計算幾何
注意:這不是一個小問題,我希望它不是功課;-)
如果您有GPU可供使用,則可以使用此技術獲取交叉點的像素數。
由於您的形狀是凸的,您可以使用蒙特卡羅面積估計。
在圓圈和三角形周圍畫一個方框。
在框中選擇隨機點,並計算圓圈中落下的數量,以及圓圈和三角形中落入的數量。
交叉區域circle圓的面積*#以圓圈和三角形為點/以圓圈為#點
當估計區域在一定數量的回合中沒有變化超過一定量時停止選擇點,或者僅根據框的區域選擇固定數量的點。 面積估計應該快速收斂,除非你的一個形狀的面積非常小。
注意:以下是確定點是否在三角形中的方法: 重心坐標
你需要多少精確? 如果您可以使用更簡單的形狀來近似圓形,則可以簡化問題。 例如,將圓形建模為在中心相交的一組非常窄的三角形並不困難。
如果只有一個三角形的線段與圓相交,那么純數學解決方案就不會太難。 一旦知道兩個交點何時出現,就可以使用距離公式來找到弦長。
根據這些方程式 :
ϑ = 2 sin⁻¹(0.5 c / r)
A = 0.5 r² (ϑ - sin(ϑ))
其中c是弦長,r是半徑,θ是通過中心的角度,A是面積。 請注意,如果切掉一半以上的圓圈,此解決方案就會中斷。
如果你只需要一個近似值,這可能是不值得的,因為它對實際的交叉點看起來有幾個假設。
我的第一直覺是變換所有東西,使圓圈以原點為中心,將三角形轉換為極坐標,並求解三角形與圓的交點(或包圍)。 我實際上並沒有在紙上完成它,但這只是一種預感。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.