ジェネラティブアート作成時の頭のなか #AltEdu2022 6 日目
6 日目のお題は「誰かのコードを参照して…」とのこと。
実のところ、私の場合、教科書以外では殆ど他人のコードは読まない。というか、読みたくない。だって教科書は - いや、教科書に限った話ではないか。教科書のような文書では、(当たり前であるが)読者に分からせようとして書かれている。そういう文章は読む。でもそうではない、単なるコードを読んで理解するというのは、そこに書かれている秘密を知りたい!という強い欲求が無い限り、読むことはない。
読まれることを意識して書かれていないコードを読み、理解するということは、いわゆるリバースエンジニアリングの範疇に入るものであり、リバースエンジニアリングはそれなりに疲れる、だから嫌だ…というのが大きな理由である(とは言え、マイクロソフトに勤務していた時は、仕事で他人のコードを読む必要があり、そうも言ってはいられなかった。おかげで今では他人のコードを読む速度も上がり、ある意味感謝している)。
話がそれた。兎に角、理解したい!という強い欲求がなければ、私の場合、他人のコードは読まない。しかし、以下に示すアさん( @yuruyurau さん)の作品については、しげしげとコードを眺めてしまう:
コードの構造についてはもちろん理解できる。しかし、そのコードが意味する本質的なものは 2 年近く経った今でも分からない。そもそも、このような計算式をどうやって思いついたのか、その端緒に 1 ミリもたどり着けてはいない。まるでラマヌジャンの数式のようでもある。
#つぶやきProcessing は、まるで現代の算額のようだな、と思う時がある。表現の場としても大変面白いものであるが、その背景を知ることは難しい。自分の作品ですら、時間の経過とともに、制作の経緯を忘れてしまう。このような状況を鑑みて、この一連の note を記している。
前置きが長くなったが、本日のお題にとりかかろう。上に示したアさんの作品を書き換えらればよいのだけれども、全然歯が立たなかった。やはり初心者むけの、きちんと解説が付いているコードの方が良い。というわけで、こちらの deconbatch さんの周期的パーリンノイズのプログラムを書き換えてみることにする。
実は、上記の記事を参考にして作品を作ったことがある。このときは、残念ながら #つぶやきProcessing な作品にはできなかったが、以下のようなアニメーションを作成している:
このプログラムのソースコードはこちらに掲載している。
ちなみに静止画版は、上でリンクしたページにあるコードから、setup 関数と draw 関数をやめて、それぞれの関数内のコードを外に出せば良い:
size(500,500)
stroke(-1)
def D(s,f,c,a,b):
w=r=s*noise(cos(f)+1,sin(f)+2,c)+s
x=r+a; y=b
for i in range(72):
t=radians(i*5)
r=s*noise(cos(t+f)+1,sin(t+f)+2,c)+s
u=r*cos(t)+a; v=r*sin(t)+b
line(x,y,u,v)
x,y=u,v
line(x,y,w+a,b)
clear()
for i in range(40):
D(10+i*9,0,i*.3,100,150-3*i)
今回はあくまでも deconbatch さんのコードを書き換えることを目指すので、上のコードは使わない。
deconbatch さんの円周(?)を点描するコードが良さそうなので、こちらを Python 版に移植する:
def setup():
size(800,800)
colorMode(HSB,360, 100, 100, 100)
smooth()
noStroke()
noLoop()
def draw():
xInt=width*.5
yInt=height*.5
rBase=width*.25
rDiv=width*.2
background(0,0,90,100)
fill(0,0,30,100)
for i in range(0,628,10):
radian=i*100
pN=noise(rBase*cos(radian),rBase*sin(radian))
pR=rBase+rDiv*noise(pN)
pX=xInt+pR*cos(radian)
pY=yInt+pR*sin(radian)
ellipse(pX,pY,3,3)
無事移植完了。画像の下の部分にグラデーションがかかっているのは、macOS の Dock の影響であり、グラデーションのためのコードは入っていない(mac book air で開発しているので、800x800 の画像が表示しきれないため)。
コードを見てみると noLoop 指定があるし、最終的には #つぶやきProcessing として発表するので静止画としてコーディングしなおす。あわせてノイズの非対称化処理も入れる。あと、今日もなんとなく黒背景にカラフルな色の方が良い気がするので、背景も黒くする。サイズもいつものように 500x500 に変更する。deconbatch さんも書かれているけど、変動量が少ないので、より大きく変化するように変動量も変化させる(rBase を半分にして、rDiv を倍にする):
size(500,500)
colorMode(HSB,360, 100, 100, 100)
smooth()
noStroke()
xInt=width*.5
yInt=height*.5
rBase=width*.125
rDiv=width*.4
clear()
fill(0,0,100,100)
for i in range(0,628,10):
radian=i*100
pN=noise(2+cos(radian),3+sin(radian))
pR=rBase+rDiv*noise(pN)
pX=xInt+pR*cos(radian)
pY=yInt+pR*sin(radian)
ellipse(pX,pY,3,3)
いい感じである。
後は色をつけて、これを何重にも描くようにしてみる。まずは色の部分であるが、現在は colorMode(HSB,360, 100, 100, 100) であるので、これをバッサリと colorMode(HSB,8) とする。色相の変化を大きくとり、メリハリをつけたいという思惑がある。8 というのは RGB それぞれ 1 ビットとすると、3 ビットカラーであり、そこからなんとなく 8 を使用している。
ちなみに、3 ビットカラーでは表現出来ない色を使う場合は 9 という値を使う時もある。もちろん、fill や stroke 関数等では浮動小数点値も使えるので、使用する色の制限にはならない。ただ、整数演算の範囲では色を限定できるので、そのような意味でなんとなく colorMode(HSB,8) としている状況である。
あと、ellipse 関数を使っているが、縦横方向に同じ値を使用しているので、#つぶやきProcessing 化を考えて circle 関数に変更する(もちろん、自由度としては ellipse 関数の方が高いので、まだ変更する可能性がある場合は ellipse のままにしておいた方が良い)。
size(500,500)
colorMode(HSB,8)
smooth()
noStroke()
xInt=width*.5
yInt=height*.5
rBase=width*.125
rDiv=width*.4
clear()
for i in range(0,628,10):
radian=i*100
pN=noise(2+cos(radian),3+sin(radian))
pR=rBase+rDiv*noise(pN)
pX=xInt+pR*cos(radian)
pY=yInt+pR*sin(radian)
fill(i/10%8,8,8)
circle(pX,pY,5)
なんだか微妙な作品になってしまった。多重にすれば良いのかな…と思い、コードを修正する:
size(500,500)
colorMode(HSB,8)
smooth()
noStroke()
xInt=width*.5
yInt=height*.5
rBase=width*.125
rDiv=width*.4
clear()
for r in range(10):
for i in range(0,628,10):
radian=i*100
pN=noise(2+(r+1)*cos(radian),3+(r+1)*sin(radian))
pR=rBase+10*r+rDiv*noise(pN)
pX=xInt+pR*cos(radian)
pY=yInt+pR*sin(radian)
fill(i/10%10,8,8)
circle(pX,pY,5)
…花火のようになってしまった。大きさの異なる円を描くときに使用する、pX,pY での計算時の変数 radian が同じなので当たり前である。これでは歪んだ円周という表現が分からなくなってしまうので、radian の計算時に、基準となる同心円の管理している変数 r を関与させるようにしてみる(r を加えてみる):
size(500,500)
colorMode(HSB,8)
smooth()
noStroke()
xInt=width*.5
yInt=height*.5
rBase=width*.125
rDiv=width*.4
clear()
for r in range(10):
for i in range(0,628,10):
radian=i*100+r
pN=noise(2+(r+1)*cos(radian),3+(r+1)*sin(radian))
pR=rBase+10*r+rDiv*noise(pN)
pX=xInt+pR*cos(radian)
pY=yInt+pR*sin(radian)
fill(i/10%10,8,8)
circle(pX,pY,5)
こんな風にうまく混ざり合う場合もあれば、
こんな風に、うずまきの花火になってしまう場合もある:
これはなかなか評価が難しい。混ざり合う方が絵としては面白い。ただ、それでは単に乱数を使って半径を変動させればよいのでは?という気持ちにもなる。
やはり(というか、そりゃそうなのではあるが)、ヒトは色毎にグループ分けされているように感じるものなので、deconbatch さんの作品のように円周毎に色分けするのが良いんだなあ、と気づく。
というわけで、そのようにコードを修正する。fill 関数のところを変数 i から r に変更する。あと、剰余計算のところが 8 ではなく 10 だったので、それも修正する(これまではある意味バグっていたとも言う):
size(500,500)
colorMode(HSB,8)
smooth()
noStroke()
xInt=width*.5
yInt=height*.5
rBase=width*.125
rDiv=width*.4
clear()
for r in range(10):
for i in range(0,628,10):
radian=i*100+r
pN=noise(2+(r+1)*cos(radian),3+(r+1)*sin(radian))
pR=rBase+10*r+rDiv*noise(pN)
pX=xInt+pR*cos(radian)
pY=yInt+pR*sin(radian)
fill(r%8,8,8)
circle(pX,pY,5)
いい感じになってきた。…でもこれって、deconbatch さんの作品と似てるよね…という気もする。コードの出発点もそうだし、そもそもアルゴリズムというか計算の本質的な部分が同じなので、そりゃそうだよな、という感もある。
同心円状の作品というのは維持しつつ、もう少し密度を上げればなんとかなるのではないかと考える。変数 r のループを 10 回だけではなく 50 回に引き上げる。また、そもそも r の値が大きくなると、点ごとの間隔が広くなってしまうので、radian 変数の変動料を r が大きくなるにつれて、より細かくするように変更する(range の第 3 引数を固定の 10 から、10-r/10 に変更する)。あと、同心円の中心も画面の真ん中では面白くないのでずらしてみる:
size(500,500)
colorMode(HSB,8)
smooth()
noStroke()
xInt=width*.5
yInt=height*.5
rBase=width*.125
rDiv=width*.4
clear()
for r in range(50):
for i in range(0,628,10-r/10):
radian=i*100+r
pN=noise(2+(r+1)*cos(radian),3+(r+1)*sin(radian))
pR=5*r+(r+5)*10*noise(pN)
pX=xInt+pR*cos(radian)+100
pY=yInt+pR*sin(radian)+100
fill(r%8,8,8)
circle(pX,pY,10)
この感じであれば、何か同心円の中心にエネルギー的なものが集中しているような表現にした方が面白いのではないだろうか?と感じる。であれば、色相は変化させずに、輝度を変化させた方がよさそうだ:
size(500,500)
colorMode(HSB,8)
smooth()
noStroke()
xInt=width*.5
yInt=height*.5
rBase=width*.125
rDiv=width*.4
clear()
for r in range(90):
for i in range(0,628,5-r/45):
radian=i*100+r
pN=noise(2+(r+1)*cos(radian),3+(r+1)*sin(radian))
pR=5*r+(r+2)*20*noise(pN)
pX=xInt+pR*cos(radian)+100
pY=yInt+pR*sin(radian)+150
fill(4+r%8*.5*.1,8,10-r*.2)
circle(pX,pY,max(1,9-r*.1))
まあ、なんとなく良い感じになってきたので、今日はこれを #つぶやきProcessing 化することにしよう。アニメーションもさせないので、ノイズではなく乱数でも良いのかな?という気もしないではないけど。でもまあ、中心部分をみると周期的ノイズの効果がでているし、そもそも本日のお題に沿ってコードを書くのが #AltEdu2022 の趣旨だし。
というわけで、ここからコードゴルフの開始となる。まず変数名が長いので変更ならびに定数値への置き換え、使っていない変数の削除を試みる。
size(500,500)
colorMode(HSB,8)
smooth()
noStroke()
clear()
for r in range(90):
for i in range(0,628,5-r/45):
radian=i*100+r
pN=noise(2+(r+1)*cos(radian),3+(r+1)*sin(radian))
pR=5*r+(r+2)*20*noise(pN)
pX=pR*cos(radian)+350
pY=pR*sin(radian)+400
fill(4+r%8*.5*.1,8,10-r*.2)
circle(pX,pY,max(1,9-r*.1))
次に smooth 関数の削除を試みる。経験的にあまり効果がないように感じているので、バッサリと削除する。あと変数名 radian は長いので、t にする。数学で角度を表すギリシャ文字として θ をよく使うが、この英語表記は theta である。なので、角度を表す変数には t をよく使っている(一時変数を表す temporary の意味で使うときもある):
size(500,500)
colorMode(HSB,8)
noStroke()
clear()
for r in range(90):
for i in range(0,628,5-r/45):
t=i*100+r
pN=noise(2+(r+1)*cos(t),3+(r+1)*sin(t))
pR=5*r+(r+2)*20*noise(pN)
pX=pR*cos(t)+350
pY=pR*sin(t)+400
fill(4+r%8*.5*.1,8,10-r*.2)
circle(pX,pY,max(1,9-r*.1))
目論見通り、smooth 関数を削除してもほとんど変化はない。
#つぶやきProcessing のためには、まだまだ文字数を減らさないといけないので、変数名を 1 文字に変更する。あと、cos(t) や sin(t) という値も複数の場所で使われているので、c,s という変数に代入してそれを再利用する。
size(500,500)
colorMode(HSB,8)
noStroke()
clear()
for r in range(90):
for i in range(0,628,5-r/45):
t=i*100+r;c=cos(t);s=sin(t)
u=noise(2+(r+1)*c,3+(r+1)*s)
v=5*r+(r+2)*20*noise(u)
x=v*c+350
y=v*s+400
fill(4+r%8*.5*.1,8,10-r*.2)
circle(x,y,max(1,9-r*.1))
変数 x,y および u は 1 箇所でしか参照されていないので、それぞれの場所に埋め込んでしまう:
size(500,500)
colorMode(HSB,8)
noStroke()
clear()
for r in range(90):
for i in range(0,628,5-r/45):
t=i*100+r;c=cos(t);s=sin(t)
v=5*r+(r+2)*20*noise(noise(2+(r+1)*c,3+(r+1)*s))
fill(4+r%8*.5*.1,8,10-r*.2)
circle(v*c+350,v*s+400,max(1,9-r*.1))
この状態で #つぶやきProcessing タグと #AltEdu2022 タグを入れると、13 文字オーバーとなる。
そこで、最小限のインデントにしつつ、インデント削減のためのマルチステートメント化を施す:
size(500,500)
colorMode(HSB,8)
noStroke()
clear()
for r in range(90):
for i in range(0,628,5-r/45):t=i*100+r;c=cos(t);s=sin(t);v=5*r+(r+2)*20*noise(noise(2+(r+1)*c,3+(r+1)*s));fill(4+r%8*.5*.1,8,10-r*.2);circle(v*c+350,v*s+400,max(1,9-r*.1))
文字数削減の結果、無事 tweet できた:
この記事が気に入ったらサポートをしてみませんか?