18


13

三角形(3つの(X、Y)のペアとして指定される)と円(X、Y、R)との間の交差面積をどのように計算するのでしょうか。 私は無駄にいくつかの検索を行った。 これは仕事用であり、学校用ではありません。 :)

C#ではこのようになります。

struct {PointF vert [3];三角形。 {PointF center;浮動半径} サークル;

// 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(三角形t、円c){... }

11 Answer


32


まず、多角形の面積を見つける方法を思い出します。 これが完了したら、多角形と円との交点を見つけるアルゴリズムは理解しやすいはずです。

多角形の面積を見つける方法

三角形の場合を見てみましょう。すべての基本的なロジックがそこに現れているからです。 次の図に示すように、三角形を反時計回りに回っていくと、頂点(x1、y1)、(x2、y2)、(x3、y3)を持つ三角形があるとします。image:https:// i .stack.imgur.com / s0gNn.png [triangleFigure]

それからあなたは式によって面積を計算することができます

A =(x 1 y 2 x 2 y 3 x 3 y 1 −x 2 y 1 −x 3 y 2 −x 1 y 3)/ 2。

この式が機能する理由を見るために、それが次の形式になるようにそれを並べ替えましょう。

A =(x 1 y 2 −x 2 y 1)/ 2(x 2 y 3 −x 3 y 2)/ 2(x 3 y 1 −x 1 y 3)/ 2。

今最初の用語は次の領域です、それは私達の場合肯定的です:image:https://i.stack.imgur.com/7RhX2.png [ここに画像の説明を入力してください]

緑色の領域の面積が実際に(x 1 y 2 - x 2 y 1)/ 2であることが明確でない場合は、https://math.stackexchange.com/a/299358 [この]を読んでください。

2番目の用語はこの分野で、これもまたプラスです。

画像:https://i.stack.imgur.com/gvrYx.png [ここに画像の説明を入力してください]

次の図に、3番目の領域を示します。 今回は面積がマイナス

画像:https://i.stack.imgur.com/51mhe.png [ここに画像の説明を入力してください]

これら3つを足し合わせると、次のようになります。

画像:https://i.stack.imgur.com/8oJBP.png [ここに画像の説明を入力してください]

三角形の外側にある緑色の領域は赤い領域によって相殺されていることがわかります。そのため、ネット領域はちょうど三角形の領域になります。これは、この場合に式が正しい理由を示しています。

上で述べたのは、面積式が正しい理由についての直感的な説明でした。 より厳密な説明は、エッジから面積を計算するときに、得られる面積は積分r ^2dθ/ 2から得られる面積と同じであるため、境界の周りでr ^2dθ/ 2を効果的に積分することです。そして、これはストークスの定理により、多角形の境界を定める領域にわたってrdrdθを積分するのと同じ結果になります。 多角形で囲まれた領域全体でrdrdθを積分すると面積が得られるため、この手順では面積を正しく求める必要があると判断します。

円と多角形の交点の面積

次の図に示すように、半径Rの円と多角形の交点の面積を求める方法について説明しましょう。

画像:https://i.stack.imgur.com/hD5G9.png [ここに画像の説明を入力してください]

私たちは緑の地域の面積を見つけることに興味があります。 単一の多角形の場合と同じように、計算を分割して多角形の各辺の面積を求め、それらの面積を足し合わせることができます。

最初の領域は次のようになります。image:https://i.stack.imgur.com/JkRJc.png [ここに画像の説明を入力してください]

2番目の領域は、画像のようになります。https://i.stack.imgur.com/UN28S.png [ここに画像の説明を入力してください]

そして3番目の領域は画像になります。https://i.stack.imgur.com/IX32R.png [ここに画像の説明を入力してください]

繰り返しますが、最初の2つの領域は、このケースではポジティブですが、3番目の領域はネガティブです。 ネットエリアが実際に私たちが興味を持っているエリアであるようにキャンセルがうまくいくことを願っています。 どれどれ。

画像:https://i.stack.imgur.com/oXC4E.png [ここに画像の説明を入力してください]

確かに面積の合計は私たちが興味を持っている面積になります。

繰り返しますが、これがなぜ機能するのかについて、より厳密な説明をすることができます。 交点で定義される領域をI、多角形をPとします。 それから前の議論から、私達は私達が私の境界のまわりの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)の面積が得られることがわかります。 言い換えれば、π(P)上でdxdyを積分するのと同じ答えが得られます。

変数の変化を再び使用して、π(P)に対するdxdyの積分は、Pに対するJdxdyの積分と同じであることがわかります。ここで、Jはπのヤコビアンです。

