Redpoll's 60
 Home / 3Dプログラミング入門 / 第2章 $§$2-23
第2章 2D空間におけるオブジェクトの運動
$§$2-1 オブジェクトの初期状態$§$2-17 衝突判定 5
$§$2-2 行列による変換の詳細 1$§$2-18 初期状態における頂点情報の取得について
$§$2-3 行列による変換の詳細 2$§$2-19 衝突判定 6 (軸平行な長方形同士の衝突)
$§$2-4 自転と公転$§$2-20 衝突判定 7 (円盤 vs 長方形)
$§$2-5 一体化したオブジェクトの運動 1$§$2-21 衝突判定 8 (回転した長方形同士の衝突)
$§$2-6 一体化したオブジェクトの運動 2$§$2-22 衝突判定 9 (「倉庫番」プログラムの作成)
$§$2-7 一体化したオブジェクトの運動 3$§$2-23 衝突判定 10 (円盤 vs 三角形)
$§$2-8 指定方向へのオブジェクトの移動 1$§$2-24 衝突判定 11 (直線 vs 長方形、円盤、直線)
$§$2-9 指定方向へのオブジェクトの移動 2$§$2-25 円と直線による補間曲線 1
$§$2-10 指定方向へのオブジェクトの移動 3$§$2-26 円と直線による補間曲線 2
$§$2-11 指定方向へのオブジェクトの移動 4 (連射プログラムの実装)$§$2-27 その他の重要事項 1 (UnityのTransformクラスによる記述 ; カメラ移動の基本)
$§$2-12 指定方向へのオブジェクトの移動 5$§$2-28 その他の重要事項 2 (画面に表示されるXY平面の範囲 ; ミニマップの実装)
$§$2-13 衝突判定 1 (点 vs 円盤、長方形 ; ローカル座標からワールド座標への変換 2D)$§$2-29 その他の重要事項 3 (スクリーン座標からワールド座標への変換 2D; スクリーンショットの撮影範囲)
$§$2-14 衝突判定 2$§$2-30 課題 1
$§$2-15 衝突判定 3$§$2-31 課題 2
$§$2-16 衝突判定 4

$§$2-23 衝突判定 10 (円盤 vs 三角形)


本節では三角形と円盤との衝突判定について解説する。三角形と円盤の衝突判定はあまり見ることはないが、比較的簡単に実装することができる。




ここでは円盤の中心を $T$、半径を $r$ とし、三角形の各頂点を $A$、$B$、$C$ とする (各頂点の並び方は時計周りであることを前提とする(これを clockwise winding order という) )。

図1 円盤
図2 三角形

まず辺 $AB$ を含む直線を便宜上「直線 $A$」とし、その進行方向を $\overrightarrow{AB}$ とする。このとき直線 $A$ の進行方向に関して左側を領域 a、右側を領域 とする (図3)。
同様に辺 $BC$ を含む直線を「直線 $B$」、その進行方向を $\overrightarrow{BC}$ とし、この進行方向に関して直線 $B$ の左側を領域 b、右側を領域 とする (図4)。さらに辺 $CA$ を含む直線を「直線 $C$」、その進行方向を $\overrightarrow{CA}$ とし、この進行方向に関して直線 $C$ の左側を領域 c、右側を領域 とする (図5)。

  • 図3 直線 A よりも左側が領域 a、右側が領域 ⓐ
  • 図4 直線 B よりも左側が領域 b、右側が領域 ⓑ (直線の進行方向はBからC)
  • 図5 直線 C よりも左側が領域 c、右側が領域 ⓒ (直線の進行方向はCからA)


このとき直線 $A$、直線 $B$、直線 $C$ によって三角形の周囲は 6 個の領域に分割され、三角形自体を含めると下図に示されるように 7 個の領域に分割される。各領域は3つの文字で表されているが下図の直線 $A$ の左側の3つの領域は、直線 $A$ の左側なのでいずれの領域においても a が含まれ、直線 $A$ の右側4つの領域は直線 $A$ の右側なのでいずれの領域においても が含まれる。
例えば領域 a であれば、この領域は直線 $A$ の左側、直線 $B$ の右側、直線 $C$ の右側という意味であり、領域 b であれば、この領域は直線 $A$ の右側、直線 $B$ の左側、直線 $C$ の右側という意味である。

