簡體   English   中英

計算圓和三角形的交點區域?

[英]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)的三角形,如下圖所示: triangleFigure

然后,您可以通過公式計算面積

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,我必須做一些邏輯來檢查leftXrightX是否實際上在-intersectionXintersectionX之間-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來計算leftXrightX之間的角度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;
    }

無論如何,這是我對算法的描述。 我認為這很好,因為它是確切的,並沒有那么多的情況要檢查。

如果你想要一個精確的解決方案(或者至少與使用浮點運算一樣精確),那么這將涉及大量的工作,因為有很多情況需要考慮。

我計算了九種不同的情況(在下圖中按圓圈內三角形的頂點數量分類,以及圓圈中相交或包含的三角形邊緣數量):

交叉的九種情況:1,2。沒有頂點,沒有邊緣;沒有頂點,一條邊;沒有頂點,兩條邊;沒有頂點,三條邊; 6.一個頂點,兩個邊緣; 7.一個頂點,三個邊緣; 8.兩個頂點,三個邊緣; 9.三個頂點,三個邊。

(然而,眾所周知,這種幾何情況的枚舉是棘手的,如果我錯過了一兩個,它就不會讓我感到驚訝!)

所以方法是:

  1. 確定三角形的每個頂點是否在圓內。 我假設你知道怎么做。

  2. 確定三角形的每個邊緣是否與圓相交。 (我在這里寫了一個方法,或者看到任何計算幾何圖書。)你需要計算在步驟4中使用的一個或多個交點(如果有的話)。

  3. 確定您擁有的九個案例中的哪一個。

  4. 計算交叉口的面積。 案例1,2和9很容易。 在其余六種情況下,我繪制了虛線,以顯示如何根據三角形的原始頂點以及在步驟2中計算的交點將交叉區域划分為三角形和圓形線段

這個算法會相當精細並且容易出錯,只會影響其中一個案例,因此請確保您擁有涵蓋所有九個案例的測試用例(我建議也可以置換測試三角形的頂點)。 特別注意三角形的一個頂點位於圓的邊緣的情況。

如果你不需要一個精確的解決方案,那么光柵化數字和計算交集中的像素(正如其他幾個受訪者所建議的那樣)似乎是一種更容易的代碼方法,相應地也不容易出錯。

我差不多一年半了,但我想也許人們會對我寫的代碼感興趣,我認為這樣做是正確的。 查看靠近底部的功能IntersectionArea。 一般的方法是挑選由圓圈限定的凸多邊形,然后處理小圓形帽。

假設你說的是整數像素,而不是真實的,那么天真的實現就是遍歷三角形的每個像素,並檢查圓心的中心與半徑之間的距離。

這不是一個可愛的公式,或者特別快,但它確實完成了工作。

嘗試計算幾何

注意:這不是一個小問題,我希望它不是功課;-)

如果您有GPU可供使用,則可以使用技術獲取交叉點的像素數。

我認為你不應該將圓形近似為一組三角形,而不是用多邊形逼近它的形狀。 天真算法看起來像:

  1. 將圓轉換為具有所需頂點數的多邊形。
  2. 計算兩個多邊形(轉換圓和三角形)的交集。
  3. 計算該交叉點的平方。

您可以通過將步驟2和步驟3組合到單個函數中來優化此算法。

閱讀此鏈接:
凸多邊形的面積
凸多邊形的交點

由於您的形狀是凸的,您可以使用蒙特卡羅面積估計。

在圓圈和三角形周圍畫一個方框。

在框中選擇隨機點,並計算圓圈中落下的數量,以及圓圈和三角形中落入的數量。

交叉區域circle圓的面積*#以圓圈和三角形為點/以圓圈為#點

當估計區域在一定數量的回合中沒有變化超過一定量時停止選擇點,或者僅根據框的區域選擇固定數量的點。 面積估計應該快速收斂,除非你的一個形狀的面積非常小。

注意:以下是確定點是否在三角形中的方法: 重心坐標

你需要多少精確? 如果您可以使用更簡單的形狀來近似圓形,則可以簡化問題。 例如,將圓形建模為在中心相交的一組非常窄的三角形並不困難。

如果只有一個三角形的線段與圓相交,那么純數學解決方案就不會太難。 一旦知道兩個交點何時出現,就可以使用距離公式來找到弦長。

根據這些方程式

ϑ = 2 sin⁻¹(0.5 c / r)
A = 0.5 r² (ϑ - sin(ϑ))

其中c是弦長,r是半徑,θ是通過中心的角度,A是面積。 請注意,如果切掉一半以上的圓圈,此解決方案就會中斷。

如果你只需要一個近似值,這可能是不值得的,因為它對實際的交叉點看起來有幾個假設。

我的第一直覺是變換所有東西,使圓圈以原點為中心,將三角形轉換為極坐標,並求解三角形與圓的交點(或包圍)。 我實際上並沒有在紙上完成它,但這只是一種預感。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM