HobbyistBox

趣味事をつらつらと

【FBX to OBJ】03 メッシュを取得しよう

はじめに

前回で FBX ファイルをインポートしてファイル内の情報へアクセスできるようになりました。

データ構造

シーンはメッシュ(3Dオブジェクト)だけではなく、ライトやカメラなども含まれています。
前回、最終的に取得した FbxScene はそういったシーンを構成するオブジェクトをノードでつないで木構造的に保持しています。

f:id:Hobbyist:20160218195331p:plain

この図で分かるようにメッシュ(FbxMesh)を取得しようと思うとノードを辿って行けばいいですね。
今回はその中からメッシュを取得していきます。

環境

作業環境は

  • Windows10
  • VisualStudio 2015 Community
    • C++ コンソールアプリ
  • FBXSDK ver.20161_2
  • Blender 2.76

で進めます。

ではプログラミングをしていきましょう。

ノード探査

まずはシーンオブジェクトの起点となるルートノードを取得します。
次に子ノード、子ノードという風に再帰探査をしていきます。

そこでノードを探査する関数を作るわけですが、プロジェクトに Util.h ファイルを作り今後追加していくユーティリティ関数をひとまとめにしたいと思います。

//---------------
// Util.h
//---------------
#pragma onece

#include<fbxsdk.h>
#include<iostream>

using namespace fbxsdk;

// ノード探査関数
void probeNode(FbxNode* node) {
  if(node) {
    std::cout << node->GetName() << std::endl;

    for(int i = 0; node->GetChildCount() > i; i++) {
      probeNode(node->GetChild(i));
    }
  }
}
//---------------
// main.cpp
//---------------
#include "Util.h"

// アプリケーションエントリーポイント
int main(int argc, char* argv[]) {

  // … //

  // インポーターの明示的な破棄
  importer->Destroy();

  // ノード探査
  FbxNode* rootNode = scene->GetRootNode();

  if(rootNode) {
    for(int i = 0; rootNode->GetChildCount() > i; i++) {
      probeNode(rootNode->GetChild(i));
    }
  }
  // 後処理
  manager->Destroy();
}

scene->GetRootNode() 関数でシーンのルートノードポインタを取得します。
次に rootNode->GetChildCount() 関数で子ノードの数を取得し、その数だけ繰り返し探査を行います。

再帰用関数 probeNode() は FbxNode* を引数に取ります。
処理自体は子ノードの数だけ繰り返し自身を呼びます。ですが、このままでは何が起きてるかわからないので node->GetName() 関数でノードの名前を取得し、表示しています。

実行すると

f:id:Hobbyist:20160218222615p:plain

Cube と Lamp と Camera という名前のノードがあったということがわかりました。

ノードのタイプを取得

ノードの名前まで取得できましたが、ノードの先にあるシーンオブジェクトが何かまではわかりません。Cube という名のライト(光源)かもしれません。

そこでノードの先のオブジェクトがメッシュかを調べたいと思います。

//---------------
// Util.h
//---------------

// … //

bool isMesh(FbxNode* node) {
  if(node) {
    int attrCount = node->GetNodeAttributeCount();

    for (int i = 0; attrCount > i; i++) {
      FbxNodeAttribute::EType attrType = node->GetNodeAttributeByIndex(i)->GetAttributeType();

      // ノードがメッシュにつながっているかチェック
      if (attrType == FbxNodeAttribute::EType::eMesh) {
        return true;
      }
    }
  }
  return false;
}

isMesh() 関数はノードポインタを引数にノードがメッシュにつながっていれば true、それ以外なら false を返します。
ノードには複数アトリビュートが設定できるため node->GetNodeAttributeCount() で設定されているアトリビュート数を取得し、個数分チェックします。

node->getNodeAttributeByIndex() 関数は取得するアトリビュートの番号を引数に成功すれば FbxNodeAttribute クラスのポインタを失敗すれば NULL を返します。
そして、FbxNodeAttributeクラスの GetAttributeType() 関数を呼びアトリビュートタイプを取得します。タイプはEType列挙型で定義されています。