これでJdxdyの積分を2つの領域に分割できます。円の中の部分と円の外の部分です。 ここでπは円内に点を単独で残すのでJ = 1であり、従ってPのこの部分からの寄与は円内にあるPの部分の面積、すなわち交点の面積である。 2番目の領域は、円の外側の領域です。 πはこの部分を円の境界まで崩壊させるので、J = 0である。

したがって、我々が計算するのは確かに交差点の面積です。

概念的にその領域を見つける方法がわかっているので、単一セグメントからの寄与度を計算する方法についてより具体的に説明しましょう。 「標準ジオメトリ」と呼ぶものの中のセグメントを見ることから始めましょう。 以下に示します。

image:https://i.stack.imgur.com/axarl.png [ここに画像の説明を入力してください]

標準ジオメトリでは、エッジは左から右へ水平になります。 これは3つの数字で表されます:xi(エッジが始まるx座標)、xf(エッジが終わるx座標)、およびy(エッジのy座標)。

今度は| y |ならば図のように<Rの場合、エッジは(-xint、y)と(xint、y)の点で円と交差します。ここで、xint =(R ^ 2-y ^ 2)^(1/2)です。 次に、計算する必要がある領域を図に示す3つの部分に分割します。 領域1と3の面積を求めるには、arctanを使ってさまざまな点の角度を求めてから、その面積をR ^ 2Δθ/ 2にすることができます。 したがって、たとえば、θi = atan 2(y、x i)およびθl = atan 2(y、-xint)と設定します。 その場合、領域1の面積はR ^ 2(θl-θi)/ 2です。 領域3の面積も同様に求めることができます。

領域2の面積はちょうど三角形の面積です。 ただし、サインには注意が必要です。 面積を正にしたいので、面積は - (xint - (-xint))y / 2とします。

留意すべきもう1つのことは、一般に、xiは-xintより小さくなければならないということではなく、xfはxintより大きくなければならないということではありません。

考慮すべき他のケースは| y |です。 > R. 図の領域1に似ているピースは1つしかないため、このケースはより単純です。

標準ジオメトリのエッジから面積を計算する方法がわかったので、残っていることは、任意のエッジを標準ジオメトリに変換する方法を説明することだけです。

しかし、これは座標の単なる変更です。 最初の頂点viと最後の頂点vfを持つものがあるとすると、新しいx単位ベクトルはviからvfを指す単位ベクトルになります。 xiは、xの中心にある円の中心からのviの変位であり、xfはxiにviとvfの間の距離を加えたものです。 一方、yは円の中心からのviの変位とxのウェッジ積で与えられます。

Code

これでアルゴリズムの説明は終わりです。今度はコードを作成します。 私は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 {

ジオメトリを計算するのに役立つ3つの静的メソッドがあります。

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];
}

2つのインスタンスフィールドがあります。円のコピーを保持するだけの 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()は面積の合計を0にする変数を設定し、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 []`を宣言します。 `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`の内積は私がxiと呼んでいた leftX`を与えてくれます。 それで私がxfと呼んでいた rightX`は単に 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’を計算し、エッジを3つの部分に分割します。これは、上の標準ジオメトリ図の領域1、2、および3に対応します。 まず地域1を扱います。

領域1を扱うために、 leftX`が本当に -intersectionX`より小さいかどうかを調べます。そうでなければ、領域1はありません。 地域1がある場合、それがいつ終わるかを知る必要があります。 それは最低 rightX`と -intersectionX`で終わります。 これらのx座標を見つけたら、この非交差領域を扱います。

私は地域3を処理するために同様のことをします。

リージョン2では、 leftX`と rightX`が実際に -intersectionX`と intersectionX`の間にあるリージョンを囲むことを確認するためのロジックをいくつかしなければなりません。 領域を見つけた後は、領域の長さと `y`だけが必要なので、これら2つの数を領域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);
}

leftX`と rightX`の角度の差を計算するために単に `atan2`を使います。 それから私は `atan2`の不連続性を扱うためのコードを追加します、しかし不連続性が180度か0度のどちらかで起こるので、これはおそらく不必要です。 それから、角度の違いを抽象メソッドに渡します。 最後に、抽象メソッドとゲッターがあります。

    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 {

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`は、予想通り、areaをゼロに設定します。 交差していないエッジを処理するときは、上記のように結論したように面積を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 {

    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`の値を覚えています、そして私達は `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;
    }

とにかく、それが私のアルゴリズムの説明です。 それは正確であり、チェックするケースはそれほど多くないので、私はそれがいいと思います。


27


厳密な解決策が必要な場合(または少なくとも浮動小数点演算を使用するのと同じくらい正確な場合)、考慮すべきケースが非常に多いため、これには多くの手間がかかります。

私は9つの異なるケースを数えます(下の図では、円の内側にある三角形の頂点の数と、円に交差する、または円に含まれる三角形のエッジの数によって分類されています)。

