直線セグメントABに最も近い点(点Pから)を取得する単純なC#関数が欲しいのですが。抽象関数は次のようになります。 SO=を検索しましたが、(私が)使用できるソリューションは見つかりませんでした。
public Point getClosestPointFromLine(Point A, Point B, Point P);
以下はRuby Point
オブジェクトにそれぞれx
およびy
フィールドがあると仮定して、疑似コードに偽装したものです。
def GetClosestPoint(A, B, P)
a_to_p = [P.x - A.x, P.y - A.y] # Storing vector A->P
a_to_b = [B.x - A.x, B.y - A.y] # Storing vector A->B
atb2 = a_to_b[0]**2 + a_to_b[1]**2 # **2 means "squared"
# Basically finding the squared magnitude
# of a_to_b
atp_dot_atb = a_to_p[0]*a_to_b[0] + a_to_p[1]*a_to_b[1]
# The dot product of a_to_p and a_to_b
t = atp_dot_atb / atb2 # The normalized "distance" from a to
# your closest point
return Point.new( :x => A.x + a_to_b[0]*t,
:y => A.y + a_to_b[1]*t )
# Add the distance to A, moving
# towards B
end
または:
Wikipediaの Line-Line Intersection から。まず、Qを見つけます。Qは、Pから「右方向」に一歩進んだときに必要な2番目のポイントです。これにより、4つのポイントが得られます。
def getClosestPointFromLine(A, B, P)
a_to_b = [B.x - A.x, B.y - A.y] # Finding the vector from A to B
This step can be combined with the next
perpendicular = [ -a_to_b[1], a_to_b[0] ]
# The vector perpendicular to a_to_b;
This step can also be combined with the next
Q = Point.new(:x => P.x + perpendicular[0], :y => P.y + perpendicular[1])
# Finding Q, the point "in the right direction"
# If you want a mess, you can also combine this
# with the next step.
return Point.new (:x => ((A.x*B.y - A.y*B.x)*(P.x - Q.x) - (A.x-B.x)*(P.x*Q.y - P.y*Q.x)) / ((A.x - B.x)*(P.y-Q.y) - (A.y - B.y)*(P.y-Q.y)),
:y => ((A.x*B.y - A.y*B.x)*(P.y - Q.y) - (A.y-B.y)*(P.x*Q.y - P.y*Q.x)) / ((A.x - B.x)*(P.y-Q.y) - (A.y - B.y)*(P.y-Q.y)) )
end
パフォーマンス上の理由から、キャッシュ、ステップのスキップなどが可能です。
上記に基づくC#XNA関数に興味がある場合:
public static Vector2 GetClosestPointOnLineSegment(Vector2 A, Vector2 B, Vector2 P)
{
Vector2 AP = P - A; //Vector from A to P
Vector2 AB = B - A; //Vector from A to B
float magnitudeAB = AB.LengthSquared(); //Magnitude of AB vector (it's length squared)
float ABAPproduct = Vector2.Dot(AP, AB); //The DOT product of a_to_p and a_to_b
float distance = ABAPproduct / magnitudeAB; //The normalized "distance" from a to your closest point
if (distance < 0) //Check if P projection is over vectorAB
{
return A;
}
else if (distance > 1) {
return B;
}
else
{
return A + AB * distance;
}
}
ポイント(X
)は、ポイントA
とB
の線形結合になります:
X = k A + (1-k) B
X
が実際にラインセグメント上にあるためには、パラメーターk
は0以上1以下でなければなりません。次のようにkを計算できます。
k_raw = (P-B).(A-B) / (A-B).(A-B)
(ピリオドは内積を表す)
次に、ポイントが実際にラインセグメント上にあることを確認します。
if k_raw < 0:
k= 0
Elif k_raw > 1:
k= 1
else:
k= k_raw
Justin L.からの回答はほぼ問題ありませんが、正規化された距離が0より小さいか、またはABベクトルの大きさより大きいかどうかはチェックしません。次に、Pベクトルプロイジェクションが範囲外(ラインセグメントABから)になると、うまく機能しません。修正された疑似コードは次のとおりです。
function GetClosestPoint(A, B, P)
{
vectorAP = (p.x - a.x, p.y - a.y) //Vector from A to P
vectorAB = (b.x - a.x, b.y - a.y) //Vector from A to B
magnitudeAB = vectorAB[0]^2 + vectorAB[1]^2
//Magnitude of AB vector (it's length)
ABAPproduct = vectorAB[0]*vectorAP[0] + vectorAB[1]*vectorAP[1]
//The product of a_to_p and a_to_b
distance = ABAPproduct / magnitudeAB
//The normalized "distance" from a to your closest point
if ( distance < 0) //Check if P projection is over vectorAB
{
returnPoint.x = a.x
returnPoint.y = a.y
}
else if (distance > magnitudeAB)
{
returnPoint.x = b.x
returnPoint.y = b.y
}
else
{
returnPoint.x = a.x + vectorAB[0]*distance
returnPoint.y = a.y + vectorAB[1]*distance
}
}
これはずっと前に書いたものですが、他の人が言ったことと大差ありませんが、PointF
という名前のクラス(または構造体)にX
とY
のメンバーがある場合、それはC#のコピー/貼り付けソリューションです。
private static PointF ClosestPointToSegment(PointF P, PointF A, PointF B)
{
PointF a_to_p = new PointF(), a_to_b = new PointF();
a_to_p.X = P.X - A.X;
a_to_p.Y = P.Y - A.Y; // # Storing vector A->P
a_to_b.X = B.X - A.X;
a_to_b.Y = B.Y - A.Y; // # Storing vector A->B
float atb2 = a_to_b.X * a_to_b.X + a_to_b.Y * a_to_b.Y;
float atp_dot_atb = a_to_p.X * a_to_b.X + a_to_p.Y * a_to_b.Y; // The dot product of a_to_p and a_to_b
float t = atp_dot_atb / atb2; // # The normalized "distance" from a to the closest point
return new PointF(A.X + a_to_b.X * t, A.Y + a_to_b.Y * t);
}
更新:コメントを見ると、受け入れられた回答で言及されているのと同じソースコードからC#に適合させたようです。
Y差をx差で除算して、ABの勾配a1を求めます。次に、垂直線を描きます(勾配a2 = -1/a1の場合、Pの座標をy = a2 * x + b2に入れることにより、オフセット(b2)を解く必要があります)。次に、2つの線(つまり、2つの線形方程式)があり、交差を解決する必要があります。それがあなたの最も近いポイントになります。
計算を正しく行うと、関数を書くのはかなり簡単になります。
少し詳しく説明します。
Original line:
y = a1 * x + b1
a1 = (By - Ay) / (Bx - Ax) <--
b1 = Ay - a1 * Ax <--
Perpendicular line:
y = a2 * x + b2
a2 = -1/a1 <--
b2 = Py - a2 * Px <--
Now you have P which lies on both lines:
y = a1 * x + b1
y = a2 * x + b2
--------------- subtract:
0 = (a1 - a2) * Px + (b1 - b2)
x = - (b1 - b2) / (a1 - a2) <--
y = a1 * x + b1 <--
私がどこかで台無しにしていないことを願っています:) [〜#〜] update [〜#〜]もちろん私はやった。最初に紙の上で物事を解決しないために私に正しく役立ちます。私はすべての反対票に値しましたが、誰かが私を訂正することを期待していました。修正済み(希望)。
矢印が道を示しています。
[〜#〜] update [〜#〜]ああ、コーナーケース。ええ、いくつかの言語は無限をうまく処理しません。私はその解決策が言語を含まないものであったと言いました...
あなたは特別なケースをチェックすることができ、それらは非常に簡単です。 1つ目は、xの差が0の場合です。これは、線が垂直であり、最も近い点が水平の垂線上にあることを意味します。したがって、x = Ax, y = Px
。
2つ目は、yの差が0で、その逆の場合です。したがって、x = Px, y = Ay
この答えは、射影幾何学からのアイデアに基づいています。
外積(Ax、Ay、1)×(Bx、By、1)=(u、v、w)を計算します。結果のベクトルは、AとBを結ぶ線を表しています。これは、式ux + vy + w = 0を持っています。ただし、(u、v、0)をその線に垂直な方向に無限に離れた点として解釈することもできます。別の外積を行うと、ハットポイントとPを結ぶ直線が得られます:(u、v、0)×(Px、Py、1)。そして、その線と線ABを交差させるために、別の外積を行います:((u、v、0)×(Px、Py、1))×(u、v、w)。結果は、この最も近い点の座標を(x/z、y/z)として読み取ることができる同種の座標ベクトル(x、y、z)になります。
すべてをまとめると、次の式が得られます。
コンピュータ代数システムを使用すると、結果の座標は次のようになります。
x = ((Ax - Bx)*Px + (Ay - By)*Py)*(Ax - Bx) + (Ay*Bx - Ax*By)*(Ay - By)
y = -(Ay*Bx - Ax*By)*(Ax - Bx) + ((Ax - Bx)*Px + (Ay - By)*Py)*(Ay - By)
z = (Ax - Bx)^2 + (Ay - By)^2
お気づきのように、多くの定期的な条件があります。これらの名前を(かなり任意の)発明すると、疑似コードで記述された次の最終結果を得ることができます。
dx = A.x - B.x
dy = A.y - B.y
det = A.y*B.x - A.x*B.y
dot = dx*P.x + dy*P.y
x = dot*dx + det*dy
y = dot*dy - det*dx
z = dx*dx + dy*dy
zinv = 1/z
return new Point(x*zinv, y*zinv)
このアプローチの利点:
最も近い点C
は、傾きがABの逆数であり、P
と交差する直線上にあります。これは宿題のように聞こえますが、スポイラーアラートレベルを上げるために、かなり強力なヒントをいくつか示します。
このような行は1つしかありません。
これは2つの線の方程式のシステムです。 x
とy
を解くだけです。
A
とB
の間に線分を描画します。これをL
と呼びます。 L
の方程式はy = mx + b
、ここでm
は、x座標に対するy座標の比率です。式でb
またはA
を使用してB
を解きます。
上記と同じことを行いますが、CP
を使用します。次に、連立線形方程式系を解きます。
Googleの検索では、いくつかの例から選択できます。
トリックを実行する必要がある拡張メソッドは次のとおりです。
public static double DistanceTo(this Point from, Point to)
{
return Math.Sqrt(Math.Pow(from.X - to.X, 2) + Math.Pow(from.Y - to.Y, 2));
}
public static double DistanceTo(this Point point, Point lineStart, Point lineEnd)
{
double tI = ((lineEnd.X - lineStart.X) * (point.X - lineStart.X) + (lineEnd.Y - lineStart.Y) * (point.Y - lineStart.Y)) / Math.Pow(lineStart.DistanceTo(lineEnd), 2);
double dP = ((lineEnd.X - lineStart.X) * (point.Y - lineStart.Y) - (lineEnd.Y - lineStart.Y) * (point.X - lineStart.X)) / lineStart.DistanceTo(lineEnd);
if (tI >= 0d && tI <= 1d)
return Math.Abs(dP);
else
return Math.Min(point.DistanceTo(lineStart), point.DistanceTo(lineEnd));
}
次に呼び出すだけです:
P.DistanceTo(A, B);
線「AB」から点「P」の距離を取得します。これをPointF
に変更するのは簡単です。
次に、最も近いポイントを見つけることは、最小距離を検索することだけの問題です。 LINQ
にはこのためのメソッドがあります。
Java + LibGdxでこれを行う方法を探している場合:
Intersector.nearestSegmentPoint
これは、ポイントから最も近いセグメントのポイントを取得するための正しいアルゴリズムです(テスト済み)(vb.net)
s2 = ClosestPointToSegment(point_x, Point_y, Segment_start_x, Segment_start_y, Segment_end_X, Segment_end_Y)
Public Shared Function DistanceTo(x1 As Double, y1 As Double, x2 As Double, y2 As Double) As Double
Return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2))
End Function
Public Shared Function DistanceTo(point_x As Double, point_y As Double, lineStart_x As Double, lineStart_y As Double, lineEnd_x As Double, lineEnd_y As Double) As Double
Dim tI As Double = ((lineEnd_x - lineStart_x) * (point_x - lineStart_x) + (lineEnd_y - lineStart_y) * (point_y - lineStart_x)) / Math.Pow(DistanceTo(lineStart_x, lineStart_y, lineEnd_x, lineEnd_y), 2)
Dim dP As Double = ((lineEnd_x - lineStart_x) * (point_y - lineStart_y) - (lineEnd_y - lineStart_y) * (point_x - lineStart_x)) / DistanceTo(lineStart_x, lineStart_y, lineEnd_x, lineEnd_y)
If tI >= 0R AndAlso tI <= 1.0R Then
Return Math.Abs(dP)
Else
Return Math.Min(DistanceTo(point_x, point_y, lineStart_x, lineStart_y), DistanceTo(point_x, point_y, lineEnd_x, lineEnd_y))
End If
End Function
Private Shared Function ClosestPointToSegment(P_x As Double, p_y As Double, A_x As Double, a_y As Double, B_x As Double, b_y As Double) As Double()
Dim a_to_p As PointF = New PointF(), a_to_b As PointF = New PointF()
Dim rikthex As Double, rikthey As Double
Dim s1(1) As Double
Dim p1_v1_X As Double, p1_v1_y As Double, distanca1 As Double, distanca2 As Double
a_to_p.X = P_x - A_x
a_to_p.Y = p_y - a_y
a_to_b.X = B_x - A_x
a_to_b.Y = b_y - a_y
Dim atb2 As Single = a_to_b.X * a_to_b.X + a_to_b.Y * a_to_b.Y
Dim atp_dot_atb As Single = a_to_p.X * a_to_b.X + a_to_p.Y * a_to_b.Y
Dim t As Single = atp_dot_atb / atb2
rikthex = A_x + a_to_b.X * t
rikthey = a_y + a_to_b.Y * t
If A_x > B_x Then
If rikthex < A_x And rikthex > B_x Then 'pika duhet ne rregulll
If a_y > b_y Then
If rikthey < a_y And rikthey > b_y Then 'pika duhet ne rregulll
Else
distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
If distanca1 < distanca2 Then
rikthex = A_x
rikthey = a_y
Else
rikthex = B_x
rikthey = b_y
End If
End If
Else
If rikthey > a_y And rikthey < b_y Then 'pika duhet ne rregulll
Else
distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
If distanca1 < distanca2 Then
rikthex = A_x
rikthey = a_y
Else
rikthex = B_x
rikthey = b_y
End If
End If
End If
Else
distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
If distanca1 < distanca2 Then
rikthex = A_x
rikthey = a_y
Else
rikthex = B_x
rikthey = b_y
End If
End If
Else
If rikthex > A_x And rikthex < B_x Then 'pika duhet ne rregulll
If a_y > b_y Then
If rikthey < a_y And rikthey > b_y Then 'pika duhet ne rregulll
Else
distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
If distanca1 < distanca2 Then
rikthex = A_x
rikthey = a_y
Else
rikthex = B_x
rikthey = b_y
End If
End If
Else
If rikthey > a_y And rikthey < b_y Then 'pika duhet ne rregulll
Else
distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
If distanca1 < distanca2 Then
rikthex = A_x
rikthey = a_y
Else
rikthex = B_x
rikthey = b_y
End If
End If
End If
Else
distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
If distanca1 < distanca2 Then
rikthex = A_x
rikthey = a_y
Else
rikthex = B_x
rikthey = b_y
End If
End If
End If
s1(0) = rikthex
s1(1) = rikthey
Return s1
End Function