図6

ここで各領域を表す3つの文字を、丸で囲まれていないものを $0$、丸で囲まれているものを $1$ に置き換える (下図7)。このとき3つの文字は「010」「011」のような3桁の数字になるが、この3桁の数字を2進数として考えると各領域は下図に示されるように 1~7 の番号が割り当てられる (以下この番号によって各領域を [1]~[7] で表す。領域 [0] は存在しない)。

図7


円盤の中心 $T$ が領域 [5] にあったとしよう (下図8)。この場合には中心 $T$ が領域 [5] のどの位置あっても、円盤から最も近い三角形の部分は辺 $BC$ である。そのため円盤が辺 $BC$ に触れることなく三角形に衝突することはできない。したがって中心 $T$ が領域 [5] にある場合には、円盤と三角形の衝突判定は円盤と辺 $BC$ の衝突判定に帰着するわけである。
この場合には中心 $T$ と辺 $BC$ の最短距離が半径 $r$ より大きいかどうかを調べればよい (ある点と線分の最短距離の求め方については後述する)。

図8


円盤の中心 $T$ が領域 [2] にあったとする (下図9)。中心 $T$ が領域 [2] にある限りその位置がどこであっても、円盤から最も近い三角形の部分は辺 $CA$ または辺 $AB$ である。そのため円盤の中心 $T$ が領域 [2] にある場合には、辺 $CA$ あるいは辺 $AB$ のいずれかに触れることなしに三角形と衝突することはできない。したがってこの場合には円盤と三角形の衝突判定は、円盤と辺 $CA$ あるいは円盤と辺 $AB$ の衝突判定に帰着するわけである。

図9

中心 $T$ が他の領域にある場合も同様である。例えば $T$ が領域 [1] にある場合には円盤と三角形の衝突判定は、円盤と辺 $AB$ あるいは円盤と辺 $BC$ の衝突判定に帰着する。$T$ が領域 [3] にある場合には円盤と三角形の衝突判定は円盤と辺 $AB$ が衝突するかどうかを調べればよい。
つまり上記のように三角形及びその周囲を7つの領域に分割するとき円盤と三角形の衝突判定は、円盤とある1つの辺、あるいは(多くても)2つの辺との衝突判定に帰着するのである ($T$ が領域 [7] にある場合にはもちろん衝突している)。

したがって、この方法においては以下のようなテーブルを事前に用意しておくと効率的である。
const int A = 0, B = 1, C = 2;    // 頂点 A、B、C (clockwise)

i_vi[1, 0] =  A;
i_vi[1, 1] =  B;
i_vi[1, 2] =  C;

i_vi[2, 0] =  C;
i_vi[2, 1] =  A;    
i_vi[2, 2] =  B;    

i_vi[3, 0] =  A;
i_vi[3, 1] =  B;
i_vi[3, 2] = -1;

i_vi[4, 0] =  B;
i_vi[4, 1] =  C;    
i_vi[4, 2] =  A;

i_vi[5, 0] =  B;
i_vi[5, 1] =  C;
i_vi[5, 2] = -1;

i_vi[6, 0] =  C;
i_vi[6, 1] =  A;
i_vi[6, 2] = -1;

i_vi[7, 0] = -1;
i_vi[7, 1] = -1;
i_vi[7, 2] = -1;


