【问题标题】:Connect two Line Segments连接两个线段
【发布时间】:2010-10-07 04:04:33
【问题描述】:

给定两条二维线段 A 和 B,我如何计算连接 A 和 B 的最短二维线段 C 的长度?

【问题讨论】:

  • 定义连接。你的意思是连接它们的末端,还是找到线上任意点之间的最短线段?
  • @strager,在欧几里得几何中,它们要么是平行的,要么端点更接近,所以你检查向量 A1-B1、A1-B2、A2-B1 和 A2-B2。
  • 2D 还是 3D? 2D 情况几乎是微不足道的,而 3D 情况需要更复杂的算法。
  • @Pax:我没有看到相反的证据。 “在被证明有罪之前是无辜的”发生了什么?
  • 最近点必须是其中一条线段的端点之一。我可以以某种方式使用从该端点到另一段的每个端点的距离来创建一个比率值,我可以使用该值沿另一段进行插值吗?

标签: geometry


【解决方案1】:

This page 有您可能正在寻找的信息。

【讨论】:

  • 3d 的答案适用于 2d,2d 只是 3d 的特例,其中 z 始终 == 0。所以在底部的 sudo 代码中 z(i) == z(j) = = 0
【解决方案2】:

【讨论】:

    【解决方案3】:

    快速提示:如果您想根据点比较距离,则无需计算平方根。

    例如要查看 P-to-Q 的距离是否小于 Q-to-R,只需检查(伪代码):

    square(P.x-Q.x) + square(P.y-Q.y) < square(Q.x-R.x) + square(Q.y-R.y)
    

    【讨论】:

      【解决方案4】:

      This page 有一个很好的简短描述,用于查找两行之间的最短距离,尽管@strager 的链接包含一些代码(在 Fortran 中!)

      【讨论】:

      • 那是 3D,它指的是线,而不是线段。此处不相关。
      • 原题没有指定2D。
      • 好的。道歉。建议任何对此投反对票的人删除他们的反对票。 (我没有。)
      • 没问题 - 作为副作用,我发现了如何查看帖子的编辑历史记录!
      【解决方案5】:

      假设您的两条线段 A 和 B 分别由两个点表示:

      由A1(x,y)、A2(x,y)表示的A线

      由 B1(x,y) B2(x,y) 表示的 B 线

      首先使用此算法检查两条线是否相交。

      如果它们相交,那么两条线之间的距离为零,连接它们的线段就是交点。

      如果他们不相交,使用这个方法:http://paulbourke.net/geometry/pointlineplane/来计算之间的最短距离:

      1. 点 A1 和线 B
      2. A2点和B线
      3. B1点和A线
      4. B2点和A线

      这四个线段中最短的就是你的答案。

      【讨论】:

      • 另外:首先检查 A 和 B 是否相互交叉 (A1 + 向量 (A1->A2) * a = B1 + 向量 (B1->B2) * b 与 a 和 b 实数! = 0)。第二次检查 A 和 B 是否平行(即向量 (A1->A2) * a = 向量 (B1->B2) 是实数!= 0)。
      • 我不相信这个答案是正确的。如果线穿过线之间最短的距离为零,则按照上述方式进行计算。然而从任何端点到另一条线的最短距离> 0。
      • 你说得对:检查交叉点是必要的。但是,我认为没有必要检查线条是否平行。
      • 我认为需要检查的是线 segments 是否相交,而不是延长线(通常会)。但不清楚如何确定(“这个算法”没有给出?),即使在检查了点到线(-segment)确定的链接之后也是如此。
      • @BodoThiesen 您的交叉点检查需要检查 a 和 b 是否在 0 和 1 之间,否则交叉点不在线段的末端。
      【解决方案6】:

      Afterlife 说,“首先使用这个算法检查两条线是否相交”,但他没有说明他的意思是什么算法。显然,重要的是 segments 线的交点,而不是延长线;任何非平行线段(不包括未定义线的重合端点)都将相交,但线段之间的距离不一定为零。所以我认为他的意思是“线段”而不是“线”。

      Afterlife 提供的链接是一种非常优雅的方法,可以找到一条线(或线段或射线)上与另一个任意点的最近点。这适用于查找从每个端点到另一个线段的距离(将计算的参数 u 限制为对于线段或射线不小于 0,对于线段不大于 1),但它不处理一条线段上的内部点比任一端点更近的可能性,因为它们实际上相交,因此需要对相交进行额外检查。

      关于确定线段交点的算法,一种方法是找到延长线的交点(如果平行则完成),然后确定该点是否在两条线段内,例如通过从交点 T 到两个端点的向量的点积:

      ((Tx - A1x) * (Tx - A2x)) + ((Ty - A1y) * (Ty - A2y))

      如果这是负数(或“零”),则 T 在 A1 和 A2 之间(或在一个端点处)。类似地检查其他线段。如果任一大于“零”,则线段不相交。当然,这取决于首先找到延长线的交点,这可能需要将每条线表示为一个方程并通过高斯归约等方法求解系统。

      但可能有一种更直接的方法,无需求解交点,即取向量 (B1-A1) 和 (B2-A1) 的叉积以及向量 (B1- A2) 和 (B2-A2)。如果这些叉积在同一方向,则 A1 和 A2 在 B 线的同一侧;如果它们的方向相反,则它们位于 B 行的相反两侧(如果为 0,则一个或两个都在 B 行on)。同样检查向量 (A1-B1) 和 (A2-B1) 以及 (A1-B2) 和 (A2-B2) 的叉积。如果这些叉积中的任何一个为“零”,或者如果 两条 线段的端点落在另一条线的相对两侧,则线段本身必须相交,否则它们不会相交。

      当然,您需要一个方便的公式来计算两个向量坐标的叉积。或者,如果您可以确定角度(正或负),则不需要实际的叉积,因为它是我们实际关心的向量之间的角度方向(或角度的正弦,真的) .但我认为叉积(二维)的公式很简单:

      交叉(V1,V2) = (V1x * V2y) - (V2x * V1y)

      这是 3-D 叉积向量的 z 轴分量(其中 x 和 y 分量必须为零,因为初始向量在平面 z=0 中),因此您可以简单地查看符号(或“零”)。

      因此,您可以使用这两种方法之一来检查 Afterlife 描述的算法中的线段相交(参考链接)。

      【讨论】:

        【解决方案7】:

        使用上述Afterlife'sRob Parker's 算法的一般思想,这里是一组方法的C++ 版本,用于获取2 个任意2D 段之间的最小距离。这将处理重叠段、平行段、相交和非相交段。此外,它使用各种 epsilon 值来防止浮点不精确。最后,除了返回最小距离外,该算法还将为您提供最接近线段 2 的线段 1 上的点(如果线段相交,这也是交点)。如果需要,也可以返回最接近 [p1,p2] 的 [p3,p4] 上的点,但我将把它作为练习留给读者:)

        // minimum distance (squared) between vertices, i.e. minimum segment length (squared)
        #define EPSILON_MIN_VERTEX_DISTANCE_SQUARED 0.00000001
        
        // An arbitrary tiny epsilon.  If you use float instead of double, you'll probably want to change this to something like 1E-7f
        #define EPSILON_TINY 1.0E-14
        
        // Arbitrary general epsilon.  Useful for places where you need more "slop" than EPSILON_TINY (which is most places).
        // If you use float instead of double, you'll likely want to change this to something like 1.192092896E-04
        #define EPSILON_GENERAL 1.192092896E-07
        
        bool AreValuesEqual(double val1, double val2, double tolerance)
        {
            if (val1 >= (val2 - tolerance) && val1 <= (val2 + tolerance))
            {
                return true;
            }
        
            return false;
        }
        
        
        double PointToPointDistanceSquared(double p1x, double p1y, double p2x, double p2y)
        {
            double dx = p2x - p1x;
            double dy = p2y - p1y;
            return (dx * dx) + (dy * dy);
        }
        
        
        double PointSegmentDistanceSquared( double px, double py,
                                            double p1x, double p1y,
                                            double p2x, double p2y,
                                            double& t,
                                            double& qx, double& qy)
        {
            double dx = p2x - p1x;
            double dy = p2y - p1y;
            double dp1x = px - p1x;
            double dp1y = py - p1y;
            const double segLenSquared = (dx * dx) + (dy * dy);
            if (AreValuesEqual(segLenSquared, 0.0, EPSILON_MIN_VERTEX_DISTANCE_SQUARED))
            {
                // segment is a point.
                qx = p1x;
                qy = p1y;
                t = 0.0;
                return ((dp1x * dp1x) + (dp1y * dp1y));
            }
            else
            {
                t = ((dp1x * dx) + (dp1y * dy)) / segLenSquared;
                if (t <= EPSILON_TINY)
                {
                    // intersects at or to the "left" of first segment vertex (p1x, p1y).  If t is approximately 0.0, then
                    // intersection is at p1.  If t is less than that, then there is no intersection (i.e. p is not within
                    // the 'bounds' of the segment)
                    if (t >= -EPSILON_TINY)
                    {
                        // intersects at 1st segment vertex
                        t = 0.0;
                    }
                    // set our 'intersection' point to p1.
                    qx = p1x;
                    qy = p1y;
                    // Note: If you wanted the ACTUAL intersection point of where the projected lines would intersect if
                    // we were doing PointLineDistanceSquared, then qx would be (p1x + (t * dx)) and qy would be (p1y + (t * dy)).
                }
                else if (t >= (1.0 - EPSILON_TINY))
                {
                    // intersects at or to the "right" of second segment vertex (p2x, p2y).  If t is approximately 1.0, then
                    // intersection is at p2.  If t is greater than that, then there is no intersection (i.e. p is not within
                    // the 'bounds' of the segment)
                    if (t <= (1.0 + EPSILON_TINY))
                    {
                        // intersects at 2nd segment vertex
                        t = 1.0;
                    }
                    qx = p2x;
                    qy = p2y;
                    // Note: If you wanted the ACTUAL intersection point of where the projected lines would intersect if
                    // we were doing PointLineDistanceSquared, then qx would be (p1x + (t * dx)) and qy would be (p1y + (t * dy)).
                }
                else
                {
                    // The projection of the point to the point on the segment that is perpendicular succeeded and the point
                    // is 'within' the bounds of the segment.  Set the intersection point as that projected point.
                    qx = ((1.0 - t) * p1x) + (t * p2x);
                    qy = ((1.0 - t) * p1y) + (t * p2y);
                    // for debugging
                    //ASSERT(AreValuesEqual(qx, p1x + (t * dx), EPSILON_TINY));
                    //ASSERT(AreValuesEqual(qy, p1y + (t * dy), EPSILON_TINY));
                }
                // return the squared distance from p to the intersection point.
                double dpqx = px - qx;
                double dpqy = py - qy;
                return ((dpqx * dpqx) + (dpqy * dpqy));
            }
        }
        
        
        double SegmentSegmentDistanceSquared(   double p1x, double p1y,
                                                double p2x, double p2y,
                                                double p3x, double p3y,
                                                double p4x, double p4y,
                                                double& qx, double& qy)
        {
            // check to make sure both segments are long enough (i.e. verts are farther apart than minimum allowed vert distance).
            // If 1 or both segments are shorter than this min length, treat them as a single point.
            double segLen12Squared = PointToPointDistanceSquared(p1x, p1y, p2x, p2y);
            double segLen34Squared = PointToPointDistanceSquared(p3x, p3y, p4x, p4y);
            double t = 0.0;
            double minDist2 = 1E+38;
            if (segLen12Squared <= EPSILON_MIN_VERTEX_DISTANCE_SQUARED)
            {
                qx = p1x;
                qy = p1y;
                if (segLen34Squared <= EPSILON_MIN_VERTEX_DISTANCE_SQUARED)
                {
                    // point to point
                    minDist2 = PointToPointDistanceSquared(p1x, p1y, p3x, p3y);
                }
                else
                {
                    // point - seg
                    minDist2 = PointSegmentDistanceSquared(p1x, p1y, p3x, p3y, p4x, p4y);
                }
                return minDist2;
            }
            else if (segLen34Squared <= EPSILON_MIN_VERTEX_DISTANCE_SQUARED)
            {
                // seg - point
                minDist2 = PointSegmentDistanceSquared(p3x, p3y, p1x, p1y, p2x, p2y, t, qx, qy);
                return minDist2;
            }
        
            // if you have a point class and/or methods to do cross products, you can use those here.
            // This is what we're actually doing:
            // Point2D delta43(p4x - p3x, p4y - p3y);    // dir of p3 -> p4
            // Point2D delta12(p1x - p2x, p1y - p2y);    // dir of p2 -> p1
            // double d = delta12.Cross2D(delta43);
            double d = ((p4y - p3y) * (p1x - p2x)) - ((p1y - p2y) * (p4x - p3x));
            bool bParallel = AreValuesEqual(d, 0.0, EPSILON_GENERAL);
        
            if (!bParallel)
            {
                // segments are not parallel.  Check for intersection.
                // Point2D delta42(p4x - p2x, p4y - p2y);    // dir of p2 -> p4
                // t = 1.0 - (delta42.Cross2D(delta43) / d);
                t = 1.0 - ((((p4y - p3y) * (p4x - p2x)) - ((p4y - p2y) * (p4x - p3x))) / d);
                double seg12TEps = sqrt(EPSILON_MIN_VERTEX_DISTANCE_SQUARED / segLen12Squared);
                if (t >= -seg12TEps && t <= (1.0 + seg12TEps))
                {
                    // inside [p1,p2].   Segments may intersect.
                    // double s = 1.0 - (delta12.Cross2D(delta42) / d);
                    double s = 1.0 - ((((p4y - p2y) * (p1x - p2x)) - ((p1y - p2y) * (p4x - p2x))) / d);
                    double seg34TEps = sqrt(EPSILON_MIN_VERTEX_DISTANCE_SQUARED / segLen34Squared);
                    if (s >= -seg34TEps && s <= (1.0 + seg34TEps))
                    {
                        // segments intersect!
                        minDist2 = 0.0;
                        qx = ((1.0 - t) * p1x) + (t * p2x);
                        qy = ((1.0 - t) * p1y) + (t * p2y);
                        // for debugging
                        //double qsx = ((1.0 - s) * p3x) + (s * p4x);
                        //double qsy = ((1.0 - s) * p3y) + (s * p4y);
                        //ASSERT(AreValuesEqual(qx, qsx, EPSILON_MIN_VERTEX_DISTANCE_SQUARED));
                        //ASSERT(AreValuesEqual(qy, qsy, EPSILON_MIN_VERTEX_DISTANCE_SQUARED));
                        return minDist2;
                    }
                }
            }
        
            // Segments do not intersect.   Find closest point and return dist.   No other way at this
            // point except to just brute-force check each segment end-point vs opposite segment.  The
            // minimum distance of those 4 tests is the closest point.
            double tmpQx, tmpQy, tmpD2;
            minDist2 = PointSegmentDistanceSquared(p3x, p3y, p1x, p1y, p2x, p2y, t, qx, qy);
            tmpD2 = PointSegmentDistanceSquared(p4x, p4y, p1x, p1y, p2x, p2y, t, tmpQx, tmpQy);
            if (tmpD2 < minDist2)
            {
                qx = tmpQx;
                qy = tmpQy;
                minDist2 = tmpD2;
            }
            tmpD2 = PointSegmentDistanceSquared(p1x, p1y, p3x, p3y, p4x, p4y, t, tmpQx, tmpQy);
            if (tmpD2 < minDist2)
            {
                qx = p1x;
                qy = p1y;
                minDist2 = tmpD2;
            }
            tmpD2 = PointSegmentDistanceSquared(p2x, p2y, p3x, p3y, p4x, p4y, t, tmpQx, tmpQy);
            if (tmpD2 < minDist2)
            {
                qx = p2x;
                qy = p2y;
                minDist2 = tmpD2;
            }
        
            return minDist2;
        }
        

        【讨论】:

        • 这不会编译,minDist2 = PointSegmentDistanceSquared(p1x, p1y, p3x, p3y, p4x, p4y); 行需要额外的参数。
        • 啊,很抱歉 - 我显然忘记包含不需要 t、qx 和 qy 的方法版本。由于这些只是返回参数,您可以在那里添加虚拟值并忽略返回的结果。
        猜你喜欢
        • 2010-12-31
        • 2021-08-06
        • 1970-01-01
        • 2018-05-02
        • 2015-12-08
        • 2016-09-16
        • 1970-01-01
        • 2014-06-22
        • 2018-08-07
        相关资源
        最近更新 更多