306.衝突判定と反発

 衝突判定は後処理も重要になります。
 前項で紹介した衝突判定の自作では反発処理は一定の固定値で行っていました。
 動かない物体との衝突の場合はそれでもいいのかもしれませんが、相手が(今は止まっていても)動く場合、あるいは動いている物体同士の後処理は計算しなければいけません。
 たとえて言うならビリヤードのような動きを実装する場合はどうしたらいいか、ということです。
 BaseCrossには単純ですが摩擦や空気抵抗を考えない形で後処理が実装されています。

 まずFullSample306を実行してみましょう。以下のような画面が出てきます。

 

図0306a

 コントローラのAボタンを押すと、左側のボールが動き、右のボールに当たります。すると、左のボールは止まって、右側だけが動きます。

衝突判定の後処理

 この処理は、ニュートンの第3法則(作用・反作用の法則)とも呼ばれ、ようは左のボールの力がそのまま右のボールに移動するという処理です。
 この処理は、SharedLibプロジェクトCollision.cppCollision::AfterCollision()関数に実装があります。
 以下抜粋ですが
void Collision::AfterCollision(const shared_ptr<Collision>& DestColl, 
    const bsm::Vec3& SrcVelocity, const bsm::Vec3& DestVelocity, 
    const bsm::Vec3& HitNormal,float AfterHitTime) {

//中略

    auto PtrRigid = GetGameObject()->GetComponent<Rigidbody>(false);
    if (PtrRigid) {
        switch (GetIsHitAction()) {
        case IsHitAction::Slide:
        {
            auto DestVelocity = PtrDestTransform->GetVelocity();
            Slide = pImpl->Slide(SrcVelocity, HitNormal);
            PtrRigid->SetVelocity(Slide);
            //重力は0にする
            PtrRigid->SetGravityVelocityZero();
        }
        break;
        case IsHitAction::Auto:
            if (horizontal) {
                //乗っているときはスライドさせる
                Slide = pImpl->Slide(SrcVelocity, HitNormal);
                PtrRigid->SetVelocity(Slide);
                //何かに乗ったときは重力は0にする
                PtrRigid->SetGravityVelocityZero();
            }
            else {
                //乗ってないときは反発
                auto DestRigid = DestColl->GetGameObject()->GetComponent<Rigidbody>(false);
                float ResultPower = -(1.0f + PtrRigid->GetReflection());
                if (DestRigid) {
                    bsm::Vec3 RelativeVelo = SrcVelocity - DestVelocity;
                    ResultPower = (-(1.0f + PtrRigid->GetReflection()) * bsm::dot(RelativeVelo, HitNormal)) /
                        (bsm::dot(HitNormal, HitNormal) * (1 / PtrRigid->GetMass() + 1 / DestRigid->GetMass()));
                }
                else {
                    bsm::Vec3 RelativeVelo = SrcVelocity;
                    ResultPower = (-(1.0f + PtrRigid->GetReflection()) * bsm::dot(RelativeVelo, HitNormal)) /
                        (bsm::dot(HitNormal, HitNormal) * (1 / PtrRigid->GetMass()));
                }
                auto Velo = PtrRigid->GetVelocity();
                Velo += (HitNormal * ResultPower) / PtrRigid->GetMass();
                PtrRigid->SetVelocity(Velo);
            }
            break;
        case IsHitAction::Stop:
            {
                //速度は0にする
                PtrRigid->SetVelocityZero();
                //重力は0にする
                PtrRigid->SetGravityVelocityZero();
            }
            break;
        }
    }
}
 赤くなっているところで、反発する力を算出しています。最終的には現在の速度に質量で割った値を、衝突法線を基準に加算します。

 では、少しパラメータを変えて実行してみましょう。
 GameSources内のGameStage.cppGameStage::CreateBalls()関数を以下のように書き換えます。
//ボールの作成
void GameStage::CreateBalls() {
    //ボールの作成
    auto Ball1 = AddGameObject<Ball>(Vec3(-2.5f, 0.125f, 5.1f), true);
    //シェア配列にプレイヤーを追加
    SetSharedGameObject(L"Ball1", Ball1);
    auto Ball2 = AddGameObject<Ball>(Vec3(2.5f, 0.125f, 5.0f), false);
    SetSharedGameObject(L"Ball2", Ball2);
}
 赤くなっている値を変更します。そしてビルドして実行すると、今度は、ちょっと違う動きになります。左のボールは止まりません。
 デバッグ文字列にある、両ボールの速度(Velocity)を見てみましょう。両方を足すと、おおむねもともと持っていた分量になっていると思います。

 このサンプルは反発率1.0にしているので完全に反発しますが、Character.cppでボールの反発率を以下のように0.5fにすると、また違う動きになります。
//初期化
void Ball::OnCreate() {
    //中略


    //Rigidbodyをつける
    auto PtrRedid = AddComponent<Rigidbody>();
    PtrRedid->SetReflection(0.5f);
    //衝突判定をつける
    auto PtrCol = AddComponent<CollisionSphere>();


    //中略
}
 このように、ある程度リアルな動きを実装するためには物理演算が必須となります。ただし、摩擦や風の影響は加味してませんのでそのあたりも必要な場合は、衝突判定メッセージなどで追加処理するか、あるいは衝突判定そのものを自作しましょう。
 また、このサンプルのボールは「重力」は加味してません。「重力」を加味すると衝突した瞬間に減速が発生することがありますので注意しましょう。