PV3D2.1: 視線とメッシュの交差判定

2009年9月 5日
Papervision3Dでウォークスルーをさせようと思ってます。
キャラクターが水平な地面を動くだけなら壁の判定だけすればいいんですが
凹凸のある地形に沿って動かしたいので
足元のポリゴンの高さを調べる必要があります。
checkheight.png

Shockwave3DにはmodelsUnderRayっていう
レーザーを飛ばしてポリゴンに当たった座標が調べられる機能があったんで
Papervision3Dにもあるかなーって調べたけど見つかりませんでした。。
見つけられなかっただけで本当はあるのかもしれないけど、
諦めてmodelsUnderRay的な機能を作る事にしました。
やってることは、対象のメッシュモデルの全三角ポリゴンに対して、
視線と三角形が交差しているかひたすらチェックしていって、
交差しているものがあったら交差情報を返すという感じです。
まず三角ポリゴンと視線が交差しているかチェックする処理ですが、
レイトレース:3角形と視線の交差判定
ここのサイトを参考にして組んでみました。
import org.papervision3d.core.math.Number3D;
/**
 * レイが三角ポリゴンに当たっているかチェック(ポリゴンは頂点反時計周りで構成)
 * @param	pos	レイ放射開始地点
 * @param	vec	レイの方向
 * @param	p0	三角ポリゴンの頂点1
 * @param	p1	三角ポリゴンの頂点2
 * @param	p2	三角ポリゴンの頂点3
 * @param	isDoubleSided	裏向きのポリゴンも交差判定する
 * @return
 */
public function collideRayToTriangle(pos:Number3D, vec:Number3D, p0:Number3D, p1:Number3D, p2:Number3D, isDoubleSided:Boolean = false):Object {
	//レイの方向(正規化)
	var v:Number3D = vec.clone();
	v.normalize();
	//法線ベクトル
	var normal:Number3D = Number3D.cross(Number3D.sub(p1, p0), Number3D.sub(p2, p0));
	normal.normalize();
	var vn:Number = Number3D.dot(v, normal);
	//1.平面と平行なら交差なし(計算誤差を考慮)
	//2.片面チェックON時に視線と法線が同じ向き(裏側を見ている)なら交差なし
	if (Math.abs(vn) < 0.0000001 || (vn > 0 && !isDoubleSided)) return null;
	var xpn:Number = Number3D.dot(Number3D.sub(pos, p0), normal);
	var distance:Number = -xpn / vn;
	//交差位置が視線と逆方向なら交差なし
	if (distance < 0) return null;
	//視線と平面との交点
	var hit:Number3D = v.clone();
	hit.multiplyEq(distance);
	hit.plusEq(pos);
	//交点が三角形内にあるかチェック(計算誤差を考慮)
	var cross0:Number3D = Number3D.cross(Number3D.sub(hit, p0), Number3D.sub(p1, p0));
	if (Number3D.dot(cross0, normal) > 0.000000001) return null;
	var cross1:Number3D = Number3D.cross(Number3D.sub(hit, p1), Number3D.sub(p2, p1));
	if (Number3D.dot(cross1, normal) > 0.000000001) return null;
	var cross2:Number3D = Number3D.cross(Number3D.sub(hit, p2), Number3D.sub(p0, p2));
	if (Number3D.dot(cross2, normal) > 0.000000001) return null;
	var res:Object = new Object();
	res.position = hit;
	res.distance = distance;
	return res;
}
この関数に「レイを放射する開始地点」と「レイの方向ベクトル」、あと「三角ポリゴンの3つの頂点」
を渡すとレイがポリゴンと交差しているかが判定できます。
isDoubleSidedをtrueにすると裏向きのポリゴンも交差判定するようになります。
交差しない場合はnullが返ってきて、交差する場合は
交差点(position)と視点から交差点までの距離(distance)を含んだObjectが返ります。
もっと処理を軽くする方法がありそうだけど、ひとまずこれで。

