ぽきぽきアニメ

| 概要 | データフォーマット | 最も簡単なサンプル | 改善目標 | 各種サンプルデータ |

夏休み明けの学生さん向けに、長期の休暇期間からのリハビリテーションを兼ねて、 アニメーションの作成サンプルです。

  1. プログラミング作業環境(Linux/X/Emacs/gcc/egg)と、その操作を思い出す
  2. ループ、if 文などの制御機構について復習
  3. 配列について復習

といったことが目的です。

概要

テーマは「ぽきぽきアニメ」と NHK が呼んでいる線画のアニメーションです。 例えば四角形は以下のような手順で描きます。

  1. 初期状態は四辺の長さを足した長さの線を描きます。
  2. 端から数えて最初の角の位置(頂点座標)から、その次の角の方向に目がけてぽきっと折り曲げます。
  3. 次はその角から、そのまた次の角の方向に目がけて折り曲げます。
  4. 最後はぴったりおさまるように、四角形のできあがりです。

実写編 もあります。

データフォーマット

例えば四角形なら、以下のようなデータが用意されます。 各頂点の位置を各行に、X, Y 座標の順にあります。 最後の -1.0 がデータの終わりを示しています。

100.0 100.0
200.0 100.0
200.0 200.0
100.0 200.0
100.0 100.0
-1.0 -1.0

四角形なのに頂点数が 4 ではなく 5 なのは、このデータが「閉じていない」図形を描くためにも使えるようになっているからです。 四角形は閉じた図形ですので、終点が始点と同じ位置になっています。

最も簡単なサンプル

いきなり完成版を作るのは難しいでしょうから、少しずつ段階的に開発していきましょう。 まず上に示したデータにもとづいて、ただ単純に「ひとふでがき」のように描く、つまり「ぽきぽき」まではやらないようにしてみます。 (動作例

これを実現するサンプルプログラムを用意しました。。

#include  <stdio.h>
#include  <eggx.h>
int main( )
{
  int win;
  float x1, y1, x2, y2;
  win=gopen(600,600);   
  winname(win, "POKI POKI ANIME");
  newpen(win, 1);
  
  scanf("%f %f", &x1, &y1);
  while(1) {
    scanf("%f %f", &x2, &y2);
    if((x2<0.0)&&(y2<0.0)) break; // 終了チェック

    line(win, x1, y1, PENUP);     // 描画と待ち
    line(win, x2, y2, PENDOWN);
    msleep(500);

    x1=x2; y1=y2; // 今回の終点を次回の始点とする
  }
	
  fillrect(win, 600-20*2, 20, 20, 20);
  ggetch(win);
  gclose(win);
  return 0;
}

試しにこのプログラムとデータを手元に保存し(Emacs を開いて Web 画面から copy & paste すると良いでしょう)、実行してください。 EGGX によるグラフィクスを利用するプログラムですから、コンパイルは egg コマンドで行います。

このプログラムは標準入力からデータを読むようになっていますので、実行時はデータファイルをリダイレクションしてください。 データを sample.dat、プログラムを sample.c に保存した場合は、以下のようになりますね。

% egg -o sample sample.c

% ./sample < sample.dat

実行が終わると、グラフィクスウィンドウ右下に小さな白い□マークが出ます。この状態でエンターキーを押せばプログラムは終了し、グラフィクスウィンドウも消えます。

改善目標

以下の要点に注意して、段階的にこのプログラムを改善してください。

  1. サンプルプログラムは読んでは描き、読んでは描き、というようになっていましたが、これを配列を使って「まずいったん全部読んで配列に格納」し、それから描くように直して下さい。
  2. それができたら、全ての辺の長さを足して、その全長ぶんを描くようにしてください。(動作例
  3. そこまでできたら、不要な線の残りを消すようにしてください。(動作例

各バージョンを作るためのヒントを以下につけておきます。

配列におさめてから描く

もとは一つの while() ループのなかに scanf() によるデータ読み取り処理と、line() による描画処理が並んでいました。 これを二つのループに分けてやります。 一つ目のループでは scanf() して配列に格納するだけ。 この時、いくつ配列を使ったか数えておきます。 二つ目のループで配列のアタマから最後までについて描く。

構成変更を図示したものと、プログラムの一部分を以下に示します。

  // 配列にいったん格納する( m が座標点の個数)
  while(1) {
    scanf("%f %f", &x[m], &y[m]);
    if((x[m]<0.0)&&(y[m]<0.0)) break; // 終了チェック
    m++;
  }
  // 配列に納めたぶんだけループして描く
  for(i=0; i<(m-1); i++) {
    line(win, x[i],   y[i],   PENUP); 
    line(win, x[i+1], y[i+1], PENDOWN);
    msleep(500);
  }

辺は全長ぶんを描く

次は、各辺を、次の座標点までではなく、全長ぶん描くように修正します。 (動作例

そのためには、各編の長さを合計した全長を予め得ておかなければなりません。 上にあげたプログラム例の二番目のループ(描画のためのループ)にはいる前の位置で、全ての辺の長さの合計を計算するようにします。(二点の座標位置がわかっているのですからそれを結ぶ各辺の長さは計算できますよね。)

これは前方のデータ読み取りのループの中でやっても良いですし、またもう一つ、長さを計算するためだけの別のループを追加してもいいでしょう。

なんと hypot() という算術関数もあります。興味があれば調べて使ってみても良いでしょう。

次に「全長ぶん」を描く、つまり「一つ先の座標点に向けて、(まだ残っている)全長分を描く」ためには、「この方向に向けて、この長さまで進んだ座標位置」を計算する必要があります。 これには二段階の作業が必要です。

  1. (原点から見て)(x,y) 位置はどの方角にあるかを求める
  2. (原点から見て)ある方角に幾ら進んだら、その座標位置 (x',y') はどこか

前者(1.)はアークタンジェントを求める atan2() 関数を使うと良いでしょう。t=atan2(y, x); で、t には(原点から x,y 位置にめがけ)角(ラジアン単位)が求められます。

後者(2.)は素直に cos() と sin() 関数が使えますね。それぞれ引数には角を与えると、その方向に長さ 1 だけ進んだ場合の x, y 座標の値が求まります。 (さすがにこれは図示無し。)

不要な線を消す

このままでは、画面には「折れ曲がる前の線」が残ったままになっています。 次は、この不要な線を消してから次の線を描く、というように修正します。 (動作例

これを消すには「いまさっき引いた白い線を、ぴたりと重なる黒い線で引き直す」のが簡単です。

画面をいったん全部消してから描き直すという方法もあります。また、その場合は layer 機能を使って、画面がチカチカするのを防止することもできます。詳しくは EGGX の clr() 関数や layer() 関数を参照して下さい。

さらなる改善

折角ですからいろいろ改善してみてください。画数が多くなるとできあがりまで時間が掛かりすぎてイライラしますから、画数の多寡に拘わらず、合計 10 秒で終わるようにしてみる、というのも、また良い改善ですね。

もっとアニメーションらしく、おれまがっていく様子をゆっくり見せる(アニメーションで言うところの「中割り」をつくる)など、いろいろ改善できるところはあるかと思います。 (動作例

各種サンプルデータ

以下に各種データをおいておきます。使って下さい。

良いデータができればぜひ送って下さい。採用します。