前序
由於業務需要,我學習了判斷點與點、點與線、線與線的關係的算法、理論,這裏彙總下,主要內容有:
- 點與點的關係
- 點與線的關係
- 線與線的關係
點與點
點與點關係相對最簡單,使用勾股定理即可:
這是怎樣計算兩個已知座標點之間的距離:
把兩點名爲 A 和 B
我們用從 A 畫的垂直線和從 B 畫的水平線,形成一個直角三角形。
勾股定理告訴我們:
a2 + b2 = c2
標出 A 和 B的座標。
xA 代表 A 的 x座標
yA 代表 A 的 y座標
水平距離 a 是 (xA − xB)
垂直距離 b 是 (yA − yB)
我們現在可以解 c (兩點之間的距離):
開始: | c2 = a2 + b2 | |
代進 a 和 b 的式: | c2 = (xA − xB)2 + (yA − yB)2 | |
結果: |
點與線的關係
這裏分爲:
點到線的最短距離,也叫點線距離或垂線長度, 這個不是今天的重點,細節移步到此。
點在線的方位:在左側還是右側?
這裏不得不先講個東西:點乘, 也被稱爲“點積”、“標量積“、”內積“
對於向量a和向量b:
a和b的點積公式爲:
其中一維向量a和向量b的行列數相同。
點乘的幾何意義是可以用來表徵或計算兩個向量之間的夾角,以及在b向量在a向量方向上的投影,有公式:
推導過程如下,首先看一下向量組成:
定義向量:
根據三角形餘弦定理有:
根據關係c=a-b(a、b、c均爲向量)有:
即:
向量a,b的長度都是可以計算的已知量,從而有a和b間的夾角θ:
根據這個公式就可以計算向量a和向量b之間的夾角。從而就可以進一步判斷這兩個向量是否是同一方向,是否正交(也就是垂直)等方向關係,具體對應關係爲:
a·b>0 方向基本相同,夾角在0°到90°之間
a·b=0 正交,相互垂直
a·b<0 方向基本相反,夾角在90°到180°之間
這樣就能判斷點在直線哪邊。
線與線的關係
常用問題:
線與線是否相交?
判斷兩條線段是否相交有兩步:
①快速排斥計算
②跨立計算
快速排斥
給出線條AB、CD,如果以AB、CD爲對角線的矩形不相交,那麼AB、CD也必不可能相交;如果矩形相交,那麼需要再通過跨立計算進行判斷。對於矩形不相交,有下面兩種情況:
對於上面兩種情況,可以分成四類來討論:
①AB兩座標中最大的x值 小於 CD兩座標中最小x值
②CD兩座標中最大的x值 小於 AB兩座標中最小x值
③AB兩座標中最大的y值 小於 CD兩座標中最小y值
④CD兩座標中最大的y值 小於 AB兩座標中最小y值
只要滿足了以上四種的其中一種,就可以認爲AB與CD不相交。
跨立計算:
首先,這裏需要用到向量叉乘的算法:其中AB與CD是三維空間上的向量,與xOy平面平行。
其次,如下圖。AB與CD相交必然有A、B在線段CD兩邊,C、D在線段AB兩邊。
根據上面的公式和右手螺旋法則:
左邊是“左手法則”, 右邊是“右手法則”, 用手錶示爲下圖:
如果相交,AB X AC的z座標值z1與AB X AD的z座標值z2必然異號;同樣的,DC X DA的z座標值z3與DC X DB的z座標值z4也必然異號。
兩個向量a和b的叉積寫作a × b(有時也被寫成a ∧ b,避免和字母x混淆)。叉積可以被定義爲:
在這裏θ表示a和b之間的角度(0° ≤ θ ≤ 180°),它位於這兩個矢量所定義的平面上。而n是一個與a和b均垂直的單位矢量。
特別的,如果B在CD上時,求得的z座標值是0。所以只要同時滿足z1 X z2 ≤ 0,z3 X z4 ≤ 0,就能保證必然相交。
參考代碼(Go)
type Point struct {
X float64
Y float64
}
type Segment struct {
A Point
B Point
}
// IsSegmentsIntersect 2個線段是否相交
func IsSegmentsIntersect(line1 Segment, line2 Segment, containsEnds bool) bool {
//判斷矩形相交
if math.Min(line1.A.X, line1.B.X) > math.Max(line2.A.X, line2.B.X) ||
math.Max(line1.A.X, line1.B.X) < math.Min(line2.A.X, line2.B.X) ||
math.Min(line1.A.Y, line1.B.Y) > math.Max(line2.A.Y, line2.B.Y) ||
math.Max(line1.A.Y, line1.B.Y) < math.Min(line2.A.Y, line2.B.Y) {
return false
}
//判斷點的位置:如果任意線段的2個端點在另一條線的兩側,則兩線相交
a1 := IsPointOnLine(line1.A, line2)
b1 := IsPointOnLine(line1.B, line2)
a2 := IsPointOnLine(line2.A, line1)
b2 := IsPointOnLine(line2.B, line1)
// a*b < 0表示2個端點在另一條線的兩端
x1 := a1 * b1
x2 := a2 * b2
if containsEnds && (x1 == 0 || x2 == 0) {
return true
}
isCross := a1*b1 < 0 && a2*b2 < 0
return isCross
}
// IsPointOnLine 判斷點是否在線段上, 返回值 > 0 在右側, = 0 在線上, < 0 在左側
func IsPointOnLine(p Point, s Segment) float64 {
return (s.B.Y-p.Y)*(s.A.X-p.X) - (s.A.Y-p.Y)*(s.B.X-p.X)
}