来源: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写得真叫一个乱,还好总算有些进展,正所谓万事开头难。下一步将研究如何导出场景的光照,物体的纹理贴图等信息。