跳到主要内容

线段之间的距离

· 阅读需 9 分钟
Pevoro
Funder of this site.

计算点和线段之间的距离,线段和线段之间的距离

空间中点和线段之间的距离​

已知点 P0P_0 ,线段 P1P2→\overrightarrow{P_1P_2}, 假设线段 P1P2→\overrightarrow{P_1P_2} 延长线上距离 P0P_0 最近的点是 PP, 设P点坐标为

P=P1+u(P2−P1)P=P_1+u(P_2-P_1)

uu 是位置标量。

由于 P0P_0 点到 PP 点是 P0P_0 到线段 P1P2→\overrightarrow{P_1P_2} 延长线上距离最近的点, 所以 PP0→\overrightarrow{PP_0} 垂直于 P1P2→\overrightarrow{P_1P_2}。

将 P=P1+u(P2−P1)P=P_1+u(P_2-P_1) 带入

(P0−P)⋅(P2−P1)=0(P_0-P)\cdot(P_2-P_1)=0

得

(P0−P1−u(P2−P1))⋅(P2−P1)=0(P_0-P_1-u(P_2-P_1))\cdot(P_2-P_1)=0

从而

u=(P0−P1)⋅(P2−P1)∣∣P2−P1∣∣2u=\frac{(P_0-P_1)\cdot(P_2-P_1)}{||P_2-P_1||^2}

如果 u<=0u \lt=0 ,且P1P_1为端点,则最小距离为∣∣P1−P0∣∣||P_1-P_0||

如果 u>=1u \gt=1,且P2P_2为端点,则最小距离为∣∣P2−P0∣∣||P_2-P_0||

如果没有端点,则最小距离为,∣∣P−P0∣∣||P-P_0||

空间线段和线段之间的最短距离​

已知线段 P1P2→\overrightarrow{P_1P_2} 和线段 P3P4→\overrightarrow{P_3P_4}, 假设直线 P1P2‾\overline{P_1P_2} 和直线 P3P4‾\overline{P_3P_4} 之间的最短连线为 PaPb→\overrightarrow{P_aP_b}, 设 PaP_a 和 PbP_b 点坐标为:

Pa=P1+ua(P2−P1)P_a=P_1+u_a(P_2-P_1)

Pb=P3+ub(P4−P3)P_b=P_3+u_b(P_4-P_3)

uau_a和ubu_b是位置标量。

由于直线P1P2‾\overline{P_1P_2}和直线P3P4‾\overline{P_3P_4}之间的最短连线为PaPb→\overrightarrow{P_aP_b},则 PaPb→⊥P1P2→\overrightarrow{P_aP_b}\perp\overrightarrow{P_1P_2}

PaPb→⊥P3P4→\overrightarrow{P_aP_b}\perp\overrightarrow{P_3P_4}

于是

(Pb−Pa)⋅(P2−P1)=0(P_b-P_a)\cdot(P_2-P_1)=0

(Pb−Pa)⋅(P4−P3)=0(P_b-P_a)\cdot(P_4-P_3)=0

即

(P3+ub(P4−P3)−(P1+ua(P2−P1)))⋅(P2−P1)=0(P_3+u_b(P_4-P_3)-(P_1+u_a(P_2-P_1)))\cdot(P_2-P_1)=0

(P3+ub(P4−P3)−(P1+ua(P2−P1)))⋅(P4−P3)=0(P_3+u_b(P_4-P_3)-(P_1+u_a(P_2-P_1)))\cdot(P_4-P_3)=0

从上面两个式子分别得到

ua=(P3−P1)⋅(P2−P1)+ub(P4−P3)⋅(P2−P1)∣∣P2−P1∣∣2u_a=\frac{(P_3-P_1)\cdot(P_2-P_1)+u_b(P_4-P_3)\cdot(P_2-P_1)}{||P_2-P_1||^2}

ub=ua(P2−P1)⋅(P4−P3)−(P3−P1)⋅(P4−P3)∣∣P4−P3∣∣2u_b=\frac{u_a(P_2-P_1)\cdot(P_4-P_3)-(P_3-P_1)\cdot(P_4-P_3)}{||P_4-P_3||^2}

设

k1=(P3−P1)⋅(P2−P1)k_1=(P_3-P_1)\cdot(P_2-P_1)

