ワイヤーフレームのプログラム



はじめに

はるか昔、学生の頃にワイヤーフレームのプログラムを作ってたのを、思い出しました。

中古で購入したMSX2パソコン (SONYのHitBit) を使い、BASIC言語で作成し、 長い時間をかけて描画した画面を、VHSのビデオにコマ録り。

そう。MSX2パソコンの選択は理由があったのです。NTSC出力でテレビに表示出来たからです。

ビデオのリモコンをばらして、基板のポーズボタンのところから、リード線をハンダ付けして引っ張り出してきて。 その線を確か、MSX2のカセットテープのモータ制御用のリレーに接続。

そうそう。当時フロッピーディスクもありましたが、未だカセットテープへのセーブ/ロードも現役でした。

そして、ワイヤーフレームの画面が1コマ分描画できたところで、 BASIC言語のmotorコマンドで、リレーをON/OFFしてビデオのポーズボタンをON/OFF。 1コマ分をビデオに録画してました。

確かこの、ON/OFF〜ON/OFF時間の微妙だったこと。
あと、下宿の部屋で一晩中走らせておいて、その側で寝てたりすると、 途中でリモコンの位置がズレて、せっせと描画してるシーンばかりが録れてしまっていたり。

という事で、当時熱中してたワイヤーフレームのプログラムを思い出して遊んでみます。


ラインを描画してみる

OSはUNIX系が前提で、Xサーバを使います。
プログラムはC言語で、なるべくXlibを直接呼び出して、GCCでビルドします。
まぁ、今どきのPCのLinux環境なら十分で、原始的過ぎるかも知れません。

まずは、グラフィック描画する環境から。
とりあえず、Xlibで画面にラインを描画するところから初めてみます。

wf.tgz

wf/util.[ch] は、 SMFを読み込み音の波形データを作るプログラム (C言語) のものを、そのまま流用してます。

$ cd /some/where/
$ tar xzf wf.tgz
$ cd wf/
$ make

$ ./wf_ex

ウィンドウを開き、ランダムなラインを表示
起動端末で ENTERキー入力で終了

私の環境ではXサーバが遅いせいか、 ウィンドウをマップしてから少し待たないと表示が出ませんでした。
そのためのオプションを用意してるので、 もし表示が出ない場合は試してみてください。

$ ./wf_ex -init_wait <待機する秒数>

古〜い環境のXサーバで試すと

$ ./wf_ex -init_wait 1

でもダメで

$ ./wf_ex -init_wait 2

でOKだったり...あやしい。何か間違っているのかも。

その他、次のオプションを用意してます。

-w <幅のピクセル数 (デフォルト640) >
-h <高さのピクセル数 (デフォルト 480) >
-d <接続するXサーバ (デフォルト ":0", 一応、環境変数DISPLAYも見てます) >
-n <線の数 (デフォルト 10) >
-ximg 指定すると、Xのライン描画に頼らず、自前のライン描画処理を使います。
$ ./wf_ex -init_wait 2 -w 160 -h 120 -n 1000

などとすると、かなり真っ白に埋まります。


碁盤を眺めてみる

続いて、3D処理用の部品を準備します。

d3.[ch]

まず、3次元 (x,y,z) の要素を持つデータとしてd3_t型を用意しておいて、
点(pos_t型)とベクトル(vec_t型)という名前でも、区別して扱えるようにします。

点とベクトルを組にして、線(line_t型)。
点とベクトル(法線)の組と見なして、平面(plane_t型)としても扱います。

点(原点)と3つのベクトルx軸,y軸,z軸で、座標系(axis_t)。

次に、それらを使って視点(eye_t型)を作ります。

eye.[ch]

typedef struct{
	double len, w, h;
	int sx, ex, sy, ey;
	pos_t p, t;

	plane_t clip[4];
	axis_t ax;
} eye_t;


p, t が基本で p が視点の位置、t が視線の先の目標の位置。
p の位置から t の位置を見る状態を表します。

len, w, h は、視線の先にある仮想スクリーン枠とでもいいましょうか。
p から t への視線上で、p から len の距離に、幅w , 高さh の矩形を想定します。

sx, ex, sy, ey は、仮想スクリーンの矩形の中の座標で、
左上が(sx,sy), 右下が(ex,ey) としてます。

3次元座標の中に矩形を置いて、その矩形の中にx,yの座標を定義してるようなイメージです。

以下 clip[4] と ax は、ワーク領域として使用します。

まず、clip[4] について。

仮想スクリーンの矩形には辺が4つありますが、その1つの辺と視点で1つの平面が定義されます。
よって、視点を含む仮想スクリーンの枠を囲むような、4つの平面を設定して使用します。

次に ax 。

視点 p から 目標位置 t に向けて、「線」が定義されますが、
その「線」を y軸と考えます。
y軸に直行するように x軸、z軸を設定して、視点を原点とした、
視線の座標系を ax に設定して使用します。

wf_ex.c でこれらの3D処理用の部品を使って、碁盤の目を、斜め上から眺めた図を描画してみました。

wf_1.patch

$ cd /some/where/
$ cat wf_1.patch | ( cd wf/ ; make clean ; patch -p1 ; make )
$ wf/wf_ex ;;; 必要なら -init_wait 2 を追加


視点を移動してみる

矢印キーの入力で視点を移動させてみます。

まず、キー入力の処理。
CUI14 で作ってたソース key.[ch] をそのまま流用してみます。
また、直接使わないですが key.c が内部的に呼び出しているので、 イモヅル式に select.[ch] というソースも取り込んでリンクしておきます。

