競プロやります。

競プロを頑張るブログです。

二つの円の交点を求める

ABC157の F - Yakiniku Optimization Problem では二つの円の交点を求める必要がありましたが、その具体的な方法がeditorialでは触れられていなかったので、残しておくことにします。

方針

円の方程式を連立させて解くのは厳しいので、ベクトルを用います。
角度や点を図のように定義します。円の中心  \vec{P_i} と 半径  r_i ( i = 1, 2) が与えられているときに、点Aの座標を  \vec{A} = \vec{P_1} + \vec{P_1H} + \vec{HA} を利用して求めるのがこの記事の目的です。
f:id:poporix:20200303215851p:plain:w300

最初に各辺の長さを求めていきます。
まず円の中心間の距離は  |P_1P_2| = d = \left|\vec{P_2} - \vec{P_1} \right| です。次に三角形  AP_1 P_2余弦定理を用いることで
  \displaystyle \cos \theta = \frac{r_1^2 + d^2 - r_2^2}{2 r_1 d}
がわかり、さらにこれを利用して
  |P_1 H| = r_c = r_1 \cos \theta
  |HA| = r_s = \sqrt{ r_1^2 - r_c^2}
となります。


次に  \vec{P_1H}  \vec{HA} を求めていきます。
まず  \vec{P_1H} に平行な単位ベクトル  \vec{e_1} を求めます。 これは  \vec{P_1P_2} をその長さで割ることで
   \displaystyle \vec{e_1} = \frac{1}{d} \left|\vec{P_2} - \vec{P_1} \right|
と求められます。

 \vec{P_1H} は単純に  \vec{e_1} |P_1H| = r_c 倍すればよく、
   \vec{P_1H} = r_c \vec{e_1}
です。

 \vec{HA}  \vec{e_1} を反時計回りに 90 度回転させたもの  \vec{e_2} |HA| = r_s 倍すればよいです。ベクトルの回転の公式
  \displaystyle \vec{e_2} = \left(
    \begin{array}{ccc}
      \cos \phi & -\sin \phi \\
      \sin \phi & \cos \phi
    \end{array}
  \right) \vec{e_1}
 \phi = \pi/2 とすることで  \vec{e_2} が求められ、これを用いて
   \vec{HA} = r_s \vec{e_2}
となります。


以上のことから  \vec{A} = \vec{P_1} + \vec{P_1H} + \vec{HA} にて点A の座標を求めることができます。
Bについては最後に  \vec{e_2} の代わりに  \vec{e_1} を -90度回転させた  \vec{e_3} を用いればよく、上記のベクトルの回転の公式で  \phi = \pi/2 の代わりに  \phi = -\pi/2 とすればよいです。

ソースコード

2つの円が交点を持たない場合には対応していませんので、良しなに処理してください。

#include <bits/stdc++.h>
using namespace std;

struct Vector2D {
    // 平面ベクトル
    double x, y;

    Vector2D(double x, double y) : x(x), y(y) {}

    // ベクトルの和
    Vector2D operator+(const Vector2D p) { return Vector2D(x + p.x, y + p.y); }
    // ベクトルの差
    Vector2D operator-(const Vector2D p) { return Vector2D(x - p.x, y - p.y); }

    // ベクトルのスカラー倍
    Vector2D operator*(const double d) { return Vector2D(d * x, d * y); }
    Vector2D operator/(const double d) { return Vector2D(x / d, y / d); }

    // ベクトルの長さ
    double norm() { return sqrt(x * x + y * y); }

    // ベクトルの回転
    Vector2D rotate(double theta) {
        return Vector2D(x * cos(theta) - y * sin(theta), x * sin(theta) + y * cos(theta));
    }
};

struct Circle {
    // 中心
    Vector2D center;
    // 半径
    double radius;

    Circle(Vector2D center, double radius) : center(center), radius(radius) {}
};

vector<Vector2D> getIntersections(Circle C1, Circle C2) {
    double d = (C2.center - C1.center).norm();
    double rc = (C1.radius * C1.radius + d * d - C2.radius * C2.radius) / (2 * d);
    double rs = sqrt(C1.radius * C1.radius - rc * rc);

    Vector2D e1 = (C2.center - C1.center) / d;
    Vector2D e2 = e1.rotate(M_PI / 2);
    Vector2D e3 = e1.rotate(-M_PI / 2);
    Vector2D A = C1.center + e1 * rc + e2 * rs;
    Vector2D B = C1.center + e1 * rc + e3 * rs;

    return {A, B};
}