k2=(P4−P3)⋅(P2−P1)=(P2−P1)⋅(P4−P3)k_2=(P_4-P_3)\cdot(P_2-P_1)=(P_2-P_1)\cdot(P_4-P_3)

k3=∣∣P2−P1∣∣2k_3=||P_2-P_1||^2

k4=(P3−P1)⋅(P4−P3)k_4=(P_3-P_1)\cdot(P_4-P_3)

k5=∣∣P4−P3∣∣2k_5=||P_4-P_3||^2

带入得到

ua=k1+ubk2k3u_a=\frac{k_1 + u_b k_2}{k_3}

ub=uak2−k4k5u_b=\frac{u_a k_2-k_4}{k_5}

将uau_a带入ubu_b得到

ub=k1+ubk2k3k2−k4k5=k1k2+ubk22−k3k4k3k5u_b=\frac{\frac{k_1+u_b k_2}{k_3} k_2-k_4}{k_5}=\frac{k_1 k_2+u_b k_2^2-k_3 k_4}{k_3 k_5}

求得

ub=k1k2−k3k4k3k51−k22k3k5=k1k2−k3k4k3k5−k22u_b=\frac{\frac{k_1 k_2-k_3 k_4}{k_3 k_5}}{1-\frac{k_2^2}{k_3 k_5}}=\frac{k_1 k_2-k_3 k_4}{k_3 k_5 - k_2^2}

将ubu_b带入到uau_a的表达式,就可以得到uau_a

从而得到PaP_a和 PbP_b

最短距离就是∣∣Pb−Pa∣∣||P_b-P_a||

当最近的点在线段上时​

上面的计算是正确的,如果最近点不在线段上,则需要考虑line1和line2是不是直线或者射线

先假设,Pa=P1,Pb=P3P_a=P_1,P_b=P_3,则最小距离Dis=∣∣Pb−Pa∣∣Dis=||P_b-P_a||

当P1P_1是端点,且ua<0u_a\lt0时,

计算P_1和P3P4→\overrightarrow{P_3P_4}的最短距离DisTDis_T,最近点为PTP_T, 如果DisT<DisDis_T \lt Dis则 Dis=DisTDis = Dis_T

Pa=P1P_a = P_1

Pb=PTP_b = P_T

当P2P_2是端点,且ua>1u_a\gt1时,

计算P_2和P3P4→\overrightarrow{P_3P_4}的最短距离DisTDis_T,最近点为PTP_T,如果DisT<DisDis_T \lt Dis则

Dis=DisTDis = Dis_T

Pa=P2P_a = P_2

Pb=PTP_b = P_T

当P3P_3是端点,且ub<0u_b\lt0时,

计算P_3和P1P2→\overrightarrow{P_1P_2}的最短距离DisTDis_T,最近点为PTP_T,如果DisT<DisDis_T \lt Dis则

Dis=DisTDis = Dis_T

Pb=P3P_b = P_3

Pa=PTP_a = P_T

当P4P_4是端点,且ub>1u_b\gt1时,

计算P_4和P1P2→\overrightarrow{P_1P_2}的最短距离DisTDis_T,最近点为PTP_T,如果DisT<DisDis_T \lt Dis则

Dis=DisTDis = Dis_T

Pb=P4P_b = P_4

Pa=PTP_a = P_T

以上是在两条直线不平行和不重叠的情况下进行的

当两条直线平行或者共线时​