キー入力の仕様ですが、とりあえず適当に次のように決めてみました。

eye.c に追加した eye_move()関数は次の通りです。

void
eye_move(eye_t *e, int mode, int h_v, int dir)
{
	line_t l;

	if(mode == 2){ /* zoom */
		double t = 1 - e->zoom_rate;

		line_set(&l, &e->t, &e->p);
		line_pos(&l, dir > 0 ? t : 1 / t, &e->p);
		return;
	}

	/* mode == 0 or 1 */

	l.v = h_v == 'h' ? e->ax.z : e->ax.x;
	if(h_v == 'v') d3_mul(&l.v, -1);

	l.p = mode == 0 ? e->t : e->p;
	pos_rot(mode == 0 ? &e->p : &e->t, &l, e->move_deg * dir);
}

碁盤の目だけでは判りにくいので、上にピラミッド的な図形を追加してみました。

ついでに、今移動モードが何なのかを示すため、スペースキーでモードを変更する度に、 ピラミッドの高さを変化させてみました。

wf_2.patch

$ cd /some/where/
$ cat wf_2.patch | ( cd wf/ ; make clean ; patch -p1 ; make )
$ wf/wf_ex ;;; 必要なら -init_wait 2 を追加

キー入力は画像を表示してるウィンドウではなく、wf_exコマンドを起動した端末に入力します。

右矢印キーで視点を右へと回転移動して回りこませます。

上矢印キーで視点を上へと回転移動して回りこませます。

スペースキー2回入力でモード0から2へ

右矢印キーで、ズームイン

などなど、色々と視点を移動して表示できるようになりました。

そして 'q' キーで終了。


3Dグラフ

とりあえず、役立ちそうなものをひとつ。
簡単な3Dグラフを表示してみます。

データファイルの形式はCSVファイルを元に、先頭行として "w=xxx,h=xxx" を追加した形式とします。
適当なデータを用意してみました。 smp_grp.txt

標準入力はキー入力で使っているので、データファイルの指定はパスを -fn xxx として指定します。

読み込んだデータは、x=列(0〜), y=行(0〜), z=値 の位置データとして取り込みます。
オプション -zoom <X方向の拡大係数>,<y方向の拡大係数>,<z方向の拡大係数> で、 多少は修正できるようにしてみました。

wf_3.patch

$ cd /some/where/
$ cat wf_3.patch | ( cd wf/ ; make clean ; patch -p1 ; make )
$ wf/wf_ex -fn smp_grp.txt -zoom 0.2,0.2,0.05 ;;; 必要なら -init_wait 2 を追加

キー操作は以前から変更なし。

eye.t 奥行き方向の移動を何とかしたいところ...


視点の移動をやや改良

視線の先の目標の位置を、奥行き方向にも移動できるようにしてみました。

4つ目の移動モードを追加します。

上空のピラミッドの形でモードを区別するのは見苦しいので廃止。

奥行き方向のズーム系の移動のときは、枠を表示するようにします。

視線の先の対象の位置(eye.t)を移動する場合は、画面を4分割表示にします。

4分割の左下の画面が、通常の視点からの画像。
左上は、45度上から眺めた画像。
右下は、45度右から眺めた画像。
右上は、45度右上から眺めた画像。

ちょっと判りにくいですが、慣れればなんとかなるかも。

あと、モードが増えたのでバックスペースキー(CTRL+H)で、 逆向にモードを切替えるようにしました。

wf_4.patch

$ cd /some/where/
$ cat wf_4.patch | ( cd wf/ ; make clean ; patch -p1 ; make )
$ wf/wf_ex -fn smp_grp.txt -zoom 0.2,0.2,0.05 ;;; 必要なら -init_wait 2 を追加

スペースキーで、移動モード1にすると4分割表示。

スペースキーで、移動モード2にすると枠を表示。

スペースキーで、移動モード3にすると枠を表示して4分割表示。

これで多少、思ったアングルを狙い易くなりました。


無駄を省いて速度アップ

線を引くときに、始点と終点について3Dから2Dへの変換処理をしてるので無駄があります。
折れ線の描画では前の線の終点が始点になるので、 線ごとに2つの点を変換していると、2倍の時間がかかります。

ワイヤーフレーム用のデータ形式を用意して、すっきりまとめてみました。

data.h

struct wire_frame{
	int n;		/* ex. n = 4 */
	pos_t *p;	/* ex. p = (pos_t[]){ {0,0,0},{1,0,0},{1,1,0},{0,1,0} } */
	int *odr;	/* ex. odr = (int[]){ 0,1,2,3,0,-1, 0,2,-1, 1,3,-1, -1 } */
	
	/* work */
	pos_t *t;
};

コメント部分の例は、四角にバッテンを描画する例です。
odr[]の中の値は、p[]へのインデックスで、負の値は終端を表します。
0,1,2,3,0,-1 という折れ線、
0,2,-1 という折れ線、
1,3,-1 という折れ線の、合計3つの折れ線があって最後に-1で終端してます。

未だ試せてませんが...
データの変換処理用としてd3.[ch]に、 アフィン変換関連の関数を追加してます。

あと、データの拡大縮小、移動、軸の回りの回転、データの複製など、 data.[ch] にコーディングしてあります。

wf_5.patch

$ cd /some/where/
$ cat wf_5.patch | ( cd wf/ ; make clean ; patch -p1 ; make )
$ wf/wf_ex -fn smp_grp.txt -zoom 0.2,0.2,0.05 ;;; 必要なら -init_wait 2 を追加

