3ds max贝塞尔曲线输出

最近通过max的IGame接口实现了贝塞尔曲线的导出,在这里分享一下。
在max里编辑关键帧动画时,一般默认使用的贝塞尔曲线控制参数随时间的变化。
这里的参数可以指各种参数,物体的位置,欧拉角,灯光的颜色,相机fov等等。
IGame提供接口可以导出贝塞尔关键帧,但是由于max sdk的文档写的众所周知的烂,所以
还是需要我们求助于google的同时开动自己的脑筋。
max sdk烂的程度从它结构体声明的注释就可见一斑。
class IGameBezierKey: public MaxHeapOperators {
 public:
  //! Float based In and out tangents
  /*! This would be accessed when using the IGameControlType::IGAME_FLOAT specifier
  */
  float fintan, fouttan;

  //! Float based value
  /*! This would be accessed when using the IGameControlType::IGAME_FLOAT specifier
  */
  float fval;
  //! Float based tangent lengths
  /*! This would be accessed when using the IGameControlType::IGAME_FLOAT specifier
  */
  float finLength, foutLength;

  ......
 };
 
基本等于什么都没有注释,只能想到fval等是关键帧的值,其他的intan,outtan,inLength,outLength代表什么意义————不知道。
经过几个小时的研究揣摩。终于搞明白了。是这样的:


如图p0-p1-p2是一段贝塞尔曲线,它有三个关键帧。其中关键帧p1有a和b两个控制点。我们的目标就是要求出a和b这两个点。
公式已经在图中,下面是我的代码示例,导出float类型的贝塞尔曲线,Point3类型的也与此类似。

bool ExportBezierKeys(IGameKeyTab &keyTabs, IGameControlType type, TiXmlElement* pXmlController)
{
 pXmlController->SetAttribute("Type", "Bezier");

 float timeScale = 4800.0f;
 float invTimeScale = 1 / timeScale;
 if (type == IGAME_FLOAT || type == IGAME_EULER_X || type == IGAME_EULER_Y || type == IGAME_EULER_Z ||
  IGAME_POS_X || IGAME_POS_Y || IGAME_POS_Z)
 {
  string val;
  char buff[128];
  for (int i = 0; i < keyTabs.Count(); i++)
  {
   float t[3], v[3];
   t[1] = keyTabs[i].t;
   v[1] = keyTabs[i].bezierKey.fval;
   if (i != 0)
   {
    float timeSpan = t[1] - keyTabs[i - 1].t;
    t[0] = t[1] - timeSpan * keyTabs[i].bezierKey.finLength;
    v[0] = v[1] + timeSpan * keyTabs[i].bezierKey.finLength * keyTabs[i].bezierKey.fintan;
   }
   else
   {
    t[0] = 99.9f * timeScale;
    v[0] = 99.9f;
   }

   if (i != keyTabs.Count() - 1)
   {
    float timeSpan = keyTabs[i + 1].t - t[1];
    t[2] = t[1] + timeSpan * keyTabs[i].bezierKey.foutLength;
    v[2] = v[1] + timeSpan * keyTabs[i].bezierKey.foutLength * keyTabs[i].bezierKey.fouttan;
   }
   else
   {
    t[2] = 99.9f * timeScale;
    v[2] = 99.9f;
   }

   for (int j = 0; j < 3; j++)
   {
    t[j] *= invTimeScale;
   }

   sprintf_s(buff, sizeof(buff), "%d %.3f %.3f %.3f %.3f %.3f %.3f|", i, t[0], v[0], t[1], v[1], t[2], v[2]);
   val += buff;
  }

  pXmlController->SetAttribute("NumFrames", keyTabs.Count());
  LinkNewXmlText(pXmlController, val.c_str());
  return true;
 }

 return false;
}

 

bool ExportKeyFrameController(IGameControl* pGameControl, TiXmlElement* pXmlController, IGameControlType type)
{
 if (!pGameControl->IsAnimated(type))
 {
  return false;
 }

 Control* pMaxControl = pGameControl->GetMaxControl(type);
 int ortBefore = pMaxControl->GetORT(ORT_BEFORE);
 int ortAfter = pMaxControl->GetORT(ORT_AFTER);
 if (ortBefore != ortAfter)
 {
  MessageBoxA(NULL, "不支持前后不一致的out of range type", "", MB_OK);
 }

 string ortType = "ONCE";
 if (ortBefore == ORT_CONSTANT)
 {
 }
 else if (ortBefore == ORT_CYCLE || ortBefore == ORT_LOOP)
 {
  ortType = "LOOP";
 }

 pXmlController->SetAttribute("PlayBack", ortType);

 IGameKeyTab keyTabs;
 if (pGameControl->GetLinearKeys(keyTabs, type))
 { 
  return ExportLinearKeys(keyTabs, type, pXmlController);
 }
 else if (pGameControl->GetBezierKeys(keyTabs, type))
 {
  return ExportBezierKeys(keyTabs, type, pXmlController);
 }
 else if (pGameControl->GetTCBKeys(keyTabs, type))
 {   
  MessageBoxA(NULL, "不支持TCB关键帧动画", "", MB_OK);
  return false;
 }

 return false;
}

现在说一下另一个话题:我们有了这样一条贝塞尔曲线,我们要如何根据时间对它差值?
贝塞尔曲线是参数方程,我们无法根据其中一个值(t),解出曲线参数u,然后再根据解出的参数求出
另一个值。即使有稳定的数值解法可以根据一个值解出曲线参数,对于实时动画应用会有性能问题。
因此一个可行的方案是在加载时将曲线离散化,运行时进行普通的线性差值。事实上,如何对曲线进行均匀的
离散化依然是一个很复杂的问题,无论是根据弧长均匀还是根据某一个值均匀离散。
更详细的内容可以参考《Computer Animation-Algorithms and Techniques》
当然了,很多时候并不一定非要均匀离散化,仅用均匀的参数离散化就够了。我自己就是这么实现的,效果也很好。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章