ぽきぽきアニメ

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

配列などを使ったアニメーションの作成サンプルです。 関数に分けて処理する、といったことをしていません(それにも向きません)から、最終課題のサンプルにはちょっと遠いですが、一つの参考として。

  1. ループ、if 文などの制御機構について復習
  2. 配列について復習

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

概要

テーマは「ぽきぽきアニメ」と 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 なのは、このデータが「閉じていない」図形を描くためにも使えるようになっているからです。 たとえば四角形は閉じた図形であり、その終点は始点と同じ位置です。 「閉じていない」図形とは、終点が始点とは異なる位置になるものです。 上のデータの例を見ると分かるように、四角形(のような閉じた図形)を描きたい場合は、終点のデータとして、始点と同じものを書いておけば良いわけです。

最も簡単なサンプル

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

これを実現するサンプルプログラムを用意しました。
[ pokib1.c ] (Handy Graphic を用いています。)

#include  <stdio.h>
#include  <handy.h>
#include 
#include 

#define WW 400.0 // 画面の幅
#define HH 400.0 // 画面の高さ

int main( )
{
    double x1, y1, x2, y2;

    // 描画準備
    HgOpen(WW, HH);
    HgSetColor(HG_BLACK);

    // データを読んで、線を引く
    scanf("%lf %lf", &x1, &y1);
    while(1) {
        scanf("%lf %lf", &x2, &y2);
        if((x2<0.0)&&(y2<0.0)) break; // 終了チェック

        HgLine(x1, y1, x2, y2);
        HgSleep(0.5);

        x1=x2; y1=y2; // 今回の終点を次回の始点とする
    }

    HgSetFillColor(HG_RED);
    HgBoxFill(WW-20.0*2.0, 20.0, 20.0, 20.0, 0);
    HgGetChar();
    HgClose();
    
    return 0;    
}

試しに このプログラムデータ を手元に保存し、実行してください。 Handy Graphic によるグラフィクスを利用するプログラムですから、コンパイルは hgcc コマンドで行います。

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

% hgcc -Wall -o sample sample.c

% ./sample < sample.txt

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

改善目標

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

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

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

配列におさめてから描く

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

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

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

辺は全長ぶんを描く

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

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

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

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

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

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

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

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

不要な線を消す

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

これを消すには「いまさっき引いた黒い線に、ぴったり重なる白い線で引き直す」のが簡単です。 (ただ、Handy Graphic は「ピッタリ重なる線では薄く痕が残ったりするので、少し太めの白い線で上塗りするのが良いです。線の太さは HgSetWidth( ) 関数で設定できます。)

画面をいったん全部消してから描き直すという方法もあります。また、その場合は layer 機能を使って、画面がチカチカするのを防止することもできます。詳しくは Handy Graphic の HgClear() 関数や「レイヤを利用したダブルバッファリング」の説明を参照して下さい。

さらなる改善

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

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

各種サンプルデータ

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

良いデータができればぜひ送って下さい。サンプル教材として採用します。


Yutaka Yasuda (yasuda@bakkers.gr.jp)