テオ・ヤンセンのビーチアニマルの動きをGDモジュールによるGIFアニメで作った

GDモジュールでGIFアニメを作ってみた - すぎゃーんメモの続編。
まぁビーチアニマルの動きを表現しているものは検索すればたくさん出てくるのだけど、
(http://video.google.co.jp/videosearch?q=theo%20jansen&lr=lang_ja&oe=utf-8&rls=org.mozilla:ja-JP-mac:official&client=firefox-a&um=1&ie=UTF-8&sa=N&hl=ja&tab=wv#)
やっぱり自分でも作ってみたい!ということでやってみたわけです。


ビーチアニマルの足の機構は各部分の長さが「11 holy numbers」として公開(?)されているので、それさえ分かっていればあとは各節点の座標を計算していって線で繋いでいくことで描画することができる。
(とはいえweb上ではその11 holy numbersが見つからず…kwappaさんに教えていただきました。ありがとうございます。)
座標の求め方については下記参照。
三角形の2点の座標と各辺の長さから、残る1点の座標を求める - すぎゃーんメモ


というわけで作ったスクリプト

#!/opt/local/bin/perl
use strict;
use warnings;

use Config::Auto;
use GD;
use Math::Trig qw(pi atan);

# 11 holy numbers
my $holy_numbers = Config::Auto::parse('holy_numbers.yaml');
# 回転半径:m (この数字は任意?)
my $radius = 10.0;

# GD
my $image = GD::Image->new(240, 150);
# 色の設定
my $white = $image->colorAllocate(0xFF, 0xFF, 0xFF);
my $red   = $image->colorAllocate(0xFF, 0x00, 0x00);
my $green = $image->colorAllocate(0x00, 0xFF, 0x00);
my $blue  = $image->colorAllocate(0x00, 0x00, 0xFF);

# GIFデータの作成
my $gifdata = $image->gifanimbegin(1, 0);
for (1..36) {
    my $degree = $_ * 10;
    # フレームの描画
    my $frame = GD::Image->new($image->getBounds);
    draw_frame($frame, $degree);
    # GIFデータへの追加
    $gifdata .= $frame->gifanimadd(0, 0, 0, 1);
}
$gifdata .= $image->gifanimend;

# データの書き出し
binmode STDOUT;
print $gifdata;


# フレームの描画
sub draw_frame {
    my $image = shift;
    my $arg   = pi() / 180 * shift;

    # 原点座標
    my $origin = {
        x => $image->width / 2,
        y => 50,
    };
    # 回転軌道
    $image->arc($origin->{x}, $origin->{y},
                $radius * 2, $radius * 2,
                0, 360, $blue);

    # 各座標
    my $O = {
        x => 0,
        y => 0,
    };
    my $A = {
        x => $radius * cos($arg),
        y => $radius * sin($arg),
    };
    my $B = {
        x => $holy_numbers->{a},
        y => 0,
    };
    my $C = calc_coordinate($A, $B, $holy_numbers->{j}, $holy_numbers->{b})->[0];
    my $D = calc_coordinate($A, $B, $holy_numbers->{k}, $holy_numbers->{c})->[1];
    my $E = calc_coordinate($B, $C, $holy_numbers->{d}, $holy_numbers->{e})->[1];
    my $F = calc_coordinate($D, $E, $holy_numbers->{g}, $holy_numbers->{f})->[1];
    my $G = calc_coordinate($D, $F, $holy_numbers->{i}, $holy_numbers->{h})->[1];

    # 回転部分
    draw_line($image, $origin, $O, $A, $green);
    # 11本のライン
    draw_line($image, $origin, $O, $B);
    draw_line($image, $origin, $B, $C);
    draw_line($image, $origin, $B, $D);
    draw_line($image, $origin, $B, $E);
    draw_line($image, $origin, $C, $E);
    draw_line($image, $origin, $E, $F);
    draw_line($image, $origin, $D, $F);
    draw_line($image, $origin, $F, $G);
    draw_line($image, $origin, $D, $G);
    draw_line($image, $origin, $A, $C);
    draw_line($image, $origin, $A, $D);
}

# 線を描画
sub draw_line {
    my ($image, $origin, $a, $b, $color) = @_;

    $image->line($origin->{x} + $a->{x}, $origin->{y} - $a->{y},
                 $origin->{x} + $b->{x}, $origin->{y} - $b->{y},
                 $color || $red);
}

# 座標の計算
sub calc_coordinate {
    my ($a, $b, $dist_from_a, $dist_from_b) = @_;

    my $alpha = atan(($b->{y} - $a->{y}) / ($b->{x} - $a->{x}));
    my $dist  = ($b->{x} - $a->{x}) / cos($alpha);
    my $s = ($dist_from_a + $dist_from_b + $dist) / 2;
    my $tempX = ($dist_from_a ** 2 - $dist_from_b ** 2 + $dist ** 2) / ($dist * 2);
    my $tempY = 2 * sqrt($s * ($s - $dist_from_a) * ($s - $dist_from_b) * ($s - $dist)) / $dist;

    return [{
        x => $tempX * cos($alpha) - $tempY * sin($alpha) + $a->{x},
        y => $tempX * sin($alpha) + $tempY * cos($alpha) + $a->{y},
    },
            {
        x => $tempX * cos($alpha) + $tempY * sin($alpha) + $a->{x},
        y => $tempX * sin($alpha) - $tempY * cos($alpha) + $a->{y},
    }];
}

「11 holy numbers」はここで公開していいものかどうかよく分からないので一応ファイル外出しで使用。


これを実行して出力リダイレクトすることでGIFが出来ます。

$ ./strandbeest.pl > result.gif

で、出来上がったものがこちら。


ちょっと応用して反対側にも同じ要領で作ってやればこういうのも簡単に作れます。


大きく描いてみたり。


今度はこれをJavaScriptインタラクティブに各部の長さを変えながらアニメーションしたりするようなのを作ってみたいなぁー