怎樣判斷平面上一個矩形和一個圓形是否有重疊?
編程演算法
設c為矩形中心,h為矩形半長,p為圓心,r為半徑。
方法是計算圓心與矩形的最短距離 u,若 u 的長度小於 r 則兩者相交。
1. 首先利用絕對值把 p - c 轉移到第一象限,下圖顯示不同象限的圓心也能映射至第一象限,這不影響相交測試的結果:
2. 然後,把 v 減去 h,負數的分量設置為0,就得到圓心與矩形最短距離的矢量 u。下圖展示了4種情況,紅色的u是結果。
3. 最後要比較 u 和 r 的長度,若距離少於 r,則兩者相交。可以只求 u 的長度平方是否小於 r 的平方。代碼:
bool BoxCircleIntersect(Vector2 c, Vector2 h, Vector2 p, float r) {
Vector2 v = abs(p - c); // 第1步:轉換至第1象限
Vector2 u = max(v - h, 0); // 第2步:求圓心至矩形的最短距離矢量
return dot(u, u) &<= r * r; // 第3步:長度平方與半徑平方比較
}
這個方法可能最早記錄於[1],而[2][3]也有相關描述。這個方法應該是最優的,而且可擴展至任何維度。如矩形不是軸對齊矩形(AABB)而是定向矩形(OBB),可以把圓心旋轉至矩形的座標系。
這個方法考慮了矩形的對稱及軸對齊性質,實際上等同5個分離軸測試[3],包括矩形4邊、圓心與矩形最近點的分離軸。後者容易被忽略。
這類型問題可以使用閔可夫斯基和轉化為圓角矩形和點的相交問題,可以應用不同的距離函數,如[5]。
[1] Arvo, "A Simple Method for Box-Sphere Intersection Testing", Graphics Gems, pp. 247-250, 1993. http://tog.acm.org/resources/GraphicsGems/gems/BoxSphere.c[2] Gottschalk, "Separating axis theorem",. Technical Report TR96-024, Department of Computer Science, UNC Chapel Hill, 1996.
[3] Philip, Eberly, Geometric tools for computer graphics, Morgan Kaufmann, pp.644-646, 2002.
[4] Gomez, "Simple Intersection Tests for Games," Gamasutra, October 1999. Gamasutra - Simple Intersection Tests For Games
[5] Quilez, "Modeling with distance functions", 2008. Iigo Quilez - fractals, computer graphics, mathematics, demoscene and more
可以計算圓心是否在矩形和圓的閔可夫斯基和的範圍內。
如果是你的矩形和坐標軸不對齊,那就是兩個簡單的凸型求交,用四條邊的直線方程就行了。(矩形而言因為四組Ax+By+C=0之間有相關性所以可以少做點乘法)。
Circle-Rectangle collision detection (intersection)如上圖所示,要判斷是否有collision,只需找到距離圓最近的矩形邊上的點P,判斷其與圓心之距是否小於半徑。
Step 1:求出圓心到矩形中心的向量,
Step 2:clamp 為矩形的半長和半寬
Step 3:矩形中心加上clamp過的向量得到P,與圓心作差求長再和半徑比較即可。
代碼:
GLboolean CheckCollision(BallObject circle, GameObject rec) {
// 找到圓心
glm::vec2 center(circle.Position + circle.Radius);
glm::vec2 aabb_half_extents(rec.Size.x / 2, rec.Size.y / 2);
// 找到矩形中心
glm::vec2 aabb_center(
rec.Position.x + aabb_half_extents.x,
rec.Position.y + aabb_half_extents.y
);
glm::vec2 d = center - aabb_center;
glm::vec2 clamped = glm::clamp(d, -aabb_half_extents, aabb_half_extents);
// 找到矩形上的P點
glm::vec2 p = aabb_center + clamped;
d = p - center;
return glm::length2(d) &< circle.Radius * circle.Radius;
}
這個其實和 @Milo Yip 貼的方法很像,不過就不需要轉換到第一象限了。
排名第一的答案說的很清楚了。另外有一本書叫《碰撞檢測演算法技術》,是elsevier出的,裡面系統的講述了基本型之間的相交測試。
想到同學學kd樹時,給我講的一個挺優美的方法。求出矩形內距圓心最近的點到圓心的距離是不是小於圓半徑。對每個維度分別求最近點的對應維度的坐標,對第i維圓心坐標為xi,矩形範圍是ai與bi,若xi在對應範圍中最近點的對應坐標yi為xi,否則取區間端點,最後就求出了矩形上的最近點y的坐標,這樣擴展到K維也比較簡便。這麼做不需要用計算幾何的任何東西(除了最後求兩點的距離)。
判斷圓心到矩形四條邊(注意是線段)
的距離取min是否小於等於r,以及圓心本身就在矩形內即可。
-vc大大求圓和直線交點真是酷炫。。-
啊,這個我不久前才幹過,方法很簡單粗暴的。首先求出四條邊的延長線和圓形的交點,一共獲得了八個點。如果這八個點都不在矩形上,那麼只有每條邊的延長線上的兩個交點都分別處於線段兩端的情況下,圓形包含矩形,否則圓形不包含矩形。其他情況則為相交。
========
@Dracarys 聚聚提醒了我沒有任何交點的情況,我看了一下之前的代碼,是我這個答案寫漏了。第一步要判斷圓心的位置,如果在矩形外面,沒有交點就是不重疊。
話說我之所以用這個方法是因為,當時在做一個機器人模擬程序,需要在重疊的地方染色,所以要求出重疊區域的邊框的各個部分的方程,做到整個人都不好了。
班門弄斧一下,如果把這個問題擴展到任意的凸多邊形,那麼可以用GJK檢測碰撞,在GJK之後可以再用EPA計算penetration vector 和 penetration depth.
一篇很好的文章詳細說明了原理與實現的偽代碼:GJK (Gilbert寫過具體的代碼實現,這裡: https://canyonkang.wordpress.com/portfolio/graphics-collision-detection-gjk-and-epms-implementation/
圓沿著矩形的周長滾一圈,圓心的軌跡為一個圓角矩形,問題簡化為圓心是否在圓角矩形中;再將圓角矩形擴展進一個新矩形(邊長=原邊長+圓半徑),問題簡化為:
1.新矩形的所有頂點不在圓中
——圓心在新矩形中必有重疊,否則無重疊。
2.新矩形的某頂點在圓中
——原矩形的頂點在圓中必有重疊,否則無重疊。
該方法的判斷條件計算量不高,易編程實現。
上面Milo Yip的演算法很牛逼。
如果我自己做的話, 我會列出 圓的方程
(x - a) ^ 2 + (y - b) ^2 = r^2,
代入 矩形 每條邊的公式, x = 。。。, 或者 y = 。。。
如果有實數解, 檢查一下, 它是否在矩形邊的坐標區間。
複雜度可能是上面的10倍
把矩形上下左右各擴圓的半徑的長度,判斷圓心是否在新矩形中,如果不在,則不相交。如果在,繼續判斷圓心是否在原矩形的某個角的外側,如果不在則相交。如果在,計算圓心與角點的距離是否小於等於半徑,如果小於,則相交。否則不相交。
先做角外側判斷再進行運算效率更佳考慮兩圖形在同一平面,首先如果兩中心距離大於直徑加上對角線長度的一半的話,肯定不會有重疊。
問題可以轉換成圖形邊界是否有交點。進一步可轉換成矩形邊界線段與圓周是否有交點。那麼就可以這麼做了,過圓心作線段的垂線段(垂心必須在線段上),若只要有可作出的垂線段的長度均大於半徑時,就沒有重疊;否則就存在重疊。疏漏之處,敬請指正。
來個更粗暴的, 如果可以自己定坐標系最好, 以圓心為原點, 以和矩形邊平行的直線為軸. 如果給定坐標系就換算, 會得到四個值, x1, x2, y1, y2, 四個頂點點的坐標是這兩組值兩兩組合.
然後就剩兩步,
x1x2&<0 y1y2&<0, 說明圓心在矩形內部, 必重疊
x1x2&>0 y1y2&>0, 則取 xm = min(x1, x2) 和 ym = min(y1, y2) 即最近頂點距離, 小於半徑即有重疊;
否則 d = min(x1, x2, y1, y2) , |d| &< r 則重疊
這裡h是個向量把,應該怎麼取,要先轉換成以矩形中心為原點的坐標系么
四條線段與圓是否相交 矩形中心是否在圓內 圓心是否在矩形內
誰能講講這個問題有啥具體實用價值
把矩形四個頂點和圓心應用同一個平移旋轉變換,該變換使矩形中心落在坐標原點,並使矩形的各邊與坐標軸平行。然後那個矩形就是自己的AABB了,分別比較坐標即可。
以前搞過一下類似的問題,有點懷念,來打個醬油。
2011年MCM的B題MCM/ICM Problems,涉及用圓覆蓋一個區域的問題。算是題主問題的推廣。當時用的辦法和當前某個匿名用戶的回答類似,就是用像素的辦法判斷。畢竟要覆蓋一個任意區域,而且涉及很多個小圓之間的相互重疊,沒有特別簡單的辦法。
首先考慮所需精度,把欲覆蓋的對象和用於覆蓋的對象都離散化。我是用MATLAB實現的,用了裡面的邏輯數組儲存相關的數據。在判斷是否覆蓋時,用邏輯與就行了。考慮問題的複雜度,我覺得這個效率還比較高。當時還得優化覆蓋方式,使圓最少。用基於像素的辦法,數字圖像處理的技術也可以用上,以完成一些更複雜的操作,比如尋找未覆蓋區域之類的。
唉,不過我大概答偏了吧。哼╭(╯^╰)╮,乃們都太文藝了,這才素真正的粗暴。
首先把圓形內部像素塗成紅的,
然後把矩形內部像素塗成藍的,
如果有像素被賦值兩次,
那麼兩圖形重疊~1. 連接矩陣中心到圓心。
2. 如果圓心到這條線段與矩陣的交點大於圓的半徑,則不重疊。
推薦閱讀: