3DsMax導出插件編寫(二)——常規SDK方法進行信息獲取和保存文件

之前已經把配置vs和maxSdk的方法介紹過了, 下面來介紹一下導出插件的具體寫法。不過這不是一個容易說的很詳細的問題。因爲我們要寫導出插件,通常都是因爲想根據自己想要的信息來導出,所以就算我把我整個工程都公開,意義也不大的,因爲那是根據我自己需要的數據寫的業務,估計不太可能和你想要的一樣的。所以我也只能簡單的說明一些幾個關鍵獲取數據的方法,和保存文件的方法,如果想看具體的導出範例工程,你可以去maxSdk文件夾下面找到maxsdk\samples\import_export\3dsexp.cpp,這是一個完整的導出3ds文件的例子。

然後maxSdk提供的操作數據的方法有兩種,一種是常規方法,一種是IGame方法。下面我們先來介紹一下常規的方法,至於IGame的方法,我會在另外一遍文章裏面說明。

我建議先看看常規方法,對maxSdk有一個瞭解,然後我在介紹IGame的時候,會將兩種方法做一個對比。
一、數據獲取
首先要知道的是,maxSdk是通過一個ITreeEnumProc類來遍歷場景裏面所有的節點的。
然後需要知道,在場景裏面,每一個物體就是一個節點,包括網格模型、燈光、攝像機、骨骼、輔助物體等,都是節點。我們可以通過獲取節點的ClassID來判斷該節點的實際類型。
最後,我們需要知道導出整個場景和導出選擇中的物體的區別。這個並不是自動功能來的,也是需要自己來寫代碼判斷的。
我這裏舉的例子比較簡單,大家可以自己擴展。
先講講判斷導出整個場景和導出選擇中物體的判斷方法:
我們先定義一個布爾變量,用作判斷導出的模型,比如
static BOOL exportSelected;
然後,在DoExport方法裏面,我們判斷:
exportSelected = (options & SCENE_EXPORT_SELECTED) ? TRUE : FALSE;
如果是true,那就是導出單獨選擇的物體,如果是false,就是導出全部物體了。
最後,我們可以在節點樹回調方法裏面,判斷,如果
if(exportSelected && node->Selected() == FALSE)這說明了開啓了導出選擇物體的模式,並且當前的節點沒有被選中,我們直接return掉就行了。

下面正式開始導出數據:
1、寫一個管理樹節點的類,並寫回調的方法

class MyTreeEnum : public ITreeEnumProc
{
public:
    int callback(INode*node);
};

int MyTreeEnum::callback(INode*node)
{
//判斷是否導出選中的物體
if(exportSelected && node->Selected() == FALSE)
return TREE_CONTINUE;
ObjectState os = node->EvalWorldState(0);
if(os.obj)
{
    if(os.obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID,0)))
    {
        //這個節點是網格模型,根據需要對數據進行操作         
        return TREE_CONTINUE;
    }
    else
    {
        switch (os.obj->SuperClassID()) 
         { 
             case CAMERA_CLASS_ID: 
             //這個節點是攝像機,根據需要對數據進行操作 
             break; 
             case LIGHT_CLASS_ID: 
            //這個節點是燈光,根據需要對數據進行操作   
             break; 
            default:
             break; 
         }  
    }
}

上面是一個簡單判斷當前節點是什麼類型的方法,我們還可以根據自己的需要,寫一些判斷某種單獨類型的方法,比如判斷一個節點是否爲骨骼,可以這樣寫:

bool isBone(INode*thisBone)
{
ObjectState pObs=thisBone->EvalWorldState(0);
Class_ID id = pObs.obj->ClassID();
SClass_ID sid = pObs.obj->SuperClassID();
if (pObs.obj->ClassID()==Class_ID(BONE_CLASS_ID,0))
{
return true; 
}
if (pObs.obj->ClassID()==BONE_OBJ_CLASSID)
{
return true; 
}
if (pObs.obj->ClassID()==Class_ID(37157,0))
{
    return true; 
}
return false;

}

最後在DoExport方法裏面
MyTreeEnum tempProc;
ei->theScene->EnumTree(&tempProc);
這樣程序就會遍歷所有的節點,然後做回調時的處理。

值得注意的問題是:
maxSdk裏面的BONE_CLASS_ID或者BONE_OBJ_CLASSID,指的都是3dsmax的經典骨骼,也就是bones,不包括biped的。所以如果只用這兩個ClassId來判斷骨骼,是不行的,會把biped漏掉。阿趙我自己通過斷點找出biped的ClassId是37157,但找不到sdk裏面對應的類ID,所以只能直接ClassID()==Class_ID(37157,0)來判斷它就是biped骨骼了。
上面這個判斷也不是絕對的,比如有些動畫師喜歡拿輔助物體作爲骨骼來控制動畫,那麼這個判斷骨骼的方法裏面,就需要加上DUMMY_CLASS_ID的判斷了,諸如此類,各位可以根據自己的業務去擴展。

