ページ 11

レイを使ったメッシュとの当たり判定

Posted: 2010年12月02日(木) 23:17
by LNE
初めまして、現在Direct3D9を使用し3Dでの横スクロール型アクションのようなゲームを制作しております。

自キャラ足元よりY軸マイナス方向にレイを飛ばし、床(ステージ)メッシュとの接触点距離を返す関数を作りました。
現在の主な流れを大まかに記述しますと、

コード:

 

void rayControl()
{
  ・レイの始点座標、方向ベクトルの代入
  始点座標は、自キャラ足元。 ベクトルはY軸マイナス方向、真下ですね
    posが、自キャラ座標となっています。
 ・bool hitCheck_Stage関数の呼び出し
  引数は、上記レイ情報2種と、最終的な接触点距離を格納する distance変数
  if( map->hitCheck_Stage(&firstPos, &firstDir, &ray.distance) )
  {
	pos.y -= (ray.distance);
	pos.y += 1.0f;
  }
    else
    {
        pos.y --;
    }
}

bool hitCheck_Stage(D3DXVECTOR3* rayPos, D3DXVECTOR3* rayDir, float* distance)
{
  ・hitCheck_Stage関数では、マップの座標、スケールを元に、ワールド変換行列を作成し
   逆行列を算出しています。 その逆行列を使い、受け取ったレイを逆トランスフォームします。

  ・hitCheck_RayMeshを呼び出し、メッシュの格納されている配列番号(enum)と、逆トランスフォーム後の
   レイ情報、最終的な接触点距離を格納する為のdistanceを引数としています。
   hitCheck_RayMesh(eMeshID::MAP, &InvRayPos, &InvRayDir, distance)
     
  ・Raymesh関数で TRUE が返ってくるとと、この関数でも true が返ります。
}

 BOOL hitCheck_RayMesh(	int	meshNum,	//	メッシュ番号
					D3DXVECTOR3*	pos,		//	レイの始点座標
					D3DXVECTOR3*	direction,	//	レイの方向ベクトル
					float*			distance ) //	
{
	BOOL	hit;
	FLOAT	u, v, dist;
	DWORD	faceIndex;

	Mesh::sObj* obj = &Mesh::table[meshNum];

	D3DXIntersect(	obj->mesh,	//	ID3DXBaseMeshインターフェイスへのポインター
				pos,		        //	レイの始点座標
				direction,	    //	レイの方向ベクトル
				&hit,		        //	レイがメッシュの三角面に接触するときTRUEを返す
				&faceIndex,	//	pHitがTRUEの場合に、レイの始点に最も近い面のインデックス値を指す
				&u,			//	重心ヒット座標U
				&v,			//	重心ヒット座標V
				&dist,		//	接触点までの距離(ベクトルを正規化しておくと純粋な距離がでる)
				NULL,		//	全ての接触面情報を受け取ることができる
				NULL );		//	全ての接触面情報の件数

	if( hit )
	{
		*(distance) = (float)dist;
		return TRUE;
	}
    return FALSE;
}

このように、接触点距離を1番最初の関数内にあるray.distanceに格納し
自キャラY座標と計算を行い、床との距離を求めています。
この流れで一応、平面上での歩行はできているのですが、床とレイ始点までの距離が0?(重なる?)になると
判定がなくなりめり込んでしまうので、 床と接触をしている場合は常に pos.y に 1.0f をプラスしています。

長くなりましたが何が言いたいのかというと、この状態だと緩やかな斜面はめり込みもせず問題ないのですが
ちょっと急な斜面になりますと、地面にうまってしまいます。
もう一つは、下記のような段差

-----
      |
       ---------

で、飛び降りると目にもとまらぬ速さで着地してしまいます。 もっとふわっと降りてほしいのですが・・・

問題点は、・pos.yに直接、算出した床との距離を代入してしまっている点。
       ・pos.y += 1.0f や  pos.y --;
がとっても怪しいと感じているのですが、どのようにしたらいいかも分からず挫折寸前です・・・
長くなりましたが、何かご教授もらえたら幸いです。 よろしくお願いします。

 
中身を省略してしまっていますが、上記の3つの関数すべて記述してしまった方がよろしいでしょうか・・・?
とりあえず、次のレスで載せておきます。

Re: レイを使ったメッシュとの当たり判定

Posted: 2010年12月02日(木) 23:22
by LNE

コード:

 
プレイヤークラスメソッド rayControl()
void cPlayer::rayControl( )
{
	D3DXVECTOR3 firstDir, firstPos;

//	レイの始点座標
	ray.rayPos	= D3DXVECTOR3(pos.x, pos.y, pos.z);
//	レイの方向ベクトル
	ray.rayDir	= D3DXVECTOR3(0.0f, -1.0f, 0.0f);

//	自キャラから出るレイとステージの当たり判定を調べる
	firstPos	= D3DXVECTOR3(ray.rayPos.x, ray.rayPos.y, ray.rayPos.z);
	firstDir	= D3DXVECTOR3(0.0f, ray.rayDir.y, 0.0f);

	if( GP::map->hitCheck_Stage(&firstPos, &firstDir, &ray.distance) )
	{
		pos.y -= (ray.distance);
		pos.y += 1.0f;
	}
	else
	{
		pos.y --;
	}
}

