Войти
ПрограммированиеФорумГрафика

Скелетная анимация 2D

#0
23:48, 25 мар 2014

Я пытаюсь сделать скелетную анимацию 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();
}


Код работает очень очень плохо, вообще не как надо и со всеми вытекающими. Ну это то до чего я дошёл сам. Я не знаю где можно про это узнать! Пожалуйста дайте советы, исправте, я правда запутался...

#1
12:16, 26 мар 2014

Зачем для 2D случая использовать 4Д матрицы?

#2
13:45, 27 мар 2014

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 во время перезагрузки тамера обнуляются. Эти переменные есть у каждой кости и они показывают на сколько интерполяция выполнена.

Но кость двигается хаотично.

#3
16:30, 27 мар 2014

воспроизведение скелетной анимации
http://two-for-the-money.narod.ru/
исходники там же - JavaScript

ПрограммированиеФорумГрафика

Тема в архиве.