方程式を解いて求めるのでは面倒なので、幾何的に求めます。
準備
点を表す構造体Vec2と円を表す構造体Circleを作ります。
コードをクリックで展開
namespace geometry {
constexpr double EPS = 1e-12;
struct Vec2 {
double x, y;
Vec2() = default;
constexpr Vec2(double _x, double _y) :x(_x), y(_y) {}
explicit Vec2(double arg) : x(cos(arg)), y(sin(arg)) {}
bool operator>(const Vec2& v) const {
if (!(fabs(x - v.x) <= EPS)) return x > v.x;
return y > v.y;
}
constexpr Vec2 operator+(const Vec2& v) const { return { x + v.x,y + v.y }; }
constexpr Vec2 operator-(const Vec2& v) const { return { x - v.x,y - v.y }; }
double length() const {
return hypot(x, y);
}
double distanceFrom(const Vec2& v) const {
return (v - *this).length();
}
double angle() const { return atan2(y, x); }
constexpr friend Vec2 operator*(double s, const Vec2& v) {
return { s * v.x,s * v.y };
}
friend std::istream& operator>>(std::istream& is, Vec2& rhs) {
return is >> rhs.x >> rhs.y;
}
};
struct Circle {
Vec2 center;
double r;
Circle() = default;
constexpr Circle(const Vec2& _center, double _r) :center(_center), r(_r) {}
friend std::istream& operator>>(std::istream& is, Circle& rhs) {
return is >> rhs.center >> rhs.r;
}
};
}
接点の数
円と点の位置関係によって3つに分けられます。
- 点が円の外側: 接点は2つ
- 点が円周上: 接点は1つ
- 点が円の内側: 接点は存在しない
点が円の外側の場合
上図より、2つの接点はそれぞれ$$O+r(\cos(\theta_1+\theta_2),\sin(\theta_1+\theta_2))$$$$O+r(\cos(\theta_1-\theta_2),\sin(\theta_1-\theta_2))$$となるので、$\theta_1$と$\theta_2$が求められれば良いです。
- $\theta_1$: atan2を使うことで求められます。
- $\theta_2$: $\angle OHA=90^\circ$なので、$$\theta_2=\cos^{-1}\frac{r}{d_1}$$となります。
点が円周上の場合
点$A$をそのまま返せば良いです。
点が円の内側の場合
接点は存在しません。今回はstd::nulloptを返します。
コード
コードをクリックで展開
namespace geometry {
constexpr double EPS = 1e-12;
struct Vec2 {
double x, y;
Vec2() = default;
constexpr Vec2(double _x, double _y) :x(_x), y(_y) {}
explicit Vec2(double arg) : x(cos(arg)), y(sin(arg)) {}
bool operator>(const Vec2& v) const {
if (!(fabs(x - v.x) <= EPS)) return x > v.x;
return y > v.y;
}
constexpr Vec2 operator+(const Vec2& v) const { return { x + v.x,y + v.y }; }
constexpr Vec2 operator-(const Vec2& v) const { return { x - v.x,y - v.y }; }
double length() const {
return hypot(x, y);
}
double distanceFrom(const Vec2& v) const {
return (v - *this).length();
}
double angle() const { return atan2(y, x); }
constexpr friend Vec2 operator*(double s, const Vec2& v) {
return { s * v.x,s * v.y };
}
friend std::istream& operator>>(std::istream& is, Vec2& rhs) {
return is >> rhs.x >> rhs.y;
}
};
struct Circle {
Vec2 center;
double r;
Circle() = default;
constexpr Circle(const Vec2& _center, double _r) :center(_center), r(_r) {}
std::optional<std::vector<Vec2>> tangentPointFrom(const Vec2& v) const {
const double
d1 = center.distanceFrom(v),
d2 = d1 * d1 - r * r;
if (d2 + EPS < 0) return std::nullopt;
if (fabs(d2) <= EPS) return std::vector<Vec2>{v};
const double
theta1 = (v - center).angle(),
theta2 = acos(r / d1);
std::vector<Vec2>res;
res.emplace_back(center + r * Vec2(theta1 + theta2));
res.emplace_back(center + r * Vec2(theta1 - theta2));
if (res[0] > res[1]) std::swap(res[0], res[1]);
return res;
}
friend std::istream& operator>>(std::istream& is, Circle& rhs) {
return is >> rhs.center >> rhs.r;
}
};
}