「線段相交與否」與「球體碰撞點」

最近在實作 Delaunay Triangulation 時,遇到需要刪除交叉線斷的問題,才想到之前碩論在做撞球的時候也有解決到類似的問題,但忘記怎麼解了,趕緊複習了一下,並想在這記錄下來,日後好再回來查找。

線段相交與否

要求線段 AB 與 線段 CD 是否相交,可以嘗試以下兩種方法。

方法一:解連立求 x

最直觀的方法,把方程式列出來後解連立。

f(x) = Mab * x + (A.y - A.x * Mab)
f(x) = Mcd * x + (C.y - C.x * Mcd)

其中 Mab 是 AB 的斜率, Mcd 是 CD 的斜率。

Mab = (B.y - A.y) / (B.x - A.x)
Mcd = (D.y - C.y) / (D.x - C.x)

連立解完會長成下面這樣:

x = ((C.y - C.x * Mcd) - (A.y - A.x * Mab)) / (Mab - Mcd)

最後在比較 x 的範圍有沒有超出 AB 的 x 與 cd 的 x 即可。

方法二:外積

本方法參考自:https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/

當兩線斷相交時,A 和 B 會被 CD 分開, C 和 D 會和 AB 分開。

你可以仔細觀察一下此時弱勢使用 CD 分開 A 和 B,並組成 CDA 和 CDB 這兩個三角形。其轉向一定是相反的。

同理,若是我們是用 AB 把 C 和 D 分開,也是一樣:

因此,我們要先有一個確認轉向的 Function:

// 3D
bool ccw(Vector3 A, Vector3 B, Vector3 C, Vector3 normal)
{
return Vector3.Dot(Vector3.Cross(A - B, C - B), normal) > 0);
}
// 2D
bool ccw(Vector2 A, Vector2 B, Vector2 C)
{
Vector2 BA = A - B;
Vector2 BC = C - B;
return BA.x * BC.y - BA.y * BC.x > 0;
}

上述式子中簡單來說,若兩向量外在 XY 平面且是逆時針,外積後得到向量必定會指向正 Z 軸。因此以 2D 的式子來說,我們直接算行列式就是外積後向量中的 Z 的量,若大於 0 即是指向正 Z,以 3D 的世子來說,若在 XY 平面上, normal 給 (0, 0, 1) 即可,若不在 XY 平面上,就需要改自己的 normal 向量。

有了 ccw 這個 Function 後,我們就只要確認 ccw(A, B, C)ccw(A, B, D)相反,ccw(C, D, A)ccw(C, D, B) 相反即可。

bool CheckIntersection(Vector3 A, Vector3 B, Vector3 C, Vector3 D)
{
return ccw(A, B, C) != ccw(A, B, D) && ccw(C, D, A) != ccw(C, D, B);
}

球體碰撞點

求球體碰撞點的方法與求線段相交很像,但又不太一樣,首先我們先改成用起始點和速度的方式來表達。

o1: ball1 的起始點
v1: ball1 的速度
o2: ball2 的起始點
v1: ball2 的速度
f1(t) = o1 + v1 * t (ball1 的路徑)
f2(t) = o2 + v2 * t (ball2 的路徑)

接下來請看下圖,因為我們要假設兩求相撞,因此在相撞時,兩求的距離一定是剩下 2r (兩倍的球半徑)。

因此我們可以列出下列式子:

|f1(t) - f2(t)| = 2r
=> |f1(t) - f2(t)|² = (2r)²
=> |o1 + v1t - o2 - v2t|² = 4r²
=> |(o1 - o2) + (v1 - v2)t|² = 4r²

以下為了方便運算,我把 o1 — o2 定義為 Δo ,v1 — v2 定義為 Δv 。

=> |Δo + tΔv|² = 4r²
=> |Δv|²t² + 2t(Δo⋅Δv) + |Δo|² = 4r²
=> |Δv|²t² + 2t(Δo⋅Δv) + (|Δo|² - 4r²) = 0

求到此處,一元二次方程式就出現了,接下來用公式解求兩根即可,因此我們要先簡化算是,找出 A, B, C 三個係數。

A = |Δv|²
B = 2(Δo⋅Δv)
C = |Δo|² - 4r²

不過在帶入公式解前先帶入判變式看看有沒有有理根:

判別式:
Δ = B² - 4AC > 0
公式解
t = (-B ± √Δ) / 2A

若判別式不成立就代表兩球不會碰撞,可能是錯過或是本來就平行之類的;另外若是 v 採用的不是速度而是距離,當 t > 1 時則代表焦點超出距離,這種判別方式是被我用在逐幀計算時,用來計算是否在此幀碰撞到,我會先用速度 * deltaTime 後算出球的移動距離內有沒有碰撞可能,若 t > 1 代表是在未來的幀才會碰撞。


留言

這個網誌中的熱門文章

MySQL 筆記:Join 合併表格,使用方式

Android 筆記:在Android Studio上製作分頁效果(FragmentTabHost 使用方法)

Debian, Parallels desktop 筆記:如何建立自己的 Shared Folder / Debian and PD(Parallels Desktop) notes: How to create your own shared folder in the Linux VM in Parallels Desktop