FAL 制作メモ

作ったものを見せびらかしたり解説したりします。自作ゲームやクリエイティブコーディングの話が中心です。作ったものの置き場所はこちら→ https://www.fal-works.com/ Twitter→ https://twitter.com/falworks_ja

動く2点間を複雑な線で繋ぐ簡単な方法(by Étienne Jacob氏)

ジェネレーティブアートに使えそうなテクニック的な話です。

作品 "Afterimage" のスクリーンショット
Afterimage



この記事の趣旨

Étienne Jacob さんというのは、こういう感じの白黒のジェネレーティブアートを制作している人です。


上記の2作品、てっきり何か複雑な物理シミュレーションでもやってるんじゃないかと思っていたのですが、なんと最近、そのタネを公開してくれました。
A trick to get looping curves with lerp and delay | necessary-disorder tutorials

読んでみると、ちょっとプログラムが書ける人なら誰でもすぐ使えそうなくらい簡単な方法です。私もさっそく冒頭の画像みたいなのを作ってみたところです。

この方法について、上記のリンクを読めばもはや説明不要な感もあるのですが、記録も兼ねて、自分の言葉で説明しなおしてみたいと思います。元記事の方が動画とソースコードがついてて分かりやすいので、イメージを掴みたい方はまず元記事の方を見ましょう。

時間によって位置が決まる点を用意する

まず、2つの点を用意します。

ここで重要なのは、「実際に動かしてみないとどういう動きをするか分からない」という状態ではなく、「時間が分かれば位置も分かる」という状態を整えることです。
プログラム的に言うと、時間 t を引数として、t を与えれば位置が返ってくるような関数を定義することになります。

ということで、ここでは始点を p_{start}(t)、終点を p_{end}(t) としましょう。

関数の内容としては、例えば

p_{start}(t) = (x, \, y) = (r \cos t, \, r \sin t)

とすれば、原点の周りを時間 t の変化に伴って半径 r で回る軌道になりますね。
とにかく f(t) の形になっていればなんでもよいのです。

※ あとで「過去」の位置を参照したいので、t がゼロからマイナスになっても連続的に繋がるように定義した方が使いやすいです。上述の円軌道はOKな例です。

2点間を補完する点の集まりを用意する

つまり、2点を繋ぐ線を構成する点の集まり、ということです。
数学的には無限個の点になりそうなところですが、プログラムでは有限個の点でごまかすしかないので、全部でn個としましょう。

これらについても、時間 t に応じて任意の k 番目の点の位置を教えてくれるような関数 p_{k}(t) を用意します。
プログラム的に考えて、k の範囲は \left(0 \leqq k < n \right) がいいですかね。(元記事では変数 m = n - 1n の代わりにループで使っているので注意)

ここで、関数の中身を簡単に書くために、点 p_k が始点からどのくらい離れているかを表す「距離」を定義します。

d_{k} = \frac{k}{n - 1} …始点からどのくらい離れているか率(0 ~ 1、終点と同じ位置のとき 1)

注:ここで「距離」というのは座標空間的なアレじゃなくて、例えば n が3個のときに2番目の点(k = 1)は真ん中にあるから d_{k} = 0.5 となってほしいのです。他のネーミングの方が良さそうなんですが思い付かなかった。

さて、関数 p_{k}(t) の中身をどうするかが問題ですが、まず一番簡単な例は直線による補完です。

p_{k}(t) = (1-d_{k}) \, p_{start}(t) + d_{k} \, p_{end}(t)

この場合、k = 0 なら始点の位置、 k = n-1 なら終点の位置に来て、その間の点は一直線に並びます。
Processingだとlerp()関数を使うことになるでしょうが、やっていることは同じです。

2点間を補完する点の動きを複雑にする

あとは上述の関数をいじっていくだけです。
どこをいじるかというと、p_{start}(t)p_{end}(t) にどんな t を与えるか、です。
まずは p_{start}(t) のところを、次のように変えてみます。

p_{k}(t) = (1-d_{k}) \, p_{start}(t - C d_{k}) + d_{k} \, p_{end}(t) (Cは適当な定数、面白い結果になるように自分で調整する)

こうすると、始点のすぐ近くの点に対しては影響が小さいですが、始点から遠ければ遠いほど、始点の「過去」の位置を参照することになります。
実際動かしてみると、始点の動いた軌跡をトレースするような動きになります。

さらに p_{end}(t) のところも変えてみます。

p_{k}(t) = (1-d_{k}) \, p_{start}(t - C d_{k}) + d_{k} \, p_{end}(t - C (1-d_{k})) (Cは定数)

これで、始点に近ければ始点の、終点に近ければ終点の動きをトレースするようになります。

応用

原理は以上です。あとは定数をいじるなり、始点や終点の動きを複雑にするなり、始点と終点のペアを増やすなり、いくらでも応用できますね。

私はとりあえず、やっつけではありますが四角形の動きをさせてみました。
( ↓ JavaScriptで動いています)


→ OpenProcessingで見る

シンプルな規則で複雑な動きをするのってやっぱり面白いですね。