转自:详细讲解3DMAX导出插件-tiamo

3dmax的导出插件是用来把做好的3d模型导出成自己引擎需要的格式的一个dll,它由3dmax加载调用.具体怎样去写一个插件,小T不多说,在3dmax的sdk里面有比较详细的介绍,在google上面也能搜索到不少的源代码,这里说的只是3dmax的数据组织方式,以及怎么获取转换3dmax的数据.
  
  3dmax里面一个比较重要的概念就是INode,3dmax的场景模型都是由一个个的INode组成,这些INode构成一棵体系树,而各个真实的模型都是附着到一个INode上面的,3dmax的sdk提供了怎样获取INode指针,怎样获取INode的几个Matrix的方法,这个能在max的sdk里面找到,也不是小T这次主要谈的东西.获取了相应的Matrix以后,用INode的EvalWorldState等等函数就能获取到附着在这个INode上面的geom object,然后能获取到vertex信息,face信息,material信息,这些都相对容易,随便的一个导出插件的例子都会有提到这些方法,小T也不多少.说了半天,小T究竟想说什么呢?嘿嘿.一个是skin mesh的weight数据获取,一个是keyframe的control数据获取以及3dmax的几种不同的control的keyframe的插值方法.
  
  先说skin mesh的weight table数据.X文件的导出插件里面使用的skin工具属于charactor studio(cs)的一个部分,小T没有找到合适的cs安装,所以小T自己的插件不准备支持cs,小T推荐的也是唯一支持的工具是3dmax5自带的skin工具.下面说的就是skin工具的数据获取.skin这个工作在3dmax里面被称为了modifier,3dmax对于每一个object都维护一个modifier stack(关于这个方面的详细信息可以查看3dmax的sdk,或者使用google),现在首先要作的就是获取到skin这个modifier的接口指针ISkin.--->使用GetModifier函数一一遍历每个modifier,检查它的class id是不是SKIN_CLASSID,然后调用GetInterface获得ISkin的指针,通过这个指针调用GetContextInterface获取ISkinContextData指针,这个指针里面就维护了weight table.首先调用ISkinContextData指针的GetNumAssignedBones,传人vertex的id(从face的数据里面获得这个id),得到了影响这个vertex的bone的数目,然后从0到bone数目减1,一一调用GetAssignedBone,传人vertex的id和bone index,得到bone id,然后使用ISkin的GetBone传人bone id获得bone的INode指针,然后调用ISkinContextData的GetBoneWeight传人vertex的id和bone的index,就能获得weight数据.有点乱,贴代码上来.
  
  // get weights ,CFace is a class that hold face info,i0,i1,i2 is face's vertexes id void CExporter::GetWeights(CFace* pFace,INode *pNode,Mesh *pMesh,int i0,int i1,int i2) { // find skin modifier Object *pObject = pNode->GetObjectRef(); if (pObject->SuperClassID() == GEN_DERIVOB_CLASS_ID) { IDerivedObject *pDerivedObject = (IDerivedObject *)pObject; int nMod = pDerivedObject->NumModifiers(); for(int i = 0; i < nMod; i++) { Modifier *pModifier = pDerivedObject->GetModifier(i); if (pModifier->ClassID() == SKIN_CLASSID) { ISkin *pSkin = (ISkin*)pModifier->GetInterface(I_SKIN);       // get ISkin interface if(pSkin) { ISkinContextData* pSkinContext = pSkin->GetContextInterface(pNode);   // get context interface int nBones,j; // bones nBones = pSkinContext->GetNumAssignedBones(i0);// param is vertex id,use pmaxMesh->faces[i].v[0] for(j = 0; j < nBones; j ++) { int nBoneIndex = pSkinContext->GetAssignedBone(i0,j); // FindNode is function that take a INode pointer reture a index id. pFace->m_vertex[0].m_ltWeights.push_back(std::make_pair(FindNode(pSkin->GetBone(nBoneIndex)), pSkinContext->GetBoneWeight(i0,j))); } nBones = pSkinContext->GetNumAssignedBones(i1); // ........same for i1 and i2 } } } }   
  skin mesh 的weight数据就算是获取完成了.接下来的是3dmax的control数据获取.这个部分是整个3dmax里面最为隐讳的一个部分,它的格式只有在3dmax的debug sdk里面才有,而这个debug sdk是要钱的,小T现在可没有那个能力支付多少多少的美圆..嘿嘿.下来的这些资料来自小T从网上收集到的各个open source的3d引擎的源代码,有一小部分是小T自己研究的结果.先列出资料的来源.首先的一个是魔兽的mdl导出插件'DeX.http://republicola.wc3campaigns.com/DeX/,然后的一个是fairy-project,还有一个就是www.nevrax.org.
  
  3dmax里面的control有很多很多,小T只是打算支持主要的3种,linear,bezier和tcb control.下面一个一个的讲.
  
  linear是最简单的,几乎不需要讲,他使用线性插值算法.对于旋转数据使用quat的slerp算法就ok.
  void CExporter::GetLinearPosition(CNode *pOurNode,INode *pMaxNode,Control *pControl,IKeyControl *pKeyControl) { ILinPoint3Key maxKey; CAnimationPositionLinearKey ourKey; for(int i = 0; i < pKeyControl->GetNumKeys(); i ++) { // abs position,local system pKeyControl->GetKey(i,&maxKey); ourKey.m_fPosition[0] = maxKey.val.x; ourKey.m_fPosition[1] = maxKey.val.z; ourKey.m_fPosition[2] = maxKey.val.y; ourKey.m_nTime = maxKey.time * 1000 / TIME_TICKSPERSEC; AddAnimationKey(pOurNode,LinearPositionKey,&ourKey); } // when do interpolation,key1 is prev key,key2 is next key,t is time,then the position at t is // pos = key1.pos + (key2.pos - key1.pos)*(t - key1.time)/(key2.time - key1.time) } // linear rotation void CExporter::GetLinearRotation(CNode *pOurNode,INode *pMaxNode,Control *pControl,IKeyControl *pKeyControl) { Matrix3 maxMatrix; ILinRotKey maxKey; CAnimationRotationLinearKey ourKey; for(int i = 0; i < pKeyControl->GetNumKeys(); i ++) { pKeyControl->GetKey(i,&maxKey); // this key's quat is an abs value,not a rel value...error in max sdk // convert to matrix maxKey.val.MakeMatrix(maxMatrix); ConvertMaxMat2OurMat(maxMatrix,ourKey.m_matNode); ourKey.m_nTime = maxKey.time * 1000 / TIME_TICKSPERSEC; AddAnimationKey(pOurNode,LinearRotationKey,&ourKey); } // when do interpolation // rotation is Quat::Slerp(key1.qRot,key2.qRot,(t - key1.time)/(key2.time - key1.time)) }   
  接下来说tcb control 这个要比linear复杂一点,tcb control使用的是hermite(埃尔米特)插值,hermite插值是指给定有限个点的值和这些点的一阶导数,构造一个多项式,在那些给定的点的值和一阶导数都和已知值相同.这个在数值分析里面有讲到,给个链接.很明显,一个物体的位置,旋转角度是一个关于时间的函数,给定一个时间,就有一个唯一的位置,一个唯一的旋转,而现在我们不可能记录任何时间的位置和旋转信息,我们只是知道在某些特定的时间点(这些点叫keyframe)的位置和旋转信息,还有这些点的导数信息,现在就要利用这些已知信息计算出任何时间点的值来.这个就叫插值.(呃,这个解释不算是完备,但是我个人觉得还是容易理解的).而利用值和导数,我们已经能用hermite插值方法计算出任何时间点的值来了,但是,实际上,获得单个点的导数信息却并不是已经很容易的事情,所以tcb就应运而生了,他并没有记录单个点的导数,而是记录了3个额外的数据,而单个点的导数信息可以通过这些已知道信息计算出来(具体的方式可以看上面的链接里面的文章),特殊的点是第一个和最后一个点,第一个点只需要计算TD的值,
  float tm = 0.5f * (1.0f - firstKey->Tension);
  firstKey->TD = tm * ((secondKey->Value - firstKey->Value) * 3.0f - secondKey->TS);
  最后一个点计算TS的值
  float tm = 0.5f * (1.0f - lastKey->Tension);
  lastKey->TS = tm * ((lastKey->Value - previousLastKey->Value) * 3.0f - previousLastKey->TD);
  然后,上面那个链接里面给出来的方法里面必须的数据就都差不多了,唯一例外的是那个s.表面上看s就是(t - key1.time)/(key2.time - key1.time),其实不是,在3dmax里面还有一个easeIn和easeOut数据,刚刚得到的结果还得经过一系列的计算才能作为插值参数s.方法列出来:
  ease : first calc float e0 = Keys[i].m_fEaseOut; float e1 = Keys[i+1].m_fEaseIn; float s = e0 + e1; if (s > 1.0) { e0 /= s; e1 /= s; } Keys[i].m_fEase0 = e0; Keys[i].m_fEase1 = e1; Keys[i].m_fEaseK = 1.0f / (2.0f - e0 - e1); if ( e0 != 0.0f ) { Keys[i].m_fEaseKOverEase0 = Keys[i].m_fEaseK / e0; } if ( e1 != 0.0f ) { Keys[i].m_fEaseKOverEase1 = Keys[i].m_fEaseK / e1; } // for the last key m_fEaseK = 0.5f when do ease if(key->m_fEaseK == 0.5f) { // keep the same s = t; } else if(t < key->m_fEase0) { s = key->m_fEaseKOverEase0 * t * t; } els

详细讲解3DMAX导出插件-tiamo相关推荐

  1. 3DMAX导出插件的编写

    原文链接: http://liweizhaolili.blog.163.com/blog/static/162307442013117731953/ 作为3D开发人员,特别是3D引擎开发人员,经常会接 ...

  2. 从3dMax导出供threeJS使用的带动作模型与加载(认真修改详尽版)

    评论区发现的建议,最近没空测试,先贴这 还有好多人说找不到插件的 https://pan.baidu.com/s/1Q5g0... 密码:b43e . 应该是他们现在只是维护blender,只有这个的 ...

  3. 3DMAX中CryEngine导出插件的配置方法

    原文地址:http://tieba.baidu.com/p/2349823822 1 找到 你的cryengine根目录\Tools\CryMaxTools\LoadCryMaxTools.ms 复制 ...

  4. tkMapper插件的详细讲解

    tkMapper插件的详细讲解 一.tkMapper简介 tkMapper就是一个MyBatis插件,是在MyBatis的基础上提供的开发工具,可以让开发变得简单,提高开发效率. 作用: 1.提供了针 ...

  5. c4d流体插件_【C4D】流体插件详细讲解2

    教程内容会持续更新... 请关注每篇文章的视频内容 ###-----------------+++-------------------### RF流体插件 链接:https://pan.baidu. ...

  6. vue-cli2、vue-cli3脚手架详细讲解

    转载自 vue-cli2.vue-cli3脚手架详细讲解 前言: vue脚手架指的是vue-cli它是vue官方提供的一个快速构建单页面(SPA)环境配置的工具,cli 就是(command-line ...

  7. react的超详细讲解

    create-react-app 项目目录 在HTML中使用react 1 2 3基础 React的注意事项 模拟的React 和 render React组件 函数组件 类组件 React 的数据源 ...

  8. vue-cli 目录结构详细讲解

    https://juejin.im/post/5c3599386fb9a049db7351a8 vue-cli 目录结构详细讲解 目录 结构预览 ├─build//保存一些webpack的初始化配置, ...

  9. oracle怎么将一列挪到另一列,详细讲解Oracle数据库的数据迁移方法

    <详细讲解Oracle数据库的数据迁移方法>由会员分享,可在线阅读,更多相关<详细讲解Oracle数据库的数据迁移方法(4页珍藏版)>请在人人文库网上搜索. 1.详细讲解 Or ...

最新文章

  1. Linux系统快速安装JDK
  2. mysql ceill_MYSQL常用函数
  3. spring applicationContext.xml最全约束
  4. java.io包有哪些方法_java.io包下常用类及常用方法介绍
  5. HALCON基础知识
  6. 权限操作-表结构分析与创建表
  7. pandas的自带数据集_用Python和Pandas进行数据清理:检测丢失值
  8. mysql 存入图_c# 如何向mysql数据库中存入图片
  9. Python学习入门5:Python到底应该怎么学?
  10. Python学习笔记之变量
  11. 语言怎么表示词谱_黄庭坚被踢了!喝火令,还词谱本来面目
  12. H83601D直插DIP千兆双口网络接口隔离滤波脉冲变压器
  13. AXURE原型设计经验总结
  14. html中加入点击事件,html中的点击事件
  15. thon3爬虫之urllib携带cookie爬取网页的方法
  16. layui 表格序号累加 翻页序号也累加
  17. JavaScript(JS)的基本语法
  18. 2018 Google IO
  19. Device disconnected
  20. SQL案例学习-数据透视表

热门文章

  1. 洛谷——P2433 【深基1-2】小学数学 N 合一
  2. 洛谷——P1567 统计天数
  3. 线程创建方式3-实现 callable接口(Java)
  4. 微信小程序自定义状态栏navigationBar样式组件,适配所有机型
  5. python使用rpa需要什么插件_使用Python制作ArcGIS插件基础篇——工具介绍
  6. python网址编码转换_刚学python,抓中文网页遇到编码的问题,怎么转换也不行……...
  7. JSON for Modern C++ 3.6.0 发布
  8. 【公众号】微信第三方登录(静默授权和非静默授权)(具体代码:U盘 新浪云SAE)...
  9. arcgis api for js之echarts开源js库实现地图统计图分析
  10. 从DB-Engines看传统数据库生存状况