web-dev-qa-db-ja.com

円と長方形の交点

長方形と円の交点の面積をすばやく決定する方法を探しています(これらの計算を何百万も行う必要があります)。

特定の特性は、すべての場合において、円と長方形には常に2つの交点があるということです。

23
user67424

与えられた2つの交点:

0個の頂点は円の内側にあります: 円形セグメントの領域

    XXXXX              -------------------
   X     X               X            X Circular segment
  X       X               XX        XX 
+-X-------X--+              XXXXXXXX 
|  X     X   |
|   XXXXX    |

1つの頂点は円の内側にあります:円形セグメントと三角形の面積の合計。

    XXXXX                   XXXXXXXXX
   X     X       Triangle ->X     _-X
  X       X                 X   _-  X 
  X    +--X--+              X _-   X <- Circular segment 
   X   | X   |              X-  XXX 
    XXXXX    |              XXXX
       |     | 

2つの頂点は円の内側にあります:2つの三角形と円形セグメントの面積の合計

    XXXXX                   +------------X
   X     X                  |      _--'/'X 
  X    +--X---    Triangle->|   _--   / X
  X    |  X                 |_--     /XX <- Circular segment
   X   +-X----              +-------XX
    XXXXX                 Triangle^

3つの頂点は円の内側にあります:長方形の面積から三角形の面積を引いたものに円形セグメントの面積を加えたもの

    XXXXX
   X  +--X+             XXX
  X   |   X         -------XXX-----+ <- Triangle outside
 X    |   |X        Rect ''.  XXX  |
 X    +---+X                ''.  XX|  
 X         X                   ''. X <- Circular segment inside 
  X       X                       ^|X 
   X     X                         | X 
    XXXXX

これらの領域を計算するには:

65

私はこれが少し前に答えられたことに気づきましたが、私は同じ問題を解決していて、私が使用できるすぐに使える実行可能な解決策を見つけることができませんでした。私のボックスは 軸整列 であることに注意してください。これはOPによって完全に指定されていません。以下の解決策は完全に一般的であり、(2つだけでなく)任意の数の交差点で機能します。ボックスが軸に沿って配置されていない場合(ただし、一般的な quads ではなく直角のボックス)、円が丸いことを利用して、ボックスがすべての座標を回転するようにすることができます。最終的に軸に揃えられ、次にこのコードを使用します。

統合を使用したい-それは良い考えのようです。円をプロットするための明白な式を書くことから始めましょう:

x = center.x + cos(theta) * radius
y = center.y + sin(theta) * radius

^
|
|**###        **
| #*  #      *          * x
|#  *  #    *           # y
|#  *  #    *     
+-----------------------> theta
     *  #  *  # 
     *  #  *  #
      *  #*  #
       ***###

これは素晴らしいですが、その円の領域をxまたはyに統合することはできません。それらは異なる量です。角度thetaでのみ積分でき、ピザスライスの領域が生成されます。私が欲しいものではありません。引数を変更してみましょう:

(x - center.x) / radius = cos(theta) // the 1st equation
theta = acos((x - center.x) / radius) // value of theta from the 1st equation
y = center.y + sin(acos((x - center.x) / radius)) * radius // substitute to the 2nd equation

それはもっと似ています。 xの範囲が与えられたので、yを積分して、円の上半分の領域を取得できます。これは、[center.x - radius, center.x + radius]xにのみ当てはまります(他の値は架空の出力になります)が、その範囲外の領域はゼロであることがわかっているため、簡単に処理できます。簡単にするために単位円を仮定しましょう。後でいつでも中心と半径を差し戻すことができます。

y = sin(acos(x)) // x in [-1, 1]
y = sqrt(1 - x * x) // the same thing, arguably faster to compute http://www.wolframalpha.com/input/?i=sin%28acos%28x%29%29+

               ^ y
               |
            ***|***     <- 1
        ****   |   ****
      **       |       **
     *         |         *
    *          |          *
----|----------+----------|-----> x
   -1                     1

この関数は、単位円の上半分であるため、実際にはpi/2の積分を持ちます(半円の面積はpi * r^2 / 2であり、すでにunitと述べています) 、つまりr = 1)。これで、yを積分することにより、x軸上にある(円の中心もx軸上にある)半円と無限に高いボックスの交差面積を計算できます。

f(x): integral(sqrt(1 - x * x) * dx) = (sqrt(1 - x * x) * x + asin(x)) / 2 + C // http://www.wolframalpha.com/input/?i=sqrt%281+-+x*x%29
area(x0, x1) = f(max(-1, min(1, x1))) - f(max(-1, min(1, x0))) // the integral is not defined outside [-1, 1] but we want it to be zero out there

        ~         ~
        |      ^  |
        |      |  |
        |   ***|***     <- 1
        ****###|##|****
      **|######|##|    **
     *  |######|##|      *
    *   |######|##|       *
----|---|------+--|-------|-----> x
   -1   x0        x1      1

無限に高いボックスは私たちが望むものではないので、これはあまり役に立たないかもしれません。無限に高いボックスの下端を解放できるようにするには、もう1つのパラメータを追加する必要があります。

g(x, h): integral((sqrt(1 - x * x) - h) * dx) = (sqrt(1 - x * x) * x + asin(x) - 2 * h * x) / 2 + C // http://www.wolframalpha.com/input/?i=sqrt%281+-+x*x%29+-+h
area(x0, x1, h) = g(min(section(h), max(-section(h), x1))) - g(min(section(h), max(-section(h), x0)))

        ~         ~
        |      ^  |
        |      |  |
        |   ***|***     <- 1
        ****###|##|****
      **|######|##|    **
     *  +------+--+      *   <- h
    *          |          *
----|---|------+--|-------|-----> x
   -1   x0        x1      1

ここで、hは、x軸からの無限ボックスの下端の(正の)距離です。 section関数は、単位円とy = hで与えられる水平線との交点の(正の)位置を計算し、次のように解くことで定義できます。

sqrt(1 - x * x) = h // http://www.wolframalpha.com/input/?i=sqrt%281+-+x+*+x%29+%3D+h
section(h): (h < 1)? sqrt(1 - h * h) : 0 // if h is 1 or above, then the section is an empty interval and we want the area integral to be zero

               ^ y
               |  
            ***|***     <- 1
        ****   |   ****  
      **       |       **
-----*---------+---------*------- y = h
    *          |          *
----||---------+---------||-----> x
   -1|                   |1
-section(h)          section(h)

今、私たちは物事を進めることができます。したがって、x軸の上の単位円と交差する有限ボックスの交差面積を計算する方法は次のとおりです。

area(x0, x1, y0, y1) = area(x0, x1, y0) - area(x0, x1, y1) // where x0 <= x1 and y0 <= y1

        ~         ~                              ~         ~
        |      ^  |                              |      ^  |
        |      |  |                              |      |  |
        |   ***|***                              |   ***|*** 
        ****###|##|****                          ****---+--+****      <- y1
      **|######|##|    **                      **       |       **
     *  +------+--+      *   <- y0            *         |         *
    *          |          *                  *          |          *
----|---|------+--|-------|-----> x      ----|---|------+--|-------|-----> x
        x0        x1                             x0        x1


               ^
               |
            ***|***
        ****---+--+****      <- y1
      **|######|##|    **
     *  +------+--+      *   <- y0
    *          |          *
----|---|------+--|-------|-----> x
        x0        x1

それはすばらしい。では、x軸より上にないボックスはどうでしょうか。すべての箱がそうであるわけではないと思います。 3つの単純なケースが発生します。

  • ボックスはx軸の上にあります(上記の式を使用してください)
  • ボックスはx軸の下にあります(y座標の符号を反転し、上記の式を使用します)
  • ボックスはx軸と交差しています(ボックスを上半分と下半分に分割し、上記を使用して両方の面積を計算し、それらを合計します)