非力なマシンで重そうなデータを試しているので、描画が速くなったのが良く判ります。
速くなっただけで、表示は前回と同じです。


繰り返しのパターンを試す

形状のデータ形式と、操作処理のデータ形式を整理してみました。

一つ形状を定義しておいて、 繰り返しコピーをとりながら展開するように描画する操作を適用して、描画を試してみました。

あと、奥行き方向の移動モードのときに表示する枠が、 あまりにも目立たなすぎたので、少し内側にズラしてみました。

wf_6.patch

$ cd /some/where/
$ cat wf_6.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -fn smp_grp.txt -zoom 0.2,0.2,0.05 ;;; 必要なら -init_wait 2 を追加

まず、従来通のオプション指定では、従来通り動作。

ちょっと視点の初期位置が変わってしまったか...

まぁだいたい、同じように動作してます。

'q'キーで終了して、-demo オプションで起動。

$ wf/wf_ex -demo

少し視点を移動してみると、このような感じです。

遠くから見ると、こんな感じ。

ぐいっと近付くと、こんな感じ。

byzanzで動画に落してみました。


動かしてみる

視点が移動していい感じなので、見てる対象の方も移動させてみます。

way.[ch] を追加して、ここで時間経過とともに座標を移動するしくみを作っておきます。

wf_ex.cに立法体を2つ追加して、ぐるぐる移動させてみました。
下の方のcube2は、3次補間で滑らかに角をカーブします。

data_t *cube = &(data_t){ type_op_data_set, &(struct op_data_set){
  .op = &(data_t){ type_slide_way, &(struct way){
    .n = 4, .vs = NULL,
    .ps = (pos_t[]){{0,0,0},{0,10,0},{10,10,0},{10,0,0}},
    .ts = (double[]){3,2,1,2}, .dg = 1 }},
  .data = &(data_t){ type_cube } }};

data_t *cube2 = &(data_t){ type_op_data_set, &(struct op_data_set){
  .op = &(data_t){ type_slide_way, &(struct way){
    .n = 4, .vs = NULL,
    .ps = (pos_t[]){{0,0,0},{0,10,0},{10,10,0},{10,0,0}},
    .ts = (double[]){1,1,1,1}, .dg = 3 }},
  .data = &(data_t){ type_cube } }};

あと、メモリリークのバグがあったのでとりあえずの対処をしました。

wf_7.patch

$ cd /some/where/
$ cat wf_7.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo ;;; 必要なら -init_wait 2 を追加


視点の位置を記録/再生してみる

キー入力による視点移動もいいのですが、 せっかく「いい感じ」のアングルを見つけても、 何となくその場限りになってしまい、もったいないかと。

動きそのものについても、どうもキーリピートが気になります。
最初に1コマ「カタ」っと動いて、ちょっと間が開いてリピートで「カタカタカタ」ってきます。

なので、視点の座標をファイルに記録しておいて、再生するしくみを入れてみます。

従来通りキー操作で視点を移動して、'r'キーを押すと視点と目標点の座標を記録するようにします。

最後に'q'キーで終了するときに、保存するファイル名が -eye_save <ファイル名> で指定されていれば、 記録してきた座標群を書き出します。

起動時に -eye_load < ファイル名 > を指定して座標群を読み込ませます。

あとは、way.[ch] の3次補間を使って、記録した位置を通るように視点を移動させます。

ファイルのフォーマットはテキスト形式で、次の仕様にします。

"s=次の位置に移動するまでの秒数" は、とりあえず1秒固定で出力する事にします。
気に入らなければテキスト形式なので、エディタで編集して適当に調整すればよろしかろう。

wf_8.patch

$ cd /some/where/
$ cat wf_8.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_save e1.txt ;;; 必要なら -init_wait 2 を追加

適当に視点を移動しつつ 'r'キー で位置を記録していって...
;;; レスポンスが何も無いので、本当に記録できてるのか不安になりますね...

一応、間違って'r'キーを押してしまい記録した時のために、'b'キーで1つ前に戻れるようにしておきました。
;;; でも、そもそも'r'キーのレスポンスが無いので、間違ってしまったという手応えもなく...

そんなこんなで記録できたら'q'キーで終了し、ファイルに吐き出します。
中身はこんな感じです。

$ cat e1.txt
n=5
p=(0.000000 -4.000000 4.000000),t=(2.000000 2.000000 0.000000),s=1.000000
p=(1.897813 -5.218363 1.970988),t=(2.000000 2.000000 0.000000),s=1.000000
p=(7.695112 -2.443062 1.955736),t=(2.000000 2.000000 0.000000),s=1.000000
p=(44.080418 12.356298 11.638047),t=(2.000000 2.000000 0.000000),s=1.000000
p=(12.540975 44.063708 11.532171),t=(2.000000 2.000000 0.000000),s=1.000000
$ 

一応リンク e1.txt も置いときます。

そのまま再生するには

wf/wf_ex -demo -eye_load e1.txt

-eye_load xxx が指定されている時は、何かキー入力すると動作を開始します。

延々と視点移動を繰り返します。
-eye_load モードでも'q'キーで終了します。

再生のときに、どれだけ頑張るかを、-fps xxx オプションで指定出来るようにしておきました。
省略時のデフォルトは -fps 60 扱いです。

遅いマシンで試しているので、描画にパワーを使いすぎると、 動画に落すコマンドの方の処理がなかなか動けずにいると、 結局カクカクした動画になると予想して追加してみました。

まぁ、せっかくXlib使ってるので、マシンがいっぱいあれば、 wf_ex も byzanz も Xserver も全部別のマシンで動かして、 分散させればいいのかも...?

