Соедините два отрезка линии - PullRequest
8 голосов
/ 12 февраля 2009

С учетом двух 2D-отрезков линии A и B, как рассчитать длину самого короткого 2D-отрезка C, который соединяет A и B?

Ответы [ 7 ]

7 голосов
/ 12 февраля 2009

Считайте, что ваши два отрезка A и B представлены двумя точками каждый:

строка A, представленная A1 (x, y), A2 (x, y)

Линия B представлена ​​B1 (x, y) B2 (x, y)

Сначала проверьте, пересекаются ли две линии, используя этот алгоритм.

Если они пересекаются , то расстояние между двумя линиями равно нулю, а соединяющий их отрезок линии является точкой пересечения.

Если они не пересекаются , используйте этот метод: http://paulbourke.net/geometry/pointlineplane/, чтобы вычислить кратчайшее расстояние между:

  1. точка А1 и линия B
  2. Точка A2 и линия B
  3. Точка B1 и линия A
  4. Точка B2 и линия A

Самый короткий из этих четырех отрезков - ваш ответ.

4 голосов
/ 12 февраля 2009

Эта страница содержит информацию, которую вы, возможно, ищете.

3 голосов
/ 11 июля 2012

Используя общую идею алгоритмов Afterlife и Роба Паркера , приведенных выше, приведена версия набора методов на C ++ для получения минимального расстояния между 2 произвольными 2D-сегментами. Это будет обрабатывать перекрывающиеся сегменты, параллельные сегменты, пересекающиеся и непересекающиеся сегменты. Кроме того, он использует различные значения epsilon для защиты от неточностей с плавающей запятой. Наконец, в дополнение к возвращению минимального расстояния, этот алгоритм даст вам точку на сегменте 1, ближайшую к сегменту 2 (которая также является точкой пересечения, если сегменты пересекаются). Было бы довольно тривиально также вернуть точку на [p3, p4], ближайшую к [p1, p2], если это необходимо, но я оставлю это в качестве упражнения для читателя:)

// 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;
}
2 голосов
/ 16 февраля 2009

Загробная жизнь сказала: «Сначала проверьте, пересекаются ли две линии, используя этот алгоритм», но он не указал, какой алгоритм он имел в виду. Очевидно, что имеет значение пересечение линии сегментов , а не протяженных линий; любые непараллельные отрезки (за исключением совпадающих конечных точек, которые не определяют линию) будут пересекаться, но расстояние между отрезками не обязательно будет равно нулю. Поэтому я предполагаю, что он имел в виду «отрезки», а не «линии».

Ссылка, предоставленная Afterlife, представляет собой очень элегантный подход к нахождению ближайшей точки на линии (или отрезке, или луче) к другой произвольной точке. Это работает для определения расстояния от каждой конечной точки до другого сегмента линии (ограничение вычисляемого параметра u не менее 0 для сегмента линии или луча и не более 1 для сегмента линии), но это не так обрабатывать возможность того, что внутренняя точка на одном отрезке линии ближе, чем любая конечная точка, потому что они фактически пересекаются, поэтому требуется дополнительная проверка на пересечение.

Что касается алгоритма определения пересечения отрезка линии, один из подходов состоит в том, чтобы найти пересечение протяженных линий (если параллельно, то вы сделали), а затем определить, находится ли эта точка в обоих отрезках линии, например взяв скалярное произведение векторов из точки пересечения T в две конечные точки:

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

Если это отрицательное значение (или «ноль»), то T находится между A1 и A2 (или в одной конечной точке). Проверьте аналогично для другого отрезка. Если любой из них больше нуля, то отрезки не пересекаются. Конечно, это зависит от того, чтобы сначала найти пересечение расширенных линий, что может потребовать выражения каждой линии в виде уравнения и решения системы путем гауссовой редукции (и т. Д.).

Но может быть более прямой путь без необходимости определения точки пересечения, если взять перекрестное произведение векторов (B1-A1) и (B2-A1) и перекрестное произведение векторов (B1- А2) и (В2-А2). Если эти перекрестные продукты находятся в одном направлении, то A1 и A2 находятся на одной стороне линии B; если они находятся в противоположных направлениях, то они находятся на противоположных сторонах линии B (и если 0, то один или оба * на линии B). Аналогичным образом проверьте перекрестные произведения векторов (A1-B1) и (A2-B1) и (A1-B2) и (A2-B2). Если какой-либо из этих перекрестных произведений равен «нулю», или если конечные точки обоих отрезков находятся на противоположных сторонах другой линии, то сами отрезки должны пересекаться, иначе они не пересекаются.

Конечно, вам нужна удобная формула для вычисления перекрестного произведения двух векторов из их координат. Или, если бы вы могли определить углы (положительные или отрицательные), вам не понадобился бы фактический перекрестный продукт, так как это направление углов между векторами, о которых мы действительно заботимся (или синус угла, на самом деле) , Но я думаю, что формула для перекрестного продукта (в 2-D) просто:

Крест (V1, V2) = (V1x * V2y) - (V2x * V1y)

Это компонент оси z трехмерного вектора кросс-произведения (где компоненты x и y должны быть равны нулю, поскольку начальные векторы находятся в плоскости z = 0), поэтому вы можете просто посмотреть на знак (или «ноль»).

Таким образом, вы можете использовать один из этих двух методов для проверки пересечения отрезка линии в алгоритме, который описывает Afterlife (ссылаясь на ссылку).

2 голосов
/ 12 февраля 2009

Быстрый совет: если вы хотите сравнить расстояния на основе точек, нет необходимости делать квадратные корни.

например. чтобы увидеть, если 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)
2 голосов
/ 12 февраля 2009
0 голосов
/ 12 февраля 2009

На этой странице есть хорошее краткое описание для поиска кратчайшего расстояния между двумя строками, хотя ссылка @ strager содержит некоторый код (на Фортране!)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...