3.BaseCross64への実装

3-1.衝突判定をアセンブラで実装してみた

 前項まではコンソールアプリケーションC/C++からアセンブリを呼び出すという形でした。
 しかし、そのままある程度複雑なものを書いても、表現する手段がテキストだけだと限られてきます。
 ですのでBaseCross64というゲームフレームワーク、レポジトリはこちらに実装してみて、アセンブラで直接記述の効果を検証してみたいと思います。
 BaseCross64側に実装するのは時期相生と思われますので、まずはこちらのレポジトリで検証したいと思います。
 まず第一弾として衝突判定を実装してみました。
 ゲームになくてはならない機能として衝突判定があるわけですが、BaseCross64ではそれをC/C++で実装しています。
 とはいえ最終的な演算はXNAMathというマイクロソフト社の計算エンジンにたどり着くので、そこでSSE2命令が発行されるわけですが、そこに至るまでに結構な量のC/C++ソースをたどったり、あるいは多数の関数呼び出しがあるので、必ずしも効率的とは言えません。
 そこで、試しに衝突判定をアセンブリで書いたらどのくらいスピードアップするのかを試してみたいと思います。
 実験するのは球対球の判定です。BaseCross64にはほかにボックス、カプセルなどがあり、それぞれに多対多で判定するわけですが、それをアセンブリで実装するのは、ちと荷が重いので、今回は球対球だけにします。
 とはいえ、単純こそ強力なもので、これを応用すると結構な効果を出せると思います。

一つの球同士の判定

 まずは、この項では1対1の判定を行います。1対多あるいは多対多は次項以降で行います。
 Sample301ディレクトリ内にBaseCrossDx11VS2017.slnBaseCrossDx11VS2019.slnがあります。VS2017の場合は前者をVS2019の場合は後者を開いてください。このサンプルはVS2017、2019両方に対応しています。

設定から実装する場合

 すでにBaseCross64を使っていて、0から設定してみたい人のために、設定の仕方を述べます。

セットアップ

 VS2019を例にとり説明します。
 まず、ダウンロードしたBaseCross64の中の雛形になりそうなサンプルをディレクトリごとにコピペして新しいサンプルディレクトリを作成します。そして好きな名前を付けます。ここではSampleMASMとします。
 その中のBaseCrossDx11VS2019.slnVS2019で開きます。
 開いたらソリューションエクスプローラからBaseCrossDx11VS2019プロジェクトを右クリックし、ビルドの依存関係-ビルドのカスタマイズを選びます。

 

図0301a

 

 でてきたダイアログでmasmをチェックしてOKします。

 

図0301b

 

 続いて、もう一度BaseCrossDx11VS2019プロジェクトの中のソースファイルを右クリックし、追加-新しい項目を選択します。

 

図0301c

 

 出てきた画面でCollisionSphere.asmを作成します。

 

図0301d

 

 ここで注意したいのはアセンブリコードは必ずBaseCrossDx11VS2019プロジェクトの中のソースファイルに配置してください。他の参照プロジェクトに配置するとうまくいかない場合があります。
 CollisionSphere.asmを右クリックしプロパティを開きます。出てきた画面で、以下のようになるように設定します。

 

図0301e

 

 CollisionSphere.asmを開き、以下のソースを記述します

.data

.code

;int CollisionSp(SPHERE* sp1, SPHERE* sp2)
;dword ptr [rcx]        sp1.m_Center.x
;dword ptr [rcx+4]      sp1.m_Center.y
;dword ptr [rcx+8]      sp1.m_Center.z
;dword ptr [rcx+12]     sp1.m_Radius
;dword ptr [rdx]        sp2.m_Center.x
;dword ptr [rdx+4]      sp2.m_Center.y
;dword ptr [rdx+8]      sp2.m_Center.z
;dword ptr [rdx+12]     sp2.m_Radius
CollisionSp proc
    ;m_Center同士の引き算
    movss   xmm4,   dword ptr [rcx]     ;sp1.m_Center.x
    subss   xmm4,   dword ptr [rdx]     ;sp2.m_Center.x
    mulss   xmm4,   xmm4                    ;x * x
    movss   xmm5,   dword ptr [rcx+4]       ;sp1.m_Center.y
    subss   xmm5,   dword ptr [rdx+4]       ;sp2.m_Center.y
    mulss   xmm5,   xmm5                    ;y * y
    movss   xmm6,   dword ptr [rcx+8]       ;sp1.m_Center.z
    subss   xmm6,   dword ptr [rdx+8]       ;sp2.m_Center.z
    mulss   xmm6,   xmm6                    ;z * z
    ;dot演算
    addss   xmm4,   xmm5                    
    addss   xmm4,   xmm6                    ; xmm4 == dot
    ;半径同士の加算
    movss   xmm1,   dword ptr [rcx+12]      ;sp1.m_Radius
    addss   xmm1,   dword ptr [rdx+12]      ;sp2.m_Radius
    ;加算結果の2乗
    mulss   xmm1,   xmm1                    
    ;半径加算2乗がdot演算結果より大きければ衝突
    comiss  xmm4,   xmm1
    JBE     truelabel
    mov     eax,    0
    ret 
truelabel:
    mov     eax,    1
    ret 
CollisionSp endp
        end
 記述したらビルド-リビルドをします。無事ビルド出来れば成功です。これで、ゲームのソース中どこからでも球体球の衝突判定を呼ぶことができます。
 関数を呼ぶ際は、呼ぶ場所から見えるところで
    extern "C" int CollisionSp(SPHERE* sp1, SPHERE* sp2);
 という関数宣言を行ってください。

Sample301の解説

 さて、実装方法を説明したところで、Sample301の説明を行います。
 Sample301ディレクトリ内のBaseCrossDx11VS2017.sln(VS2017の場合)BaseCrossDx11VS2019.sln(VS2019の場合)を開いてみましょう。
 リビルドして実行すると以下の画面が出てきます。

 

図0301f

 

 ここでコントローラでプレイヤーを動かしAボタンでジャンプさせて、正面の球に当たると、球が消滅します。Xボタンで復帰します。
 この判定は上記のアセンブリコードを使っています。
 呼び出しているのはCharacter.cpp内のHitTestSphere::OnUpdate()です。
 付近のコードは以下です。
    extern "C" int CollisionSp(SPHERE* sp1, SPHERE* sp2);

    //操作
    void HitTestSphere::OnUpdate() {

        SPHERE sp1;
        sp1.m_Center = m_Position;
        sp1.m_Radius = m_Scale / 2;

        auto ptrPlayer = GetStage()->GetSharedGameObject<Player>(L"Player");
        auto sp2 = ptrPlayer->GetComponent<CollisionSphere>()->GetSphere();
        if (CollisionSp(&sp1, &sp2)) {
            SetUpdateActive(false);
            SetDrawActive(false);
            auto ptrShadow = AddComponent<Shadowmap>();
            ptrShadow->SetDrawActive(false);
        }

    }

 冒頭に
    extern "C" int CollisionSp(SPHERE* sp1, SPHERE* sp2);
 という記述があります。これがないと呼び出せません。
 赤くなっているとことが、アセンブリ関数を呼び出しているところです。衝突すると、消えるように記述しています。

 次項では、1対1ではなく、1対多の挑戦してみます。