i_viint[,]型のインスタンス変数であり(2次元配列)、各要素は各領域において衝突判定で使われる辺(の頂点)を表している。i_vi[i, j] の1次元目は領域であり、i_vi[1, j] ならば領域 [1]、i_vi[2, j] ならば領域 [2] のデータを意味する (そのため i_vi[1, j]~i_vi[7, j] までが用意されている。領域 [0] は存在しないので i_vi[0, j] は使われない)。
例えば中心 $T$ が領域 [2] にある場合には円盤と辺 $CA$ または辺 $AB$ との衝突判定を行うので i_vi には
i_vi[2, 0] =  C;
i_vi[2, 1] =  A;
i_vi[2, 2] =  B;
としてセットされている。
同様に $T$ が領域 [4] にある場合には辺 $BC$ または辺 $CA$ との衝突判定を行うので i_vi には
i_vi[4, 0] =  B;
i_vi[4, 1] =  C;    
i_vi[4, 2] =  A;
としてセットされている。
$T$ が領域 [5] にある場合には衝突判定は辺 $BC$ のみが対象となるので i_vi には
i_vi[5, 0] =  B;
i_vi[5, 1] =  C;
i_vi[5, 2] = -1;
としてセットされている (1つの辺のみと衝突判定を行う場合には2次元目の最後の要素に $-1$ がセットされる)。


上で見たように3つ直線の右側か左側かに分けることで、三角形及びその周囲は自動的に7つの領域に分割されるが、XY平面上のある点が指定の直線の右にあるか左にあるかは次のようにして調べることができる。

図10 点A、Bは直線上の2点 (P が直線よりも右にある場合)
図11  P が直線よりも左にある場合

XY平面上に引かれた直線上の2点を $A$、$B$ とし、直線外に置かれた点を $P$ とする。また、$\overrightarrow{AP} = (a_x,\ a_y)$、$\overrightarrow{AB} = (b_x,\ b_y)$ とする。
このとき、点 $P$ が直線の右側にある場合には $a_x b_y - a_y b_x > 0$ であり、直線の左側にある場合には $a_x b_y - a_y b_x < 0$ である ($=0$ のときは $P$ は直線上にある。詳しくは 3-6節参照)。
つまり、点 $P$ が2点 $A$、$B$ を通る直線の右にあるか左にあるかを調べるには $a_x b_y - a_y b_x$ の符号を調べればよいわけである。例えば上図10の場合はこの符号はプラスになり、図11の場合にはマイナスになる。


最終的に円盤と三角形の衝突判定は次の形にまとめられる。
[CollisionTest_Disk_Triangle(..)  (24/04/01 修正版)]
bool CollisionTest_Disk_Triangle(Vector2 T, float r,  Vector2[] vrts)
{
    float rr = r * r;
    int k = 0;

    // AT x AB
    Vector2 a = (T - vrts[0]);
    Vector2 b = (vrts[1] - vrts[0]);
    if (a.x * b.y - a.y * b.x >= 0.0f) { k += 4; }

    // BT x BC
    a = (T - vrts[1]);
    b = (vrts[2] - vrts[1]);
    if (a.x * b.y - a.y * b.x >= 0.0f) { k += 2; }

    // CT x CA
    a = (T - vrts[2]);
    b = (vrts[0] - vrts[2]);
    if (a.x * b.y - a.y * b.x >= 0.0f) { k += 1; }

    if (k == 7) { return true; }

    int i0 = i_vi[k, 0];
    int i1 = i_vi[k, 1];
    int i2 = i_vi[k, 2];
    return (ComputeSqrDistance_Point_Segment(T, vrts[i0], vrts[i1]) <= rr) ||
            (i2 != -1 && ComputeSqrDistance_Point_Segment(T, vrts[i1], vrts[i2]) <= rr);
}



