Список работ

Воспроизведение анимации 2d-скелета персонажа

Курсовая работа по дисциплине "Основы построения трансляторов"

Иванова А. М., A-13-03, alibi_@mail.ru

Содержание

Постановка задачи

2d-модель скелета персонажа (рис. 1) можно представить в виде однонаправленного графа-дерева (рис. 2).

Представление скелета в виде графа

Рис. 1. Скелет персонажа

Скелет персонажа в виде однонаправленного графа-дерева

Рис. 2. Представление скелета персонажа в виде однонаправленного графа-дерева

Такой граф можно расположить на 4-х ярусах (рис. 3).

4-уровневое представление графа-модели персонажа

Рис. 3. Ярусное представление модели

В работе решаются две следующие задачи:

  1. Создать язык представления графа и координат его вершин в заданные моменты времени.
  2. Написать транслятор, заполняющий данные класса model, воспроизводящего объект и его движение.

После ввода данных имеющимися в классе model средствами воспроизводится модель и ее анимация. Для вывода изображения использована графическая библиотека OpenGL.
Выделим в модели корневую точку (корень) - вершину на первом ярусе. Иерархию вершин определим в терминах предок - потомок (соответственно начальная и конечная точки ребра ориентированного графа).

Ограничения модели:

Проектирование языка описания модели

Основным элементом языка является описатель.
Определим описатель как структуру, имеющую голову и тело.
Голова описателя состоит из имени и атрибутов, адресующих к элементу модели.
Тело описателя содержит значения (параметры), позволяющие отобразить на плоскости элемент модели.
Тело от головы будем отделять двоеточием, параметры будем разделять запятой, точка с запятой будет завершать тело описателя.
Введем для задания корневой точки описатель knot, имеющий 2 параметра - координаты х, у корня в глобальной системе координат.
Ребра графа представляет описатель line.

Параметры, задающие ребро графа-модели

Рис. 4. Представление ребра в глобальной системе координат

Определим в описателе 2 атрибута - номера вершин ребра.
Таким образом, голова описателя принимает следующий вид: line [k][i].
Тело описателя содержит 2 параметра - угол наклона fi ребра и длину ребра h.
Пример описателя line.

line [1][2]:fi=30, h=4;

Введенными описателями knot и line можно только задать объект и его начальное положение.

Описатель next задает положение объекта через заданный промежуток времени, который определяется как атрибут описателя. Параметры описателя - это новые значения либо углов ребер, либо координат корневой точки. Каждый описатель next определяет один кадр анимации.
Замечание. Описатели next следуют после представления графа модели описателями knot и line.

Формальное описание языка

Используется нотация Бекуса-Наура.

<модель>::= <описание структуры объекта> | <описание структуры объекта> <описание движения>
<описание структуры объекта>::=<описатель line> | <описатель knot> | <описатель line><описание структуры объекта> | <описатель knot><описатель структуры объекта>
<описание движения>::= <описатель next> | <описатель nехt>|<описание движения>
<описатель>::= <голова>:<тело>
<голова>::= <имя><атрибуты>
<атрибуты>::=NULL | [<число>] | [<число>]<атрибуты>
<число>::=<знак><беззаковое число>
<знак>::=NULL | -
<беззнаковое число>::=<целое> | <дробное>
<целое>::= <цифра> | <цифра><целое>
<дробное>::=<целое>.<целое>
<имя>::= <символ> | <символ><имя> | <имя><число>
<символ> ::= А | ... | Z | а | ... | z
<цифра> ::= 0 | ... | 9
<тело>::= <параметр>; | <параметр>,<тело>
<параметр>::= <имя><атрибут>=<число>

Служебные символы: : - разделяет имя описателя и тело описателя;
, - разделяет параметры описателя;
; - окончание описателя;
= - присваивает параметру значение;
[,] - в квадратных скобках указываются атрибуты для описателя или параметра.

Описатели:

ОписательХарактеристика
knot
  • атрибутов не имеет;
  • параметры х, у типа float задают начальное положение корня;
  • допускается отсутствие одного любого из параметров, в этом случае ему присваивается значение по умолчанию 0;
  • допускается отсутствие описателя в программе, в этом случае обоим параметрам присваивается значение по умолчанию 0
line
  • имеет два атрибута (номера смежных точек);
  • допустимые параметры h, fi;
  • параметр h > 0 типа float задает длину ребра графа;
  • параметр fi типа int задает угол наклона ребра в градусах (см. рис. 4);
  • допускается отсутствие одного любого из параметров, в этом случае ему присваивается значение по умолчанию 0
next
  • имеет один атрибут (время движения);
  • допустимые параметры х, у, fi;
  • параметры х, у типа float задают очередное положение корня;
  • параметр fi типа int задает угол наклона ребра в градусах (номер вершины, в которую приходит ребро, указывается в квадратных скобках);
  • допускается отсутствие любого из параметров;
  • если отсутствует х или у, то отсутствующему параметру присваивается значение по умолчанию 0;
  • допускается отсутствие описателя в программе;
  • описатели должны находиться после описания структуры модели

Примеры описаний модели

Пример 1. Неподвижная фигура персонажа.

knot: у=0, х=-5;
line[0][1]: h=2, fi=90;
line[0][2]: h=2, fi=-90;
line[1][3]: h=6, fi=-60;
line[1][4]: h=6, fi=-120;
line[1][5j: h=3, fi=90;
line[2][6]: h=4, fi=-100;
line[2][7]: h=4, fi=-50;
line[6][8]: h=4, fi=-120;
line[7][9]: h=4, fi=-70;

Пример 2. Одно движение персонажа (задаются начальный и последующий кадры).

line[0][1]: h=2, fi=90;
line[0][2]: h=2, fi=-90;
line[1][3]: h=6, fi=-60;
line[1][4]: h=6, fi=-120;
line[1][5]: h=3, fi=90;
line[2][6]: h=4, fi=-100;
line[2][7]: h=4, fi=-50;
line[6][8]: h=4, fi=-120;
line[7][9]: h=4, fi=-70;
knot: y=0, x=-5;
next[0.5]: x=-3, y=0.5, fi[6]=-110, fi[7]=-80, fi[8]=-130, fi[9]=-110;

Разработка транслятора приложения

Класс translator

Класс translator позволяет заполнять данные класса model.

class translator: public model {
FILE *fin;
 string str;
 char ch;
 void error(int nom);
 bool read();
 bool sifr(bool p,bool f);
 bool knot();
 bool line(int i,int j);
 bool TV(float time);
public:
 bool input_tip(FILE*fin1);
};

fin - текстовый файл, содержащий описание модели:
str - строка, предназначенная для хранения очередного считанного слова;
ch - очередной считанный символ.

Методы класса:
error(int nom) - выдает сообщение об ошибке, где nom - номер ошибки;
read() - считывает слово;
sifr(bool p.bool f) - проверяет, является ли str представлением числа, заданного формата, за формат отвечают входные параметры: если p=false, то число положительное, если f=false, то число целое, иначе оно может быть дробным;
knot() - читает тело описателя knot;
line(int i.int j) - читает тело описателя line со входными параметрами i и j;
TV(float time) - читает тело описателя next, со входным параметром time;
input_tip(FILE *fin1) - ввод данных модели из файла описания (функция, выполняющая роль транслятора между языком описания модели и классом model).

Реализация класса translator

void translator::error(int nom) {
 char * err[] = {
  "синтаксическая ошибка",
  "используется неизвестный параметр",
  "неверный формат числа",
  "ошибка в описании модели",
 };
 ShowMessage(err[nom]);
}
bool translator::read() {
 str="";
 do
 fscanf(fin,"%c",&ch);
 while (((ch==' ',)||(ch=='\n'))&&(!(feof(fin))));
 if(feof(fin)) goto A;
 while((ch!=':')&&(ch!='[')&&(ch!=']')&&(ch!='=')&&(ch!=',')&&(ch!=';')&&(ch!=' ')&&(ch!='\n')) {
  str+=ch;
  fscanf(fin,"%c",&ch);
 }
 if(ch==' ') while (ch=='') fscanf(fin,"%c",&ch);
 A:
 if(str!="")return true;
 else return false;
}
bool translator::sifr(bool p, bool f) {
 bool flag=true;
 char c;
 int cint,n=str.length();
 for(int i=0;i<n;i++) {
  c=str.c_str()[i];
  cint=c;
  if ((cint<48)||(cint<57)) {
   if((c=='-')&&(i==0)&&p)p=false;
   else if((c=='.')&&f) {
    str.begin()[i]=',';
    f=false;
   }
   else {
    flag=false;
    break;
   }
  }
 }
 return flag;
}
bool translator::knot() {
 bool flag=false;
 do {
  if (read()&&(ch=='='))
   if (str=="x") {
    if(read()&&((ch==',')"||(ch==';'))) {
     if(sifr(true,true)) {
      x0=StrToFloat(str.c_str());
      A[0].x=x0;
     }
     else {error(2);break;}
     if (ch==';') flag=true;
    }
    else {error(0);break;}
   }
   else if (str=="y") {
    if((read())&&((ch==',')||(ch==';'))) {
     if(sifr(true,true)) {
      y0=StrToFloat(str.c_str());
      A[0].y=y0;
     }
     else {error(2);break;}
     if (ch==';') flag=true;
    }
    else {error(0);break;}
   }
   else {error(1);break;}
  else {error(0);break;}
 } while(ch!=';');
 return flag;
}
bool translator::line(int i,int j) {
 bool flag=false;
 A[j].last=&A[i];
 if(j<=n)n=j+1;
 int nom=0,r=0;
 for(;r<=i;r++) nom+=p[r];
 int nom2=nom:
 for(;r<n;r++) nom2+=p[r];
 for(r=nom2;r<nom:r-) q[r]=q[r-1];
 q[nom]=j;
 p[i]++;
 do {
  if ((read())&&(ch=='='))
  if (str=="h") {
   if((read())&&((ch==',')||(ch==';'))) {
    if(sifr(false,true)) A[j].h=StrToFloat(str.c_str());
    else {error(2);break;}
    if (ch==';') flag=true;
   }
   else {error(0); break;}
  }
  else if (str=="fi") {
   if((read())&&((ch==',')ll(ch==';'))) {
    if(sifr(true, false)) {
     fi0[j]=StrToFloat(str.c_str())/180;
     while (fi0[j]<1)fi0[j]-=2;
     while (fi0[j]<-1)fi0[j]+=2;
     A[j].fi=fi0[j];
    }
    else {error(2);break;}
    if (ch==';') flag=true;
   }
   else {error(0);break;}
  }
  else {error(1);break;}
  else {error(0); break;}
 } while(ch!=';');
 return flag;
}
bool translator::TV(float time) {
 bool flag=false; float SS,xx=0,yy=0;
 float VV[lis];
 int i;
 for(i=0;i<lis;i++)VV[i]=0;
 do {
  if(read())
   if (ch=='[')
    if (str=="fi") {
     if((ch==T)&&(read())&&(ch==']'))
      if(sifr(false,false)) {
       i=StrToInt(str.c_str());
       if((!(read()))&&(ch=='=')&&(read())&&((ch==',')||(ch==';'))) {
        if(sifr(true,false)) {
         VV[i-1 ]=StrToFloat(str.c_str())/180;
         while (VV[i-1]<1) VV[i-1]-=2;
         while (VV[i-1]<-1) VV[i-1]+=2;
         if(abs(SS=VV[i-1]-A[i].fi)<1)
          if(SS<0) SS=1-SS;
          else SS=-1*(SS+1);
         A[i].fi=VV[i-1];
         VV[i-1]=SS/time;
        }
        else {error(2); break;}
        if (ch==';') flag=true;
       }
       else {error(0); break;}
      }
      else {error(2);break;}
     else {error(0);break;}
    }
    else {error(0); break;}
   else if (ch=='=')
    if (str=="x") {
     if((read())&&((ch==',')||(ch==';'))) {
      if(stfr(true,true)) {
       xx=StrToFloat(str.c_str());
       xx=(xx-A[0].x)/time;
       A[0].x=StrToFloat(str.c_str());
      }
      else {error(2);break;}
      if (ch==V) flag=true;
     }
     else {error(0);break;}
    }
    else if (str=="y") {
     if((read())&&((ch==',')||(ch==';'))) {
      if(sifr(true,true)) {
       yy=StrToFloat(str.c_str());
       yy=(yy-A[0].y)/time;
       A[0].y=StrToFloat(str.c_str());
      }
      else {error(2);break;}
      if (ch==';') flag=true;
     }
     else {еггог(0); break;}
    }
    else {error{1);break;}
   else (error(O);break;}
  else {error(O); break;}
 } while(ch !=';');
 if (k==0) video.creat(time,xx,yy,VV);
 else video.add(time,xx,yy,VV);
 k++;
 return flag;
}
bool translator::input_tip(FILE *fin1) {
 model();
 float t;
 int i,j;
 fin=fin1;
 n=1;
 do {
  if(read())
   if(str=="knot")
    if(ch==':')
     if (knot()) continue; else break;
    else {error(0); break;}
   else if(str=="line")
    if((ch=='[')&&(read())&&(ch==']')
     if(sifr(false,false)) {
      i=StrToInt(str.c_str());
      if((!(read()))&&(ch=='[')&&(read())&&(ch==']'))
       if(sifr(false,false)) {
        j=StrToint(str.c_str());
        if((!(read()))&&(ch==':'))
         if(i<j)
          if (line(j,i)) continue; else break;
         else if (i<j)
          if (line(i,j)) continue; else break;
         else {error(3);break;}
        else {error(0);break;}
       }
       else {error(2);break;}
      else {error(0);break;}
     }
     else {error(2);break;}
    else {error(0);break;}
   else if(str=="next")
    if((ch=='[')&&(read())&&(ch==']'))
     if(sifr(false,true)) {
      t=StrToFloat(str.c_str());
      if((!(read()))&&(ch==':'))
       if (TV(t) continue;
       else break;
      else {error(0);break;}
     }
     else {error(2);break;}
    else {error(0); break;}
   else {error(1);break;}
  else if(!(feof(fin))) {error(0);break;}
 }while (!(feof(fin)));
 last0();
 if(feof(fin)) return true;
 else return false;
}

Приложение 1. Воспроизведение анимации

Среда реализации : Borland С++ Builder.

Вспомогательные классы knot и listTV

При описании модели использованы следующие классы:

class knot {
public:
 float x,y;
 float h;
 float fi;
 knot last;
 knot();
 float gox();
 float goy();
};

x,y - координаты вершины (в глобальной системе);
h - длинна ребра, соединяющего вершину с ее предком;
fi - угол наклона ребра (см. рис. 4);
last - указатель на предка.

Методы класса:
knot() - конструктор класса;
gox() - функция возвращает значение координаты х;
goу() - функция возвращает значение координаты у.

class listTV {
public:
 float t;
 float dx, dy;
 float V[lis];
 listTV * next;
 void creat(float tt, float xx, float yy, float VV[lis]);
 void onround(listTV * A);
 void offround(int k);
 void add(float tt, float xx,float yy, float VV[lis]);
};

Класс представляет собой очередь, где:
t - время составного движения;
dx, dy - проекции скорости корневой точки на ОХ и OY соответственно;
V- вектор скоростей изменения угла fi в течение времени t;
next - указатель на следующее движение;

Методы класса:
creat(float tt, float xx, float yy, float VV[lis]) - создание первого движения (первого элемента очереди), где tt - время движения,
хх, уу - проекции скорости корневой точки, VV - вектор скоростей изменения угла fi;
add(float tt, float xx, float yy, float W[lis]) - добавление движения; входные данные аналогичны данным предыдущей функции;
onround(listTV * А) - устанавливает указатель последнего движения на А, с помощью этой функции можно "зациклить" процесс движения (в примерах будет использовано в для воспроизведения движения);
offround(int к) - устанавливает указатель k-го элемента на NULL, будет использована для разрыва цикла.

Класс model воспроизведения модели и ее движения

class model {
 float t; public:
 float dt;
 int n;
 int p [lis];
 int q [lis];
 knot A[lis];
 float x0.y0;
 float fi0 [lis];
 int k;
 listTV *poz;
 listTV video;
 model();
 void last0();
 void box();
 void next();
 bool input(FILE *fin);
 void output(FILE *fin);
};

t - текущее время движения;
dt - интервал времени между прорисовкой кадров;
n - количество вершин модели;
{р, q} - MFO-представление графа;
А - вектор вершин модели;
x0.у0 - начальные координаты узла;
fi0 - начальные углы наклона ребер (задают начальное положение модели);
k - количество простых движений;
poz - указатель на текущее движение;
video - очередь, представляющая движение;

Методы класса:
model() - конструктор класса;
last0() - задает начальное состояние модели ;
bох() - прорисовка модели;
next() - вычисление следующего положения модели;
input(FILE *fin) - ввод описания модели из файла;
output(FILE *fin) - создание файла с описанием модели.

Реализация класса knot

knot::knot() {
 х=0; У=0; h=0; fi=0;
 last=NULL;
}
float knot::gox() {
 return (float) h*cos(pi*fi)+last-<x;
}
float knot::goy() {
 return (float) h*sin(pi*fi)+last-<y;
}

Реализация класса listTV

void listTV::creat(float tt, float xx, float yy, float VV[lis]) {
 next=NULL;
 t=tt;
 dx=xx;
 dy=yy;
 for(int i=0;i<lis;i++) V[i]=VV[i];
}
void listTV::onround(listTV * A) {
 if (next==NULL) next=A; else next-<onround(A);
}
void listTV: :offround(int A) {
 if (A==1) next=NULL; else next-<offround(A-1);
}
void listTV: :add(float tt, float xx, float yy, float VV[lis]) {
 if (next==NULL) {
  next= new listTV;
  next-<creat(tt,xx,yy,VV);
 }
 else next-<add(tt,xx,yy,VV);
}

Реализация класса model

model::model() {
 x0=0; y0=0;
 for(int i=0;i<lis;i++) {
  fiO[i]=0;
  p[i]=0;
  q[i]=0;
 }
 n=0; k=0;
 poz=&video;
 t=0;
 dt=0.01;
}
void model::last0() {
 A[0].x=x0;
 A[0].y=y0;
 for (int i=1;i<n;i++) {
  A[i].fi=fiO[i];
  A[i].x=A[i].gox();
  A[i].y=A[i].goy();
 }
 poz=&video;
 t=0;
}
bool model::input(FILE *fin) {
 int i;
 fscanf(fin,"%d",&n);
 for(i=0;i<n;i++) fscanf(fin,"%d",&p[i]);
 for(i=0;i<n-1;i++) fscanf(fin,"%d",&q[i]);
 for(i=1;i<n;i++) fscanf(fin,"%f',&A[i].h);
 fscanf(fin,"%f',&x0);
 fscanf(fin,"%f',&y0);
 for(i=1;i<n;i++) fscanf(fin,"%f",&fi0[i]);
 int m,j,w=0;
 for(i=0;i<n;i++)
  for(j=0;j<p[i];j++) {
   m=q[w++]; A[m].last=&A[i];
  }
 fscanf(fin,"%d",&k);
 float tt,xx,yy;
 float VV[lis];
 fscanf(fin,"%f,&tt);
 fscanf(fin,"%f',&xx);
 fscanf(fin,"%f',&yy);
 for(i=0;i<n-1;i++) fscanf(fin,"%f',&W[i]);
 video.creat(tt,xx,yy,W);
 for(int j=1;j<k;j++) {
  fscanf(fin,"%f'.&tt);
  fscanf(fin,"%f'.&xx);
  fscanf(fin,H%f",&yy);
  for(i=0;i<n-1 ;i++) fscanf(fin,"%f',&W[i]);
  video.add(tt,xx,yy,VV);
 }
 last0();
 return true;
}
void model::output(FILE *fin) {
 int i;
 fprintf(fin,"%d \n",n);
 for{i=0;i<n;i++) fprintf(fin,"%d ",p[i]);
 fprintf(fin,"\n");
 for(i=0;i<n-1;i++) fprintf(fin;"%d ",qrj]);
 fprintf(fin,"\n");
 for(i=1;i<n;i++) fprintf(fin,"%f",A[i].h);
 fprintf(fin,"\n");
 fprintf(fin,"%f %f \n",x0,y0);
 for(i=1;i<n;i++) fprintf(fin,"%f ",fiO[i]);
 fprintf(fin,"\n");
 fprintf(fin,"%d \n",k);
 listTV *poz1;
 poz1=&video;
 int j=1;
 while((poz1 !=NULL)&&(j<=k)) {
  fprintf(fin,"%f ",poz1-<t);
  fprintf(fin,"%f ",poz1-<dx);
  fprintf(fin,"%f ",poz1-<dy);
  for(i=0;i<n-1;i++) fprintf(fin,"%f ",poz1-< fprintf(fin,"\n");
  poz1=poz1-<next;
 }
}
void model::box() {
 int i,j,k=0;
 float red=1, grin=0;
 for(i=0;i<n;i++)
 for(j=0;j<p[i];j++,k++) {
  glColor3f(red,grin,0);
  glBegin (GL_LINES);
   glVertex3f (A[q[k]].x,A[q[k]].y,0); 
   glVertex3f (A[i].x,A[i].y,0);
  glEnd();
  red+=grin;
  grin=red-grin;
  red-=grin;
 }
}
void model::next() {
 t+=dt;
 if ((poz!=NULL)&&(poz-<t<t)) {
  t=0;
  poz=poz-<next;
 }
 if (pozi=NULL) {
  A[0].x+=dt*poz-<dx;
  A[0].y+=dt*poz-<dy;
  for(int i=1; i<n;i++) {
   A[i].fi+=dt*poz-<V[i-1];
   A[i].x=A[i].gox();
   A[i].y=A[i].goy();
  }
 }
}

Приложение 2. Пример работы программы

Возьмем следующее описание модели персонажа и его движения:

knot: х = -5;
line[0][1]: h=2, fi=90;
line[0][2]: h=2, fi=-90;
line[1][3]: h=6, fi=-60;
line[1][4]: h=6, fi=-120;
line[1][5]: h=3, fi=90;
line[2][6]: h=4, fi=-100;
line[2][7]: h=4, fi=-50;
line[6][8]: h=4, fi=-120;
line[7][9]: h=4, fi=-70;
next[0.5]:
 x=-3, y=0.5,
 fi[6]=-110,
 fi[7]=-80,
 fi[8]=-130,
 fi[9]=-110;
next[0.6]:
 fi[7]=-100,
 fi[6]=-50,
 fi[9]=-120,
 fi[8]=-70;
next[0.5]:
 x=5,
 y=0.5,
 fi[7]=-110,
 fi[6]=-80,
 fi[9]=-130,
 fi[8]=-110;
next[0.6]:
 x=7,
 fi[6]=-100,
 fi[7]=-50,
 fi[8]=-120,
 fi[9]=-70;

Созданное приложение обеспечивает чтение файла описания модели и адекватное воспроизведение анимации (все части модели движутся неразрывно).
На рис. 5 приведены кадры анимации персонажа в различные моменты времени.

Проверка работы транслятора и программы воспроизведения анимации модели персонажа

Рис. 5. Три кадра анимации

Источники

  1. Ахо А., Сети Р., Ульман Д. Компиляторы: принципы, технологии, инструменты. Вильямс, 2003.
  2. Кохов А. С., Перевезенцева Е. С. Решение задач на графах. Методическое пособие по курсу "Теория графов и комбинаторика". М.: Изд-во МЭИ, 1998.

Список работ

Рейтинг@Mail.ru