给出两个线段,找到线段之间的距离为 d 的两个点。
这与“两个线段之间的最短距离问题”相似,不同之处在于,我们正在求解线段上由给定距离 d 隔开的两个点。
每个线段都包含两个3维点。
我在Google上找到的数学搜索使我感到恐惧和困惑。我是一名程序员,但是我很难理解解决此类问题背后的证明和分析。
输入:2个线段和一个距离 d
输出:每个线段上两个点之间的距离为 d ,如果没有两个点,则为 None
>答案 0 :(得分:1)
假设线A的2个端点与线B的最近端点的距离不同,我将使用蛮力法。我将选择线A的中心点作为线C的一端,并以“插入距离”的步长将线C的另一端滑到线B上,直到我在距离“ d”的“插入距离”之内。
如果我最接近“ d”的位置太大,则将A上的线C的新端点设置为b / t A中心点的一半,而A线的最接近端点与B线的最接近端点。 。如果我最接近“ d”的位置太小,我会将新端点移至A的中心b / t的一半,并将A线的最远端点移至B线的最接近端点。
重复此过程以进行“插入步骤”迭代,如果在达到最大迭代次数之前未找到可接受的值,则返回给我最接近“ d”距离的端点。然后,您可以确定您的算法是否不允许足够的步骤,或者是否具有过于严格的值来接近“ d”。
如果线A的两个端点到线B的最近端点的距离相同,则使用线B的最远端点。如果这两个端点相同,则在任意方向上进行初始步骤是任意的。 >
此外,不是简单地在行B上滑动第二个端点,而是可以使用相同的算法(沿正确方向)跳到越来越小的中点以节省计算量。
答案 1 :(得分:1)
这是一种迭代算法,需要一些数学知识,但对数学优化没有深入的了解。它很强大,但可能不是特别快。
从高层次上讲,该算法类似于二进制搜索(技术上是三元搜索)。在每一对迭代,我们砍掉了什么仍然是各段的固定比例,同时注意如果存在保持一个有效的解决方案。我们可以从数学上证明,在迭代次数增加的极限内,两个片段都缩小为点,并且如果存在一个点,则这些点是有效的解决方案。实际上,我们会在经过多次迭代(例如,一百次或当片段足够短时)后停止,并在每个片段上返回任意点。
此算法使用两个数学成分。首先是计算一个点和线段之间的距离的公式。第二个问题是,正如我们扫点沿一个段,该距离到另一个减少和然后增加的事实。
如果有时间,我将扩展此说明。
from __future__ import division
def squared_distance_between_points(p, q):
"""Returns the squared distance between the point p and the point q."""
px, py, pz = p
qx, qy, qz = q
return (px - qx)**2 + (py - qy)**2 + (pz - qz)**2
def squared_distance_between_point_and_segment(p, q, r):
"""Returns the squared distance between the point p and the segment qr."""
px, py, pz = p
qx, qy, qz = q
rx, ry, rz = r
# Translate the points to move q to the origin (p' = p - q and r' = r - q).
px -= qx
py -= qy
pz -= qz
rx -= qx
ry -= qy
rz -= qz
# Project p' onto the line 0r'.
# The point on this line closest to p' is cr'.
c = (px * rx + py * ry + pz * rz) / (rx * rx + ry * ry + rz * rz)
# Derive c' by clamping c. The point on the segment 0r' closest to p is c'r'.
c = min(max(c, 0), 1)
# Compute the distance between p' and c'r'.
return squared_distance_between_points((px, py, pz),
(c * rx, c * ry, c * rz))
def trisect(p, q):
"""Returns the point one-third of the way from the point p to the point q."""
px, py, pz = p
qx, qy, qz = q
return ((2 * px + qx) / 3, (2 * py + qy) / 3, (2 * pz + qz) / 3)
def find_points_on_segments_at_distance(p, r, s, u, d, iterations=100):
"""Returns a point q on the segment pr and a point t on the segment su
such that the distance between q and t is approximately d.
If this is not possible (or barely possible), returns None."""
d **= 2
feasible = False
for i in range(2 * int(iterations)):
q1 = trisect(p, r)
d1 = squared_distance_between_point_and_segment(q1, s, u)
q2 = trisect(r, p)
d2 = squared_distance_between_point_and_segment(q2, s, u)
if d <= min(d1, d2):
# Use convexity to cut off one third of the search space.
if d1 <= d2:
r = q2
else:
p = q1
elif d <= max(d1, d2):
# There is certainly a solution in the middle third.
feasible = True
p = q1
r = q2
elif (d <= squared_distance_between_points(p, s)
or d <= squared_distance_between_points(p, u)):
# There is certainly a solution in the first third.
feasible = True
r = q1
elif (d <= squared_distance_between_points(r, s)
or d <= squared_distance_between_points(r, u)):
# There is certainly a solution in the last third.
feasible = True
p = q2
else:
# Definitely infeasible.
return None
# Swap the segments.
p, r, s, u = s, u, p, r
if not feasible:
return None
return p, r
答案 2 :(得分:1)
这是一个非迭代的解决方案。我担心数学可能会激怒您,尽管这里没有什么复杂的事情。
首先,最简单的方法是在整个距离上平方。
其中一条3D线由点P和Q描述,另一条由点R和S描述,那么一种表示问题的方式是,我们希望找到标量m和n,以使点a之间的距离成平方沿第一条线的分数m,沿第二条线的分数n是给定的dsq。
我们必须将m和n限制在0到1(包括0和1)之间,以便我们的点确实在线段上。
如果有m和n,则点为
X = P + m*(Q-P)
Y = R + n*(S-R)
假设我们首先找到Dsq的最小值和最大值。这将告诉我们是否存在解决方案:如果dsq的给定值小于最小值或大于最大值,则没有解决方案,我们可以停下来。
将出现最小值的点的m和n值设为m_min和n_min,将最大值的点的m和n值设为m_max和n_max。如果我们引入一个新的变量z(在[0,1]中),那么我们可以考虑一个m,n个值的“线”:
m(z) = m_min + z*(m_max-m_min)
n(z) = n_min + z*(n_max-n_min)
当z为0时,这些是最小Dsq的值,而对于z = 1,它们是最大Dsq的值。因此,当我们将z从0增加到1时,Dsq的值必须通过我们想要的值!也就是说,我们只需要搜索使Dsq成为所需值的z值即可。
(相对)简单的原因是,X和Y之间的distanceSquared是m和n中的二阶多项式。具体来说,一些繁琐的代数表明,如果Dsq是X和Y之间的距离的平方,
Dsq = a + 2*b*m + 2*c*m + d*m*m + 2*e*m*n + f*m*m
where, in terms of dot products
a = (P-R).(P-R)
b = (P-R).(Q-P)
c =-(P-R).(S-R)
d = (Q-P).(Q-P)
e =-(Q-P).(S-R)
f = (S-R).(S-R)
最大值和最小值必须出现在两个角之一((m,n)=(0,0)或(0,1)或(1,0)或(1,1))或沿着其中一个角边缘(对于某些n为(0,n)或(1,n),对于某些m为(m,0)或(m,1)的边缘)或Dsq的导数的中间点(相对于至m和n)均为0)。 例如,请注意,在边缘说(0,n),Dsq的n二次方,因此很容易找到最大值。
此外,当我们沿着最小值和最大值之间的“线”看时,如果将m(z)和n(z)代入Dsq的公式中,则经过更繁琐的代数运算后,得出z的平方,因此很容易找到可以得到所需Dsq值的z值。
嗯,这篇文章已经很长了,所以这里是实现这些想法的C代码。我为这些点尝试了一百万个随机值,当距离始终在最大值和最小值之间时,它总是找到合适的3d点。在我的(相当普通的)Linux桌面上,这花了几秒钟。
// 3d vectors
static void v3_sub( double* P, double* Q, double* D)
{ D[0] = P[0]-Q[0];
D[1] = P[1]-Q[1];
D[2] = P[2]-Q[2];
}
static double v3_dot( double* P, double* Q)
{ return P[0]*Q[0] + P[1]*Q[1] + P[2]*Q[2];
}
// quadratic in one variable
// return *x so X -> r[0] + 2*r[1]*X + r[2]*X*X has minumum at *x
static int quad_min( const double*r, double* x)
{ if ( r[2] <= 0.0)
{ return 0;
}
*x = -r[1]/r[2];
return 1;
}
// return x so r[0] + 2*r[1]*x + r[2]*x*x == d, and whether 0<=x<=1
static int solve_quad( const double* r, double d, double* x)
{
double ap = r[0] - d;
if ( r[1] > 0.0)
{
double root1 = -(r[1] + sqrt( r[1]*r[1] - ap*r[2])); // < 0
*x = ap/root1;
}
else
{
double root1 = (-r[1] + sqrt( r[1]*r[1] - ap*r[2])); // >= 0
if ( root1 < r[2])
{ *x = root1/r[2];
}
else
{ *x = ap/root1;
}
}
return 0.0 <= *x && *x <= 1.0;
}
// quadratic in 2 variables
typedef struct
{ double a,b,c,d,e,f;
} quad2T;
static double eval_quad2( const quad2T* q, double m, double n)
{
return q->a
+ 2.0*(m*q->b + n*q->c)
+ m*m*q->d + 2.0*m*n*q->e + n*n*q->f
;
}
// eval coeffs of quad2 so that quad2(m,n) = distsq( P+m*(Q-P), R+n*(S-R))
static quad2T set_quad2( double* P, double* Q, double* R, double* S)
{
double D[3]; v3_sub( P, R, D);
double U[3]; v3_sub( Q, P, U);
double V[3]; v3_sub( S, R, V);
quad2T q;
// expansion of lengthSq( D+m*U-n*V)
q.a = v3_dot( D, D);
q.b = v3_dot( D, U);
q.c = -v3_dot( D, V);
q.d = v3_dot( U, U);
q.e = -v3_dot( U, V);
q.f = v3_dot( V, V);
return q;
}
// if gradient of q is 0 in [0,1]x[0,1], return (m,n) where it is zero
// gradient of q is 2*( q->b + m*q->d + n*q->e, q->c + m*q->e + n*q->f)
// so must solve ( q->d q->e ) * (m) = -(q->b)
// ( q->e q->f ) (n) (q->c)
static int dq_zero( const quad2T* q, double* m, double* n)
{
double det = q->d*q->f - q->e*q->e;
if ( det <= 0.0)
{ // note matrix be semi-positive definite, so negative determinant is rounding error
return 0;
}
*m = -( q->f*q->b - q->e*q->c)/det;
*n = -(-q->e*q->b + q->d*q->c)/det;
return 0.0 <= *m && *m <= 1.0
&& 0.0 <= *n && *n <= 1.0
;
}
// fill *n with minimising value, if any in [0,1], of n -> q(m0,n)
static int m_edge_min( const quad2T* q, double m0, double* n)
{
double r[3]; // coeffs of poly in n when m == m0
r[0] = q->a + 2.0*m0*q->b + m0*m0*q->d;
r[1] = q->c + m0*q->e;
r[2] = q->f;
return ( quad_min( r, n)
&& *n > 0.0 && *n < 1.0
);
}
// fill *m with minimising value, if any in [0,1], of m -> q(m,n0)
static int n_edge_min( const quad2T* q, double* m, double n0)
{
double r[3]; // coeffs of poly in m when n == n0
r[0] = q->a + 2.0*n0*q->c + n0*n0*q->f;
r[1] = q->b + n0*q->e;
r[2] = q->d;
return ( quad_min( r, m)
&& *m > 0.0 && *m < 1.0
);
}
// candidates for min, man
typedef struct
{ double m,n; // steps along lines
double d; // distance squared between points
} candT;
static int find_cands( const quad2T* q, candT* c)
{
int nc = 0;
double x, y;
// the corners
c[nc++] = (candT){ 0.0,0.0, eval_quad2( q, 0.0, 0.0)};
c[nc++] = (candT){ 0.0,1.0, eval_quad2( q, 0.0, 1.0)};
c[nc++] = (candT){ 1.0,1.0, eval_quad2( q, 1.0, 1.0)};
c[nc++] = (candT){ 1.0,0.0, eval_quad2( q, 1.0, 0.0)};
// the edges
if ( m_edge_min( q, 0.0, &x))
{ c[nc++] = (candT){ 0.0,x, eval_quad2( q, 0.0, x)};
}
if ( m_edge_min( q, 1.0, &x))
{ c[nc++] = (candT){ 1.0,x, eval_quad2( q, 1.0, x)};
}
if ( n_edge_min( q, &x, 0.0))
{ c[nc++] = (candT){ x, 0.0, eval_quad2( q, x, 0.0)};
}
if ( n_edge_min( q, &x, 1.0))
{ c[nc++] = (candT){ x, 1.0, eval_quad2( q, x, 1.0)};
}
// where the derivatives are 0
if ( dq_zero( q, &x, &y))
{ c[nc++] = (candT){ x, y, eval_quad2( q, x, y)};
}
return nc;
}
// fill in r so that
// r[0] + 2*r[1]*z + r[2]*z*z = q( minm+z*(maxm-minm), minn+x*(maxn-minn))
static void form_quad
( const quad2T* q
, double minm, double maxm
, double minn, double maxn
, double* r
)
{
double a = minm;
double c = maxm-minm;
double b = minn;
double d = maxn-minn;
r[0] = q->a + 2.0*q->b*a + 2.0*q->c*b + q->d*a*a + 2.0*q->e*a*b + q->f*b*b;
r[1] = q->b*c + q->c*d + q->d*a*c + q->e*(a*d+b*c) + q->f*b*d;
r[2] = q->d*c*c + 2.0*q->e*c*d + q->f*d*d;
}
static int find_points
( double* P, double* Q, double* R, double* S, double dsq, double* X, double* Y
)
{
double m, n;
quad2T q = set_quad2( P, Q, R, S);
candT c[9];
int nc = find_cands( &q, c); // find candidates for max and min
// find indices of max and min
int imin = 0;
int imax = 0;
for( int i=1; i<nc; ++i)
{ if ( c[i].d < c[imin].d)
{ imin = i;
}
else if ( c[i].d > c[imax].d)
{ imax = i;
}
}
// check if solution is possible -- should allow some slack here!
if ( c[imax].d < dsq || c[imin].d > dsq)
{ return 0;
}
// find solution
double r[3];
form_quad( &q, c[imin].m, c[imax].m, c[imin].n, c[imax].n, r);
double z;
if ( solve_quad( r, dsq, &z))
{ // fill in distances along
m = c[imin].m + z*(c[imax].m - c[imin].m);
n = c[imin].n + z*(c[imax].n - c[imin].n);
// compute points
for( int i=0; i<3; ++i)
{ X[i] = P[i] + m*(Q[i]-P[i]);
Y[i] = R[i] + n*(S[i]-R[i]);
}
return 1;
}
return 0;
}
答案 3 :(得分:1)
这可以使用基本代数来解决,只需求解二次多项式即可。查看以下推导:
鉴于由点P1和P2定义的线段P和由点Q1和Q2定义的线段Q,我们可以将射线P(t)定义为:
P(t)= P1 + t V
其中t是一个正标量,V是单位矢量:
V =(P2-P1)/ | P2-P1 |
线段Q(t)为:
Q(t)= Q1 + t(Q2-Q1)
其中t是区间[0 1]中的正标量。
线Q(t)中任何点到线P的最短距离由点在线P上的投影给出。投影沿线P的法线向量。
Q(t)
|
|
P1 ------------x------------ P2
因此,我们正在P线中寻找点x,以使向量(x-Q(t))的长度等于d:
| x-Q(t)| ^ 2 = d ^ 2
可以使用射线P(t)计算点x 因为t =(Q(t)-P1)•V:
x = P((Q(t)-P1)•V)
x = P1 +((Q(t)-P1)•V)V
x = P1-(P1•V)V +(Q(t)•V)V
x = P1-(P1•V)V +(Q1•V)V + t((Q2-Q1)•V)V
其中(•)是点积。
x = C + t D
哪里
C = P1-(P1•V)V +(Q1•V)V
D =((Q2-Q1)•V)V
现在等式如下:
| C + t D-Q1-t(Q2-Q1)| ^ 2 = d ^ 2
| C-Q1 + t(D-Q2 + Q1)| ^ 2 = d ^ 2
分组术语:
| t A + B | ^ 2 = d ^ 2
哪里
A =(D-Q2 + Q1)
B = C-Q1
在广场上,我们有
(t A + B)•(t A + B)= d ^ 2
t ^ 2(A•A)+ 2 t(A•B)+(B•B-d ^ 2)= 0
这是一个简单的二次多项式。求解t,我们可以得到两个值,如果两个都是复数,则没有真正的答案。如果两个都是真实的,那么我们可能有两个答案,可能是由于对称性,我们必须在[0 1]区间中选择一个t。
一旦有了t,就可以使用Q(t)和线P中的对应点x计算线段Q中的点
x = P((Q(t)-P1)•V)
如果参数(Q(t)-P1)•V在[0 L]区间内,其中L是向量的长度(P2-P1),则x位于段线P的末端,否则x在外面,则找不到答案。