引数には円盤の中心 $T$、半径 $r$、三角形の頂点がセットされたVector2型配列 vrts が送られてくる (頂点 $A$ が vrts[0]、$B$ が vrts[1]、$C$ が vrts[2] にセットされており、上でも述べたように $A$、$B$、$C$ の並びは時計周りである)。
このメソッドでは最終的に円盤の半径 $r$ との大小を比較するが、実際に使うのは $r$ ではなく、効率化のためその2乗で比較する。3行目の rr はそのための変数である。それに関連して比較の際にはベクトルの大きさの2乗を表すプロパティ sqrMagnitude が使われる。例えば v.sqrMagnitude であればベクトル $\boldsymbol{\mathsf{v}}$ の大きさの2乗 $|\boldsymbol{\mathsf{v}}|^2$ のことであり、(B - A).sqrMagnitude であればベクトル $\overrightarrow{AB}$ の大きさの2乗 $|\overrightarrow{AB}|^2$ を意味する。
まず19行目までにおいて、円盤の中心 $T$ が7つの領域のうちどの領域に含まれているのかを調べるが、それは $T$ が3つの直線の右にあるか左にあるかを調べることに帰着する。7~9行目が直線 $A$、12~14行目が直線 $B$、17~19行目が直線 $C$ に関する処理である。3つの直線のそれぞれに対し、右側にあるか左側にあるかを調べることで上で述べたように各領域が、ba のように3つの文字として表される (上図6)。
そしてこの3つの文字を2進数に変換すると(丸で囲まれていなければ $0$、囲まれているならば $1$)、「101」「011」のようになるがこれを10進数に変換した値が領域の番号である。10進数に変換するには「101」であれば $4 + 0 + 1 = 5$、「011」であれば $0 + 2 + 1 = 3$ であるが、9行目の k += 4、14行目の k += 2、19行目の k += 1 はこの2進数から10進数への変換の計算である (ローカル変数 k は $T$ が含まれる領域番号を表す)。
中心 $T$ がどの領域にあるかが分かれば円盤と三角形の衝突判定は、円盤と1つの辺、あるいは多くても2つの辺との衝突判定に帰着するが、23行目以降がその処理である ($T$ が領域 [7] にある場合には衝突しているものとして、その時点で処理を返す(21行目) )。

三角形のある辺と円盤が衝突しているかどうかは、円盤の中心とその辺との最短距離を求め、その最短距離が円盤の半径より大きいかどうかを調べればよい。すなわち、点と線分の最短距離を求めることに帰着するわけである。
26、27行目の ComputeSqrDistance_Point_Segment(..) は点と線分の最短距離を求め、その2乗を返すメソッドである。その内容を以下に示す。
[ComputeSqrDistance_Point_Segment(..)]
float ComputeSqrDistance_Point_Segment(Vector2 P, Vector2 A, Vector2 B)
{
    Vector2 AB = B - A;
    Vector2 AP = P - A;

    float dot1 = Vector2.Dot(AP, AB);
    if(dot1 < 0.0f)
    {
        return AP.sqrMagnitude;
    }

    float dot2 = AB.sqrMagnitude;
    if(dot1 > dot2)
    {
        return (P - B).sqrMagnitude;    
    }

    return AP.sqrMagnitude - (dot1 * dot1) / dot2;
}


このメソッドは具体的には、引数として送られてくる点 $P$ と線分 $AB$ の最短距離の2乗を計算して返すものである。

例えば点 $P$ と線分 $AB$ が次のような位置関係にあったとしよう。

図12 点 P と線分 AB

このとき、$P$ と $AB$ 間が最短になるのは $P$ から $AB$ に下ろした垂線の足 $Q$ においてであり、$|PQ| = d$、$AP$ と $AB$ のなす角を $\theta$ とすれば $d = |AP|\sin\theta$ であるから\[ d^2 = |AP|^2 - (|AP|\cos\theta)^2 \]また $AP\cdot AB = |AP||AB|\cos\theta$ であるから\[ \frac{(AP\cdot AB)^2}{|AB|^2} = |AP|^2 \cos ^2 \theta \]したがって、\[ d^2 = |AP|^2 - \frac{(AP\cdot AB)^2}{|AB|^2} = AP\cdot AP - \frac{(AP\cdot AB)^2}{AB\cdot AB} \]として $d$ (の2乗)が求められる。
ただし上図の $P_1$ や $P_2$ の位置に $P$ がある場合には線分 $AB$ に垂線を下ろすことはできないので、このような状況では $P$ と線分 $AB$ の最短距離は $AP$ あるいは $BP$ となる。具体的には $AP\cdot AB < 0$ であれば最短距離は $AP$、 $AP\cdot AB > AB\cdot AB$  であるときは最短距離は $BP$ となる (上記のメソッドでいえば $AP\cdot AB < 0$ の場合には9行目、$AP\cdot AB > AB\cdot AB$ の場合には15行目で処理が返される)。