にしても Xserver にリクエストが押し寄せすぎるので、 やはり適当なsleepは必要かも知れません。


時間をズラしてみる

回転方向の移動とかコピー処理とかが未だ試せてないですが...
先に時間方向の移動とコピーを試してみます。

これまでの cube2 を元に、時間軸方向に 0.3 秒遅らせながら、3個にコピーしてみます。

data_t *cube3 = &(data_t){ type_op_data_set, &(struct op_data_set){
  .op = &(data_t){ type_copy_timeshift, &(struct copy_timeshift){
    .n = 3, .init_sec = 0, .step_sec = -0.3, .init = {0,0,0}, .step = {0,0,0} } },
  .data = cube2 }};

wf_9.patch

$ cd /some/where/
$ cat wf_9.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo ;;; 必要なら -init_wait 2 を追加

一応リンク e2.txt も置いときます。

$ wf/wf_ex -demo -eye_save e2.txt ;;; 必要なら -init_wait 2 を追加


コーヒーカップの動き

遊園地の「のりもの」のコーヒーカップの動きを真似てみました。

追加のデータはこのような感じです。

data_t *cup = &(data_t){ type_op_data_set, &(struct op_data_set){
  .op = &(data_t){ type_arr, (data_t[]){
    { type_slide, &(d3_t){20,20,0} },
    { type_copy_rot, &(struct copy_rot){ .n=4, .l=LINE_Z, .init_deg=0, .step_deg=90 } },
    { type_rot_way, &(struct rot_way){
      .l=LINE_Z,
      .deg_way={ .n=2, .vs=(double[]){0,360*4}, .ts=(double[]){15,15}, .ps=NULL, .dg=3} } },
    { type_slide, &(d3_t){5,0,0} },
    { type_rot_way, &(struct rot_way){
      .l=LINE_Z,
      .deg_way={ .n=2, .vs=(double[]){0,360*2}, .ts=(double[]){4,4}, .ps=NULL, .dg=3} } },
    { type_copy, &(struct copy){ .n={2,1,1}, .init={-2,0,0}, .step={4,0,0} } },
    { type_end }
  }},
  .data = &(data_t){ type_cube } }};

底に操作する対象データとして立方体(type_cube)を配置しておいて、 操作手順を配列(type_arr)で並べてます。

操作はスタックされ末尾から順に施していくので、下から順に実行されると考えてください。

wf_10.patch

$ cd /some/where/
$ cat wf_10.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo ;;; 必要なら -init_wait 2 を追加

前回、前々回の視点の動きが速過ぎ、酔いそうなので、おとなしめのゆっくりなデータにしておきます。

一応リンク e3.txt も置いときます。

$ wf/wf_ex -demo -eye_save e3.txt ;;; 必要なら -init_wait 2 を追加


観覧車

コーヒーカップに続いて、遊園地の観覧車を。

data_t *ferris_wheel = &(data_t){ type_op_data_set, &(struct op_data_set){
  .op = &(data_t){ type_arr, (data_t[]){
    { type_slide, &(d3_t){20,20,-10} },
    { type_copy_timeshift, &(struct copy_timeshift){
      .n=12, .init_sec=0, .step_sec=10.0/12, .init={0,0,0}, .step={0,0,0} } },
    { type_rot_way, &(struct rot_way){
      .l=LINE_Y, .deg_way={ .n=2, .vs=(double[]){0,-360}, .ts=(double[]){10,0}, .ps=NULL, .dg=1 } } },
    { type_end } }},
  .data = &(data_t){ type_arr, (data_t[]){
    { type_op_data_set, &(struct op_data_set){
      .op = &(data_t){ type_zoom_and_slide, (d3_t[]){{3,1,1},{10-3,0,0}} },
      .data = &(data_t){ type_square } }},
    { type_op_data_set, &(struct op_data_set){
      .op = &(data_t){ type_arr, (data_t[]){
	{ type_slide, &(d3_t){10,0,0} },
	{ type_rot_way, &(struct rot_way){
	  .l=LINE_Y, .deg_way={ .n=2, .vs=(double[]){0,360}, .ts=(double[]){10,0}, .ps=NULL, .dg=1 } }},
	{ type_slide, &(d3_t){0,0,-1} },
	{ type_end } }},
      .data = &(data_t){ type_cube } }},
    { type_end }
  }}
}};

スタック的に処理されるので、下から順に解説してみると

視点の移動 e3.txt そのままで見切れるように、地下に配置してみました。

wf_11.patch

$ cd /some/where/
$ cat wf_11.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo ;;; 必要なら -init_wait 2 を追加

$ wf/wf_ex -demo -eye_save e3.txt ;;; 必要なら -init_wait 2 を追加


メリーゴーランド

遊園地シリーズということでメリーゴーランド追加です。

data_t *merry_go_round = &(data_t){ type_op_data_set, &(struct op_data_set){
  .op = &(data_t){ type_arr, (data_t[]){
    { type_slide, &(d3_t){-20,20,0} },
    { type_copy_rot, &(struct copy_rot){ .n=8, .l=LINE_Z, .init_deg=0, .step_deg=45 } },
    { type_rot_way, &(struct rot_way){
      .l=LINE_Z, .deg_way={ .n=2, .vs=(double[]){0,360}, .ts=(double[]){10,0}, .ps=NULL, .dg=1 } } },
    { type_copy_timeshift, &(struct copy_timeshift){
      .n=3, .init_sec=0, .step_sec=2.0/3, .init={10+-3,-3,0}, .step={3,3,0} } },
    { type_slide_way, &(struct way){ .n=2, .ps=(pos_t[]){{0,0,0},{0,0,3}}, .ts=(double[]){2,2}, .vs=NULL, .dg=3 } },
    { type_end } }},
  .data = &(data_t){ type_cube }
}};