enum EType
eUnknown
eNull
eMarker
eSkeleton
eMesh
eNurbs
ePatch
eCamera
eCameraStereo
eCameraSwitcher
eLight
eOpticalReference
eOpticalMarker
eNurbsCurve
eTrimNurbsSurface
eBoundary
eNurbsSurface
eShape
eLODGroup
eSubDiv
eCachedEffect
eLine

取得した attrType が eMesh と合致すればそのノードの先にメッシュがあるということです。
eMesh を eCamera とすればカメラノードの判別ができますね。

メッシュを取得

メッシュにつながるノードがわかったのでいよいよメッシュを取得します。

//---------------
// Util.h
//---------------

// … //

// ノード探査関数
void probeNode(FbxNode* node) {
  if(node) {
    std::cout << node->GetName();

    if(isMesh(node)) {
      std::cout << " - isMesh" << std::endl;

      FbxMesh* mesh = node->getMesh();

      if(mesh != NULL) {
        // ここに処理を追加していきます。
      }

    } else {
      std::cout << std::endl;
    }


    for(int i = 0; node->GetChildCount() > i; i++) {
      probeNode(node->GetChild(i));
    }
  }
}

メッシュの取得自体は簡単で node->getMesh() 関数で一発です。 成功すれば FbxMesh クラスのポインタ、失敗すれば NULL が返ります。

node->getMesh() 関数はキャスト処理をしてくれているので FbxMesh* mesh = (FbxMesh*)node; としても動きます。ですが、ノードの先がメッシュでない場合に厄介なのでやはり GetMesh() 関数を呼んでやりましょう。

実行結果

f:id:Hobbyist:20160219002425p:plain

最後に

やっとこさメッシュにたどり着きました。次から OBJ 形式へのコンバートを視野にいれながらプログラミングをしていきたいと思います。

今回説明した一連のソースコードです。前回と重複する箇所は省略してあります。

//---------------
// main.cpp
//---------------
#include<iostream>
#include<fbxsdk.h>

#include"Util.h" // 追加

using namespace fbxsdk;

// アプリケーションエントリーポイント
int main(int argc, char* argv[]) {
  // マネージャーの作成...
  
  // インポーターの作成...

  // ファイルを開く...

  // シーンの作成...

  // ファイルからシーンを読み込む...

  // インポーターの明示的な破棄
  importer->Destroy();

  // ノード探査
  FbxNode* rootNode = scene->GetRootNode();

  if(rootNode) {
    for(int i = 0; rootNode->GetChildCount() > i; i++) {
      probeNode(rootNode->GetChild(i));
    }
  }

  // 後処理
  manager->Destroy();

  return 1;
}
//---------------
// Util.h
//---------------
#pragma onece

#include<fbxsdk.h>
#include<iostream>

using namespace fbxsdk;

// メッシュノード判定
bool isMesh(FbxNode* node) {
  if(node) {
    int attrCount = node->GetNodeAttributeCount();

    for (int i = 0; attrCount > i; i++) {
      FbxNodeAttribute::EType attrType = node->GetNodeAttributeByIndex(i)->GetAttributeType();

      // ノードがメッシュにつながっているかチェック
      if (attrType == FbxNodeAttribute::EType::eMesh) {
        return true;
      }
    }
  }
  return false;
}

// ノード探査関数
void probeNode(FbxNode* node) {
  if(node) {
    std::cout << node->GetName();

    // メッシュノード判定
    if(isMesh(node)) {
      std::cout << " - isMesh" << std::endl;

      FbxMesh* mesh = node->getMesh();

      if(mesh != NULL) {

        /*** ここに処理を書いていきます ***/

      }

    } else {
      std::cout << std::endl;
    }


    for(int i = 0; node->GetChildCount() > i; i++) {
      probeNode(node->GetChild(i));
    }
  }
}

ここで紹介しているプログラムを使用する場合は自己責任でお願いします。