碰撞檢測可分為 Broad Phase (粗略檢測)與 Narrow Phase (精細檢測) 兩個階段。粗略檢測階段可直接比較兩個物體的AABB包圍框是否碰撞以節省計算量和時間。在精細檢測中,SAT(Separating Axis Theorem,分離軸定理)碰撞檢測算法直觀且高效,它的原理清晰易懂,即若兩個物體沒有發生碰撞,則總會存在一條直線,能將兩個物體分離。分離軸適用的是凸多邊形之間的檢測,不適用於凹多邊形,凹多邊形的檢測,可以通過算法將凹多邊形分割成多個凸多邊形再進行計算。

算法步驟如下:
步驟一:從需要檢測的多邊形中取出一條邊,並找出它的法向量,這個向量將會是我們的一個“投影軸”。
步驟二:循環獲取第一個多邊形的每個點,並將它們投影到這個軸上。
步驟三:對第二個多邊形做同樣的處理。
步驟四:分別得到這兩個多邊形的投影,並檢測這兩段投影是否重疊。
如果你發現了這兩個投影到軸上的“陰影”有間隙,那么這兩個圖形一定沒有相交。但如果沒有間隙,那么它們則可能接觸,你需要繼續檢測直到把兩個多邊形的每條邊都檢測完。如果你檢測完每條邊后,都沒有發現任何間隙,那么它們是相互碰撞的。
根據上面步驟兩個凸多邊形之間碰撞檢測的關鍵代碼如下(參考collision.py):
1 def flatten_points_on(points, normal, result): 2 minpoint = math.inf 3 maxpoint = -math.inf 4 5 for i in range(len(points)): 6 dot = points[i].dot(normal) 7 if dot < minpoint: 8 minpoint = dot 9 if dot > maxpoint: 10 maxpoint = dot 11 12 result[0] = minpoint 13 result[1] = maxpoint 14 15 16 def is_separating_axis(a_pos, b_pos, a_points, b_points, axis): 17 range_a = [0, 0] 18 range_b = [0, 0] 19 20 offset_v = b_pos-a_pos 21 22 projected_offset = offset_v.dot(axis) 23 24 flatten_points_on(a_points, axis, range_a) 25 flatten_points_on(b_points, axis, range_b) 26 27 range_b[0] += projected_offset 28 range_b[1] += projected_offset 29 30 if range_a[0] > range_b[1] or range_b[0] > range_a[1]: 31 return True 32 33 return False 34 35 36 def test_aabb(b1,b2): 37 return b1[0][0] <= b2[1][0] and b2[0][0] <= b1[1][0] and b1[0][1] <= b2[2][1] and b2[0][1] <= b1[2][1] 38 39 40 def test_poly_poly(a, b): 41 a_points = a.rel_points 42 b_points = b.rel_points 43 a_pos = a.pos 44 b_pos = b.pos 45 46 for n in a.normals: 47 if is_separating_axis(a_pos, b_pos, a_points, b_points, n): 48 return False 49 50 for n in b.normals: 51 if is_separating_axis(a_pos, b_pos, a_points, b_points, n): 52 return False 53 54 return True 55 56 57 def collide(a, b): 58 if not test_aabb(a.aabb, b.aabb): 59 return False 60 61 return test_poly_poly(a, b)
對於參數指定的兩個凸多邊形,碰撞檢測函數collide先調用test_aabb來判斷其AABB包圍框是否相交,若外包圍框不相交則證明兩個多邊形之間不可能發生碰撞,函數值返回False,反之則使用分離軸算法進行精細檢測。函數test_poly_poly按照算法流程遍歷兩個多邊形上的每一條邊,若在某一條邊上存在分離軸使得兩個多邊形在其上的投影不重合則證明多邊形不相交,直接返回False。函數is_separating_axis根據給定的兩個多邊形的位置、多邊形頂點的局部坐標以及分離軸向量,來計算多邊形在分離軸向量上投影是否重合。
需要注意的是:對於很多圖形視圖和動畫框架來說,圖形項一般使用自己的局部坐標系來繪制,通常以其中心為原點,圖形項的位置是其原點在其父圖形項或者場景中的位置。分離軸判斷函數中的坐標基准應一致,假如以第一個輸入參數多邊形$a$的位置為參考原點,則多邊形$b$的頂點坐標在該參考系下,其局部坐標要加上$\overrightarrow{ab}$這個offset,因此多邊形$b$在向分離軸投影時也要加上$\overrightarrow{ab}$投影后的偏移量$ \overrightarrow{ab} \cdot \overrightarrow{n}$
參考:
