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》
當然了,很多時候並不一定非要均勻離散化,僅用均勻的參數離散化就夠了。我自己就是這麼實現的,效果也很好。

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