來源:yhforchina的專欄 - CSDNBlog
要從3ds Max中導出場景信息,大概有兩種方式:1.利用3ds Max的sdk制作插件。2.利用3ds Max的Max Script編寫場景輸出腳本。兩種方式各有優劣,這里僅敘述我用了數天研究3ds Max SDK并制作插件的學習心得。
1.前期環境配置工作。
首先肯定要安裝帶SDK的3ds Max,我安裝的是3ds Max7。安裝后在maxsdk\help下有個sdkapwz.zip,把這個文件解壓到VS 6.0或VS 2003的application wizard路徑下,啟動VS就會有3ds Max plug-in的應用程序向導來生成插件程序的框架。
2.制作插件需要了解的幾個基本概念。
2.1 我所了解的插件原理是:3ds Max會公布一些接口,插件制作者需要做的是實現這些接口。例如利用向導生成一個用于場景導出的插件。就會發現在生成的程序中有一個類繼承于class SceneExport,而 SDK 中關于這個接口的描述是:
This is a base class for creating file export plug-ins. The plug-in implements methods of this class to describe the properties of the export plug-in and a method that handles the actual export process.
再看下面的函數說明,可以看到函數是虛函數的方式聲明的,所以必須要將其所有的函數進行實現。
2.2 3ds Max是怎樣識別插件的接口?還是以上面的場景到處插件為例,程序中還會生成一個繼承于 ClassDesc2的類,這個類會實現一些關于類的ID,層次信息處理的函數。估計系統就是根據這個識別處我的插件的接口,沒有具體去研究,只了解個大概。
2.3 如何調試所編寫的插件,SDK中有說明,我這里簡單說一下,將工程屬性設置為Hybrid(默認是Debug),并且把輸出dle文件的路徑設為3ds Max的plug-in的路徑,再把調試的可執行程序設為3ds Max.exe,這樣 調試的時候就會啟動3ds Max主程序,其他諸如設置斷點,單步等調試手段和普通程序的調試方法一樣。
3.通過一個例子學習插件編程。
這部分還真不好寫,涉及到一些代碼,代碼中又有很多API需要講解,API中又有很多基本知識需要說明,唉,硬著頭皮來吧。
還是以那個場景導出類為例,可以看到,SceneExport中有個非常重要的函數需要實現:
virtual int DoExport(const TCHAR *name,ExpInterface *ei,Interface *i,
BOOL suppressPrompts=FALSE, DWORD options=0) = 0;
先看看參數:
name 表示要導出的文件名。
ei 用來枚舉場景,需要注意的是:由于這個函數是由系統調用的,所以這個參數是系統傳遞的,不 用去思考怎么實現ExpInterface這個接口。
i 提供一個用來調用3ds Max方法的指針,可以把它視作一個指向3ds Max的指針。同樣,這個指針 也是由系統傳遞的。
剩下兩個參數暫不關心。
現在來研究ei和i兩個參數:
class ExpInterface僅包含一個成員:IScene *theScene。這樣的類設計的簡直是“無恥”。再去研究IScene吧。IScene中有一個很重要的函數:
virtual int EnumTree( ITreeEnumProc *proc )=0;
根據SDK的說明,該函數的功能是:用來枚舉場景中的每個INode。因此需要一個ItreeEnumProc*作為參數,由于是自己調用整個函數,因而必須自己實現ItreeEnumProc接口,還好這個接口不是很復雜,把這個回調函數實現就可以了:
virtual int callback( INode *node )=0;
因為是回調函數,所以node也是系統傳遞進來的,為了證明這一點,我們可以編程實驗一下:
class MyEnumProc: public ITreeEnumProc
...{
public: int callback( INode *node )
...{
int a = 0; //在這里設置斷點
return a;
}
}
在MyExport中添加這一個成員變量:
MyEnumProc MyProc;
int MyExport::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
...{
/**//*在函數中添加這行代碼*/
ei->theScene->EnumTree(&this->MyProc );
if(!suppressPrompts)
DialogBoxParam(hInstance,
MAKEINTRESOURCE(IDD_PANEL),
GetActiveWindow(),
MY5OptionsDlgProc, (LPARAM)this);
return FALSE;
}
調試這個例子,在場景中繪制三個立方體,可以看到系統會調用callback三次,這說明一個物體就是一個Node。那么怎么來導出一個Node幾何信息呢?看下面這個代碼。
public: int callback( INode *node )
...{
Object *lobj;
lobj = node->GetObjectRef();
if (lobj->SuperClassID()== GEOMOBJECT_CLASS_ID)
...{
GeomObject* gobj = (GeomObject*)lobj;
Class_ID triID = Class_ID(TRIOBJ_CLASS_ID,0);
Class_ID boxID = Class_ID(BOXOBJ_CLASS_ID,0);
if (lobj->ClassID()==boxID)
...{
if (lobj->CanConvertToType(triID))
...{
TriObject *triobj = (TriObject *)lobj->ConvertToType
(0,triID);
Mesh mesh = triobj->mesh;
int numVerts = mesh.getNumVerts();
}
IParamArray* array = lobj->GetParamBlock();
float length = 0.0f;
float height = 0.0f;
float width = 0.0f;
array->GetValue( lobj->GetParamBlockIndex(BOXOBJ_LENGTH),
0, length, FOREVER);
array->GetValue( lobj->GetParamBlockIndex(BOXOBJ_HEIGHT),
0, height, FOREVER);
array->GetValue( lobj->GetParamBlockIndex(BOXOBJ_WIDTH),
0, width, FOREVER);
}
}
首先node->GetObjectRef()會返回這個節點的物體引用。關于ObjectRef有一套幾何流水線的說明,這里實在是沒功夫寫了。接著首先判斷這物體的SuperClassID是否為GEOMOBJECT_CLAS- S_ID,如果是,則再看它是否能轉換為TriObject,即由三角形組成的物體,至于為什么要這樣轉,我只能說只有這個類可以返回一個Mesh,而通 過Mesh能夠獲得諸如頂點,法線,面等一般3D程序所需要的幾何信息(這里只獲取了該Mesh的面的個數)。當然,對于一個Box,我們可能只想獲得它 的長寬高,所以,代碼中又提供了另一個方法來返回其幾何信息。
雖然只實現了這么短的代碼,但卻花了數天的時間,主要對3ds Max的結構不熟悉,加上SDK寫得真叫一個亂,還好總算有些進展,正所謂萬事開頭難。下一步將研究如何導出場景的光照,物體的紋理貼圖等信息。