wf_12.patch
e4.txt

$ cd /some/where/
$ cat wf_12.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo ;;; 必要なら -init_wait 2 を追加

$ wf/wf_ex -demo -eye_save e4.txt ;;; 必要なら -init_wait 2 を追加


海賊船みたいなのとモロモロ追加

遊園地シリーズで、よくある海賊船みたいなのを追加。
メリーゴーランドの下の北西の地下に配置してみました。

エグザイル的な動きをする四角の団体をその上空に配置。

あちこちをリサージュ図形的に移動するキューブを追加。

wf_13.patch

$ cd /some/where/
$ cat wf_13.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo ;;; 必要なら -init_wait 2 を追加


曲線っぽく

ワイヤーフレームの一つの直線を、 お得意の3次補間で曲線っぽい折れ線に変換する処理を追加してみました。

試しに3連星の立方体を太らせたり、痩せさせたりしてみました。

3Dグラフも控えめに曲線に変化させてみましたが、なんか先端がえらい感じに描画されています。

wf_14.patch

$ cd /some/where/
$ cat wf_14.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo ;;; 必要なら -init_wait 2 を追加
				   
$ wf/wf_ex -fn smp_grp.txt ;;; 必要なら -init_wait 2 を追加


円を追加

曲線とは別に単純な円を追加しておきます。
ついでに球も...

リサージュ図形的に動くキューブを円に変更してみました。

データが込み入って分かりにくいので、よくつかうway構造体のデータ数2つのパターンをマクロにしてみました。
今更、焼け石に水感が漂ってますが。

起動時のコマンドラインで直接指定して、リサージュ図形的に視点を移動させるオプションを追加してみました。

wf_15.patch

$ cd /some/where/
$ cat wf_15.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-30,-30,-30),pe=(30,30,30),sec=(35,40,45),init_sec=(0,0,0)'
;;; 必要なら -init_wait 2 を追加


ハーフパイプ

スノーボードのハーフパイプのコースを、ぐるっと丸めたものを南西の地下に配置してみました。

wf_16.patch

$ cd /some/where/
$ cat wf_16.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-80,-80,-30),pe=(20,20,10),sec=(35,40,45),init_sec=(0,0,0)'
;;; 必要なら -init_wait 2 を追加

さてでは、このコースを滑る動きを作ってみます。

だいたい時計の2時から6時を経由して10時くらいの角度の振動にすれば良さそうです。
2時から3時までと、9時から10時までの角度がジャンプです。

このジャンプ区間と、ハーフパイプに接してる区間を区別したいのですが、 3次補間の角度を与えた回転動作にしたいので、境界が簡単には求められないようです。

という事で、ニュートン法で解いてみます。

先のパッチ wf_16.patch
に既に追加してある nt.c がそのプログラムです。

#include "data.h"

static double
fz(double sec, int ac, char **av)
{
	double r = 30;
	double sky_deg = 10;
	pos_t p = {0,0,-r};
	data_t data = { type_op_data_set, &(struct op_data_set){
	  .op = &(data_t){ type_rot_way, &(struct rot_way){
	    .l=LINE_Y,.deg_way=WAY_V2(-90-sky_deg,90+sky_deg, 10,10, 3) }},
	  .data = &(data_t){ type_test_conv, &p }
	}};

	eye_t eye;
	prm_t prm;

	eye_init(&eye, 640, 480, ac, av);
	d3_set(&eye.p, 0,0,0);
	d3_set(&eye.t, 0,1,0);

	eye.sec = sec;
	prm_set_eye_update(&prm, &eye);
	data_draw(&data, &prm);
#ifdef DBG
	printf("(%f %f %f)\n", p.x, p.y, p.z);
#endif
	return p.z;
}

int
main(int ac, char **av)
{
	double l_sec = 0, r_sec = 5, sec = 2.5;

	while(r_sec - l_sec > 5*0.0000001){
		sec = (l_sec + r_sec) * 0.5;
		if(fz(sec, ac, av) < 0) r_sec = sec;
		else l_sec = sec;
	}
	printf("sec=%f,5-sec=%f\n", sec, 5-sec);
	printf("%f:%f=%f:5\n", sec, 5-sec, sec*5/(5-sec));
	return 0;
}

真下の底を0度として、-90度、90度がジャンプ境界で、sky_deg=10度までジャンプする事にしてます。
-100度から100度までを10秒かけて3次補間で動かして、100度から-100度までを10秒かけて戻ります。

時刻0秒はジャンプ中で、時刻5秒は真下の位置のはず。
あとはニュートン法でジャンプから着地する瞬間の時刻を求めます。

$ wf/nt
sec=1.353504,5-sec=3.646496
1.353504:3.646496=1.855896:5

5秒のうち、最初の1.352504秒がジャンプ中で、残りの3.646496秒がパイプに接地してます。
この比率でパイプに5秒載ってるとしたら、ジャンプ中は1.855896秒になります。

wf_17.patch

試してみたら、スローモーションのようで迫力に欠けるので、nt.c のパラメータを変更してやり直してみみした。
ジャンプの角度sky_degは10度から40度に変更し、パイプに接地してる時間を2秒にしてみました。

$ ./nt
sec=2.478571,5-sec=2.521429
2.478571:2.521429=1.966005:2.000000

あと、type_slice というデータ形式を追加してます。
type_arr は配列で複数のdata_tを扱いますが、type_slice は時刻によって複数のdata_tを切り替えます。
操作用とデータ用のdata_t型の箇所で対応してコーディングしましたが、操作用としての使い方しか試せてません。
これを使って、内側のジャンプのときだけ、スピンするようにしてみました。

$ cd /some/where/
$ cat wf_17.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-50,-50,-20),pe=(-10,-10,0),sec=(35,40,45),init_sec=(0,0,0)'
;;; 必要なら -init_wait 2 を追加


花火

スノーボーダーのスピンを修正し、季節外れですが花火のデータを追加してみました。

あと、type_sliceのデータ用の処理の確認として、最初の頃に空間の基準的な感じで配置した 十字を点滅させたり、四角形に置き換えたりしてみました。

さすがにデータ量が増えたので、随分と処理が重くなってきました。

wf_18.patch

$ cd /some/where/
$ cat wf_18.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-50,-50,-20),pe=(-10,-10,0),sec=(35,40,45),init_sec=(0,0,0)'
;;; 必要なら -init_wait 2 を追加


電車

wf_19.patch

実家の側に梅田貨物が通ってて、コンテナが運ばれていくのをよく眺めてたものです。

この電車の動きを再現してみようと思い立ち、考えを巡らせてみましたが...
いやー、なかなかテゴワいです。
妥協しつつ、なんとか既出の手持ちの構造体を出来るだけ使ってまとめてみました。
ここでもニュートン法で、バイナリサーチしてるので、負荷が増えてしまったかも。

南東方面に仮想の二階立てのレールを敷いてみました。

あと、花火の描画がうるさ過ぎて重たいので、打ち上げ箇所を6箇所から半分の3箇所に減らしてみました。

$ cd /some/where/
$ cat wf_19.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-30,30,0),pe=(100,-100,20),sec=(350,40,450),init_sec=(0,0,200)'
;;; 必要なら -init_wait 2 を追加

変に速度変化をつけすぎたせいか、意に反しキショイ動きになってたので、パラメータを変更してみました。

ついでにコピーして団体行動させてみました。

wf_20.patch

$ cd /some/where/
$ cat wf_20.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-30,30,0),pe=(100,-100,20),sec=(350,40,450),init_sec=(0,0,200)'
;;; 必要なら -init_wait 2 を追加


再帰

データに対して操作を再帰的に適用する処理を入れてみました。

北東上空方面にマトリョーシカ的なものを配置してみました。

久々に追加したデータの、それなりの解説をば。

data_t *matryoshka = &(data_t){ type_op_data_set, &(struct op_data_set){
  .op = &(data_t){ type_arr, (data_t[]){
    { type_slide, &(d3_t){20,20,20} },
    { type_recursive, &(struct recursive){
      .op = &(data_t){ type_arr, (data_t[]){
	{ type_zoom, &(d3_t)D3_ALL(0.8) },
	{ type_rot_way, &(struct rot_way){.l=LINE_Z,.deg_way=WAY_V2(0,90, 3,3, 3) }},
	{ type_end } }},
      .n=20, .cnt=0 }},
    { type_rdiv, &(struct rdiv){ .n=8, .rate=0.3 } },
    { type_zoom, &(d3_t)D3_ALL(20) },
    { type_end } }},
  .data = &(data_t){ type_octahedron } }};

例によって、下側からの順になります。

ある1つの八面体に注目すると、外側の八面体の世界全体が回転する立ち位置から、 さらに、内側に入ってる八面体の世界を回転させてます。
なので最も内側の八面体は、外界に対して猛スピードで回転します。

wf_21.patch

$ cd /some/where/
$ cat wf_21.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-80,-80,-80),pe=(100,100,100),sec=(35,40,45),init_sec=(0,0,0)'
;;; 必要なら -init_wait 2 を追加

$ wf/wf_ex -demo -eye_liss_p 'ps=(0,0,0),pe=(40,40,40),sec=(35,40,45),init_sec=(0,0,0)'


ハト時計

南西方面上空にハト時計を配置。

いくつか便利データ形式を追加。

wf_22.patch

$ cd /some/where/
$ cat wf_22.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-30,-40,15),pe=(-10,0,25),sec=(35,40,45),init_sec=(0,0,0)'


拡大縮小関連いくつか追加

時間とともに拡大縮小率を変化させる処理などを追加してみました。

あちこち動きまわる円の大きさが変化するようにしたのと、 初めの頃に追加した、原点付近をぐるぐる周回する立方体に、 こんにゃくのように拡大縮小する動きをつけてみました。

さらに、ハト時計の中の正八面体を、出てくる時以外は0倍に縮小してみました。

あと、マトリョーシカが少々大き過ぎなので、半分のサイズに縮小してみました。

wf_23.patch

$ cd /some/where/
$ cat wf_23.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-30,-40,15),pe=(-10,0,25),sec=(35,40,45),init_sec=(60,60,60)'

$ wf/wf_ex -demo -eye_liss_p 'ps=(-10,-10,-5),pe=(20,20,5),sec=(35,40,45),init_sec=(0,0,0)'


波紋

データ形式などをブラッシュアップしてみました。

まず、op_data_set構造体やrecursive構造体で、data_t型をポインタで保持してたため、 wf_ex.cでの構造体の初期化が、やたら冗長になってたので修正。
これでかなりスッキリしました。

構造体の初期化で、指定のないポインタのメンバはNULLがセットされる事を前提に、 ワーク領域のメモリ割り付けを整理して、d3.hに専用のマクロ群を追加。