image:https://i.stack.imgur.com/4A8JJ.png [交差点の9つのケース:1、2。 頂点もエッジもありません。 3。 頂点なし、片端。 4。 頂点なし、2辺5。 頂点なし、3辺6。 1つの頂点、2つの辺7。 1つの頂点、3つの辺8。 2つの頂点、3つの辺9。 3つの頂点、3つの辺

(しかし、このような幾何学的なケースの列挙は扱いにくいことがよく知られています。1つか2つを見逃してもまったく驚かないでしょう。)

だからアプローチは:

  1. 三角形の各頂点が円の内側にあるかどうかを調べます。 私はあなたがそうする方法を知っていると仮定するつもりです。

  2. それが円と交差するかどうか、三角形の各辺について決定します。 (私は1つの方法を書き上げました here、または計算幾何学の本を見てください。)交差点を計算する必要があります(ある場合)ステップ4で使用します。

  3. あなたが持っている9つのケースのうちどれを決めます。

  4. 交差点の面積を計算します。 ケース1、2、9は簡単です。 残りの6つのケースでは、三角形の元の頂点に基づいて、交差領域を三角形と circular segmentに分割する方法を示すために破線を描画しました。ステップ2で計算した交点に

このアルゴリズムはかなり微妙で、1つのケースにしか影響しないエラーを起こしやすいので、9つすべてのケースをカバーするテストケースがあることを確認してください(そしてテスト用三角形の頂点も並べ替えることをお勧めします)。 三角形の頂点の1つが円の端にある場合には特に注意してください。

厳密な解決策が必要ない場合は、図形をラスタライズして交差点のピクセルを数える(他の2〜3人の回答で示唆されているように)と、コード化がはるかに簡単になり、エラーが発生しにくくなります。


2


私はほぼ1年半遅れていますが、私が書いた ここにコードに興味がある人もいるでしょう私はこれを正しくしていると思います。 一番下の近くの関数IntersectionAreaを見てください。 一般的な方法は、円で囲まれた凸多角形をピックオフしてから、小さな円形のキャップを扱うことです。


1


実際のピクセルではなく整数ピクセルを話していると仮定すると、単純な実装では三角形のすべてのピクセルをループ処理し、円の中心からその半径までの距離を確認します。

それはかわいい式ではありません、または特に速いです、しかしそれは仕事をやり遂げます。


1


http://www.cgal.org/[計算幾何学]をお試しください

注:これは些細な問題ではありません、私はそれが宿題ではないと思います;-)


1


あなたが自由に使えるGPUを持っているなら、あなたはhttps://stackoverflow.com/questions/534989/what-algorithm-can-i-use-to-determine-points-within-a-semi-circle/535059#を使うことができます交差点の画素数を求めるための535059の手法。


1


円を多角形で近似できるのではなく、円を三角形の集合として近似するべきではないと思います。 単純なアルゴリズムは次のようになります。

  1. いくつかの望みの数の頂点を持つ円を多角形に変換します。

  2. 2つの多角形(変換された円と三角形)の交点を計算します。

  3. その交点の二乗を計算します。

ステップ2とステップ3を組み合わせて単一の関数にすることで、このアルゴリズムを最適化できます。

このリンクを読む: 凸多角形の領域 凸多角形の交差


1


あなたの形状は凸型なので、モンテカルロ面積推定を使うことができます。

円と三角形の周りにボックスを描きます。

ボックス内のランダムな点を選択して、円に入る数と、円と三角形の両方に入る数を数えます。

交点の面積≒円の面積*#は円と三角形の点/#は円の点

推定面積が一定数のラウンドにわたって一定量を超えて変化しない場合はポイントの選択をやめるか、ボックスの面積に基づいて固定数のポイントを選択します。 形状の1つに非常に小さな面積がない限り、面積の見積もりはかなり速く収束するはずです。

注:これは、点が三角形の中にあるかどうかを判断する方法です。 重心座標


0


あなたはどれだけ正確である必要がありますか? より単純な形状で円を近似できる場合は、問題を単純化できます。 たとえば、円を中心で出会う非常に狭い三角形の集合としてモデル化することは難しくありません。


0


三角形の線分の1つだけが円と交差する場合、純粋な数学的解法はそれほど難しくありません。 交点が2つある時点がわかったら、距離の公式を使用して弦の長さを求めることができます。

http://mathforum.org/dr.math/faq/faq.circle.segment.html#7[これらの方程式]によると:
θ= 2sin-1(0.5c / r)A = 0.5r2(θ− sin(θ))

ここで、cは弦の長さ、rは半径、θは中心を通る角度、Aは面積です。 この解決策は、円の半分以上が切り取られると破綻することに注意してください。

実際の交差点がどのように見えるかについてはいくつかの仮定があるので、近似値が必要なだけなら、おそらく努力する価値はありません。