# Code1
次のプログラムは下図の円盤(Disk)と三角形(Triangle)の衝突判定を行うものである。
ここでもDiskの中心を $T$、三角形の各頂点を $A$、$B$、$C$ とする。

  • 図13 Disk 初期状態 (T はDiskの中心であり、初期状態では原点に置かれている)
  • 図14 Triangle 初期状態
  • 図15 Code1 実行結果

実行中のキー操作は以下のとおり。
H, J, K, L  :  Diskを上下左右に動かす。
R      :  Triangleを回転させる。
S, T  :  Triangleを拡大/縮小させる (Shiftと同時押しで縮小) 。

[Code1]  (実行結果 図15)
if (!i_INITIALIZED)
{
    Disk.SetPosition(-4, 0);
    
    i_scaleX = 2.0f;
    i_scaleY = 2.0f;
    Triangle.SetMatrix(TH2DMath.GetScale3x3(i_scaleX, i_scaleY));
    
    i_INITIALIZED = true;
}

// Disk
Vector2 T = Disk.GetPosition();

float speed = 0.10f;
if (Input.GetKey(KeyCode.H))
{
    T.x -= speed;
}
else if (Input.GetKey(KeyCode.L))
{
    T.x += speed;
}

if (Input.GetKey(KeyCode.J))
{
    T.y -= speed;
}
else if (Input.GetKey(KeyCode.K))
{
    T.y += speed;
}

Disk.SetPosition(T);

// Triangle
if (Input.GetKey(KeyCode.R) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.T))
{
    if (Input.GetKey(KeyCode.R))
    {
        i_rotDeg += THUtil.IsShiftDown() ? -0.5f : 0.5f;
    }
    else if (Input.GetKey(KeyCode.S))
    {
        i_scaleX += THUtil.IsShiftDown() ? -0.02f : 0.02f;
    }
    else if (Input.GetKey(KeyCode.T))
    {
        i_scaleY += THUtil.IsShiftDown() ? -0.02f : 0.02f;
    }
    
    THMatrix3x3 R = TH2DMath.GetRotation3x3(i_rotDeg);
    THMatrix3x3 S = TH2DMath.GetScale3x3(i_scaleX, i_scaleY);
    Triangle.SetMatrix(R * S);
}

// Collision Test
THMatrix3x3 M = Triangle.GetMatrix();
Vector2 A = Vector2.zero;              // A,B,C は時計周り
Vector2 B = M * new Vector3(0.8f, 1.8f, 1.0f);
Vector2 C = M * new Vector3(2.4f, 0.0f, 1.0f);
float r = 1.0f;

bool bColl = CollisionTest_Disk_Triangle(T, r, new Vector2[]{A, B, C});
Hit(bColl);


12~34行目がDiskに対する変換処理、36~55行目がTriangleに対する変換処理、それ以降が衝突判定である。





(以下の内容は以前に掲載していた「円盤 vs 三角形」のアルゴリズムである。しかし、上記のアルゴリズムの方が約2.5倍速いので実際には以下の内容を読む必要はない。参考のため一定期間の間だけ掲載を続けるが、いずれは削除する)


円盤の中心を $T$、半径を $r$ とし、三角形の各頂点を $A$、$B$、$C$ とする。

図16 円盤
図17 三角形

考え方は長方形と円盤の衝突判定(2-20節)と同じである。まず 三角形 $ABC$ を円盤の半径 $r$ 分だけ拡張した三角形 $DEF$ を用意する (図18)。$\triangle{ABC}$ と $\triangle{DEF}$ の対応する各辺は平行であるから、この2つの三角形は相似である。したがって、$\angle{A} = \angle{D}$、$\angle{B} = \angle{E}$、$\angle{C} = \angle{F}$ である。

図18 三角形DEF (三角形ABCを円盤の半径分だけ拡張したもの)
図19

