圓形與矩形碰撞(有旋轉矩形)

承上篇,如果是寬高與兩軸不平行,也就是經過旋轉的矩形,如何檢定他和圓形的碰撞?如下圖:


因為上一篇已經完成了圓形與擺正的矩形之碰撞檢定,那就可以將不平行的矩形轉正,利用已完成的函式來解決這個問題。首先要將矩形轉正,就需要找到角度,能夠直接從物件中得到旋轉角度是最好的(我自己設計遊戲物件就有做旋轉角度的 getter setter),沒有的話可能要考慮多寫一個方法。

如上圖,找到矩形從擺正到現在傾斜是轉了θ度的話,接下來考慮將圓形對矩形的中心旋轉 ( - θ ) 度。為什麼要這麼做呢?

其實是將圓形與矩形兩個都對矩形中心旋轉 ( - θ ) 度。如此一來兩個圖形就符合上一篇的前提,這樣來判斷相交情形,就代表原來的兩圖形是否碰撞的結果是一致的,因為對同一個旋轉中心旋轉同樣的角度,之前之後的兩圖形相對位置並沒有改變。再加上矩形本身對自己的中心旋轉,其寬的一半、高的一半和中心坐標,還是一樣,所以只需要考慮圓形對矩形中心旋轉後的結果,又半徑也不受旋轉影響,就剩會影響「兩中心點水平差」和「兩中心點鉛直差」的新圓心坐標了,所以結論是:

目標是找出圓心對矩形中心旋轉 ( - θ ) 後的新坐標

將圓形與矩形對矩形中心旋轉 ( - θ ) 得到的結果:


有旋轉得新坐標的方法是最好,不難也可以再重寫,步驟大概有:

  1. 先平移 ( - 矩形中心 ),即平移後坐標 = 圓心坐標 - 矩形中心坐標,
  2. 乘上 ( - θ ) 的旋轉矩陣 [ [cos(-θ) , - sin(-θ)] , [ sin(-θ) , cos(-θ) ] ]
  3. 再平移矩形中心坐標,坐標加上 矩形中心坐標

得圓形旋轉後新坐標,就可以引入前一篇的函式:

  public boolean isCircRotateRectCollide(Circle circ, Rectangle rect) {

    double theta = Math.toRadians(rect.Angle() * (-1));       // 旋轉負的角度

    Circle rotatedCirc = new Circle();

    int transX = circ.X() - rect.X();        // 平移
    int transY = circ.Y() - rect.Y();
    int newX = (int)(Math.cos(theta)*transX - Math.sin(theta)*transY) + rect.X();
    int newY = (int)(Math.sin(theta)*transX + Math.cos(theta)*transY) + rect.Y();
           // 乘上旋轉矩陣並平移回來
    rotatedCirc.setX(newX);            // 另外建立一個圓形物件,設定 x, y, r 
    rotatedCirc.setY(newY);
    rotatedCirc.setRadius(circ.Radius());
    
    return isCircRectCollide(rotatedCirc, rect);    // 藉由呼叫之前的函式
  }                                                 // 引入已轉正的圓形和矩形

不呼叫之前函式的話,就再寫一遍內容:

  public boolean isCircRotateRectCollide(Circle circ, Rectangle rect) {

    double theta = Math.toRadians(rect.Angle() * (-1));       // 旋轉負的角度

    int transX = circ.X() - rect.X();           // 平移
    int transY = circ.Y() - rect.Y();
    int newX = (int)(Math.cos(theta)*transX - Math.sin(theta)*transY) + rect.X();
    int newY = (int)(Math.sin(theta)*transX + Math.cos(theta)*transY) + rect.Y();
		
    int deltaX = Math.abs( newX - rect.X() );   // 用新坐標求水平差和鉛直差
    int deltaY = Math.abs( newY - rect.Y() );
    double halfWidth = rect.Width() / 2f;
    double halfHeight = rect.Height() / 2f;
    int radius = circ.Radius();

    if(deltaX > halfWidth + radius || deltaY > halfHeight + radius) {
      return false;
    }

    if(deltaX <= halfWidth || deltaY <= halfHeight) {
      return true;
    }

    double distance = Math.pow( deltaX - halfWidth, 2) + Math.pow( deltaY - halfHeight, 2);
    return distance < Math.pow(radius, 2);
  }

留言