キーフレームアニメーションと呼ばれる手法を用いて、簡単なアニメーションを作成してみよう。
今、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) を用いる。