[processing]魔方陣とかSF円とか(Processing Advent Calendar 2011 – 20日目)

Processing Advent Calendar 2011の2回目の登場です。
20日目の担当ということで、最近Processingと疎遠でphpやらpythonやらと戯れている私は既にネタ切れですw

Processingは何かのプロダクトのプロトタイプを作ったり、自分が作りたいアニメーションを作ったりするのに向いていると言われています。
今回は私の趣味で、魔方陣とかSFちっくな円とか、そういうものを2種類公開いたします。

ミッドチルダ式魔方陣

[p5code]
/**
* ミッドチルダ式魔法陣 (元ネタ:魔法少女リリカルなのは)
*
* @title mugityax | 背景作成
* @link http://www.mugityax.com/weblog/2006/06/post_1.html
*
* @title ミッドチルダフォント置き場
* @link http://midfont.refy.net/
*/

float rot1, rot2;
PFont font1, font2;

void setup() {
size(400, 400);
colorMode(HSB, 100);
frameRate(30);
smooth();
background(0);

textAlign(CENTER, CENTER);

font1 = loadFont(“MID-CHILDA_Regular-30.vlw”);
font2 = loadFont(“MID-CHILDA_Regular-15.vlw”);
//font1 = createFont(“mid-childa_R.ttf”, 30, true);
//font2 = createFont(“mid-childa_R.ttf”, 15, true);

rot1 = rot2 = 0;
}

