|
オブジェクトの配置ができたら、ナビゲーションメッシュに合わせてオブジェクトを移動してみます。
オブジェクトを移動するには、現在の位置(開始点)と新しい位置(終着点)を使います(以降、現在の位置から新しい位置までの動きをモーションと呼びます)。
終着点は、この時点ではナビゲーションのセルの内部にあるかどうかは気にする必要はなく、単純に移動したい位置を指定します。
モーションは開始点から終着点へ向かって、セルの繋がりを見ながらナビゲーション内を移動させて行きます。
このとき、オブジェクトの配置で行ったのと同じように、各座標をXZ平面の二次元座標に変換して計算します。
図6のようにモーションの開始点をP0、終着点をP1とします。
まず、現在のセルはCell0だとわかっているはずですので、P0からP1へ移動する際にCell0のどの辺を通過するかを調べます。
これは、オブジェクトの配置でも行った、辺と点との位置関係を使えばわかります。

(図6)
辺を通過しているという事は、P1が辺の法線とは逆方向にあるということになりますので、Cell0の各辺の法線とP1-V0の内積が負の値の時には、
モーションがその辺を通過しているかもしれないということになります。
かもしれないというのは、これだけでは辺をただの直線としか見ていないため、辺の法線とは逆方向に点があることが分かっても、
実際に辺を通過しているかどうかはわかりません。
例えば図7のように、線分P0P1は辺L1を通過していますが、辺の法線と逆方向であることだけで調べると、辺L0と辺L1の両方の外側にあることになります。

(図7)
辺の法線との内積結果を見るのは、複雑な計算を行う前の第一段階のテストです。
このテストをパスした場合には、本当に辺を通過しているかどうか、線分と線分の交差判定を行って調べます。
線分の交差判定には様々な方法があるかと思いますが、ここでは線分の交差を、
図8・図9のように平行四辺形の面積を使って判定します。同時に交点も求まります。P0P1はモーションの線分、V0V1は辺の線分です。
緑色で塗りつぶした部分は、モーションと辺の線分を少し移動して考えてみるとわかると思いますが、
P0P1とV0V1で構成される平行四辺形の面積です。
この面積は、ベクトルの外積の大きさはその2つのベクトルで構成される平行四辺形の面積と等しいことから、
P1-P0ベクトルとV1-V0ベクトルの外積で計算できます。
次に、どの位置で線分が交差しているのかを調べるために、先ほどと同じ方法でV0P0とV0V1で構成される平行四辺形の面積を計算します。
この面積は、図9の紫色で塗りつぶした部分になります。

(図8)

(図9)
ここで、緑色の部分と紫色の部分を重ねてみます。
図10の①と②の部分の面積は同じですので、移動して図11のように考えます。

(図10)

(図11)
ここまで出来たら交点の計算は簡単です。P1-P0ベクトルがV1-V0ベクトルと交差する位置は、
先ほど計算した2つの平行四辺形の面積比率と同じ比率で分断されている事が図11より分かります。
P1-P0ベクトルをVecP1P0、緑色の平行四辺形の面積をArea1、紫色の平行四辺形の面積をArea2とすると、
交点Intersectionは次の式で求めることができます(Normalはベクトルの正規化、Lengthはベクトルの長さを求める関数です)。
Intersection = P0 + Normal(VecP1P0) * (Length(VecP1P0) * (Area2 / Area1))
しかし、まだこの時点では直線上での交点が分かっただけですので、線分同士の交差判定が必要です。
判定を行う前に、まず紫色の平行四辺形の面積を計算した時と同じ方法で、V0P0とP0P1で構成される平行四辺形の面積を計算し、
図12のように考えます。

(図12)
この水色の平行四辺形の面積をArea3とすると、線分P0P1と線分V0V1の関係は、次のように考えることができます。
- Area1が0.0の時、P0P1とV0V1は平行です。
- Area1が0.0かつArea2が0.0の時、P0P1とV0V1は平行かつオーバーラップして(重なって)います。
- Area2がArea1以下かつArea3がArea1以下の時、線分P0P1と線分V0V1は交差しています。
- Area2がArea1より大きく、Area3がArea1以下の時、線分V0V1はP0,P1を通る直線と交差します。
- Area2がArea1以下で、Area3がArea1より大きい時、線分P0P1はV0,V1を通る直線と交差します。
- 上記の全ての条件に当てはまらない時、P0,P1を通る直線とV0,V1を通る直線は交差します。
これらの関係を、簡単に条件分岐で書いてみると以下のようになります。
Area2とArea1の比率(Area2 / Area1)をFactorP、Area3とArea1の比率(Area3 / Area1)をFactorVとします。
if(Area1 == 0.0){
if(Area2 == 0.0){
}
else{
}
}
else{
if((FactorP > 0.0) && (FactorP <= 1.0) && (FactorV >= 0.0) && (FactorV <= 1.0)){
}
else if((FactorV >= 0.0) && (FactorV <= 1.0)){
}
else if((FactorP >= 0.0) && (FactorP <= 1.0)){
}
else{
}
}
このように同時に様々な交差判定結果がわかりますが、今回は線分同士の交差判定が行えれば良いので、そこに着目してモーションの通過する辺を確定します。
通過する辺が分かったら、モーションと辺の交点で開始点(P0)を更新し、
通過する辺と隣接するセルがあるかどうか調べて、隣接セルがある場合はそのセルに移動し(図6の場合Cell1へ移動)、
今度はそのセルのどの辺を通過するのかを調べて行きます。
この作業を、モーションの終着点に到達するまで繰り返します。モーションの終着点に到達したかどうかは、
オブジェクトの配置でも行ったように、セルの全ての辺の法線と終着点との位置関係を内積で調べ、結果が全て正になればそのセル内部に終着点があることになり、
目的のセルへ到達したことになります。
目的のセルへ到達したら、XZ平面で計算を行っていたので、到達したセルの平面方程式と終着点のXZ座標からY座標を再計算し三次元座標に戻します。
あとは、その位置にオブジェクトを置けば、無事にナビゲーションメッシュでの移動は完了です。
もし、終着点に到達する前に隣接するセルがなくなってしまった場合は、壁に衝突したことになるので、
衝突した点でとめたり、跳ね返らせたり、壁に合わせてずったりして補正をしてください。
補正方法はゲームの仕様によって様々ですので、何が最適かというのはありません。
モーションをナビゲーションメッシュを元に移動させる一連の流れを仮想言語で書くと次のようになります。
P0
P1
CellVertex[3]
LineNormal[3]
Inside
loop{
Inside = 0
for(i = 0; i <= 2; ++i){
if(dot(P1 - CellVertex[i], LineNormal[i]) < 0.0){
if(線分P0P1と線分CellVertex[(i + 1) % 3]CellVertex[i]が交差した){
if(隣接セルがある){
隣のセルに移動
CellVertex = 隣のセルの頂点
P0 = 線分P0P1と線分CellVertex[(i + 1) % 3]CellVertex[i]の交点
break;
}
else{
壁に衝突
break;
}
}
}
else{
++Inside
}
}
if(Inside == 3){
終着点に到着
break;
}
else if(壁に衝突した){
衝突の補正をする
P1 = 補正後の座標
break;
}
}
P1.y = 最終セルの平面方程式とP1のXZ平面の座標からY座標を計算
移動終了(P1が実際の終着点)
|