Я пытаюсь сделать скелетную анимацию 2d(на деле пытаюсь по вращать пару палочекОх что то я запутался и ничего не выходит. Не кидайтесь помидорами но я все это придумал сам от и до.
Немножко прокомментирую что бы много кода не выкладывать.
C_Line - это класс который командой draw() рисует линию
Есть ещё пару команд этого класса:
setMatrix() и getMatrix() которы устанавливают и возвращают значение матрицы.
Кстати эта матрица при вызове команды draw() отправляется в шейдер как modelview matrix.
Класс C_Anim по моей задумке это скелетная анимация всего одной линии.
#pragma once #include "C_Line.h" struct S_Bone { glm::mat4 matrix; //матрица поворота }; struct S_Key { float angle; float x, y; int time; }; class C_Anim { std::vector<S_Bone*> mBones; //все кости тут, но пока она одна std::vector<S_Key*> mKeys; //ключи boost::timer mTimer; C_Line* mLine; GLfloat mRotate; public: C_Anim(C_SkeletonProgram *prog); ~C_Anim( ); static S_Key interpolate( S_Key *v1, S_Key *v2, int alphaTime, int nowTime); void update( ); void draw( ); };
Я решил не использовать кватернионы, я решил просто использовать градусы, самые обычные и их интерполировать.
Вот так вот я интерполирую:
S_Key C_Anim::interpolate(S_Key *v1, S_Key *v2, int alphaTime, int nowTime) { S_Key result; result.angle = v2->angle / alphaTime * nowTime; result.x = ( v2->x - v1->x) / alphaTime * nowTime; result.y = ( v2->y - v1->y) / alphaTime * nowTime; return result; }
Как происходит анимация, я запускаю таймер, затем в функции update()
Я проверяю самый последний элемент массива ключей, беру его время и сравниваю с таймером, если время на таймере меньше значит анимация все ещё выплняется,
иначе сбрасываю таймер. Если мы прошли этот контроль, я перебираю все ключи с самого первого в поиске того у которого время больше чем на таймере и беру этот ключ как k2 и из массива mKeys[i-1] беру предыдущий ключ и между ними интерполирую.
#include "C_Anim.h" C_Anim::C_Anim(C_SkeletonProgram *prog) { mRotate = 0; mLine = new C_Line( ); mLine->setProgram( prog); S_Bone *bone = new S_Bone; bone->matrix = glm::mat4( ); S_Key *k0 = new S_Key; k0->angle = 0.0f; k0->x = 0.0f; k0->y = 0.0f; k0->time = 0; S_Key *k1 = new S_Key; k1->angle = 90.0f; k1->x = 0.0f; k1->y = 0.0f; k1->time = 700; k1->index = 1; S_Key *k2 = new S_Key; k2->angle = 180.0f; k2->x = 0.0f; k2->y = 0.0f; k2->time = 1200; S_Key *k3 = new S_Key; k3->angle = 90.0f; k3->x = 0.0f; k3->y = 0.0f; k3->time = 1200; S_Key *k4 = new S_Key; k4->angle = 0.0f; k4->x = 0.0f; k4->y = 0.0f; k4->time = 1700; mBones.push_back( bone); mKeys.push_back( k0); mKeys.push_back( k1); mKeys.push_back( k2); mKeys.push_back( k3); mLine->rightMultOnMatrix( glm::translate( glm::vec3( 300.0f, 300.0f, 0.0f))); } C_Anim::~C_Anim( ) { } void C_Anim::update( ) { int time = mTimer.elapsed( ) * 1000; S_Key *k1 = NULL; S_Key *k2 = NULL; if ( time <= mKeys[mKeys.size( ) - 1]->time) // проверяю не вышло ли время за диапазон последнего ключа { for ( int i = 0; i < mKeys.size( ); i++) { if ( time <= mKeys[i]->time) { k2 = mKeys[i]; k1 = mKeys[i - 1]; break; } } //interpolation S_Key res = interpolate( k1, k2, k2->time - k1->time, time); mBones[0]->matrix = glm::rotate( res.angle, glm::vec3( 0.0f, 0.0f, 1.0f)); //кладу матрицу поворотп } //restart timer if time out animation else { mTimer.restart( ); } } void C_Anim::draw( ) { mLine->setMatrix( glm::translate( glm::vec3( 300.0f, 300.0f, 0.0f)) * mBones[0]->matrix); mLine->draw( ); }
Код работает очень очень плохо, вообще не как надо и со всеми вытекающими. Ну это то до чего я дошёл сам. Я не знаю где можно про это узнать! Пожалуйста дайте советы, исправте, я правда запутался...
Зачем для 2D случая использовать 4Д матрицы?
nes
Ты абсолютно прав. Я переписал все от и до, вообще все. Я понял что моя главная ошибка в том что я плохо структурировал вообще все.
Написал пару структур, но я никак не могу понять как интерполировать.
Я написал систему на Joint'ах структура работает просто прекрасно. Рисуется как надо.
Вот пример:
S_Joint *tmp1, *tmp2; root = addChild(NULL, 100, 100, 0); tmp1 = addChild( root, 50, 50, M_PI_4); tmp2 = addChild( root, 50, 50, -M_PI_4); addChild( tmp1, 30, 30, -M_PI_4); addChild( tmp2, 30, 30, M_PI_4);
Где:
S_Joint* addChild(S_Joint* parent, float x, float y, float angle);
Вот изображение:
И вывожу структуру ещё.
От использования матриц отказался, храню угол в радианах.
А вот для анимации использую такую структуру:
Если вкратце структура S_Animation содержит ключи анимации S_Keyframe. Сам S_Keyframe представляет собой дерево.
Я решил использовать для ключа анимации целое дерево которое будет отражать S_Joint, потому что если бы я искал элемент по идексу в дереве мне пришлось бы много раз пробегать по всему дереву в поисках, а так я интерполирую все ветви одним разом. (Ну пока так)
С интерполяцией проблема. Сразу говорю шаг програмного цикла зафиксировал.
Вот как выглядит каждый keyframe:
Для таймера использую boost::timer
S_Joint *root; S_Animation *anim; boost::timer mTimer;
Вот этот код у меня в главной функции update():
if (doAnimation( root, anim, mTimer.elapsed( ) * 1000)) { mTimer.restart( ); }
Вот это функция doAnimation()
timeOut - это маркер, если не найдёно ключей на время, то положение костей в дефолт и таймер рестарт.
Далее функция doInterpolate()
Так как функция update() у меня свободная, передвижение не всегда будет одинаковым.
Вот это:
root->aX += (( key2->data.x - key1->data.x) / time * nowTime) - root->aX; root->aY += ( ( key2->data.y - key1->data.y) / time * nowTime) - root->aY; root->aAngle = root->aAngle + ( ( ( key2->data.angle - key1->data.angle) / time * nowTime) - root->aAngle);
aX, aY, aAngle во время перезагрузки тамера обнуляются. Эти переменные есть у каждой кости и они показывают на сколько интерполяция выполнена.
Но кость двигается хаотично.
воспроизведение скелетной анимации
http://two-for-the-money.narod.ru/
исходники там же - JavaScript
Тема в архиве.