あとはメッシュモデル内にある全三角ポリゴンと視線とを交差判定していけばおしまいなんですが・・・
三角ポリゴン(Triangle3Dクラス)の各頂点がモデルの基点から見た相対的なローカル座標なので、
モデルが回転していたりPapervision3Dとは座標系の違うcolladaファイルを使ってたりすると
正確な頂点座標でチェックできません(ここでしばらくつまづいてました)

Papervision3Dで頂点のローカル座標をワールド座標に変換する方法ですが、

  • 頂点ローカル座標にそのモデルの回転・移動・スケールを適用すればワールド座標になる
  • モデルの最終的な回転・移動・スケールはDisplayObject3D.world(Matrix3Dクラス)で調べられる
  • Matrix3D.multiplyVector(Matrix3D, Number3D);でNumber3Dに変形後の座標が適用される
という事らしいので、
Matrix3D.multiplyVector(DisplayObject3D.world, 頂点座標);
でワールド座標に変換できる事がわかりました。
ちなみにMatrix3Dクラスというのは変換行列を扱うクラスで、
オブジェクトの移動・回転・スケールがまとまったものらしいです。

(追記)
DisplayObject3D.worldはシーンをレンダリングするタイミングでしか値が更新されない気がします。
レンダリング以外にworld値を更新するメソッドか何かがあればいいんだけど今のところみつからず・・・
どうしても常に回転しているオブジェクトのworld値をレンダリング直前に調べたいって場合は
こっちのエントリーのコードで無理矢理調べる事もできます。


あとはワールド座標に変換した三角ポリゴンの3つの頂点で交差判定をしていきます。
import org.papervision3d.core.geom.renderables.Triangle3D;
import org.papervision3d.core.geom.TriangleMesh3D;
import org.papervision3d.core.math.Matrix3D;
import org.papervision3d.core.math.Number3D;
/**
 * 視線とメッシュの交差判定をする
 * @param	pos	レイ放射地点
 * @param	vec	レイの方向
 * @param	mesh	メッシュオブジェクト
 * @param	isDoubleSided	裏向きのポリゴンも交差判定する
 * @return
 */
public function collideRayToMesh(pos:Number3D, vec:Number3D, mesh:TriangleMesh3D, isDoubleSided:Boolean = false):Array {
	var res:Array = new Array();
	for each(var face:Triangle3D in mesh.geometry.faces) {
		var ps:Array = new Array();
		for (var i:int = 0; i < 3; i++) {
			var n:Number3D = face["v" + String(i)].toNumber3D();
			//ここでnをワールド座標に変換
			Matrix3D.multiplyVector(mesh.world, n);
			ps.push(n);
		}
		var hit:Object = collideRayToTriangle(pos, vec, ps[0], ps[1], ps[2], isDoubleSided);
		if (hit != null) {
			hit.triangle = face;
			res.push(hit);
		}
	}
	res.sortOn("distance", Array.NUMERIC);
	return res;
}
最初にメッシュモデル内の全ての三角ポリゴンを調べて、
各ポリゴンの3つの頂点をワールド座標にしてます。
ワールド座標にした頂点を使って交差判定をして、交差していれば配列に追加。
最後に交差結果の配列を交差点までの距離でソートして、 交差点が近い順に並べ替えています。
戻り値は交差情報を含んだObjectの配列で、交差したポリゴンの数だけ配列に格納されてます。
実際の使い方は↓こんな感じ。
//交差チェック(結果の配列が0なら交差なし)
var list:Array = collideRayToMesh(pos, vec, mesh, isDoubleSided);
//最初に交差した三角ポリゴン(Triangle3D)の参照
trace(list[0].triangle);
//最初の交差点までの距離(Number)
trace(list[0].distance);
//最初の交差点の座標(Number3D)
trace(list[0].position);
できたー。できたけど、多分重いんだろうな。。
サンプルはポリゴン少ないから問題なさそうだけど。
collidetest1.jpg

これでキャラクターの位置から真下にレイを飛ばして地面の高さを調べたり、
キャラクターの位置からカメラに向かってレイを飛ばして
カメラがポリゴン内に埋まっていないかをチェックできるかな。

トラックバック(0)

トラックバックURL: http://www.morocoshi.net/mt/mt-tb.cgi/5

コメントする