2、獲取網格模型信息:
剛纔我們已經能判斷到某個節點是網格模型了,所以我們接下來就可以對其進行信息的獲取。
獲取節點的名稱:
node->GetName();
獲取網格模型的具體網格:
TriObject* tri = (TriObject*)os.obj->ConvertToType(0, Class_ID(TRIOBJ_CLASS_ID,0));
Mesh *pMesh = &tri->GetMesh();
獲取網格的點的數量:
int VerticesNum = pMesh->getNumVerts();
獲取網格面的數量:
int FaceNum = pMesh->getNumFaces();
遍歷所有的面,然後獲取每個面的頂點索引:

for(i = 0;i<FaceNum;i++)
 { 
     Face face = pMesh->faces[i]; 
    //自己寫個數組把它們存起來
     indexList.push_back(face.v[0]); 
     indexList.push_back(face.v[1]); 
     indexList.push_back(face.v[2]); 
}

遍歷所有頂點,獲取頂點座標:

for(i = 0;i<VerticesNum;i++)
{ 
    Vertex_t vertInfo; 
    int FaceNumber;
    Point3 pos = pMesh->getVert(i);
    //自己寫個數組把它們存起來
    vertList.push_back(pos); 
}
    //uv座標
        for(i = 0;i<FaceNum;i++)
        {
            TVFace tvFace = pMesh->tvFace[i];
            for (int k=0; k<3; k++)  
            {
                Point3 uv = pMesh->tVerts[tvFace.t[k]];
                //自己寫個數組存起來
                uvList.push_back(uv); 
            }
        }
這樣,網格模型需要的頂點座標、索引、uv都有了,如果還需要法線或者其他信息,自己可以擴展。

3、獲取蒙皮修改器:
對於已經蒙皮的模型,我們需要查找它的蒙皮修改器(這裏用的是skin修改器)

ISkin * FindSkinModifier(INode *pINode)
{ 
Object * pObject = pINode->GetObjectRef(); 
if(pObject == 0) return 0; 
// 循環檢測所有的DerivedObject 
while(pObject->SuperClassID() == GEN_DERIVOB_CLASS_ID){ 
IDerivedObject * pDerivedObject = static_cast<IDerivedObject *>(pObject); 
for(int stackId = 0; stackId < pDerivedObject->NumModifiers(); stackId++){ 
Modifier * pModifier = pDerivedObject->GetModifier(stackId); 
//檢測ClassID是不是Skin修改器
if(pModifier->ClassID() == SKIN_CLASSID) { return (ISkin*)pModifier->GetInterface(I_SKIN);} 
} 
pObject = pDerivedObject->GetObjRef();//下一個Derived Object 
} 
return 0; 

}

在得到了有蒙皮信息之後,就可以獲取它上面的蒙皮信息數據:

ISkinContextData* pSkinCtx = skin->GetContextInterface(node);
int nBones = pSkinCtx->GetNumAssignedBones(i);  
for (j = 0;j<nBones;j++)
{
    INode *pBone = skin->GetBone(j);
    //骨骼名稱
    char* bName = pBone->GetName();
    //權重
    float weight = pSkinCtx->GetBoneWeight(i,j);
    //其他信息可以自己去擴展
    //……自己想辦法存起來
}

我建議在獲取蒙皮信息之前,最好先遍歷所以節點把骨骼全部找出來,並排好序。因爲我們最後保存的蒙皮信息裏面的骨骼名稱,我們可以變成骨骼的索引,包括骨骼父物體也是保存成索引,那麼文件的容量會減少很多。

以上我是簡單的說明了幾種常用的信息的獲取方法,在完全理解了之後,其他的信息的獲取方法是一樣的,具體可以查找一下maxSdk的api文檔

二、保存文件
獲取好數據之後,最後我們就要來保存了。很多人以爲這個保存也是maxSdk提供的方法,其實不是的,保存文件,就是直接用c++自己的方法。比如我們可以用ofstream來保存。我們可以把之前獲得的所有數據,都集中的轉換成字符串,比如json,或者自己覺得解析起來比較方便的格式的字符串,然後就可以用ofstream來保存了。
具體的保存路徑,是DoExport的傳入參數name。

發佈了69 篇原創文章 · 獲贊 79 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章