得到最近的一条线

VOX*_*VOX 32 c# math geometry

我想有一个直接的C#函数来获得最近点(从点P)到线段AB.抽象函数可能如下所示.我搜索了SO,但没有找到可用的(由我)解决方案.

public Point getClosestPointFromLine(Point A, Point B, Point P);
Run Code Online (Sandbox Code Playgroud)

Jus*_* L. 37

这里的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
Run Code Online (Sandbox Code Playgroud)

或者:

来自维基百科的Line-Line Intersection.首先,找到Q,这是从"正确的方向"P开始迈出的第二个要点.这给了我们四点.

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
Run Code Online (Sandbox Code Playgroud)

出于性能原因,可以使用缓存,跳过步骤等.

  • 注意问题是要求线段,因此t的参数值应该被限制为[0-1]在段上. (7认同)
  • 即使我标记了C#并且您在ruby中回答,您的代码也能完美运行.朋友,谢谢. (3认同)
  • @VOX很高兴能够帮助:)我通常使用ruby,因为它很容易被大多数人阅读和理解. (2认同)
  • SavagePanda是绝对正确的.我一开始没有看到他/她的评论,花了很多时间想知道为什么我的结果不是有效点.必须将T钳位在0和1之间,以便从功能中正确输出. (2认同)

小智 31

如果有人对基于上述内容的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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 为了记录,这也适用于3个维度. (7认同)
  • +1,这比选择的答案略好。如果您使用此代码来检查碰撞检测的深度,则所选答案的两端都会出现令人讨厌的推回,而此代码则不会(据我测试)。 (2认同)
  • 我知道这个帖子很老了.但是,这个解决方案帮助我解决了我的问题.如果你能指出我的方向来学习用于答案的源或算法,我将不胜感激.我想进一步了解这方面的问题. (2认同)

com*_*orm 10

你的point(X)将是点的线性组合,A并且B:

X = k A + (1-k) B
Run Code Online (Sandbox Code Playgroud)

为了X实际上在线段上,参数k必须介于0和1之间(包括0和1).您可以按如下方式计算k:

k_raw = (P-B).(A-B)  /  (A-B).(A-B)
Run Code Online (Sandbox Code Playgroud)

(句点表示点积)

然后,要确保该点实际上在线段上:

if k_raw < 0:
    k= 0
elif k_raw > 1:
    k= 1
else:
    k= k_raw
Run Code Online (Sandbox Code Playgroud)


小智 6

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
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 此外,关于幅度AB的名称和评论是误导性的 - 这是一个平方幅度,而不是幅度. (3认同)
  • 我认为这是不正确的,因为距离是标准化距离.你应该将距离与1.0进行比较,而不是(square-of)magnitudeAB. (2认同)

Dar*_*nas 5

我很久以前写过这个,它与其他人所说的没有太大不同,但是如果您有一个PointF以成员命名的类(或结构),它是 C# 中的复制/粘贴解决方案,X并且Y:

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);
}
Run Code Online (Sandbox Code Playgroud)

更新:查看评论,看起来我从接受的答案中提到的相同源代码将其改编为 C#。

  • 实际上,该函数返回穿过 A 和 B 的直线上最近的点 - 添加 `if (t &gt; 1) t = 1; if (t &lt; 0) t = 0;` 以确保该点确实位于线段上 (2认同)