目 录
1.1 基本思想
1.2 系统管理插件的接口部分
1.3 用户启用插件生成物体时的接口部分
1.1、基本思想
3DS MAX SDK是一组库及相应的头文件,其中包含了3DS MAX的大部分核心代码,利用库中的函数就可与系统内核通信。3DS
MAX的插件(Plugins)就是一种动态链接库,每个插件都包含有若干个类及对象和公用函数。有一组公用函数及几个类的对象是提供给系统挂接和管理插件用的,另外一些类则实现插件本身的功能(如造型或修改)。
插件中的每种活动都是由相应如的类对象来完成的,如系统获取插件中定义的类的描述、插件对象创建时的鼠标交互响应、用户对插件对象参数的修改等都有相应的类,系统会调用指定的公用函数取得这些对象的地址,并在适当的时候激活它们。这就是3DS
MAX的回调机制。
1.2、系统管理插件的接口部分
// 定义插件对象的32位类ID。
#define SPHERE_C_CLASSID1 8239283498
#define SPHERE_C_CLASSID2 8239283498
// 插件描述类对象
SphereClassDesc sphereDesc;
ClassDesc* GetSphereDesc() { return
&sphereDesc;}
// 系统管理所需方法
// 这几个方法都是dllexport型的
LibVersion() {return VERSION_3DSMAX;}
LibNumberClasses() {return 1;}
LibClassDesc() {return GetSphereDesc();}
LibDescription() {return _T("Sphere Object. Call
1-800-plig-in");}
// 插件描述类,成员为供系统管理用的方法
class SphereClassDesc: public ClassDesc
{
public:
int IsPublic() {return 1;} // true可由用户选取,
// false由其它插件调用不与用户打交道。
void * Create(BOOL loading=FALSE) {return new
SphereObject;}
const TCHAR * ClassName() { return _T("Sphere_c");}
SClass_ID SuperClassID(){ return GEOMOBJECT_CLASS_ID;}
Class_ID ClassID() { return
CLASS_ID(SPHERE_C_CLASSID1,SPHERE_C_CLASS_ID2); }
const TCHAR* Category() { return _T("How To");}
}
1.3、用户启用插件生成物体时的接口部分
// 创建时期的用户鼠标交互接口
// 由一个CreateMouseCallBack衍生类的对象定义
class SphereObjCreateCallBack: public CreateMouseCallBack
{
IPoint2 sp0;
SphereObject *ob;
Point3 p0;
public:
// 开发者定义的创建时期的用户交互方法
int proc(ViewExp *vpt, int msg, int point, int flags, IPoints2
m, Matrix3 &mat);
void SetObj(SphereObject *obj) { ob=obj;}
}
// 创建时对用户鼠标交互活动的响应代码
int SphereObjCreateCallBack::proc(ViewExp *vpt,
int msg, int point, int flags, IPoints2 m, Matrix3
&mat)
{
float r;
Point3 p1,center;
if(msg==MOUSE_POINT || msg==MOUSE_MOVE)
{
switch (point)
{
case 0:... break;
case 1:... break;
}
else if(msg==MOUSE_ABORT) { return CREATE_ABORT; }
return TRUE;
}
// 定义创物体创建时的回调类对象。
static SphereObjCreateCallBack sphereCreateCB;
// 在插件所创建的物体的类中定义获取创建回调函数地址的方法。
CreateMouseCallBack* SphereObject::GetCreateMouseCallBack()
{
sphereCreateCB.SetObj(this);
return &sphereCreateCB;
}
// 创建时期对用户修改物体参数的响应
// 在用户编辑实体参数(创建时或修改时)系统将调用BeginEditParams(),该方法负责为面添加并注册滚转页
// 在编辑完成时系统将会调用EndEditParams()。
//BeginEditParams用来在用户进入参数编辑框时
void SphereObject::BeginEditParams(IObjParam*ip,ULONG
flags,Animatable *prev)
{
SimpleObject::BeginEditParams(ip,flags,prev);
this->ip=ip;
if(pmapCreate&&pmapParam)
{
pmapCreate->SetParamBlock(this);
pmapTypeIn->SetParamBlock(this);
pmapParam->SetParamBlock(pblock);
}
else
{
if (flags&GEGIN_EDIT_CREATE)
{
pmapCreate=CreateCPParamMap(
descCreate,
CREATEDESC_LENGTH,
this,
ip,
hInstance,
MAKEINTRESOURCE(IDD_SPHEREPARAM1),
_T("Creation Method"),
0 );
pmapTypeIn=CreateCPParamMap(
descTypeIn,
TYPEINDESC_LENGTH,
this,
ip,
hInstace,
MAKEINTRESOURCE(IDD_SPHEREPARAM3),
_T("Keyboard Entry"),
APPENDROOL_CLOSED );
}
pmapParam=CreateCPParamMap(
descParam,
PARAMDESC_LENGTH,
pblock,
ip,
hInstace,
MAKEINTRESOURCE(IDD_SPHEREPARAM2),
_T("arameters"),
0 );
}
if(pmapTypeIn)
{
pmapTypeIn->SetUserDlgProc(new
SphereTypeInDlgProc(this));
}
}
void SphereObject::EndEditParams(IObjParam *ip,ULONG
flags,Animatable *next)
{
SimpleObject::EndEditParams(ip,flags,next);
this->ip=NULL;
if(flags&END_EDIT_REMOVEUI)
{
if(pmapCreate) DestroyCPParamMap(pmapCreate);
if(pmapTypeIn)DestroyCPParamMap(pmapTypeIn);
DestroyCPParamMap(pmapParam);
pmapParam=NULL;
pmapTypeIn=NULL;
pmapCreate=NULL;
}
pblock->GetValue(PB_SEGS,ip->GetTime(),dlgSegments,FOREVER);
pblock->GetValue(PB_SMOOTH,ip->GetTime(),dlgSmooth,FOREVER);
}
class SphereTypeInDlgProc: public ParamMapUserDlgProc
{
public:
SphereObject *so;
SphereTypeInDlgProc(SphereObject *s){so=s;}
BOOL DlgProc(TimeValue t,IParamMap*map,HWIND hWnd,UINT
msg,WPARAM wParam,LPARAM lParam);
void DeleteThis(){delete this;}
};
BOOL SphereTypeInDlgProc:lgProc(TimeValue,IParamMap *map,HWIND
hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_TI_CREATE:
{
if(so->crtRadius==0.0) return
TRUE;
if(so->TestAFlag(A_OBJ_CREATING))
{
so->pblock->SetValue(PB_RADIUS,0,so->crtRadius);
}
Matrix3 tm(l);
tm.SetTrans(so->crtPos);
so->ip->NonMouseCreate(tm);
return TRUE;
}
break;
}
return FALUSE;
}
目 录 2.1 基本的介绍 2.2 插件必须的函数 2.3 类描述器方法成员 3DS MAX
SDK插件是以DLL形式存在的。通常我们用Microsoft Visual C++来开发。建立一个新的工程的描述见“Creating
A New Plugin Project”开发者可以将这些DLL插件存放在任何地方,但是要想法子让3DS
MAX知道到哪里去找这些文件。这部分是在“Plug-In Directory Search
Mechanism”里讨论的。插件开发者可以为应用加上在线帮助,并使用望可在Max Help菜单里访问。细节见“Plug-In
Help
System”。有一个标准的位置供开发者保存插件所需的任何配置文件。这些可能是.ini文件,二进制配置文件或任何需要的文件。详见“Plug-In
Configuration System”。2.1、基本的介绍 2.1.1
标准DLL函数 所有的插件DLL都必须实现一套标准的函数: DLLMain() LibDescription() LibNumberClasses() LibClassDesc() LibVersion() 这些允许3DS
MAX访问、维护在DLL内在插件并与之协同工作。这些函数的详情见“DLL, LIbrary Functions, and Class
Descriptors”。 2.1.2 重入与线程安全的插件 3DS
MAX插件必须是可重入与线程安全的。详在高级主题的“Thread Safe Plug-Ins”章节里。2.2、插件必须的函数 3DS
MAX进行DLL装入、分类、管理插件。包括DLL例程和类描述类。“Class
Descriptors”提供插件类的信息,用以实现LibClassDesc()函数。 2.2.1 DLL
functions DllMain(HINSTANCE hinstDLL, ULONG fdwReason, LPVOID
lpvReserved)
当DLL被装入时由windows调用。该函数也会在时间关键性操作期间被多次调用,如渲染。所以开发者在该函数内要小心谨慎。注意以下的示意代码中,在DLL第一次调用以后只有很少的语句被执行。该函数应该返回TRUE。 int
controlsInit = FALSE; BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG
fdwReason,LPVOID lpvReserved) { // Hang on to this DLL's
instance handle. hInstance = hinstDLL; if (!
controlsInit) { controlsInit = TRUE; // Initialize MAX's
custom controls InitCustomControls(hInstance); //
Initialize Win95
controls InitCommonControls(); } return(TRUE); } 2.2.2
LibNumberClasses() 当3DS
MAX启动之后,它找到并装入这些DLLs。然后,它需要有个方法判断DLL中的插件类数目。开发者应当在本函数中提供,例如: __declspec(dllexport)
int LibNumberClasses() { return 1; } 返回值即插件类的个数。 2.2.3
LibClassDesc(i) 插件必须向系统提供一个方法以获取插件定义的类的描述器(Class
Descriptors)。类描述器向系统提供DLL中的插件类的信息。本函数使系统可以访问类描述器,返回值应该是指向第i个类描述器的指针。(一个DLL中可以有许多个类描述器)。例如: __declspec(dllexport)
ClassDesc *LibClassDesc(int i) { switch(i) { case 0:
return &MeltCD; case 1: return
&CrumpleCD; default: return
0; } } 这里是有关必须被LibClassDesc(i)返回的类描述器的资料。类描述器(Class
Descriptors)向系统提供DLL中插件类的信息。类描述的一个方法负责分配插件类的新实例(Create)。开发者通过从ClassDesc衍生一个子类并实现若干方法成员来建立类描述器。下面是个简单的类描述器及其静态实例的例子。 class
MeltClassDesc : public ClassDesc { public: int IsPublic()
{ return TRUE; } void * Create(BOOL loading=FALSE) { return
new MeltMod(); } const TCHAR * ClassName() { return _T("Melt");
} SClass_ID SuperClassID() { return OSM_CLASS_ID;
} Class_ID ClassID() { return Class_ID(0xA1C8E1D1,
0xE7AA2BE5); } const TCHAR* Category() { return _T("");
} }; static MeltClassDesc MeltCD; 2.2.4
LibDescription() 当包含入口(过程物体、修改器或控制器等)的MAX文件被装入且系统还没有访问它时(如DLL无效),一个消息被发给用户。当DLL不可用时,系统要求每个DLL返回一个字符串向用户说明情况。例如,假定用户有一个融化修改器,他将此融化修改器应用到场景中的某个节点上,并保存此文件。当他将这个文件给一个没有这个融化DLL的朋友,这个朋友打开这个文件时,系统将发出一条消息说明文件中的一个入口所依赖的DLL找不到,这个消息可能是“融化修改器。想要请打电话025-1234PLUG-INS”。 DLL必须实现LibDescription()才能向系统提供这个字符串。该函数返回值即为当找不到该DLL时要显示的文字。该字符串也将显示在Summary
Info/Plug-In
Info...对话框中。一旦DLL中的一个插件已经在场景中使用过,系统就会把这个字符串保存到max文件里(以便在DLL丢失时显示)。 注意:即使DLL缺席时场景仍然会被打开。3DS
MAX保留任何DLL丢失的节点(entities),这样如果文件被修改并保存然后再在有DLL的系统打开修改后的文件,这些节点仍然存在并链接进场景。不能访问其DLL的入口称为封闭入口(orphaned
entities)。 封闭入口将作为其超类(SupperClass)的通用代表导入。该代表将在场景中显示最少的信息。举个实例:如果入口是个修改器,它将在修改器清单中显示自己的名字,但不会显示任何参数。如果没有对象类型信息,那么将在场景中显示为虚物体(dummy)。它们可以被移动、旋转、缩放、链接、组合、删除……任何与节点相关的操作。丢失的控制器只提供不变的默认值,这些值是不可调的。 参考“Read
Only
Plug-Ins”部分。通过允许插件工作在只读模式,用户可以自由分发DLL,其他人除非通过了基于硬件锁ID的认证可以运行它,否则使用将受到限制直到购买自己的拷贝。以下是该函数实现的一个例子: __declspec(
dllexport ) const TCHAR *LibDescription() { return _T("Melt
Modifier. Call 1-800-PLUG-INS to obtain a copy"); } 2.2.5
LibVersion() 开发者必须实现一个函数以便系统处理不同版本的3DS
MAX插件DLL。因为MAX体系与插件的关系如此之紧密,系统有时候需要阻止插件的老版本被调用。要使MAX能够完成它,DLL必须实现一个名为LibVersion()的函数。这个函数只简单的返回一个预定义的常量,这个常量表明在插件编译时系统的版本。未来版本的MAX可能更新该常量,而老的DLL总是返回以前的值。该函数使得系统可以检查任意一个DLL是否已经被装入,如果是这样则显示一条消息。 __declspec(
dllexport ) ULONG LibVersion() { return VERSION_3DSMAX;
} 注意:开发者可以用下面的全局函数获取该值。 DWORD
Get3DSMAXVersion(); 返回正在运行的MAX版本被编译时“\MAXSDK\INCLUDE\PLUGAPI.H”文件中包含的VERSION_3DSMAX宏定义的状态。 总结 插件必须实现这五个函数:DLLMain()、LibNumberClasses()、LibClassDesc(i)、LibDescription()、LibVersion()。这些函数允许系统取得DLL中插件的信息。2.3、六个类描述器方法成员 下面我们来讲讲六个类描述器方法成员:IsPublic()、Create()、ClassName()、SuperClassID()、ClassID()及Category()。 2.3.1
IsPublic() 该方法返回一个布尔值。如果插件可以被用户选取和指派,正是常见的情况,返回TRUE。某些插件可能是同一DLL的实现的其它插件私有专用的,并不出现在清单供用户选择。这些插件将返回FALSE。 2.3.2
Create(BOOL loading =
FALSE) MAX在需要得到一个指向插件类的新实例的时候调用该方法。例如,如果MAX从磁盘打开一个包含前边用过的插件的文件,它将调用插件的Create()方法。插件负责分配一个插件类的新实例。在上边的例子的是用一个“new”操作简单实现的。 Create()的可选参数是一个标识表明要创建的类是否将从一个磁盘文件中装入。如果该标识为TRUE,插件可以不必做任何初始化工作,因为装入进程将会处理它。见“Loading
and
Saving”章节。 当系统需要删除一个插件类的实例时,它会调用Animatable的DeleteThis()方法。插件开发者必须实现该方法。因为开发使用new操作分配内存,它也应该用delete操作释放之。如开发者可以如下实现DeleteThis(): void
DeleteThis() { delete this; } 进一步的细节参考“Memory
Allocation”章节。 2.3.3
ClassName() 该方法返回类的名字。这个名字将出现在MAX用户界面的插件按钮上。该方法也在调试时显示类的名字。 2.3.4
SuperClassID() 该方法返回系统预定义的常量,该常量表示插件类是从哪个类衍生来的。例如,弯曲修改器返回OSM_CLASS_ID。这个超类ID被所有对象空间修改器使用。其它的超类ID例子有:CAMERA_CLASS_ID、LIGHT_CLASS_ID、SHAPE_CLASS_ID、HELPER_CLASS_ID、SYSTEM_CLASS_ID。完整的清单见“List
of Super Class IDs”。 2.3.5 ClassID() 该方法必须为对象返回一个唯一性ID。3DS MAX
SDK中包含一个生成这种ClassID的程序。使用该程序为你的插件创建ClassID是非常重要的。如果如果你使用任何一个例子程序的源代码来建立自己的插件,必须改变已经存在Class_ID。如果不这样,将会出现冲突。如果两个ClassID冲突,系统将加载它找到的第一个(并将在试图加载第二个时显示存在Class_ID冲突)。 一个Class_ID包含两个无符号的32位整数。建构函数为每一个赋值,如Class_ID(0xA1C864D1,
0xE7AA2BE5)。参见“Class Class
ID”。 注意在MAX使用的插件样本代码将类ID第二个32位整数设为0,只有与MAX一起发售的内建插件才可以这样做。所有的插件开发者都应该同时使用两个32位整数。还有,确保你使用SDK提供程序建立类ID,这可确保两个插件类之间不会冲突。要生成一个随机的Class_ID并可选的将其拷贝至剪帖板中,单击DLL
Function and Class部分的“Generate a Class_ID”。 2.3.6
Category() 在建立面板底部下拉选单选择类别。如果设成已经存在的类别(i.e. "Standard Primitives",
"article Systems",
etc),插件将会出现在那个类别里。开发者不应该加到MAX提供的类别里(见下边的注释)。如果类别还不存在,则将被创建。如果插件不需要出现在清单中,它可以简单的返回一个null字符串_T("")。Category()也被按钮设置对话框中用于插件分类。 重要说明:MAX体系在Create分支面板里有每类12个插件的限制。为预防每个类别有太多插件的问题,开发者应该总是为自己的插件建立一个新的类别而不是使用一个MAX标准插件已经使用的类别。注意早于1.2版本的MAX在每个类别有多于12个按钮时会崩溃。
管理过程物体创建过程并编辑其参数 这部分讨论以下方法: GetCreateMouseCallBack() proc() BeginEditParams() EndEditParams() GetValue() SetValue NumSubs() SubAnim() SubAnimName() 当MAX用户将要建立一个新的过程物体时,系统会调用插件的一个方法接管创建阶段的用户输入活动。插件可以任意实现其用户界面,但必须向MAX提供一个途径与用户交互过程联系。插件要实现GetCreateMouseCallBack()函数以向MAX提供该途径。这个函数返回一个指向CreateMouseCallBack衍生类实例的指针。这个类有一个proc()方法,程序员在这个函数里定义物体创建阶段的用户交互活动。系统实际上需要一个函数指针,这个函数是由插件实现可以被系统调用。参考sphere_c.cpp源代码。 插件必须实现一些方法以处理用在命令面板中的输入。插件要负责实现从Animatable类继承来的两个方法,这两个方法用来处理用户在命令面板中的输入:BeginEditParams()和EndEditParams()。Begin在用户可以编辑实体参数时由系统调用(物体创建或修改已经存在的实体时都可能调用),它负责向面板里添加卷展栏并将其注册到系统中。加入卷展栏函数带有一个Dialog
Proc参数。这个对话处理过程控制用户与对话框控件的交互活动。过程球体的例子使用了参数映射机制来简化开发者与管理用户界面控制相关工作任务。参数映射被用于管理UI交互活动。见参数映射。 End方法则在用户结束编辑一个物体的参数时被调用。系统将传递一个标识给EndEditParams()以指示卷展栏是否应该删除。如果为TURE,插件必须注销卷展栏,并将其从面板中删掉。在某些情况,物体的卷展栏应该保留在命令面板里。例如如果用户已经完成一个过程球体的建立,它的EndEditParams()方法被调用。然而用户可能希望建立另一个球,这样开发者不可以立刻移走卷展栏。这样用户界面不会因为删除后立刻加回来而闪烁。 参数映射 这部分对于理解例子及简化开发相当有用,所以列入基本内容。参数映射用于最小化插件管理用户界面参数所需的编程工作。一个简单插件,如过程球体,拥有由类似微调控件(to
be continued...)。 |