如果两条直线平行或者共线,则需要进行特殊讨论

  1. P3P4→\overrightarrow{P_3P_4}在P1P2‾\overline{P_1P_2}的后侧,且P4P_4和P1P_1较近, 即(P3−P1)⋅(P2−P1)<=(P4−P1)⋅(P2−P1)<=0(P_3-P_1)\cdot(P_2-P_1)\lt=(P_4-P_1)\cdot(P_2-P_1)\lt=0,如果P4P_4和P1P_1为端点,则

    Pa=P1P_a=P_1

    Pb=P4P_b=P_4

  2. P3P4→\overrightarrow{P_3P_4}在P1P2‾\overline{P_1P_2}的后侧,且P3P_3和P1P_1较近,即(P4−P1)⋅(P2−P1)<=(P3−P1)⋅(P2−P1)<=0(P_4-P_1)\cdot(P_2-P_1)\lt=(P_3-P_1)\cdot(P_2-P_1)\lt=0,如果P3P_3和P1P_1为端点,则

    Pa=P1P_a=P_1

    Pb=P3P_b=P_3

  3. P3P4→\overrightarrow{P_3P_4}在P1P2‾\overline{P_1P_2}的前侧,且P3P_3和P2P_2较近,即0<=(P3−P2)⋅(P2−P1)<=(P4−P2)⋅(P2−P1)0\lt=(P_3-P_2)\cdot(P_2-P_1)\lt=(P_4-P_2)\cdot(P_2-P_1),如果P3P_3和P2P_2为端点,则

    Pa=P2P_a=P_2

    Pb=P3P_b=P_3

  4. P3P4→\overrightarrow{P_3P_4}在P1P2‾\overline{P_1P_2}的前侧,且P4P_4和P2P_2较近,即0<=(P4−P2)⋅(P2−P1)<=(P3−P2)⋅(P2−P1)0\lt=(P_4-P_2)\cdot(P_2-P_1)\lt=(P_3-P_2)\cdot(P_2-P_1),如果P4P_4和P2P_2为端点,则 Pa=P2P_a=P_2

    Pb=P4P_b=P_4

    以上四种情况的距离为

    Dis=∣∣Pb−Pa∣∣Dis = ||P_b-Pa||

  5. 如果上述四种情况均不符合,则说明有无数多最近点

    Dis=∣∣(P3−P1)⊗(P2−P1)∣∣P2−P1∣∣∣∣Dis = ||\frac{(P_3-P_1)\otimes(P_2-P_1)}{||P_2-P_1||}||

Python代码实现​

import numpy as np

class distance():
def __init__(self):
return

def points(point1, point2):
'''
Calculate distance between two points or
minimum distance between two point sets
'''
# First adjust points coordinates data shape
# point date type is like: np.array([[x1,y1,z1],[x2,y2,z2],...)
# Caution: double '['in point1 and point2
point1 = np.array(point1).reshape(-1, 3)
point2 = np.array(point2).reshape(-1, 3)
if point1.shape[0]*point2.shape[0] == 0: # 如果两个数组中有一个为空
# print("at least one points set in pointsDistance is void")
return 0
nrows, ncols = point1.shape
dtype = {'names': ['f{}'.format(i) for i in range(ncols)],
'formats': ncols * [point1.dtype]}
inter12 = np.intersect1d(point1.view(dtype), point2.view(dtype))
if inter12.shape[0] != 0:
# print("Intersected")
return 0
disArray = np.array([])
for pointa in point1:
for pointb in point2:
disArray = np.append(disArray, np.linalg.norm(pointa-pointb))
disArray = np.sort(np.unique(disArray))
return disArray

def pointLine(point, line, end1=1, end2=2, dimension=3):
'''
pointLine(point,line,end1=0,end2=0)
point: the point coordinate [x0,y0,z0] or for 2D: [x0,y0]
line: two points on line. p1 and p2.[x1,y1,z1,x2,y2,z2]
for 2D:[[x1,y1,x2,y2]]
end1: if end1=1, then p1 is one end of the line segment
if end1=0, then p1 is not end of the line segment
end2: the other end of line segment
'''
p0 = np.array(point).reshape(-1, dimension)[0]
# np.array([[[x0,y0,z0]]]) only one point
p1 = np.array(line).reshape(-1, dimension)[0]
p2 = np.array(line).reshape(-1, dimension)[1]
# np.array([[[x1,y1,z1],[x2,y2,z2]]]) only two points
u = (p0-p1).dot(p2-p1)/np.linalg.norm(p2-p1)**2.
if end1 == 1 and u <= 0:
p = p1
distance = np.linalg.norm(p-p0)
elif end2 == 1 and u >= 1:
p = p2
distance = np.linalg.norm(p-p0)
else:
p = p1 + u*(p2-p1)
distance = np.linalg.norm(p - p0)
return p, distance