ステージクラスメソッド hitCheck_Stage
bool cObj::hitCheck_Stage(D3DXVECTOR3* rayPos, D3DXVECTOR3* rayDir, float* distance)
{
	D3DXMATRIX matScale, matTrans, matInv;
			
//	拡大(縮小)行列を作成する
	D3DXMatrixScaling(	&matScale,
					1.0f,
					1.0f,
					1.0f);
//	平行移動行列を作成する
	D3DXMatrixTranslation(	&matTrans,
						pos.x,
						pos.y,
						pos.z);

//	各行列を統合してワールド変換行列を作成する
	D3DXMATRIX matW = matScale * matTrans;

//	逆行列算出
	D3DXMatrixInverse(&matInv, NULL, &matW);

//	レイの逆トランスフォーム
	D3DXVECTOR3 InvRayPos, InvRayDir;
	D3DXVec3TransformCoord(&InvRayPos, rayPos, &matInv);
	D3DXVec3TransformNormal(&InvRayDir, rayDir, &matInv);

	if( ML::Mesh::hitCheck_RayMesh(  	eMeshID::MAP,
								&InvRayPos,
								&InvRayDir,
								distance) )
	{
		return true;
	}
	return false;
}

Meshライブラリ 
BOOL hitCheck_RayMesh( int meshNum,    //  メッシュ番号
                    D3DXVECTOR3*    pos,        //  レイの始点座標
                    D3DXVECTOR3*    direction,  //  レイの方向ベクトル
                    float*          distance ) //   
{
    BOOL    hit;
    FLOAT   u, v, dist;
    DWORD   faceIndex;
 
    Mesh::sObj* obj = &Mesh::table[meshNum];
 
    D3DXIntersect(  obj->mesh,  //  ID3DXBaseMeshインターフェイスへのポインター
                pos,                   //  レイの始点座標
                direction,        //    レイの方向ベクトル
                &hit,                 //  レイがメッシュの三角面に接触するときTRUEを返す
                &faceIndex,           //  pHitがTRUEの場合に、レイの始点に最も近い面のインデックス値を指す
                &u,                       //  重心ヒット座標U
                &v,                       //  重心ヒット座標V
                &dist,                   //  接触点までの距離(ベクトルを正規化しておくと純粋な距離がでる)
                NULL,                   //  全ての接触面情報を受け取ることができる
                NULL );                 //  全ての接触面情報の件数
 
    if( hit )
    {
        *(distance) = (float)dist;
        return TRUE;
    }
    return FALSE;
}
 

Re: レイを使ったメッシュとの当たり判定

Posted: 2010年12月02日(木) 23:37
by ookami
posは、座標だけでなく、速度に関するパラメータはありますか?

すとっ
と落ちている直接の原因は、
> pos.y -= (ray.distance);
だと思います。

Re: レイを使ったメッシュとの当たり判定

Posted: 2010年12月02日(木) 23:45
by LNE
やはりそのあたりですよね・・・
現段階ですと、プレイヤークラス内に速度に関するパラメータを所持させてはいるものの
全く使用していない状況です。

仮の移動制御も、GetAsyncStateで座標を弄っているだけです。

Re: レイを使ったメッシュとの当たり判定

Posted: 2010年12月03日(金) 00:02
by ookami
> ふわっと降りてほしい

というのは、「放物線を描いて落下」といったイメージですよね。
速度パラメータを使って、こんな感じで実装するのがいいと思います。

コード:

// posの速度を(sx,sy,sz)とする
// yは「下」がマイナスとする

    // 変更前
    if( GP::map->hitCheck_Stage(&firstPos, &firstDir, &ray.distance) )
    {
        pos.y -= (ray.distance);
        pos.y += 1.0f;
    }
    
↓

    // 変更後
    pos.sy-=0.1; // 0.1は適当。大きいとより重力が強く働く。
    if(着地している) pos.sy=0;
    if(着地している && ジャンプキー) pos.sy=10; // 10は適当。大きいとより高くジャンプする。
    if( GP::map->hitCheck_Stage(&firstPos, &firstDir, &ray.distance) )
    {
        if(pos.sy>ray.distance) {
            pos.y+=pos.sy;
        } else {
            pos.y -= (ray.distance);
            pos.y += 1.0f;
        }
    }
コードの全体が無いので未検証ですが、
ご了承くださいませw

Re: レイを使ったメッシュとの当たり判定

Posted: 2010年12月05日(日) 14:49
by LNE
返信が遅れまして大変申し訳ございません・・・。
ありがとうございました! 重力の働く、ふわっという落ち方が実装できました・・!

ただやはり、空中に足場がある場合などの2層のようになっていると
うまくいってくれないようです・・・

3Dステージとの当たり判定について、こういう風がいいよという意見をお持ちの方
ぜひお願いします。