少し前に以下について書きました。
#つぶやきProcessing が楽しい - memorandums
ツイートが制約になるためコードサイズは必然的に小さくなり、できることも少なくなるわけですが、隙間の時間で集中できて達成感があるのがなかなか媚薬的な活動であります。
とはいえ、ある程度、連続して投稿してきたのですが出切ってしまうとなかなかアイデアは出てこないもので。。。色々な方のツイートをみて「すげーなぁ」と感動させてもらっていました。
そんなところに #つぶやきProcessingの創始者のはぅ君さんの以下のツイートを見かけました。
float t,x,y,r,N;
— はぅ君 (@Hau_kun) March 18, 2020
void setup(){size(720,720);stroke(-1);strokeWeight(5);}
void draw(){
clear();
t+=.02;
for(y=0;y<772;y+=52)
for(x=y/52%2*90;x<900;x+=180)
for(r=0;r<TAU;r+=PI/3)
line(x,y,cos(N=noise(int((x+y+t)/(PI/3)))>.6?r+x+y+t:r)*58+x,sin(N)*58+y);
}#つぶやきProcessing pic.twitter.com/duQkqSGBHa
「え?この短いソースでどうしてこんな複雑な動きが表現できるんやろ?」と思ったわけです。
そう感じてしまえばしめたもので(←自分自身のことをこう表現するのも変なんですが。。。年をとるとそういう驚きが発見が少なくなり。。。だからこそ、そう感じることができたときは大切にしたいな。。。という価値観があります)、早速、通勤電車でバラしてコードを眺めさせてもらいました。楽しかったぁ。。。
元ソースはJavaモードだったのでp5js版に変換して、置き換えて動きのある部分を削除して、まず模様だけ描くようにしました。これがスタートです。位置の調整は難しいですが、60度ずつTAU(2π)分まわして*のようなマークを描きます。あの図形を分解してこの表現するのはそもそもすごい発想かと思います。
分解してみればとりあえず理解はできます。
function setup(){createCanvas(200,200)} function draw(){ for(y=0;y<200;y+=25) for(x=y/25%2*40;x<200;x+=80) for(r=0;r<TAU;r+=PI/3){ line(x,y,cos(r)*25+x,sin(r)*25+y) }}
つづいて、Nという変数がでてくるので、rをNに置き換えただけのソースを作ります。結果は変わりません。
function setup(){createCanvas(200,200)} function draw(){ for(y=0;y<200;y+=25) for(x=y/25%2*40;x<200;x+=80) for(r=0;r<TAU;r+=PI/3){ N=r line(x,y,cos(N)*25+x,sin(N)*25+y) }}
つづいて、動くようにします。はぅ君さんのは、あちこちの*が選択的に動いたり止まったりするのですが、まず単純化して全部の*が動くようにしました。r+x+y+tのうちtが0.02ラジアン(ほぼ1度)ずつ変化します。2重for文で*を格子状に配置したのちに、それぞれの図形を描画しつつ、t分だけ開始角度がずれるので、結果として回転しているように見せることができるようです。素晴らしい。。。さすが。
t=0 function setup(){createCanvas(200,200)} function draw(){ clear() t+=.02 for(y=0;y<200;y+=25) for(x=y/25%2*40;x<200;x+=80) for(r=0;r<TAU;r+=PI/3){ N=r+x+y+t line(x,y,cos(N)*25+x,sin(N)*25+y) }}
ご本人の意図は想像するしかありませんが、私が感動した現象を産んだコードは「★ココ!!!」でした。前後しましたが、私が何に感動?したのかというと。ソースコードを見るとforの2重ループしかないんですね。でも実行結果をみると*のマーク?が一定の回転を終えるまでアニメーションが続き、回転が終わったら別の場所の*が回転しだすという不思議な動作が実現できていたんです。なぜ、このシンプルなコードでこんなに複雑な動きが実現できるんだろう。。。?と思いました。
t=0 function setup(){createCanvas(200,200)} function draw(){ clear() t+=.02 for(y=0;y<200;y+=25) for(x=y/25%2*40;x<200;x+=80) for(r=0;r<TAU;r+=PI/3){ N=noise(int((t+x+y)/(PI/3)))>.6?r+x+y+t:r //★ココ!!! line(x,y,cos(N)*25+x,sin(N)*25+y) }}
ソースコードに話を戻します。Nの値は「noise(int( (t+x+y)/(PI/3) ) )>.6」がtrueなら、「r+x+y+t」(←*が動きっぱなし)で、falseなら「r」(←*が止まる)です。 つまり「noise(int( ( t+x+y)/(PI/3) ) )>.6」の部分に仕掛けがありそうです。
で、調べてみました。
noiseは、引数の値から決められた計算式によって確定的に0から1の間のランダムな値を返します。p5.jsのドキュメントにそこまでは書いていなかったので実験で確かめてみたところ、noiseの引数は整数で出力が変わるものの、同じ整数値であれば小数桁の数値が変化してもnoiseの戻り値は変化しない、という性質があるようです。(2020/3/21追記:嘘でした。ジェネラティブアートの132ページを読んでいて間違いに気付きました。noise関数は小数だろうがなんだろうが値が異なれば異なる値を返します、よね。。。小数点以下の変動に変化しないのはnoise関数の引数でint()で整数化しているからです。ごめんなさい。)ここがミソなんですね。
noiseの引数である「int( (t+x+y)/(PI/3) )」の部分はPI/3つまり60度内で増減します。tの値は0.02ずつ増えていますが整数桁になるまでは「int( (t+x+y)/(PI/3) )」の値は同じ値を返します。noise関数はランダムではあるけど同じ引数の値に対しては同じ値を返すため、PI/3つまり60度までは整数値が変わらないので回転する*の場所が固定されます。固定されている間にもtの値は増え続けますので、同じ場所の*が60度回転するまで回転し続ける。。。という動作が実現できるわけですね。。。ワインが入っているので。。。言葉でうまく説明できているかわかりませんが。うまいとしか言いようがありません。
勉強しただけではもったいないので、自分でも真似して実装してみました。単純ですが原理ははぅ君さんのコードを使わせていただいています。
t=0
— タカハシラボ (@ke_takahashi) March 19, 2020
function setup(){createCanvas(200,200);fill(0)}
function draw(){
clear()
t+=.1
for(x=0;x<200;x+=10){
N=noise(int*1>0.6?x+t:0
rect(x,width/2*sin(N)+width/2,10,10)
}}
//学習ソース⇨https://t.co/WEooMKEWKh#つぶやきProcessing pic.twitter.com/caBN2IiMpg
オープンソースな時代、読もうと思えばコンパイラやOSやフレームワークなどたくさんの教材がただで手に入る時代ではありますが。。。なかなかそこまで時間とやる気を確保するのは難しくもあります。
コードが短いからこそ、自分の時間とやる気と興味にあわせて、こういうテクニックを勉強できるのも、つぶやきProcessingの魅力だな。。。と思った次第でした。
ぜひ、みなさんも面白いなぁ。。。と感じたソースコードをコピペして分解して遊んでみてはいかがでしょうか?
*1:x+t)/PI