首页 > 代码库 > 3DsMax导出插件编写(二)——常规SDK方法进行信息获取和保存文件
3DsMax导出插件编写(二)——常规SDK方法进行信息获取和保存文件
之前已经把配置vs和maxSdk的方法介绍过了,如果不懂怎样配置的朋友请参考:
http://liweizhaolili.blog.163.com/blog/static/162307442013117731953/
下面来介绍一下导出插件的具体写法。不过这不是一个容易说的很详细的问题。因为我们要写导出插件,通常都是因为想根据自己想要的信息来导出,所以就算我把我整个工程都公开,意义也不大的,因为那是根据我自己需要的数据写的业务,估计不太可能和你想要的一样的。所以我也只能简单的说明一些几个关键获取数据的方法,和保存文件的方法,如果想看具体的导出范例工程,你可以去maxSdk文件夹下面找到maxsdk\samples\import_export\3dsexp.cpp,这是一个完整的导出3ds文件的例子。
然后maxSdk提供的操作数据的方法有两种,一种是常规方法,一种是IGame方法。下面我们先来介绍一下常规的方法,至于IGame的方法,我会在另外一遍文章里面说明:http://liweizhaolili.blog.163.com/blog/static/16230744201311219926255/
我建议先看看常规方法,对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。
【转】http://liweizhaolili.blog.163.com/blog/static/162307442013111592849927/
3DsMax导出插件编写(二)——常规SDK方法进行信息获取和保存文件
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。