以前,使用旋轉分離軸實現過, 矩形旋轉碰撞,OBB方向包圍盒演算法實現 。但這個演算法,本身有點複雜,並且在邊越多的時候計算量增長的會很快,擴充套件到3D層面會更加的複雜。而且這個演算法碰撞後獲取碰撞點的座標有點繁瑣。射線檢測演算法,是一個比較簡單清晰的思路,實現起來複雜度也不高,碰撞點也容易獲得,擴充套件到3D世界依然有效。
要用射線去檢測碰撞,之前我們先從一個點開始。如果能夠判斷一個點是否和多邊形碰撞,那麼就可以輕易的擴充套件到,線和多邊形,多邊形和多邊形的碰撞。點與多邊形的碰撞是基於這個實現,PNPOLY – Point Inclusion in Polygon Test。
其核心的思路是,判斷這個點,和多邊形每條邊的位置關係。在一個多條邊圍成的區域,點在一條邊的右側,這個點可能在多邊形內部,也可能在外部。但是如果判斷完點和每一條邊的左右關係,如果在右邊的邊是奇數個,那麼點就在內部,如果是偶數,那麼點就在外部。通過這個規則,就可以判斷,點和多邊形的碰撞關係。有兩個注意點,多邊行必須是凸多邊形,並且如果點落在邊上,我們算在左邊,這樣落在邊上是算在內部。
那麼,如果判斷一個點和一條邊的位置關係。這裡需要用到一個向量叉積公式。比如,點(x, y),與線 (x1, y1) (x2, y2) 的位置關係。我們先求出兩個向量,(x – x1, y – y1) 和 (x2 – x1, y2 – y1)。對這兩個向量做叉積的結果是 (x – x1) * (y2 – y1) – (y – y1) * (x2 – x1), 如果結果是0,那麼點線上上。如果結果大於0,點線上的左邊。如果結果小於0,點線上的右邊。利用這個公式,我們就能判斷點是否在多邊形的內部還是外部。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/** * Test polygon contains point, true inside or false outside * one vertex contains pair of x, y */ static bool TestPolygonPoint(Array(float)* vertexArr, float x, float y) { int preIndex = vertexArr->length - 2; bool inside = false; float* vertexData = AArray_GetData(vertexArr, float); for (int i = 0; i < vertexArr->length; i += 2) { float vertexY = vertexData[i + 1]; float preY = vertexData[preIndex + 1]; if ((vertexY < y && preY >= y) || (preY < y && vertexY >= y)) { float vertexX = vertexData[i]; // cross product between vector (x - vertexX, y - vertexY) and (preX - vertexX, preY - vertexY) // result is (x - vertexX) * (preY - vertexY) - (y - vertexY) * (preX - vertexX) // if result zero means point (x, y) on vector (preX - vertexX, preY - vertexY) // if result positive means point on left vector // if result negative means point on right vector if (vertexX + (y - vertexY) / (preY - vertexY) * (vertexData[preIndex] - vertexX) <= x) { inside = !inside; } } preIndex = i; } return inside; } |
在能判斷,點在多邊形之後,我們就能夠判斷,線與多邊形,多邊形與多邊形的關係。就是判斷多邊形的每一個點,是否在另一個多邊形裡即可。當然,兩個多邊形都要用對方的點檢測一次。相對來說比較耗時,真實的計算中,可以先用AABB或是圓形碰撞做粗略的檢測過濾掉一部分,在做精確的檢測。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/** * Test polygonA each vertex in polygonB, true inside or false outside * not test through and cross each others */ static bool TestPolygonPolygon(Array(float)* polygonA, Array(float)* polygonB) { bool inside = false; for (int i = 0; i < polygonA->length; i += 2) { float x = AArray_Get(polygonA, i, float); float y = AArray_Get(polygonA, i + 1, float); int preIndex = polygonB->length - 2; // test polygonB contains vertex for (int j = 0; j < polygonB->length; j += 2) { float vertexY = AArray_Get(polygonB, j + 1, float); float preY = AArray_Get(polygonB, preIndex + 1, float); if ((vertexY < y && preY >= y) || (preY < y && vertexY >= y)) { float vertexX = AArray_Get(polygonB, j, float); // cross product between vector (x - vertexX, y - vertexY) and (preX - vertexX, preY - vertexY) // result is (x - vertexX) * (preY - vertexY) - (y - vertexY) * (preX - vertexX) // if result zero means point (x, y) on vector (preX - vertexX, preY - vertexY) // if result positive means point on left vector // if result negative means point on right vector if (vertexX + (y - vertexY) / (preY - vertexY) * (AArray_Get(polygonB, preIndex, float) - vertexX) <= x) { inside = !inside; } } preIndex = j; } if (inside) { return true; } } return inside; } |
在兩個多邊形,碰撞之前,以上演算法是可以計算的。但是存在一種互相穿透的情況,這種檢測就會失效,因為可能一個多邊形所有的點都在另一個的外面,但是兩者確是交織的。這種情況會出現在碰撞過後,繼續運動產生的情況。所以剛體的碰撞判斷上面的演算法即可。
但,這種交織的情況,射線法仍然可以通過某些手段進行判斷的。核心的思路就是,計數左邊的點,和右邊的點,如果兩邊計數是相等的就是穿透的情況。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
/** * Test polygonA each vertex in polygonB, true inside or false outside * Can test through and cross each others * */ static bool TestPolygonPolygonFull(Array(float)* polygonA, Array(float)* polygonB) { int leftCount = 0; int rightCount = 0; for (int i = 0; i < polygonA->length; i += 2) { float x = AArray_Get(polygonA, i, float); float y = AArray_Get(polygonA, i + 1, float); int preIndex = polygonB->length - 2; // test polygonB contains vertex for (int j = 0; j < polygonB->length; j += 2) { float vertexY = AArray_Get(polygonB, j + 1, float); float preY = AArray_Get(polygonB, preIndex + 1, float); if ((vertexY < y && preY >= y) || (preY < y && vertexY >= y)) { float vertexX = AArray_Get(polygonB, j, float); // cross product between vector (x - vertexX, y - vertexY) and (preX - vertexX, preY - vertexY) // result is (x - vertexX) * (preY - vertexY) - (y - vertexY) * (preX - vertexX) // if result zero means point (x, y) on vector (preX - vertexX, preY - vertexY) // if result positive means point on left vector // if result negative means point on right vector if (vertexX + (y - vertexY) / (preY - vertexY) * (AArray_Get(polygonB, preIndex, float) - vertexX) <= x) { leftCount++; } else { rightCount++; } } preIndex = j; } if (leftCount % 2 != 0) { return true; } } return leftCount != 0 && leftCount == rightCount; } |
最後,線與線的碰撞,和多邊形的檢測有些有不同。因為兩個都是線的話,就不存在一個封閉的空間,那麼久不能用點在多邊形內部的規則判斷。但,我們仍然使用點與線的位置關係來判斷。就是,兩條線如果相交,那麼各自的兩個點,一定是在另一條線的兩邊的。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
/** * Test one lineA intersect lineB */ static bool TestLineLine(Array(float)* lineA, Array(float)* lineB) { int flag[2] = {0, 0}; float vertexX1 = AArray_Get(lineB, 0, float); float vertexX2 = AArray_Get(lineB, 2, float); float vertexY1 = AArray_Get(lineB, 1, float); float vertexY2 = AArray_Get(lineB, 3, float); for (int i = 0; i < 4; i += 2) { float x = AArray_Get(lineA, i, float); float y = AArray_Get(lineA, i + 1, float); if ((vertexY1 < y && vertexY2 >= y) || (vertexY2 < y && vertexY1 >= y)) { // cross product between vector (x - vertexX1, y - vertexY1) and (vertexX2 - vertexX1, vertexY2 - vertexY1) // result is (x - vertexX1) * (vertexY2 - vertexY1) - (y - vertexY1) * (vertexX2 - vertexX1) if (vertexX1 + (y - vertexY1) / (vertexY2 - vertexY1) * (vertexX2 - vertexX1) <= x) { flag[i >> 1] = 1; } else { flag[i >> 1] = 2; } } } // test lineA two points both sides of lineB if (flag[0] + flag[1] == 3) { return true; } flag[0] = 0; flag[1] = 0; vertexX1 = AArray_Get(lineA, 0, float); vertexX2 = AArray_Get(lineA, 2, float); vertexY1 = AArray_Get(lineA, 1, float); vertexY2 = AArray_Get(lineA, 3, float); for (int i = 0; i < 4; i += 2) { float x = AArray_Get(lineB, i, float); float y = AArray_Get(lineB, i + 1, float); if ((vertexY1 < y && vertexY2 >= y) || (vertexY2 < y && vertexY1 >= y)) { // cross product between vector (x - vertexX1, y - vertexY1) and (vertexX2 - vertexX1, vertexY2 - vertexY1) // result is (x - vertexX1) * (vertexY2 - vertexY1) - (y - vertexY1) * (vertexX2 - vertexX1) if (vertexX1 + (y - vertexY1) / (vertexY2 - vertexY1) * (vertexX2 - vertexX1) <= x) { flag[i >> 1] = 1; } else { flag[i >> 1] = 2; } } } // test lineB two points both sides of lineA return flag[0] + flag[1] == 3; } |
通過,射線演算法的判定,不僅可以判斷碰撞,還能獲得碰撞點的座標,還能擴充套件到3D層面。可以就此擴充套件一個簡單的物理碰撞系統了。