簡単ですか?いくつかのコードを書いてみましょう:

float section(float h, float r = 1) // returns the positive root of intersection of line y = h with circle centered at the Origin and radius r
{
    assert(r >= 0); // assume r is positive, leads to some simplifications in the formula below (can factor out r from the square root)
    return (h < r)? sqrt(r * r - h * h) : 0; // http://www.wolframalpha.com/input/?i=r+*+sin%28acos%28x+%2F+r%29%29+%3D+h
}

float g(float x, float h, float r = 1) // indefinite integral of circle segment
{
    return .5f * (sqrt(1 - x * x / (r * r)) * x * r + r * r * asin(x / r) - 2 * h * x); // http://www.wolframalpha.com/input/?i=r+*+sin%28acos%28x+%2F+r%29%29+-+h
}

float area(float x0, float x1, float h, float r) // area of intersection of an infinitely tall box with left Edge at x0, right Edge at x1, bottom Edge at h and top Edge at infinity, with circle centered at the Origin with radius r
{
    if(x0 > x1)
        std::swap(x0, x1); // this must be sorted otherwise we get negative area
    float s = section(h, r);
    return g(max(-s, min(s, x1)), h, r) - g(max(-s, min(s, x0)), h, r); // integrate the area
}

float area(float x0, float x1, float y0, float y1, float r) // area of the intersection of a finite box with a circle centered at the Origin with radius r
{
    if(y0 > y1)
        std::swap(y0, y1); // this will simplify the reasoning
    if(y0 < 0) {
        if(y1 < 0)
            return area(x0, x1, -y0, -y1, r); // the box is completely under, just flip it above and try again
        else
            return area(x0, x1, 0, -y0, r) + area(x0, x1, 0, y1, r); // the box is both above and below, divide it to two boxes and go again
    } else {
        assert(y1 >= 0); // y0 >= 0, which means that y1 >= 0 also (y1 >= y0) because of the swap at the beginning
        return area(x0, x1, y0, r) - area(x0, x1, y1, r); // area of the lower box minus area of the higher box
    }
}

float area(float x0, float x1, float y0, float y1, float cx, float cy, float r) // area of the intersection of a general box with a general circle
{
    x0 -= cx; x1 -= cx;
    y0 -= cy; y1 -= cy;
    // get rid of the circle center

    return area(x0, x1, y0, y1, r);
}

そして、いくつかの基本的な単体テスト:

printf("%f\n", area(-10, 10, -10, 10, 0, 0, 1)); // unit circle completely inside a huge box, area of intersection is pi
printf("%f\n", area(-10, 0, -10, 10, 0, 0, 1)); // half of unit circle inside a large box, area of intersection is pi/2
printf("%f\n", area(0, 10, -10, 10, 0, 0, 1)); // half of unit circle inside a large box, area of intersection is pi/2
printf("%f\n", area(-10, 10, -10, 0, 0, 0, 1)); // half of unit circle inside a large box, area of intersection is pi/2
printf("%f\n", area(-10, 10, 0, 10, 0, 0, 1)); // half of unit circle inside a large box, area of intersection is pi/2
printf("%f\n", area(0, 1, 0, 1, 0, 0, 1)); // unit box covering one quadrant of the circle, area of intersection is pi/4
printf("%f\n", area(0, -1, 0, 1, 0, 0, 1)); // unit box covering one quadrant of the circle, area of intersection is pi/4
printf("%f\n", area(0, -1, 0, -1, 0, 0, 1)); // unit box covering one quadrant of the circle, area of intersection is pi/4
printf("%f\n", area(0, 1, 0, -1, 0, 0, 1)); // unit box covering one quadrant of the circle, area of intersection is pi/4
printf("%f\n", area(-.5f, .5f, -.5f, .5f, 0, 0, 10)); // unit box completely inside a huge circle, area of intersection is 1
printf("%f\n", area(-20, -10, -10, 10, 0, 0, 1)); // huge box completely outside a circle (left), area of intersection is 0
printf("%f\n", area(10, 20, -10, 10, 0, 0, 1)); // huge box completely outside a circle (right), area of intersection is 0
printf("%f\n", area(-10, 10, -20, -10, 0, 0, 1)); // huge box completely outside a circle (below), area of intersection is 0
printf("%f\n", area(-10, 10, 10, 20, 0, 0, 1)); // huge box completely outside a circle (above), area of intersection is 0

これの出力は次のとおりです。

3.141593
1.570796
1.570796
1.570796
1.570796
0.785398
0.785398
0.785398
0.785398
1.000000
-0.000000
0.000000
0.000000
0.000000

それは私には正しいようです。コンパイラがそれを実行することを信頼していない場合は、関数をインライン化することをお勧めします。

これは、x軸と交差しないボックスには6 sqrt、4 asin、8 div、16 mul、17の加算を使用し、交差するボックスにはその2倍(およびさらに1つの加算)を使用します。除算は半径によるものであり、2つの除算と1つの乗算に減らすことができることに注意してください。問題のボックスがx軸と交差しているが、y軸と交差していない場合は、すべてをpi/2だけ回転させ、元のコストで計算を行うことができます。

サブピクセル精度のアンチエイリアスサークルラスタライザをデバッグするためのリファレンスとして使用しています。地獄のように遅いです:)、アルファを取得するには、円のバウンディングボックス内の各ピクセルと円の交差面積を計算する必要があります。それが機能し、数値のアーティファクトが表示されていないように見えることがわかります。下の図は、半径が0.3ピクセルから約6ピクセルに増加し、らせん状に配置された一連の円のプロットです。

circles

11
the swine

そのような古い質問への答えを投稿するのは悪くない形であることを願っています。私は上記の解決策を検討し、ダニエルズの最初の答えに似ているが、少しきついアルゴリズムを考え出しました。

要するに、全領域が長方形内にあると仮定し、外部半平面の4つのセグメントを減算してから、4つの外部象限の領域を追加し、途中で些細なケースを破棄します。

pseudocde(私の実際のコードはわずか12行です。)

find the signed (negative out) normalized distance from the circle center
to each of the infinitely extended rectangle Edge lines,
ie.
d_1=(xcenter-xleft)/r
d_2=(ycenter-ybottom)/r
etc

for convenience order 1,2,3,4 around the Edge. If the rectangle is not
aligned with the cartesian coordinates this step is more complicated but
the remainder of the algorithm is the same

If ANY d_i <=- 1 return 0

if ALL d_i >=  1 return Pi r^2

this leave only one remaining fully outside case: circle center in
an external quadrant, and distance to corner greater than circle radius:

for each adjacent i,j (ie. i,j=1,2;2,3;3,4;4,1)
     if d_i<=0 and d_j <= 0 and d_i^2+d_j^2 > 1 return 0

now begin with full circle area  and subtract any areas in the
four external half planes

Area= Pi r^2
for each  d_i>-1
     a_i=arcsin( d_i )  #save a_i for next step
     Area -= r^2/2 (Pi - 2 a_i - sin(2 a_i)) 

At this point note we have double counted areas in the four external
quadrants, so add back in:

for each adjacent i,j
   if  d_i < 1 and   d_j < 1  and d_i^2+d_j^2 < 1
       Area += r^2/4 (Pi- 2 a_i - 2 a_j -sin(2 a_i) -sin(2 a_j) + 4 sin(a_i) sin(a_j))

return Area

ちなみに、平面象限に含まれる円の面積の最後の式は、円形セグメント、2つの直角三角形、および長方形の合計として簡単に導き出されます。

楽しい。

5
agentp

