【AndroidでOpenGL ES】3D空間の距離を測る

distance_top

はじめに


2Dでも3Dでも、世の中に出ているゲームの大半は物理法則を人工的に埋め込んでいます。

敵と衝突したら一定量、逆のベクトルへ飛び、壁があればその先へ進むことができません。

プログラムでは、密着しているかどうかを判定することになりますが、同じように、木箱と一人称カメラの距離はどのくらいなのか?という問題はどうやって解決すればいいのでしょうか。

今回はAndroid×OpenGL ESで3次元上の2点間の距離を測ってみましょう。

具体的な手順


最初に、これを実現するための具体的な手順をまとめます。

1.3D空間上の2点の座標を特定する

2.2点間の距離を求める

3.(2)で求めた距離に応じた処理を加える

OpenGL ESで3Dオブジェクトの中心座標を特定する


まずは、3Dオブジェクトの中心座標を特定しない限りには何をすることもできません。

OpenGL ESの描画メソッドで、直接中心座標を指定してオブジェクトを空間に置くものは存在しません。

他の描画API、ライブラリにはDrawSphere()の1行で座標や大きさ、回転する等のオプションを設定した上で球体を描画することができたりしますが、OpenGL ESではonDrawFrame()内でglTranslatef()を使って同期を取るのが一番ラクです。

private float x, y, z;

/** 中略 */
gl.glTranslatef(x, y, z);

x, y, zの引数3つをフィールドに定義しておいて、外部から使いまわせるようにしておきましょう。

3D座標2つの距離を求める


pitag

ここで出てくるのが三平方の定理。ピタゴラスの定理のことです。

直角三角形の斜めの辺の長さがわかる定理ですが、2点の座標がわかれば、

2dot

これらを直角三角形に置き換えることができます。

2dot_pitag

でも直角三角形は平面上の図形だし、今やっているのは3Dだから3Dの図形で・・・と、いうことには、なりません。

実は”2DのZ軸は常に0”だからです。

3Dでも数値が1個増えるだけで、基本は同じくルートの中にZを追加してあげるだけです。

つまり、2Dでは√(x^2 + y^2 + 0^2)の0^2を省略しているわけですね。

2dot_pitag3d

ワールドの原点(0, 0, 0)からオブジェクトx, y, zの距離をコードに直すと、以下になります。

// Math.powは演算速度が遅いので手動で行う

float disX = (0 - x) * (0 - x);
float disY = (0 - y) * (0 - y);
float disZ = (0 - z) * (0 - z);

// 3Dの距離

float distance3D = (float) Math.sqrt(disX + disY + disZ);

求めた距離に応じた処理を行う


ここまで来たら距離に応じた処理を行います。

10m以内に来たら襲いかかってくる敵や、衝突形状(衝突を検知する物体の大きさ)1m圏内に他のオブジェクトが接近したら検知するなどがあります。

例として簡単に衝突検知を自作してみましょう。

2つのオブジェクトが空間上にあるとします。

collision_shape

グレーの部分の中に入ると衝突を検知します。

衝突形状は球体で、半径は1m。

さきほどの三平方の定理を使って、毎フレームで距離を測ります。

collision_shape-1m

赤と青の球の衝突形状の半径が1mだとすると、単純に2つのオブジェクトの距離が2mを下回ったら衝突です。

(距離 < 2つの衝突形状の半径の和)がtrueなら逆のベクトルへ移動を開始すれば、シンプルに物理法則を実現することができます。

ちなみに


余談ですが、例のようなアルゴリズムで衝突形状を複雑にしてしまうと、かなり処理が多くなってしまいます。

そのため多くの物理エンジンは、衝突検知に段階を設けています(JBulletでは、BroadPhaseとNarrowPhaseと呼ばれます)。

broadNarrow

普段は大きめの一つの衝突形状の検知のみを行い、そこからさらに近づいた時だけ細かく衝突した時の動作を処理し始めるという最適化を行っています。

すると、画像で言えば、近づいていなければ処理する作業は2分の1になります。

Androidは特に速度の問題が出やすいので、最適化は大事ですね。

参考サイト様

三平方の定理でゲームキャラの距離をわり出す。 – ブログ「サイバー少年」