復習課題:キーフレームアニメーション

キーフレームアニメーションと呼ばれる手法を用いて、簡単なアニメーションを作成してみよう。

キーフレームアニメーションの考え方

今、A と B という2つの画像があったとして、A から B へと次第に変化して行くようなアニメーションを作りたいとする。A と B の中間の画像を何枚か作らねばならないが、それらを人間が1つずつ作るのは面倒である。そこで、A と B のデータを「混ぜ合わせる」ことで、中間の画像を自動的に作ってしまおうというのである。

簡単のため、A は a0, … ,an-1 という n 個の点で表現されていて、B も同様に b0, … ,bn-1 という n 個の点で表現されているとする。 このとき、ci を ai と bi の中点とすれば、 c0, … ,cn-1というn個の点によって表現される画像 C は、 A と B のちょうど中間の画像になりそうである。

例えば、下の2つの図において、A は a0 = (150, 300), a1 = (350, 300) という2つの点を結んだ線分であり、B は b0 = (210, 160) と b1 = (350, 300) を結んだ線分である。 (図は 1/2 に縮小してある)

線分A 線分B

このとき、a0と b0 の中点 c0 = ((150+210)/2, (300+160)/2) = (180, 230) であり、c1 = a1 = b1 であるから、C は下のような線分になる:

線分C

これを見ると、確かに C は A と B の中間の図形になっていることがわかる。 A → C → B の順で画面に表示すれば、A から B へ2ステップで変化するアニメーションになる。

しかし、2ステップの変化ではなめらかに動くアニメーションにならないだろう。 そこで、A = C0 → C1 → C2 → … Cm-1→ Cm = B のように m ステップかけて変化させることにする。これは、A, B 間を m 分割 していることになるから、k 番目の画像 Ck を求めるには、 2点を k : (m - k) に内分する点を求める公式を使えばよいだろう。 すなわち、Ck が ck,0, … ,ck,n-1というn個の点によって表現されるとすると、 ck,i は ai と bi を k : (m - k) に内分する点として求めればよいことになる。 つまり、

ということになる。この計算式において、k = 0 とすると a の座標が得られ、k = m とおくと b の座標になることに注意する。

以上がキーフレームアニメーションの基本的な考え方である。ただし、実際にはもっと複雑な計算式が用いられることもある。

データ形式

A と B を適当な形式のデータで表したものをプログラムに読み込ませると、 画像が A から B へ変化するアニメーションが表示されるようにしたい。 そのためのデータ形式を以下のような形に定めよう:

n m wait
ax0   ay0   bx0   by0   flag0
ax1   ay1   bx1   by1   flag1
…
axn-1  ayn-1  bxn-1  byn-1  flagn-1

ここで、1行目の n m wait の意味であるが、

2行目以降は、各行に5つの数が書かれている。 (axi, ayi) は、 A を表現する i 番目の点 ai の座標である。 同様に、 (bxi, byi) は、 B を表現する i 番目の点 bi の座標である。 画像は、これらの点を i = 0, 1, 2, … の順に 線分で結ぶことで描かれるものとする。

ただし、flagi が 1 のときだけは、i 番目の点とその直前の点は 線分で結ばない。例えば、flag3 が 1 なら、a2 と a3は線分で結ばない。 つまり、EGGX の line 関数を用いて i 番目の点へ向けて直線を引くとき、 flagi が 1 のときは、第4引数に PENUP を指定し、 そうでなければ PENDOWN を指定する。 flagi は必ず 0 か 1 のどちらかである。 flag0 は常に 1 が指定されるものと仮定する。本来 0 番目の点には直前の点がないわけだが、eggx で描画することを考えると、 直前の点とは描画の原点と考えればよい。

データファイルの例:

上に示した線分 A が線分 B に変化するアニメーションのデータファイルは以下のようになる。 ただし、ステップ数は50、ステップ間の待ち時間は20ミリ秒としてある。

% cat line.data
2 50 20
150.0 300.0 210.0 160.0 1
350.0 300.0 350.0 300.0 0

課題

上のような形式のデータを読み込んでアニメーションを表示するプログラムを EGGX を使って作りなさい。プログラム名は anim.c とする。 データファイルのファイル名はコマンドラインの第1引数で与えるものとする。 ウィンドウのサイズは、タテ・ヨコとも500とする。

コンパイル・実行の例:

% egg -o anim anim.c
gcc -O2 -Wall -o anim anim.c -I/usr/local/include -L/usr/local/lib -I/usr/X11R6/include -L/usr/X11R6/lib -leggx -lX11 -lm
% ./anim ~yasuda/animation/line.data

サンプルデータとして、~yasuda/animation/ に line.data, eto.data, dragonfly.data inu.data という4つのファイルを用意してあるので、プログラムが書けたら動作チェックをしてみること。

ヒント

プログラムの概形は以下の通り:

#include <eggx.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 256
int main(int argc, char **argv)
{
  FILE *datafile;
  char linebuffer[BUFSIZE];
  int n_points, n_steps, wait;	// 1行目を読み込むための変数
  float *ax, *ay, *bx, *by;	// ax[i] は i 番目の点の x 座標
  int *flag;		// flag[i] は、i 番目のフラグ
  …
  
  datafile = fopen(?????);	// データファイルを開く
  …
  fgets(?????);  // 一行目を読む
  sscanf(?????);

  ax = malloc(?????);    // A の点のx座標を入れるためのメモリを確保
  …                     // 他のデータのためのメモリを確保 

  for (i = 0; i < n_points; i++) {    // 二行目以降を読む
    fgets(?????);
    sscanf(?????);
  }
  win = gopen(500,500);

  for (k = 0; k <= n_steps; k++) {
    gclr(win);
    for(i = 0; i < n_points; i++){
      float x = (????? * (n_steps - k) + ????? * k)/ n_steps;
      float y = (????? * (n_steps - k) + ????? * k)/ n_steps;
      if (flag[i] == 1) {
	line(win, x, y, PENUP);
      } else {
	line(win, x, y, PENDOWN);
      }
    }
    ?????
  }
  …
  return(0);
}

ミリ秒単位で待ち時間をとるには、msleep(t) を用いる。