【DirectX】FBX SDKのラッパークラス【FBX SDK】

# FBX SDK を利用したラッパークラス
アニメーションしないものは、適当にサンプルコードをコピペしていればできますが、
アニメーションするものは結構詰まってしまったので、ここにメモさせていただきます。

どのようにクラス化したのかの簡単な経緯と、詰まった部分のメモのみです。
中身を解析したい方はこちらのリンクへ。

[GitHub](https://github.com/Nyarll)
[FBXModel.h](https://github.com/Nyarll/3D-Shooting-Game/blob/master/Frameworks/FBXModel.h)
[FBXModel.cpp](https://github.com/Nyarll/3D-Shooting-Game/blob/master/Frameworks/FBXModel.cpp)

願わくば、FBX SDKとDirectXを使って描画しようとしている変わり者のあなたへ。

# クラス化と中身
ひとつのオブジェクトとして、クラス化して利用したいですよね。
リソース管理をするにも何をするにも、クラス化をしておくと非常に便利です。

```C++:FBX_Model.h
#pragma once

namespace FBX_LOADER
{
    // <一つの頂点情報を格納する構造体>
    struct VERTEX 
    {
        DirectX::XMFLOAT3 Pos;
    };

    // <GPU(シェーダ側)へ送る数値をまとめた構造体>
    struct CONSTANT_BUFFER {
        DirectX::XMMATRIX mWVP;
    };

    class FbxModel
    {
         // 省略
    public:
        void Load();
        void Draw();
    }
}

```

とりあえず、データのロードと描画は必須ですので、書いておきます。
アニメーションするもの、しないもので分けるのであれば、クラスを二つにするのも手ですが、それをすると何らかの基底クラスを継承させてみたり、なんやかんや管理が面倒になってしまいます。
ですので、今回はデータをロードするときにアニメーションするかどうかを決めるようにしました。

```cpp:FBX_Model.h
//省略
class FBX_Model
{
     //
private:
     bool is_animation = false;
public:
     void Load(......, bool isAnimation = false);
}
```

デフォルト引数を付けるかどうかは好みですね。
ただ、この方法で分ける場合、メンバ変数でアニメーションするかどうかを覚えておく必要があります。

様々なサイトを巡ったのですが、これだ！というものが無く、迷走しながら作ったので見るに堪えないかもしれませんが、ご了承ください。

# メッシュデータの取り扱い
モデルを描画するには、メッシュデータに格納されているポリゴンの数だとか頂点のデータだとか、まあ色々なデータが必要になるわけです。
大抵のサイトに書いてあったことは、簡単に言うと...。

まず、親ノードがあります。
[RootNode]

これにぶら下がって、メッシュやらボーンやらがくっついています。
[RootNode]
　　　┣[Mesh]
　　　┗[Bone]

実際はもっと複雑かもしれないですが、大方こんなものです。

さて、これをFbxNodeクラスに用意されているGetChild関数で辿っていこう！
メッシュかどうかは、Attribute？とかいうそれっぽいやつがあるので、それでチェックしましょう！


と、書いてあるサイトが多かったです。
正直、これをやると処理が重くなりましたし、再帰処理に慣れていない方はこんがらがってしまうと思います。

もちろん、この方法でもできなくはないですが、ちょっとツライです。

再帰処理を使ってノードを辿っていかなくても、なんとかなる方法がありました。

```cpp:Sample.cpp
void Load(......)
{
     //......
     int meshCount = m_fbxScene->GetSrcObjectCount<FbxMesh>();
	for (int i = 0; i < meshCount; ++i)
	{
		// <たったこれだけで全てのメッシュデータを取得できる>
		FbxMesh* mesh = m_fbxScene->GetSrcObject<FbxMesh>(i);
		std::string name = mesh->GetName();
		//m_fbxMeshNames.push_back(name);
		//m_fbxMeshes.insert({ mesh, name });
		m_fbxMeshes.push_back(mesh);
	}
     //......
}
```

GetSrcObject関数で、FbxMeshを指定すると、Scene内のメッシュデータを取り出すことができます。
Meshの名前を取得したりしているのは、ちゃんと取れているのかチェックしていたからなので、気にしないでください。

これで、メッシュだけをvectorなどでまとめておけば、毎回サーチしなくていいので大幅に処理が軽くなりました。(体感)(あいまいな記憶)

ここで取得したメッシュデータを、描画のときに使います。

# 面倒な描画
```cpp:Sample.cpp
void Draw(......)
{
     if (is_animation)
     {
          timeCount += FrameTime;
          if (timeCount > stop)
                timeCount = start;
         this->DrawAnimation(m_fbxScene->GetRootNode(), world, view, proj);
     }
     else
     {
          this->DrawModel(m_fbxScene->GetRootNode(), world, view, proj);
     }
}
```

今回はとりあえず、privateで描画の関数を分けました。
他にも良い方法があるかもしれません。

アニメーションしないやつは、おそらくできると思うのでアニメーションするやつだけ。
アニメーションするやつは、まずアニメーションしないモデル、と言ってわかるでしょうか。
人型のモデルだと、両腕を真横にまっすぐ伸ばして立っている、ああいうやつです。

あの状態を基本とし、アニメーションデータ(ボーンの動き)を取り出し、それを各ポリゴン(頂点)と計算する必要があります。
面倒ですね。

とりあえず、先ほど回収しておいたメッシュデータを使います。

```cpp:Sample.cpp
{
     int Count = m_fbxMeshes.size(); // メッシュの数だけ行う必要がある
     for (int i = 0; i < Count; i++)
     {
          FbxMesh* mesh = m_fbxMeshes[i];
          VERTEX* vertices = new VERTEX[mesh->GetControlPointsCount()];
     }
}
```

この下はバッファを生成したり設定したりが並ぶので、最初のほうにあるリンクでも見て適当に書いてください。

さて、ボーンのデータからの計算です。

```cpp:Sample.cpp
{
// <各頂点に掛けるための最終的な行列の配列>
		FbxMatrix *clusterDeformation = new FbxMatrix[mesh->GetControlPointsCount()];
		memset(clusterDeformation, 0, sizeof(FbxMatrix) * mesh->GetControlPointsCount());

		FbxSkin *skinDeformer = (FbxSkin *)mesh->GetDeformer(0, FbxDeformer::eSkin);
		int clusterCount = skinDeformer->GetClusterCount();
		// <各クラスタから各頂点に影響を与えるための行列作成>
		for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) {
			// <クラスタ(ボーン)の取り出し>
			FbxCluster *cluster = skinDeformer->GetCluster(clusterIndex);
			FbxMatrix vertexTransformMatrix;
			FbxAMatrix referenceGlobalInitPosition;
			FbxAMatrix clusterGlobalInitPosition;
			FbxMatrix clusterGlobalCurrentPosition;
			FbxMatrix clusterRelativeInitPosition;
			FbxMatrix clusterRelativeCurrentPositionInverse;
			cluster->GetTransformMatrix(referenceGlobalInitPosition);
			referenceGlobalInitPosition *= geometryOffset;
			cluster->GetTransformLinkMatrix(clusterGlobalInitPosition);
			clusterGlobalCurrentPosition = cluster->GetLink()->EvaluateGlobalTransform(timeCount);
			clusterRelativeInitPosition = clusterGlobalInitPosition.Inverse() * referenceGlobalInitPosition;
			clusterRelativeCurrentPositionInverse = globalPosition.Inverse() * clusterGlobalCurrentPosition;
			vertexTransformMatrix = clusterRelativeCurrentPositionInverse * clusterRelativeInitPosition;
			// <上で作った行列に各頂点毎の影響度(重み)を掛けてそれぞれに加算>
			for (int cnt = 0; cnt < cluster->GetControlPointIndicesCount(); cnt++) {
				int index = cluster->GetControlPointIndices()[cnt];
				double weight = cluster->GetControlPointWeights()[cnt];
				FbxMatrix influence = vertexTransformMatrix * weight;
				clusterDeformation[index] += influence;
			}
		}


		// <最終的な頂点座標を計算しVERTEXに変換>
		int count = mesh->GetControlPointsCount();
		for (int cnt = 0; cnt < count; cnt++) {
			FbxVector4 outVertex = clusterDeformation[cnt].MultNormalize(mesh->GetControlPointAt(cnt));
			float x = (FLOAT)outVertex[0];
			float y = (FLOAT)outVertex[1];
			float z = (FLOAT)outVertex[2];

			vertices[cnt].Pos.x = x;
			vertices[cnt].Pos.y = y;
			vertices[cnt].Pos.z = z;
		}

		delete[] clusterDeformation;
}
```

この部分を作ったのは結構前なので、全くどうなっているのか記憶にないので説明できないですが、頑張って読んでください。
読まなくても、コピペ等で使えます。

そしてまた、バッファをいじったりなんやかんやします。

最終的に、

```cpp:Sample.cpp
{
// <描画実行>
		int polygonVertexCount = mesh->GetPolygonVertexCount();
		m_context->DrawIndexed(polygonVertexCount, 0, 0);
}
```
これで描画して終わり！

なお描画も、メッシュの数だけ行う必要があります。

ですので、あまりにポリゴンが多かったり、メッシュが多いモデルは大変です。
メッシュが多いモデルはあまりないでしょうが。

ここまでで、どこが詰まったのかと言いますと。

・メッシュデータを毎回ノードを辿って探していた
・毎回探していたので、複雑化しすぎてわからなくなっていた
・描画のためのバッファの用意し忘れ
・そもそもモデルにアニメーションがついてなかった

などですね。
最後の二つは完全にミスですが、皆さんも気を付けましょう！

# 終わりに
役に立つかどうかは分かりませんが、我が後輩達よ、こんなクソコード書くんじゃないぞ。
リファクタリングしようかとも思いましたが、動くところまで漕ぎつけるのに３回作り直してますからね、気力が持ちませんでした。
誰かリファクタリングしたい人は、してどうぞ。

クソゴミコードなので、コピペして使ったりするのは構いませんが、せめて自分のコードに書き直すくらいはしてね！バレちゃうぞ！