点(x, y)と線分(点A(x1, y1)と点B(x2, y2)を結ぶ線分)の最短距離を求めます。いろいろなやり方があるかもしれませんが、鳩は以下のように考えました。
平行移動と回転移動で最短距離を求める
全体を平行移動させて点Aが原点にくるようにする
原点とX軸の正の部分と移動させた点Bで構成される角の角度を求める
全体をこの角度だけ逆方向に回転させる(これによって点BがX軸上にくる)
直線ABと点の距離であれば回転させた点のY座標の絶対値が距離となる
直線ABではなく線分ABとの距離であれば回転させた点のX座標によって切り分けが必要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public class Program { static double Solve(long x, long y, long x1, long y1, long x2, long y2) { // 並行移動させる x -= x1; y -= y1; x2 -= x1; y2 -= y1; x1 = 0; y1 = 0; // 回転させる角度を求める double angle = Math.Atan2(y2, x2); angle *= -1; // 逆方向に回転させるので double rotatedX2 = x2 * Math.Cos(angle) - y2 * Math.Sin(angle); double rotatedY2 = x2 * Math.Sin(angle) + y2 * Math.Cos(angle); // rotatedY2 は 0になっているはず double rotatedX = x * Math.Cos(angle) - y * Math.Sin(angle); double rotatedY = x * Math.Sin(angle) + y * Math.Cos(angle); if (rotatedX < 0) return Math.Sqrt(Math.Pow(rotatedX, 2) + Math.Pow(rotatedY, 2)); else if (rotatedX2 < rotatedX) return Math.Sqrt(Math.Pow(rotatedX - rotatedX2, 2) + Math.Pow(rotatedY, 2)); else return Math.Abs(rotatedY); } } |
内積を使う方法
他に線分と点の内積を使う方法があります。点Bから点AをベクトルBA、点Bから点CをベクトルBCとしたとき、ベクトルBAとベクトルBCの内積は xa × xc + ya × yc になります。またこの内積は(ベクトルBAの大きさ)×(ベクトルBCの大きさ)×cos∠ABCとなります。内積が負数になる場合は∠ABCが直角より大きくなっているので点Aから線分BCへ垂線を下ろすことができません。
内積が正になる場合

内積が負になる場合(この場合は明らかに点から下ろした垂線は線分と交わらない)

ということはベクトルBAとベクトルBCの内積が負数の場合、点Aからもっとも近いのは点Bということになります。またベクトルCAとベクトルCBの内積が負数の場合は、点Aからもっとも近いのは点Cということになります。それ以外の場合は点Aから線分BCへ垂線を下ろしたときの交点と点Aの距離が求める距離ということになります。
点Aから線分BCへ垂線を下ろした場合、直角三角形ができます。

三角比より垂線の長さは(BAの長さ)×sin∠ABCです。またベクトルBAとベクトルBCの内積は(BAの長さ)×(BCの長さ)×cos∠ABCであり、xa × xc + ya × ycです。双方を二乗するとsinとcosの2乗ができて、sin2乗 + cos2乗 = 1 なので三角関数を消すことができます。垂線の長さは(BAの長さ)と(BCの長さ)と(xa × xc + ya × yc)で表わすことができます。(BAの長さの2乗)- (内積の2乗)/(BCの長さの2乗)の平方根が求める距離となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
static double Solve2(long x, long y, long x1, long y1, long x2, long y2) { // 並行移動させて点B(x1, y1)を原点にする x -= x1; y -= y1; x2 -= x1; y2 -= y1; x1 = 0; y1 = 0; double dot = x * x2 + y * y2; if (dot < 0) return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2)); double dot2 = (x - x2) * (0 - x2) + (y - y2) * (0 - y2); if (dot2 < 0) return Math.Sqrt(Math.Pow(x - x2, 2) + Math.Pow(y - y2, 2)); double a2 = Math.Pow(x, 2) + Math.Pow(y, 2); double b2 = Math.Pow(x2, 2) + Math.Pow(y2, 2); return Math.Sqrt(a2 - Math.Pow(dot, 2) / b2); } |
鳩でも分かるC#管理人からのお願い
できる仕事であれば請け負います。鳩でもわかるC#管理人はクラウドワークスに在宅ワーカーとして登録しています。お仕事の依頼もお待ちしております。
⇒ 仕事を依頼する
コメントについて
コメントで英語などの外国語でコメントをされる方がいますが、管理人は日本語以外はわからないので基本的に内容が理解できず、承認することもありません。それからへんな薬を売っているサイトやリンク先のサイトが存在しないというスパムコメントも多々あります。
Some people make comments in foreign languages such as English, but since the manager does not understand anything other than Japanese, he basically cannot understand the content and does not approve it. Please use Japanese when making comments.
そんななか日本語のコメントもいただけるようになりました。「○○という変数はどこで宣言されているのか?」「××というメソッドはどこにあるのか」「例外が発生する」「いっそのことソース丸ごとくれ」という質問ですが、管理人としては嬉しく思います。「自分が書いた記事は読まれているんだな」と。疑問点には可能な限り答えます。記事に問題があれば修正いたします。
そのうえでお願いがあります。「匿名」という味も素っ気もない名前ではなく、捨てハンでいいのでなにかハンドルネームをつくってほしいと思います。
管理人のモチベーションアップのために
よろしければご支援お願いします。
⇒ 管理人の物乞いリスト