$\triangle{DEF}$ の各頂点の座標は次のように求められる (どの頂点でも同じであるからここでは頂点 $D$ の場合で見ていく)。
$\angle{A}\ (= \angle{D}) = \theta$ とおく (図19)。また 頂点 $A$ から辺 $DE$、$DF$ に下ろした垂線の足を $P$、$Q$ とする。このとき、図19に示されるように $\triangle{APD}$ と $\triangle{AQD}$ は直角三角形であり、$AP = AQ\ (= r)$ であるから、$DP = DQ\ (= \sqrt{AD^2 - r^2})$ である。
すなわち $\triangle{APD}$ と $\triangle{AQD}$ は合同であり、$\angle{ADP} = \angle{ADQ} = \theta/2$ であることがわかる。辺 $DP$ の長さを $a$ とすれば、$\triangle{APD}$ は直角三角形であるから三角関数の公式より\[\frac{r}{a} = \tan{\frac{\theta}{2}} = \sqrt{\frac{1-\cos\theta}{1+\cos\theta}} \]となるが、一番右の辺はさらに
\[\sqrt{\frac{1-\cos\theta}{1+\cos\theta}} = \frac{\sqrt{1-\cos\theta}}{\sqrt{1+\cos\theta}} \frac{\sqrt{1-\cos\theta}}{\sqrt{1-\cos\theta}} = \frac{1-\cos\theta}{\sqrt{1-\cos^2\theta}} = \frac{1-\cos\theta}{\sin\theta}\]
と変形することができる。
したがって、\[a = \frac{r\sin\theta}{1 - \cos\theta} \quad\quad (0^\circ < \theta < 180^\circ)\]である 。
$\overrightarrow{AB}$ の単位ベクトルを $\boldsymbol{u_1}$、$\overrightarrow{AC}$ の単位ベクトルを $\boldsymbol{u_2}$ とすれば\[ \boldsymbol{u_1}\cdot \boldsymbol{u_2} = \cos\theta \]である。
また $\overrightarrow{AP}$ の単位ベクトルを $\boldsymbol{\mathsf{w}}$ とすれば、$\boldsymbol{\mathsf{w}}$ は $\boldsymbol{u_1}$ を $90^\circ$ 回転させたベクトルであり、$AB /\!/ DE$ であるから $\overrightarrow{DE}$ の単位ベクトルも $\boldsymbol{u_1}$ であり、結局 頂点 $D$ は\[ D = A + r\boldsymbol{\mathsf{w}} - a\boldsymbol{u_1}\]として求められる (図19)。

図20 aD, aE, aF
残りの2つ頂点 $E$、$F$ の求め方も同様である。
なお頂点 $D$ を求める際に、頂点 $A$ から辺 $DE$、$DF$ に下ろした垂線の足を $P$、$Q$ とし、$DP (= DQ)$ の長さを $a$ としたが、便宜上 以降の文章中ではこの $a$ を $aD$ と表記する。
同様に頂点 $E$ を求める際にも頂点 $B$ から辺 $EF$、$ED$ に垂線を下ろし、その垂線の足を $P'$、$Q'$ とすれば $EP' = EQ'$ となるが、この長さを $aE$ と表記する。頂点 $F$ の場合にも同様に頂点 $C$ から2つの辺 $FD$、$FE$ に垂線を下ろした際、それらの垂線の足と $F$ との距離を $aF$ と表記する (図20)。


実際に三角形と円盤が衝突しているとみなされるのは両者に重なりがある場合であり、具体的には円盤の中心 $T$ が下図22の赤い領域に含まれている場合である。

  • 図21 円盤と三角形が衝突している状態
  • 図22
  • 図23

図23に示されるようにこの赤い領域は $\triangle{DEF}$ の3つの頂点部分を切り取ったもので、赤い領域の3つの角部分はそれぞれ内側の三角形の頂点 $A$、$B$、$C$ を中心とする半径 $r$ の扇形になっており、3つの扇形を合わせると半径 $r$ の円になる。

円盤の中心 $T$ が赤い領域に含まれているかどうかは次の手順で調べていけばよい。

(1)  $T$ が $\triangle{DEF}$ に含まれているか。

(2)  $T$ が $\triangle{DEF}$ に含まれているならば、$T$ が下図24の赤い領域に含まれているか (この赤い領域は $\triangle{DEF}$ から頂点 $D$ を中心とする半径 $aD$ の円、頂点 $E$ を中心とする半径 $aE$ の円、頂点 $F$ を中心とする半径 $aF$ の円を切り取った領域である)。

