図0304a
コントローラを動かすとプレイヤーが移動します。しかし、奥のボックスに当たっても素通りしてしまいます。struct SPHERE { ///中心点の座標 Vec3 m_Center; ///半径 float m_Radius; //以下略 };
static bool SPHERE_SPHERE(const SPHERE& sp1, const SPHERE& sp2){ bsm::Vec3 d = sp1.m_Center - sp2.m_Center; float dist2 = bsm::dot(d,d); float radiussum = sp1.m_Radius + sp2.m_Radius; return dist2 <= radiussum * radiussum; }
struct OBB{ // 中心点の座標 bsm::Vec3 m_Center; // XYZ の各座標軸の傾きを表す方向ベクトル bsm::Vec3 m_Rot[3]; // OBB の各座標軸に沿った長さの半分(中心点から面までの長さ) bsm::Vec3 m_Size; //以下略 };
//-------------------------------------------------------------------------------------- /*! @brief pointから見たOBBの最近接点を得る @param[in] point 基準点 @param[in] obb OBB @param[out] retvec 最近接点を返す参照 @return なし */ //-------------------------------------------------------------------------------------- static void ClosestPtPointOBB(const bsm::Vec3& point, const OBB& obb, bsm::Vec3& retvec){ bsm::Vec3 d = point - obb.m_Center; retvec = obb.m_Center; float dist; for(int i = 0; i < 3; i++) { dist = bsm::dot(d,obb.m_Rot[i]); if(dist > obb.m_Size[i]) { dist = obb.m_Size[i]; } if(dist < -obb.m_Size[i]) { dist = -obb.m_Size[i]; } retvec += dist * obb.m_Rot[i]; } } //-------------------------------------------------------------------------------------- /*! @brief 球とOBBとの衝突判定 @param[in] sp 球 @param[in] obb OBB @param[out] retvec 最近接点が代入される参照 @return 衝突していればtrue */ //-------------------------------------------------------------------------------------- static bool SPHERE_OBB(const SPHERE& sp, const OBB& obb, bsm::Vec3& retvec){ ClosestPtPointOBB(sp.m_Center,obb,retvec); bsm::Vec3 v = retvec - sp.m_Center; return bsm::dot(v,v) <= sp.m_Radius * sp.m_Radius; }
void Player::OnUpdate2() { //Transformコンポーネントを取り出す auto PtrTrans = GetComponent<Transform>(); //SPHEREの作成 SPHERE sp; sp.m_Center = PtrTrans->GetPosition(); sp.m_Radius = 0.125f; //Rigidbodyを取り出す auto PtrRigid = GetComponent<Rigidbody>(); //現在の速度を取り出す Vec3 Velo = PtrRigid->GetVelocity(); //ステージ上のオブジェクトの配列を取得 auto& ObjectVec = GetStage()->GetGameObjectVec(); for (auto& v : ObjectVec) { if (v->FindTag(L"FixedBox")) { //FixedBoxというタグを持っているオブジェクトを検証 auto PtrBoxTrans = v->GetComponent<Transform>(); //ボックスのワールド行列を使ってOBBを作成 OBB obb(Vec3(1.0f, 1.0f, 1.0f), PtrBoxTrans->GetWorldMatrix()); //最近接点の変数 Vec3 Ret; if (HitTest::SPHERE_OBB(sp, obb, Ret)) { //衝突した(最近接点から法線を計算) Vec3 Normal = sp.m_Center - Ret; //正規化 Normal.normalize(); //反発を計算 Velo = XMVector3Reflect(Velo, Normal); //反発した速度を設定 PtrRigid->SetVelocity(Velo); //ボックスの領域からプレイヤーを追い出す Vec3 NewPos = Ret + Normal * 0.125f; PtrTrans->ResetPosition(NewPos); } } } }
図0304b
ゲーム上で何かが動くということはアニメーションをするということです。それはOnUpdateやOnDrowが呼ばれるタイミングで、約60分の1秒に一度更新や描画されます。
図0304c
赤い球は右から左に移動してます。1ターン内のどこかのタイミングでボックスと衝突します。ということは、大きな青い球がボックスと衝突するのと同じ意味になります。
図0304d
それを繰り返していくと、判別する球の半径が限りなく小さくなります。ある一定の大きさ以下になったら、その場所を衝突点として、衝突点が導かれるまでの分割数を求め、それを時間に換算します。1ターンは約60分の1秒ですから、例えば、480分の1秒後に衝突しているのが算出されるわけです。正確には衝突する直前を算出するわけですが、その関数は以下のようになります。以下は球対ボックス(OBB)の判定です。//-------------------------------------------------------------------------------------- /*! @brief Sphereと動かないObbの衝突判定 @param[in] SrcSp Srcの球 @param[in] SrcVelocity ソース速度 @param[in] DestObb DestのOBB @param[in] StartTime 開始時間 @param[in] EndTime 終了時間 @param[out] HitTime ヒット時間 @return 衝突していればtrue */ //-------------------------------------------------------------------------------------- static bool CollisionTestSphereObb(const SPHERE& SrcSp, const bsm::Vec3& SrcVelocity, const OBB& DestObb, float StartTime, float EndTime, float& HitTime){ const float m_EPSILON = 0.005f; SPHERE SrcSp2; float mid = (StartTime + EndTime) * 0.5f; SrcSp2.m_Center = SrcSp.m_Center + SrcVelocity * mid; SrcSp2.m_Radius = (mid - StartTime) * bsm::length(SrcVelocity) + SrcSp.m_Radius; bsm::Vec3 RetVec; if (!HitTest::SPHERE_OBB(SrcSp2, DestObb, RetVec)){ return false; } if (EndTime - StartTime < m_EPSILON){ HitTime = StartTime; return true; } if (CollisionTestSphereObb(SrcSp, SrcVelocity, DestObb, StartTime, mid, HitTime)){ return true; } return CollisionTestSphereObb(SrcSp, SrcVelocity, DestObb, mid, EndTime, HitTime); }
void AttackBall::OnUpdate2() { //Transformコンポーネントを取り出す auto PtrTrans = GetComponent<Transform>(); //SPHEREの作成 SPHERE sp; sp.m_Center = PtrTrans->GetPosition(); sp.m_Radius = 0.05f; //Rigidbodyを取り出す auto PtrRigid = GetComponent<Rigidbody>(); //現在の速度を取り出す Vec3 Velo = PtrRigid->GetVelocity(); //前回のターンからの時間 float ElapsedTime = App::GetApp()->GetElapsedTime(); //ステージ上のオブジェクトの配列を取得 auto& ObjectVec = GetStage()->GetGameObjectVec(); for (auto& v : ObjectVec) { if (v->FindTag(L"FixedBox")) { //FixedBoxというタグを持っているオブジェクトを検証 auto PtrBoxTrans = v->GetComponent<Transform>(); //ボックスのワールド行列を使ってOBBを作成 OBB obb(Vec3(1.0f, 1.0f, 1.0f), PtrBoxTrans->GetWorldMatrix()); float HitTime; if (HitTest::CollisionTestSphereObb(sp, Velo, obb, 0, ElapsedTime, HitTime)) { //ターン時間内のどこかで衝突した //ヒットする直前まで戻る //1ターン前の位置を取得 auto BeforePos = PtrTrans->GetBeforePosition(); //ヒット位置の計算 auto HitPos = BeforePos + Velo * HitTime; sp.m_Center = HitPos; Vec3 Ret; //最近接点を得る HitTest::SPHERE_OBB(sp, obb, Ret); //衝突した(最近接点から法線を計算) Vec3 Normal = sp.m_Center - Ret; //正規化 Normal.normalize(); //ボックスの領域からプレイヤーを追い出す //HitPosを新しい位置にすると、少しずれるので以下で求める Vec3 NewPos = Ret + Normal * 0.05f; float OtherTime = ElapsedTime - HitTime; //衝突後の余った時間をスライド処理 if (OtherTime > 0.0f) { //HitTime以降の残った時間はスライドさせる //thisと法線から直行線の長さ(内積で求める) float Len = dot(Velo, Normal); //その長さに伸ばす Vec3 Contact = Normal * Len; //スライドする方向は現在のベクトルから引き算 Vec3 SledeVelo = Velo - Contact; NewPos += SledeVelo * OtherTime; } //新しい位置を設定(ResetPositionを使用) PtrTrans->ResetPosition(NewPos); //反発を計算 Velo = XMVector3Reflect(Velo, Normal); //反発した速度を設定 PtrRigid->SetVelocity(Velo); } } } }
float HitTime;
if (HitTest::CollisionTestSphereObb(sp, Velo, obb, 0, ElapsedTime, HitTime)) {
//ターン時間内のどこかで衝突した //ヒットする直前まで戻る //1ターン前の位置を取得 auto BeforePos = PtrTrans->GetBeforePosition(); //ヒット位置の計算 auto HitPos = BeforePos + Velo * HitTime; sp.m_Center = HitPos;
//最近接点を得る HitTest::SPHERE_OBB(sp, obb, Ret);
//衝突した(最近接点から法線を計算) Vec3 Normal = sp.m_Center - Ret; //正規化 Normal.normalize(); //ボックスの領域からプレイヤーを追い出す //HitPosを新しい位置にすると、少しずれるので以下で求める Vec3 NewPos = Ret + Normal * 0.05f;
図0304e
これは、ある移動ターンで、球OがB地点まで移動しようとしていて時にA地点で衝突した場合のケースを示しています。
図0304f
まず、スライドするスピードを考えます。速度(Vecocity)は方向とスピードを両方持つベクトルですから、そのスピードです。これは、もともとのVelicityを衝突面(基準面)の法線への内積で求められます。float ADの長さ = Dot(ベクトルAO,正規化された基準面の法線);
Vec3 ベクトルAD = 正規化された基準面の法線 * ADの長さ;
Vec3 ベクトルAC = ベクトルAD - ベクトルAO;
ベクトルAO + ベクトルAC = ベクトルAD;
float OtherTime = ElapsedTime - HitTime; //衝突後の余った時間をスライド処理 if (OtherTime > 0.0f) { //HitTime以降の残った時間はスライドさせる //thisと法線から直行線の長さ(内積で求める) float Len = dot(Velo, Normal); //その長さに伸ばす Vec3 Contact = Normal * Len; //スライドする方向は現在のベクトルから引き算 Vec3 SledeVelo = Velo - Contact; NewPos += SledeVelo * OtherTime; }
//新しい位置を設定(ResetPositionを使用) PtrTrans->ResetPosition(NewPos);
//反発を計算 Velo = XMVector3Reflect(Velo, Normal); //反発した速度を設定 PtrRigid->SetVelocity(Velo);
//スライドした速度を設定 PtrRigid->SetVelocity(SledeVelo);