给定两个2D线段A和B,如何计算连接A和B的最短2D线段C的长度?
答案 0 :(得分:7)
考虑您的两个线段A和B,每个线段由两个点表示:
由A1(x,y),A2(x,y)
表示的线A.由B1(x,y)B2(x,y)
表示的线B.首先使用此算法检查两条线是否相交。
如果它们相交,则两条线之间的距离为零,连接它们的线段为交点。
如果它们不相交,请使用以下方法:http://paulbourke.net/geometry/pointlineplane/来计算之间的最短距离:
这四个线段中最短的是你的答案。
答案 1 :(得分:4)
This page包含您可能正在寻找的信息。
答案 2 :(得分:3)
使用上面Afterlife's和Rob Parker's算法的一般概念,这里是一组C ++版本的一组方法,用于获得2个任意2D段之间的最小距离。这将处理重叠段,平行段,交叉段和非交叉段。此外,它使用各种epsilon值来防止浮点不精确。最后,除了返回最小距离之外,该算法还将为您提供距离段2最近的段1上的点(如果段相交,这也是交点)。如果需要的话,也可以将[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;
}
答案 3 :(得分:2)
答案 4 :(得分:2)
快速提示:如果您想根据点数比较距离,则无需进行平方根。
E.g。看看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)
答案 5 :(得分:2)
来世说,“首先检查两条线是否相交使用这种算法”,但他没有说明他的意思是什么算法。显然,它是行段的交集,而不是重要的扩展行;任何非平行线段(不包括未定义线的重合端点)将相交,但线段之间的距离不一定为零。所以我认为他的意思是“线段”而不是“线”。
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行上)。类似地,检查载体(A1-B1)和(A2-B1)以及(A1-B2)和(A2-B2)的叉积。如果这些交叉产品中的任何一个为“零”,或者两个线段的端点落在另一条线的相对两侧,则线段本身必须相交,否则它们不相交。
当然,您需要一个方便的公式来计算其坐标中两个向量的交叉积。或者,如果你可以确定角度(正面或负面),你就不需要实际的交叉积,因为它是我们实际关心的矢量之间的角度方向(或角度的正弦,实际上) 。但我认为交叉产品的公式(二维)只是:
交叉(V1,V2)=(V1x * V2y) - (V2x * V1y)
这是三维交叉积矢量的z轴分量(其中x和y分量必须为零,因为初始矢量在z = 0的平面内),所以你可以简单地看一下标志(或“零”)。
因此,您可以使用这两种方法中的一种来检查Afterlife描述的算法(参考链接)中的线段交叉。
答案 6 :(得分:0)
This page有一个很好的简短描述,用于找到两条线之间的最短距离,尽管@ strager的链接包含一些代码(在Fortran中!)