(3)  $T$ が $\triangle{DEF}$ に含まれているが図24の赤い領域には含まれないのであれば、$T$ は図24の3つの緑の部分のいずれかにあることになるが、この場合には頂点 $A$、$B$、$C$ を中心とする半径 $r$ の円に含まれているかを調べる。それは $T$ が図25の3つの赤い領域のいずれかに含まれているかを調べることと同じである。

図24
図25

すなわち、上記の (2) あるいは (3) で $T$ が赤い領域に含まれているとわかれば衝突しているということである (図24と図25の赤い領域を合わせたものが図22の赤い領域である)。

以下の CollisionTest_Disk_Triangle_OLD_VER(..) は今述べてきたことを実装したものである。
[CollisionTest_Disk_Triangle_OLD_VER(..)]
public static bool CollisionTest_Disk_Triangle_OLD_VER
                    (Vector2 T, float r, Vector2 A, Vector2 B, Vector2 C)
{
    Vector2 uAB = (B - A).normalized;
    Vector2 uAC = (C - A).normalized;
    Vector2 uBC = (C - B).normalized;

    Vector2 u1 = uAB;
    Vector2 u2 = uAC;
    float aD = ComputeA(u1, u2, r);
    Vector2 w = new Vector2(-u1.y, u1.x);
    Vector2 D = A + r * w - aD * u1;

    u1 = uBC;
    u2 = -uAB;
    float aE = ComputeA(u1, u2, r);
    w = new Vector2(-u1.y, u1.x);
    Vector2 E = B + r * w - aE * u1;        

    u1 = -uAC;
    u2 = -uBC;
    float aF = ComputeA(u1, u2, r);
    w = new Vector2(-u1.y, u1.x);
    Vector2 F = C + r * w - aF * u1;

    bool inDEF = CollisionTest_Point_Triangle(T, D, E, F);
    if (inDEF)
    {
        if ((T - D).magnitude >= aD && (T - E).magnitude >= aE && (T - F).magnitude >= aF)
        {
            return true;
        }
        else if ((T - A).magnitude <= r || (T - B).magnitude <= r || (T - C).magnitude <= r)
        {
            return true;
        }
    }

    return false;
}

メソッドの冒頭では各辺の方向を表す単位ベクトルを計算している。8~24行目では $\triangle{ABC}$ を拡張した $\triangle{DEF}$ の各頂点を求めている。この箇所で使われているローカル変数 aDaEaF は上記の解説における $aD$、$aE$、$aF$ と同じであり、それらを算出する際に使われている補助メソッド ComputeA(..) は具体的には以下の内容である。

[ComputeA(..)]
float ComputeA(Vector2 u1, Vector2 u2, float r)
{
    float cosT = Vector2.Dot(u1, u2);
    float sinT = Mathf.Sqrt(1.0f - cosT * cosT);

    return r * sinT / (1.0f - cosT);
}

これは上の解説で述べた\[a = \frac{r\sin\theta}{1 - \cos\theta} \quad\quad (0^\circ < \theta < 180^\circ)\]を実装しただけである。

頂点 $D$、$E$、$F$ を求める際に使用する上記の u1w はそれぞれの場合で違う値であるが、どの場合でも wu1 を $90^\circ$ 回転させた方向を表す単位ベクトルである (ベクトル $(a,\ b)$ を $90^\circ$ 回転させると $(-b,\ a)$ になる)。
26行目の CollisionTest_Point_Triangle(T, D, E, F) は円盤の中心 $T$ が $\triangle{DEF}$ に含まれているかを調べるものであり、名前は異なるが処理内容自体は 2-15節の CollisionTest_Point_TriMesh(..) と同じである。
もし含まれているならば上図24の赤い領域に含まれているかを調べるが、その処理が29行目のifブロックである。図24の赤い領域に含まれていない場合は図25の赤い領域に含まれているかを調べるが、33行目のifブロックがその処理である。


















© 2020-2024 Redpoll's 60 (All rights reserved)