【Unity】衝突判定をゼロから実装 -球と直方体-

ゼロから実装する衝突判定シリーズ第三弾、球と直方体の衝突判定です。

考え方

直方体は複雑なのでまず二次元の長方形で考えます。

下図のように、矩形の中心を原点、最小点の座標を  {P_{min}} 、最大点の座標を {P_{max}}とします。
円の半径は {r}、中心座標は {P_c}とします。

f:id:halya_11:20180904174336p:plain:w400

まず {P_{c}}のxy座標がいずれも {P_{max}}よりも大きい場合、最短距離は {P_c-P_{max}}の長さとなります。

f:id:halya_11:20180904175008p:plain:w400

次に {P_c}のx座標だけ {P_{max}}よりも大きく、y座標は矩形の範囲内にある場合を考えます。
この場合は下図のように、最短距離は {P_c}のx座標- {P_{max}}のx座標が表すベクトルの長さとなります。

f:id:halya_11:20180904175228p:plain:w400

同様に、 {P_c}のx座標が矩形の中にあり、y座標が最小点よりも小さい場合、
最短距離は {P_c}のy座標- {P_{min}}のy座標が表すベクトルの長さとなります。

f:id:halya_11:20180904175639p:plain:w250

このように、各軸について円の中心が矩形の内側にあるか外側にあるかを判定し、
外側にある場合のみ長さの計算に組み入れることで最短距離を求めることができます。

三次元の直方体に関しても軸が一つ増えるだけで考え方は同様です。

実装の前提

以前、球と球との当たり判定に関する記事を書きました。

light11.hatenadiary.com

下記のインタフェイスとクラスは上記の記事のものを流用します。

  • ICollider: コライダー用のインタフェイス
  • SampleCollideDetector: 毎フレームISphereとIColliderの衝突判定を行い、衝突していたら赤いSphereGizmoに描画する

実装

直方体型のコライダーを作ります。

using UnityEngine;

public class ColliderCube : MonoBehaviour, ICollider {

    [SerializeField]
    private Vector3 _center = Vector3.zero;
    public Vector3 Center { get { return _center; } }
    [SerializeField]
    private Vector3 _size = Vector3.one;
    public Vector2 Size { get { return _size; }  }
    
    private Transform _transform;

    private void Awake()
    {
        _transform = transform;
    }

    /// <summary>
    /// 球との当たり判定
    /// </summary>
    public bool CheckSphere(ISphere collider)
    {
        var cubeToLocal     = Matrix4x4.TRS(_center, Quaternion.identity, Vector3.one);
        var worldToCube     = cubeToLocal.inverse * transform.worldToLocalMatrix;

        // Cubeの空間における球の中心点
        var sphereCenter    = worldToCube.MultiplyPoint(collider.WorldCenter);

        // 距離の二乗を求める
        var sqLength = 0.0f;
        for (int i = 0; i < 3; i++) {
            var point   = sphereCenter[i];
            var boxMin  = _size[i] * -0.5f;
            var boxMax  = _size[i] * 0.5f;
            if (point < boxMin) {
                sqLength += (point - boxMin) * (point - boxMin);
            }
            if (point > boxMax) {
                sqLength += (point - boxMax) * (point - boxMax);
            }
        }

        // 距離の二乗が0だったらCubeの内部にSphereの中心があるということ
        if (sqLength == 0.0f) {
            return true;
        }

        return sqLength <= collider.Radius * collider.Radius;
    }

    private void OnDrawGizmos()
    {
        var cubeToLocal     = Matrix4x4.TRS(_center, Quaternion.identity, Vector3.one);
        var worldToCube     = cubeToLocal.inverse * transform.worldToLocalMatrix;

        // Gizmoの変数を変更
        var preColor        = Gizmos.color;
        var preMatrix       = Gizmos.matrix;
        Gizmos.color        = Color.blue;
        Gizmos.matrix       = worldToCube.inverse;
        
        // Cubeを描画する
        Gizmos.DrawWireCube(Vector3.zero, _size);

        // Gizmoの変数を戻す
        Gizmos.matrix       = preMatrix;
        Gizmos.color        = preColor;
    }
}

衝突判定は上記で説明した通りの方法で行っています。

結果

f:id:halya_11:20180815204822g:plain

正常に判定されていることが確認できました。

参考

その11 AABBと点の最短距離

今回はCubeの空間に座標変換することでAABBと球との距離を求めましたが、
OBBと球との距離を求める下記のような方法もあるようです。

その12 OBBと点の最短距離

関連

light11.hatenadiary.com

light11.hatenadiary.com