0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

簡単に点から円への接点を求める

Last updated at Posted at 2020-10-01

方程式を解いて求めるのでは面倒なので、幾何的に求めます。

準備

点を表す構造体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つ
  • 点が円の内側: 接点は存在しない

キャプチャ.png

点が円の外側の場合

ダウンロード.png
上図より、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;
		}
	};
}

使用例: AOJ 円の接線 提出

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?