ヘッダファイルのインクルード行の整理。

あと、時間をズラしてコピーする操作で、拡大縮小と位置のスライドを伴うタイプを追加。 (type_copy_timeshift_zoom_slide 長い...)

追加したコピー操作の確認のため、水面の波紋の動きを南西方面に追加してみました。

wf_24.patch

$ cd /some/where/
$ cat wf_24.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-30,-30,-20),pe=(10,10,20),sec=(35,40,45),init_sec=(0,0,0)'


波紋をヒントに「波」を追加してみました。
西側のはるか上空を覆うように配置してみました。

寄せては返す波のようにしたり、サーフィンのBig Waveのようにできないものかと パラメータをいじってみましたが、なんとなくうねってるような感じになってしまいました。

wf_25.patch

data_t *wave = &(data_t){ type_op_data_set, &(struct op_data_set){
  .op = { type_arr, (data_t[]){
    { type_zoom_and_slide, (d3_t[]){D3_ALL(2),{0,0,120}} },
    { type_copy_timeshift_rot, &(struct copy_timeshift_rot){
      .n=30,.init_sec=0,.step_sec=-0.2,.l=LINE_Z,.init_deg=0,.step_deg=180.0/30 }},
    { type_copy_timeshift, &(struct copy_timeshift){
      .n=20,.init_sec=0,.step_sec=-0.2,.init={0,50,0},.step={0,3,0}} },
    { type_rot_way, &(struct rot_way){.l=LINE_X_NEG,.deg_way=WAY_V2(0,360, 4,0, 1)} },
    { type_end } }},
  .data = { type_cross, (pos_t[]){{-1,0,10},{1,0,10}} } }};

回転してるものを時間をズラしながら並べて、波の動きを真似てみましたが、オーロラのような印象です。

$ cd /some/where/
$ cat wf_25.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-200,-200,-120),pe=(100,100,360),sec=(35,40,45),init_sec=(0,0,0)'


曲線

無かったので、単なる折れ線と3次曲線を追加します。

3次曲線といっても既出 曲線っぽく の rdiv処理を内部で使い回してます。

レールというか電車の中央を貫くコースを、折れ線と曲線で描画してみました。

各車両の前後端からちょっと中に食い込んだ車輪の位置が、曲線や、折れ線の点にのっかって移動してます。
(よかった。ちゃんと合ってた)

wf_26.patch

$ cd /some/where/
$ cat wf_26.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-30,30,0),pe=(100,-100,20),sec=(350,40,450),init_sec=(0,0,200)'


波 (その2)

以前の波の回転半径を刻々と変化させたら、波の激しさが変化してより複雑になるかな?
それならさらに時間をズラして並べるのも円弧状に並べるようにして、その半径も変化させてみようか?
どうなるだろう? などと思いついてやってみました。

結果、なんともエグい動きになってしまいました。 深海生物とでも言いましょうか...

あと、視点の動きと同様に視線の先の目標点の位置も、リサージュ図形的に指定出来るようにしてみました。
目標点の範囲を狭めて指定しておけば、狙った位置を見続けやすくなりました。

wf_27.patch

$ cd /some/where/
$ cat wf_27.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -eye_liss_p 'ps=(-30,-30,20),pe=(-10,-10,40),sec=(51,52,53),init_sec=(0,17,34)' -eye_liss_t 'ps=(-20,-20,30),pe=(-20,-20,30),sec=(1,1,1),init_sec=(0,0,0)'


ドミノ配置

カーブのデータに沿って、ドミノを並べるように、データを配置していく処理を追加しました。

最下層の電車のレールというか、中央を貫くカーブに沿って、矩形を配置してみました。

-demo のオプション指定でなんもかも表示してると、かなり込み入ってきたので、 オプション指定を分けました。
-demo だけだと、X,Y,Z軸の表示だけで、-demo -all で、従来の全部表示です。

リサージュ図形的に視点を移動させるときのオプション指定も、簡略化した方法を追加しました。

wf_28.patch

$ cd /some/where/
$ cat wf_28.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -rail -eye_liss_t 'p=(70,-70,0),r=0,sec1=1' -eye_liss_p 'p=(0,0,0),r=70,sec1=3,t=20'

$ wf/wf_ex -demo -train -rail -eye_liss_t 'p=(70,-70,0),r=0,sec1=1' -eye_liss_p 'p=(0,0,0),r=70,sec1=3,t=40'

$ wf/wf_ex -demo -train -rail -eye_liss_t 'p=(70,-70,0),r=0,sec1=1' -eye_liss_p 'p=(0,0,0),r=50,sec1=3,t=40'


扇風機

扇風機を止めてしばらく羽が回ってるとき、逆回転してるように見えたりしますよね。

どんな感じになるか、やってみました。

再生するフレームレートの影響が大きいと思いますが、 こちらで試してみてる環境では、結構それらしい感じで見えました。

視点移動の箇所も少し修正。

wf_29.patch

$ cd /some/where/
$ cat wf_29.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -cross -fan -eye_liss_t 'p=(0,0,0)' -eye_liss_p 'p=(0,-20,0),r=10,t=40'


ギャロップ

1つ作って時間をズラしてコピーの発想です。
お馬さんの「ハヤ駆け」の脚を試してみました。

例によって、なんというか不気味な感じに仕上がってしまいました。

少しでもデータのタイプ量を減らすべく、 DEND, DTYPE_REC(name) マクロを追加して、 従来のデータもざっと書き換えてみました。

wf_30.patch

$ cd /some/where/
$ cat wf_30.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -cross -gallop -eye_liss_t 'p=(0,0,0)' -eye_liss_p 'p=(0,0,10),r=20,t=40'


