初めての骨格アニメーション

はじめに



良い一日。 私はここで初めて記事を書いていますが、私の目標は非常に単純です。骨格アニメーションのビジョンを共有したいので、批判と助けをもらいたいと強く望んでいます。 このシステムの作成全体は試行錯誤によって行われました。これを行う方法、実際の仕組み、および使用方法に関する書籍は見つかりませんでした。 しかし今、私は行き止まりに達したので、ここからこの方法を見つけたいと思います。



何を使用しましたか?






どこから始めますか?



私は最初に構造を定義する必要があると決めました。そして、それがどのように機能するかを理解することは私にとって非常に簡単になるでしょう。

まず、#definesを表示します。

#define M_PI 3.1415926535 #define M_PI_2 M_PI/2 #define M_PI_3 M_PI/3 #define M_PI_4 M_PI/4 #define RAD1 M_PI/180.0f #define DEG1 180.0f/M_PI #define RAD2DEG(rad) rad*DEG1 #define DEG2RAD(deg) deg*RAD1 #define MAX_CHILDREN_COUNT 10 #define MAX_KEYFRAME_COUNT 20
      
      







そして、私は最終的にそのような構造に来ました:

ジョイントはジョイントです


  typedef struct _Joint { //inheritanse _Joint* root; /* Pointer to root element. All elements have it. */ _Joint* parent; /* Pointer to parent */ //simple joints variables float x, y; /* Position */ float angle; /* Angle of rotate. In radians!!! */ uint8_t level; /* Level of hierarchy. Root have 0, next level +1 */ float dX, dY; /* Default Position */ float dAngle; /* Default Angle */ float aX, aY; /* Animation Position */ float aAngle; /* Animation Angle */ //children uint8_t childCount; /* Number of children */ _Joint* child[MAX_CHILDREN_COUNT]; /* Array of children */ //index uint16_t indexCount; /* Last number for index. Only Root have the var. */ uint16_t index; /* Unique index of joint */ } S_Joint;
      
      







dX、dY、dAngle-これらはデフォルト値です。 0〜6.28の角度をラジアンで保存することにしました。 そして、これには多くの問題がありました。 しかし、それについては後で。

aX、aY、aAngle-これらはアニメーションオプションです。 これらは、フレームがすでに補間されている量を示します。 つまり、過去のアニメーションが記録されます。 たとえば、アニメーションの持続時間は900ミリ秒( TIME )、450ミリ秒( NOW )ですが、ミリ秒ごとにアニメーションを更新することはできません。したがって、前に400ミリ秒、450 = 50( KEY1 )、2番目の100( KEY2 )では、 Xの最終値は次のとおりです。

_Joint.x += (KEY2 - KEY1) / TIME * NOW - _Joint.aX;





_Joint.aX = (KEY2 - KEY1) / TIME * NOW;





22,3 += (100-50) / 900 * 450 - 22,3





22.3-これはXサイコロです。時間( NOW )が0の場合、 Xもゼロであったことを考慮してください



KeyDataとKeyframeはアニメーションの構造です


 typedef struct _KeyData { float x, y, angle; } S_KeyData; typedef struct _Keyframe { S_KeyData data; /* data of Joint */ uint16_t time; /* ~32 sec.(32768 ms) is maximum time for animation. Only Root have it */ uint16_t index; /* Index of joint, which we want interpolate */ _Keyframe* parent; uint8_t childCount; /* Number of children */ _Keyframe* child[MAX_CHILDREN_COUNT]; } S_Keyframe;
      
      







補間のためにボーンの検索を整理する方法を長い間考えていました。 私の意見では、ツリー内での検索はアニメーションのかなり曖昧なプロセスです。 私は頭の中で2つの決定をしました。 最初の解決策は、インデックスを使用してブランチを示すことです。これにより、検索が少し高速化されます。 それは通常のアドレス指定です。 しかし、私は2番目のソリューションを選択しました。 私の意見では、ツリー全体の更新は、ツリーのどの部分よりもはるかに簡単で高速です。 非常に簡単です。メインボーンにはそれぞれ10個の子があり、さらに5個のボーンがあるとします。その結果、51個のボーンしかありません(ところで、ルートジョイントはまったく表示されません。オブジェクト全体の位置)。

5反復の10サイクル、つまり50反復のみが必要です。 そして最後に、ツリー全体を更新するときに51の補間を取得します。 それ以外の場合、ボーン検索を使用する場合、イテレーションでは最後に追加されたボーンを検索するために50回、最後から2番目のボーンを検索するために49回が必要になります。 インデックス0の _Keyframeは、このアニメーション化の時間を格納します。彼のすべての子には0の時間パラメーターがあります。



アニメーション-キーのリストを保存します


  typedef struct _Animation { uint8_t keyNumber; uint8_t keyCount; S_Keyframe* key[MAX_KEYFRAME_COUNT]; } S_Animation;
      
      





keyNumberは再生するアニメーションのインデックスで、デフォルトはゼロです。 すべてのアニメーションキーは、時​​系列でキー配列に格納されます。 実装のすべての方法、構造の削除の作成、およびそれらへの新しいオブジェクトの追加は、ここで多くのコードを積み重ねないように非表示にします。 ただし、アニメーション自体の機能は表示します。

 bool doAnimation(S_Joint *root, S_Animation *anim, uint16_t time) { if (!root) return false; if (!anim) return false; bool timeOut = true; for (int i = 0; i < anim->keyCount; i++) { if (time < anim->key[i]->time) //search keyframes for interpolation { //  1200,  2400, 2400-1200=1200 uint16_t mtime = anim->key[i]->time - anim->key[i - 1]->time; //nowTime is 1560, mtime = 1200(ERROR), 1560 - 1200(last key)=360(realtime) uint16_t nowTime = time - anim->key[i - 1]->time; if (i != anim->keyNumber) //    keyNumber,        { setDefaultAnimTree(root); //set to 0.0 animation changes(aX,yX,aAngle) } anim->keyNumber = i; //   doInterpolate(root, anim->key[i - 1], anim->key[i], mtime, nowTime); timeOut = false; break; } } if (timeOut == true) { setDefaultTree(root); //          } return timeOut; }
      
      







関数がtrueを返すと、タイマーはゼロにリセットされます(Boost :: timer)



そして最後の機能:

 void doInterpolate(S_Joint* root, S_Keyframe* key1, S_Keyframe* key2, uint16_t time, uint16_t nowTime) { if (root->index != key2->index) return; float x = (key2->data.x - key1->data.x) / time * nowTime; //        float y = (key2->data.y - key1->data.y) / time * nowTime; float angle = (key2->data.angle - key1->data.angle) / time * nowTime; root->x += x - root->aX; //root->aX -        ,        root->y += y - root->aY; root->angle += angle - root->aAngle; root->aX = x; //     ,     x,y,angle root->aY = y; root->aAngle = angle; //     for (int i = 0; i < root->childCount; i++) { doInterpolate(root->child[i], key1->child[i], key2->child[i], time, nowTime); } }
      
      







ここにそのような骨格アニメーションがあり、それはかなりうまく機能します。 Qtでプログラムを作成する段階で多くの問題が発生しました。最初の問題は、マウスによる骨の動き、そして実際には骨の動きです。 6.28の円内の骨の回転長、つまり2 * Piまたは360度。 しかし、結果の動きは一方向にのみ発生します。 取得した場合、計算4.47で、反対方向に移動したい場合、原則としてこれを行うことができます。

4,47-6.28=-1,81





逆方向に動くようです。 しかし、それはあまり便利ではありません。 また、回転ターゲットへの最適なパスを検索しようとしました。 位置が1.57(A)であると想像すると、目標は5.57(B)になります。

 negAngle = BA; posAngle = (B-6.28)-A; if(fabs(posAngle) > fabs(negAngle)) { /* */ }
      
      







ただし、最適なパスが常に必要なわけではありません。 したがって、これも完全には適合しません。 これらの問題を解決するための2つのオプションについて考えます。 最初のオプションは、厳密な補間を計算することです。 つまり、アニメーションのキーはボーンになります。 2番目の方法は、キーのみの動きを求めることです。 しかし、実際には、私は行き詰まっており、次に何をすべきかわかりません。 Qtプログラムは比較的成功しますが、アニメーション自体は悪くないようですが、GUIエディターを書いている間、その非効率性と煩わしさを理解しました。 骨格アニメーションを一般的な物理学に制限する方法(私はBox2dに参加したいという欲求に燃えています)が、これを行う方法がわかりません。 移植性のあるものすべてをやり直すことにしましたが、アドバイス、何をどのように間違ったかについての批判を聞きたいと思います。 繰り返しますが、これは私のシステムであり、例に従っていませんが、これは骨格アニメーションの私の見解です。厳密に判断しないでください。 ご清聴ありがとうございました!



All Articles