def lines(line1, line2, end1=1, end2=1, end3=1, end4=1, dimension=3):
'''
Calculate the distance between two line segments
Reference:
http://paulbourke.net/geometry/pointlineplane/
https://stackoverflow.com/questions/2824478/shortest-distance-between-two-line-segments
'''
''' Given two lines segments defined by numpy.array
line: two points on line. p1 and p2.[x1,y1,z1,x2,y2,z2]
for 2D:[[x1,y1,x2,y2]]
There are four points for these two lines
end1: if end1=1, then p1 is one end of the line1 segment.
if end1=0, then p1 is not end of the line1 segment.
end2: the other end of line1 segment.
end3 and end4: ends of line2 segment.
'''
p1 = np.array(line1).reshape(-1, dimension)[0]
p2 = np.array(line1).reshape(-1, dimension)[1]
# np.array([[[x1,y1,z1],[x2,y2,z2]]]) two points for first line
p3 = np.array(line2).reshape(-1, dimension)[0]
p4 = np.array(line2).reshape(-1, dimension)[1]
# np.array([[[x3,y3,z3],[x4,y4,z4]]]) two points for second line
# 先判断是不是平行和共线
line1 = p2 - p1
line2 = p4 - p3
line1Norm = np.linalg.norm(line1)
line12cross = np.cross(line1, line2)
line12crossNorm = np.linalg.norm(line12cross)
if line12crossNorm != 0: # 不平行且不共线
# calculate mid variables
k1 = (p3-p1).dot(line1)
k2 = (line2).dot(line1)
k3 = np.linalg.norm(line1)**2.
k4 = (p3-p1).dot(line2)
k5 = np.linalg.norm(line2)**2.
ub = (k1 * k2 - k3 * k4)/(k3 * k5 - k2**2.)
ua = (k1 + ub*k2)/k3
pa = p1 + ua*(line1)
pb = p3 + ub*(line2)
if 0 <= ua <= 1 and 0 <= ub <= 1: # 当最近点都在线段上时
distance = np.linalg.norm(pb-pa)
return pa, pb, distance
# print("ua,ub,pa,pb:", ua, ub, pa, pb)
# 两个直线最近点不在线段上
pa = p1
pb = p3
distance = np.linalg.norm(pb-pa) # 假设最近距离为p3-p1之间的距离
if end1 == 1 and ua <= 0:
pT, distanceT = pointLine(
p1,
[p3, p4],
end1=end3,
end2=end4,
dimension=dimension)
if distanceT <= distance:
distance = distanceT
pa = p1
pb = pT
if end2 == 1 and ua >= 1:
pT, distanceT = pointLine(
p2,
[p3, p4],
end1=end3,
end2=end4,
dimension=dimension)
if distanceT <= distance:
distance = distanceT
pa = p2
pb = pT
if end3 == 1 and ub <= 0:
pT, distanceT = pointLine(
p3,
[p1, p2],
end1=end1,
end2=end2,
dimension=dimension)
if distanceT <= distance:
distance = distanceT
pa = pT
pb = p3
if end4 == 1 and ub >= 1:
pT, distanceT = pointLine(
p4,
[p1, p2],
end1=end1,
end2=end2,
dimension=dimension)
if distanceT <= distance:
distance = distanceT
pa = pT
pb = p4
else:
# print("平行或者共线")
if ((p3-p1).dot(line1) <= (p4-p1).dot(line1) <= 0 and
end4 == 1 and end1 == 1): # line2 在line1后侧且p4距离p1近
# print("#line2 在line1后侧且p4距离p1近")
pa = p1
pb = p4
distance = np.linalg.norm(pb-pa)
elif ((p4-p1).dot(line1) <= (p3-p1).dot(line1) <= 0 and
end3 == 1 and end1 == 1): # line2 在line1后侧且p3距离p1近
# print("#line2 在line1后侧且p3距离p1近")
pa = p1
pb = p3
distance = np.linalg.norm(pb-pa)
elif (0 <= (p3-p2).dot(line1) <= (p4-p2).dot(line1) and
end3 == 1 and end2 == 1): # line2 在line1前侧且p3距离p2近
# print("#line2 在line1前侧且p3距离p2近")
pa = p2
pb = p3
distance = np.linalg.norm(pb-pa)
elif (0 <= (p4-p2).dot(line1) <= (p3-p2).dot(line1) and
end4 == 1 and end2 == 1): # line2 在line1前侧且p4距离p2近
# print("#line2 在line1前侧且p4距离p2近")
pa = p2
pb = p4
distance = np.linalg.norm(pb-pa)
else:
# print("无穷多最近点")
pa = None
pb = None
distance = np.linalg.norm(np.cross((p3-p1), line1)/line1Norm)
# print("pa,pb,distance:", pa, pb, distance)
return pa, pb, distance