以下は、円の中心が長方形の外側にある、円と長方形の間の重なり合う領域を計算する方法です。他のケースはこの問題に減らすことができます。

面積は、円の方程式y = sqrt [a ^ 2 xh)^ 2] + kここで、aは半径(h、k)は円の中心で、曲線の下の領域を見つけます。領域を多くの小さな長方形に分割してそれらの合計を計算するコンピューター積分を使用するか、ここで閉じた形式を使用できます。

alt text

そして、これが上記の概念を実装したC#ソースです。指定されたxが円の境界の外側にある特別な場合があることに注意してください。ここでは簡単な回避策を使用します(すべての場合に正しい答えが得られるわけではありません)

public static void RunSnippet()
{
    // test code
    double a,h,k,x1,x2;
    a = 10;
    h = 4;
    k = 0;
    x1 = -100;
    x2 = 100;

    double r1 = Integrate(x1, a, h, k);
    double r2 = Integrate(x2, a, h, k);

    Console.WriteLine(r2 - r1);

}

private static double Integrate(double x, double a,double h, double k)
{
    double a0 = a*a - (h-x)*(h-x);

    if(a0 <= 0.0){
        if(k == 0.0)
            return Math.PI * a * a / 4.0 * Math.Sign(x);
        else
            throw new Exception("outside boundaries");
    }

    double a1 = Math.Sqrt(a*a - (h-x)*(h-x)) * (h-x);
    double area = 0.5 * Math.Atan(a1 / ((h-x)*(h-x) - a*a))*a*a - 0.5 * a1 + k * x;
    return area;
}

注:この問題は---(Google Code Jam 2008予選ラウンド 問題:の問題と非常によく似ています。たたき飛ぶ。スコアのリンクをクリックして、ソリューションのソースコードをダウンロードすることもできます。

3
Gant

答えてくれてありがとう、

面積の見積もりで十分だと言うのを忘れました。それ; sなぜ最終的に、すべてのオプションを確認した後、モンテカルロ推定を使用して、円内にランダムな点を生成し、それらがボックス内にあるかどうかをテストしました。

私の場合、これはおそらくよりパフォーマンスが高いです。 (円の上にグリッドを配置し、各グリッドセルに属する円の比率を測定する必要があります。)

ありがとう

2
user67424

この問題の別の解決策は次のとおりです。

public static bool IsIntersected(PointF circle, float radius, RectangleF rectangle)
{

        var rectangleCenter = new PointF((rectangle.X +  rectangle.Width / 2),
                                         (rectangle.Y + rectangle.Height / 2));

        var w = rectangle.Width  / 2;
        var h = rectangle.Height / 2;

        var dx = Math.Abs(circle.X - rectangleCenter.X);
        var dy = Math.Abs(circle.Y - rectangleCenter.Y);

        if (dx > (radius + w) || dy > (radius + h)) return false;


        var circleDistance = new PointF
                                 {
                                     X = Math.Abs(circle.X - rectangle.X - w),
                                     Y = Math.Abs(circle.Y - rectangle.Y - h)
                                 };


        if (circleDistance.X <= (w))
        {
            return true;
        }

        if (circleDistance.Y <= (h))
        {
            return true;
        }

        var cornerDistanceSq = Math.Pow(circleDistance.X - w, 2) + 
                    Math.Pow(circleDistance.Y - h, 2);

        return (cornerDistanceSq <= (Math.Pow(radius, 2)));
}
1
Bassam Alugili

おそらく、 この質問 の答えを使用できます。ここでは、円と三角形の交点の面積が尋ねられます。長方形を2つの三角形に分割し、そこで説明されているアルゴリズムを使用します。

もう1つの方法は、2つの交点の間に線を引くことです。これにより、領域がポリゴン(3つまたは4つのエッジ)と 円形セグメント に分割されます。どちらの場合も、ライブラリを簡単に見つけることができるはずです。自分で計算します。

1
schnaader