曼荼羅

単純なマトリョーシカ以外も、再帰データがちゃんと描画できるかと「曼荼羅」的な形状を試してみました。

recursive構造体が保持してる「状態」の扱いがまずかったので修正しました。

また、コピー系のオペレーションでは、 「コピーをとって位置をズラして配置」とか、 「コピーをとって時間をずらして配置」などを用意してましたが、 「コピーをとって何らかの操作」という汎用的な形式がなかったので追加しました。

あと、立方体(cube)、四角(square)、棒(bar)のデータを使う場合、 初期サイズを固定としていたので、 何をするにつけ、まずは「拡大縮小操作(zoom系)をかけて、位置をズラして配置」 というパターンになってました。

もうちょっと便利なように、一般的な箱型(box)を用意してみました。
x,y,z方向のサイズと、初期位置を指定できるようにしてあって、 生成するときに、サイズが0以下の次元については内部で削除します。

例えばz方向のサイズ指定を0にすると、内部ではx,y方向の四角として扱うので、 処理のオーバーヘッドも軽減されます。

従来のサンプルデータについて、出来るだけboxを使うように書き換えてみました。

-demo と共に指定する起動オプションが増えてきたので、 -help -demo で一覧を表示するようにしました。

そして追加した曼荼羅のデータについて

data_t *mandala = &(data_t){ DTYPE_REC(op_data_set){
  .op = { type_arr, (data_t[]){
    { DTYPE_REC(slide_way_liss){.ps=D3_ALL(-1),.pe=D3_ALL(1),.sec={11,12,13}} },
    { DTYPE_REC(rot_way){.l=LINE_Z,.deg_way=WAY_V2(0,360, 10,10, 3)} },
    { DTYPE_REC(recursive){
      .n = 3,
      .op = { type_copy_ops, (data_t[]){
	  { type_zoom_and_slide, (d3_t[]){ D3_ALL(0.5), D3_O } },
	  { type_arr, (data_t[]){
	    { DTYPE_REC(copy_rot){.n=3,.l=LINE_I,.step_deg=120} },
	    { DTYPE_REC(copy){.n={2,1,1},.init={-0.75,0,0},.step={1.5,0,0} }},
	    { type_zoom_all, (double[]){0.25} },
	    DEND }},
	  DEND }} }},
    { DTYPE_REC(copy_rot){.n=3,.l=LINE_I,.step_deg=120} },
    DEND }},
  .data = { DTYPE_REC(circle){.r=1,.n=100} } }};

例によって、下から上への解説

wf_31.patch

$ cd /some/where/
$ cat wf_31.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

wf/wf_ex -demo -mandala -eye_liss_t 'p=(0,0,0)' -eye_liss_p 'p=(0,0,0),r=2,t=20'

フラクタル

先の曼荼羅の動きを少し修正。回転軸をZ軸から、x=y=z軸に変更してみました。

再帰処理がそれなりに使えそうなので、フラクタルな形を試してみました。

data_t *fractal = &(data_t){ DTYPE_REC(op_data_set){
  .op = { type_arr, (data_t[]){
    { DTYPE_REC(copy_timeshift){.n=3,.step_sec=1,} },
    { DTYPE_REC(recursive){
      .n = 6,
      .op = { type_arr, (data_t[]){
	{ DTYPE_REC(copy_rot){.n=3,.l=LINE_Z,.init_deg=-60,.step_deg=60} },
	{ DTYPE_REC(rot_way){.l={{0,-1,0},D3_X},.deg_way=WAY_V2(-180,0, 10,2, 3)} },
	{ type_zoom_and_slide, (d3_t[]){ D3_ALL(0.5), {0,-(1+0.5),0} } },
	DEND }} }},
    DEND }},
  .data = { DTYPE_REC(circle){.r=1,.n=6} } }};

例によって下から目線な解説

再帰が使えると、少しのデータ量の定義で複雑っぽい形状が表示出来ますね。

しかしながら調整が微妙なので、 誤ると、実際の処理するデータ量が膨大に膨れ挙がり、負荷が爆発してしまいます。

wf_32.patch

$ cd /some/where/
$ cat wf_32.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -cross -mandala -eye_liss_t 'p=(0,0,0)' -eye_liss_p 'p=(0,0,0),r=2,t=30'
$ wf/wf_ex -demo -cross -fractal -eye_liss_t 'p=(0,-1,1)' -eye_liss_p 'p=(0,0,1),r=4,t=30'


フラクタル2

フラクタルその2ということで、フラクタル的な動きを狙ってみました。

data_t *fractal2 = &(data_t){ DTYPE_REC(op_data_set){
  .op = { DTYPE_REC(recursive){
    .n = 4,
    .op = { type_arr, (data_t[]){
      { DTYPE_REC(copy_timeshift){.n=6,.step_sec=0.5,} },
      { DTYPE_REC(slide_way_liss){.ps=D3_ALL(-1),.pe=D3_ALL(1),.sec={3,4,5}} },
      { type_zoom_way, &(struct way)WAY_P2(0,0,0, 0.5,0.5,0.5, 10,10, 3) },
      DEND }} }},
  .data = { type_cube } }};

例によって下からの解説

wf_33.patch

$ cd /some/where/
$ cat wf_33.patch | ( cd wf/ ; make clean ; patch -p1 ; make )

$ wf/wf_ex -demo -cross -fractal2 -eye_liss_t 'p=(0,0,0)' -eye_liss_p 'p=(0,0,0),r=4,t=40'