void draw() {
background(0);
pushMatrix();
{
noFill();
translate(width / 2, height / 2);
final float rl = 120 / sqrt(2); // 84.85281
final float ro = 149;

// 固定円
{
drawLightEllipse(0, 0, 180*2, 180*2); // 外周円-二重外円
drawLightEllipse(0, 0, 175*2, 175*2); // 外周円-二重外円
drawLightEllipse(0, 0, 120*2, 120*2); // 外周円-内円
drawLightEllipse(0, 0, rl*2, rl*2); // 内周方形内-外円
drawLightEllipse(0, 0, 60*2, 60*2); // 内周方形内-内円
}

// 外周小円・内周方形1
pushMatrix();
{
rotate(rot1);
// 外周小円
final float[] rm = {50, 45};
for (int i = 0; i < rm.length; i++) { drawLightEllipse(0, -ro, rm[i], rm[i]); drawLightEllipse(ro, 0, rm[i], rm[i]); drawLightEllipse(0, ro, rm[i], rm[i]); drawLightEllipse(-ro, 0, rm[i], rm[i]); } // 内周方形1 drawLightQuad(0,-120,120,0,0,120,-120,0); } popMatrix(); // 内周方形2 pushMatrix(); { rotate(rot2); drawLightQuad(-rl,-rl,rl,-rl,rl,rl,-rl,rl); } popMatrix(); final String[][] ot = { {"Y","S","T","D","U","X","K","j","L","z","D","K","F","A","M","B","Y","o","h","Z","O","T","U","V","i"}, {"J","Z","p","V","A","Y","F","r","j","s","v","W","T","h","G","S","c","H","X","K","k","o","d","B","u"}, {"b","F","M","r","g","R","V","y","N","B","A","x","q","C","T","p","E","L","K","a","w","j","k","J","P"}, {"H","a","G","d","W","S","n","M","X","s","E","x","A","o","Z","C","b","f","k","U","V","i","Y","B","D"}, {"E","y","t","H","L","o","Q","W","T","h","a","d","C","B","b","w","v","D","s","Y","z","e","R","n","S"}, {"K","B","f","T","k","R","e","d","w","z","S","m","D","J","P","F","j","o","n","r","u","x","g","v","b"}, {"z","G","q","e","u","w","W","a","U","X","i","r","A","Y","D","t","N","V","k","P","Q","p","g","E","K"}, {"b","v","d","B","g","z","D","h","j","Q","n","U","G","Z","q","u","F","r","T","X","a","m","W","t","Y"}, {"Z","K","U","T","e","A","G","c","X","i","Q","o","R","F","k","t","p","D","J","d","r","q","n","V","E"}, }; final int[][] a_set = { {14, 78, 4}, {102, 170, 4}, {192, 260, 4}, {282, 350, 4} }; // 外周文字 pushMatrix(); { rotate(rot1); fill(100); textFont(font1); for (int i = 0; i < 4; i++) { int c = 0; for (int a = a_set[i][0]; a < a_set[i][1]; a+=a_set[i][2]) { pushMatrix(); rotate(radians(a)); translate(ro, 0); rotate(HALF_PI); text(ot[i][c++], 0, 0); popMatrix(); } } } popMatrix(); // 内周文字 pushMatrix(); { rotate(rot2); fill(100); textFont(font2); for (int i = 0; i < 4; i++) { int c = 0; for (int a = a_set[i][0]; a < a_set[i][1]; a+=a_set[i][2]) { pushMatrix(); rotate(radians(a)); translate(70, 0); rotate(HALF_PI); text(ot[i][c++], 0, 0); popMatrix(); } } } popMatrix(); } popMatrix(); update(); } void update() { rot1 += 0.04; rot2 -= 0.04; } void drawLightEllipse(float a, float b, float c, float d) { strokeWeight(5); stroke(100, 100, 80, 40); ellipse(a, b, c, d); strokeWeight(3); stroke(95, 100, 80, 60); ellipse(a, b, c, d); strokeWeight(2); stroke(90, 100, 80, 80); ellipse(a, b, c, d); strokeWeight(1.5); stroke(100); ellipse(a, b, c, d); } void drawLightQuad(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { strokeWeight(5); stroke(100, 100, 80, 40); quad(x1, y1, x2, y2, x3, y3, x4, y4); strokeWeight(3); stroke(95, 100, 80, 60); quad(x1, y1, x2, y2, x3, y3, x4, y4); strokeWeight(2); stroke(90, 100, 80, 80); quad(x1, y1, x2, y2, x3, y3, x4, y4); strokeWeight(1.5); stroke(100); quad(x1, y1, x2, y2, x3, y3, x4, y4); } void mousePressed() { save("mid_tilde_magic_circle.jpg"); } [/p5code] まずは、魔方陣です。魔法少女リリカルなのはというアニメの中に出てくるミッドチルダ式魔方陣というものです。元ネタは全く知らないのですが、魔方陣的に描きやすかったので、参考にしました。専用のフォントも使用しています。プログラムは4つの構成要素になっていて、外周や内周の固定や回転する円、内周の正方形、外周円の文字、内周円の文字となっています。rotate()とmatrixを上手く使用して、座標軸を回転させることで描画する方式をとっています。仕組みさえわかってしまえば簡単にバリエーションが作成できます。

SF円

[p5code]
float cx, cy;
int ringCount = 0;
Ring[] rings = new Ring[1];

void setup() {
size(400, 400);
background(0);
noFill();
smooth();
strokeCap(SQUARE);
ellipseMode(RADIUS);

cx = width / 2;
cy = height / 2;

float pRadius = 20, pWeight = 2;
while (pRadius < 180) { Ring r = addRing(); r.setWeight(random(2, 25)); r.setPosition(cx, cy); r.setRadius(pRadius + (pWeight + r.weight) / 2 + 1); r.setRadians(random(TWO_PI)+PI/3, random(TWO_PI)+PI/3); r.setColor(#ffffff); r.setRot(random(-0.05, 0.05)); pRadius = r.r; pWeight = r.weight; } } void draw() { background(0); for (int i = 0; i < ringCount; ++i) { rings[i].draw(); rings[i].move(); } } Ring addRing() { if (rings.length == ringCount) { rings = (Ring[]) expand(rings); } Ring r = new Ring(); rings[ringCount++] = r; return r; } class Ring { float x, y, r; float weight; float startRad, endRad; float rot; color c; void draw() { stroke(c); strokeWeight(weight); arc(x, y, r, r, startRad, endRad); } void move() { startRad += rot; endRad += rot; if (startRad > TWO_PI && endRad > TWO_PI) {
startRad -= TWO_PI;
endRad -= TWO_PI;
}
}

void setPosition(float x, float y) {
this.x = x;
this.y = y;
}

void setRadius(float r) {
this.r = r;
}

void setRadians(float start, float end) {
this.startRad = start;
this.endRad = end;
}

void setWeight(float weight) {
this.weight = weight;
}

void setColor(color c) {
this.c = c;
}

void setRot(float rot) {
this.rot = rot;
}
}

void mousePressed() {
save(“sf_arc_ring_magic_circle.jpg”);
}
[/p5code]

正式に何といったらよいのかわかりませんが、SF映画とかのコンソール画面とかの後ろ側で無駄にかっこ良く回ってるアレですw プログラムの仕組みとしては、円弧(arc)を何十にも重ねてそれぞれ別々の弧の長さや回転速度を持たせているだけです。プログラム上の工夫としては、円弧の部分をRingというクラスにまとめ、draw()ループから呼び出すメソッドはRing.draw()やRing.move()に限定することで、draw()ループを見やすくしています。このように、似たような動作をするオブジェクトをクラスとして定義して、パラメータの違いによって動作を変え、描画や移動の時はdraw()やmove()などのメソッドを作成して呼ばせるような手法は、プログラムをすっきりとさせるし、動作の塊がオブジェクトとしてまとまっているので、大変見やすくなっています。

最後に

と、今回は2つの作品の紹介で終わりでちょっと物足りなかったかと思います。

私はCGクリエイターと言うよりはCGに興味があるプログラマーなので、自分が作りたいものが作れれば良いという考えではなく、「この作品を作るにはどういうプログラムにすると上手く書けるか」という視点でProcessingのコードを書くことが多いです。前回の伝統模様の記事でも、いろんな書き方で伝統模様を書いていたのをご覧になっているかと思います。私の目標としては”CG自体の美“の次くらいに”プログラムコードの美“を追求していきたいと思っています。

めちゃめちゃなコードでもその場限りで描ければ良しとする考えもあるのですが、数カ月後とか、1年後とかにProcessingのコードを見て、何やっているかわからないと困る場合もありますよね。その点、プログラムコードの美しさも最初から追求していれば、後から読んでも読みやすいプログラムなので、理解がしやすいかと思います。

とまぁ、作品紹介ついでに本職プログラマーならではのプログラミングの視点をご紹介してみました。

About: uechoco