見出し画像

【作品解説】 #102 : Crystalized trees on planet

こんにちは、Almina です。
先日 note を更新したばっかりですが、もう新しい note 書いちゃいます。(今回は更新が早い!エライ!)

さて、先日投稿した記事にて、@deconbatch さんからこんなコメントをいただきました。

作品の解説ぜひ読みたいです!
例えばこれとかどう考えて作ったのかなんて教えていただけると嬉しいカモ。🦆
https://twitter.com/Code4_11/status/1200460290812334080

こうして自分の作品に興味を持ってもらえるとは、なんと光栄なことか...。ホントに嬉しい!
@deconbatch さん、ありがとうございます🙇‍♂️

ということで今回は、【#102 : Crystalized trees on planet】の作りかたを解説をしていきます。制作プロセスも交えつつ、どう作っているかを語ろうかと思います。
また、記事の最後にコードを載せていますので、ぜひ作ってみてください。

では早速、はじめていきましょう!

解説

まず結論から言うと、実はこの作品は ”再帰木” の応用になってます。
再帰木の枝を、単に直線ではなく ”三角形” にして分岐させることで作っているんです。

...まぁ、タネを明かしてしまえば、なんともシンプルでして。
でもせっかくなので、ここからは制作プロセスをなぞりつつ、一緒に作ってみましょう!

画像3

作ってみよう

はじめはこんな感じでした。再帰木を描くだけ。

/**
* カラーパレット
*/
let pal = ["#1f2421","#216869","#49a078","#9cc5a1","#dce1de"];

let bgColor;
let seed;

function setup() {
 createCanvas(600, 600);
 angleMode(DEGREES);
 background(255);
 noLoop();

 let cid = int(random(pal.length));
 bgColor = pal[cid];
 pal.splice(cid, 1);
 pal = shuffle(pal);

 seed = random(1e+4);

 background(bgColor);
}

function draw() {
 randomSeed(seed);
 background(bgColor);
 
 drawBranch(300, 500, 100, 180, 5);
}

/**
* 関数: 再帰的に枝を描画
*/
function drawBranch(x, y, len, ang, n) {
 /**
 * 入力,
 *   x, y: 始点の座標
 *   len: 枝の長さ
 *   ang: 枝の角度
 *   n: 深さ(>0)
 */

 // 枝の端点座標
 let ex = x + sin(ang) * len;
 let ey = y + cos(ang) * len;
 let c = color(pal[n%4]);
 
 strokeWeight(5);  stroke(c);
 line(x, y, ex, ey);  // 枝を描画
 
 // 深さが0以上の場合,新たな枝を描画
 if (n > 0) {
   drawBranch(ex, ey, len * 0.85, ang + random(-40, 40), n-1);
   drawBranch(ex, ey, len * 0.85, ang + random(-40, 40), n-1);
   drawBranch(ex, ey, len * 0.85, ang + random(-40, 40), n-1);
 }
}

画像5

とってもシンプル。これはこれで魅力的ですケド。

ここで一度、前回の記事になぞらえて、ザックリ要素に分解しておきます。

1. (x, y) から角度 ang,距離 len の端点 (ex, ey) を求める
2. (x, y) から (ex, ey) に直線(枝)を描く
3. 深さが n>0 である限り,(1)(2) を繰り返す

さて、ここからアレンジを加えていくわけですが...

あ、そうだ。今は枝を直線で描いていますよね。
じゃあ、これを「直線じゃなくて三角形」で描くとどうなるんでしょう?
なんか面白い画ができそうだし、この方針でいきます。

...

ではまず、枝をどうやって三角形に変えるかを考えます。
直線の場合だと、一方の端点を根っこに、もう一方の端点から新しい枝を生やしてました。
これが三角形の場合、頂点が3個ありますから、ある1点を根っこに、残りの2点から新しい枝を生やすのはどうでしょう。

画像6


次に、新しい枝としての三角形をどう描くか考えます。
直線の場合だと、始点からある角度に向かって伸ばした点を求め、線を引いてました。
三角形なら、指定された角度から +d 度・-d 度の2点を求め、始点とその2点で三角形を描くようにしたら良さそうです。

画像7


よし、まずはこれをベースに作ってみましょうか。

function setup() { ... }

function draw() {
 randomSeed(seed);
 background(bgColor);
 
 drawBranch(300, 500, 120, 180, 10, 4);
}

/**
* 関数: 再帰的に枝を描画(三角形バージョン)
*/
function drawBranch(x, y, siz, ang, d, n) {
 /**
 * 入力,
 *   ...
 *   d: 三角形の角度幅
 *   ...
 */

 // 三角形の2頂点を求める
 let px1 = x + sin(ang + d) * siz, py1 = y + cos(ang + d) * siz;
 let px2 = x + sin(ang - d) * siz, py2 = y + cos(ang - d) * siz;
 let c = color(pal[n%4]);

 noStroke();  fill(c);
 triangle(x, y, px1, py1, px2, py2);  // 枝を描画
 
 if (n > 0) {
   drawBranch(px1, py1, siz * 0.85, ang + random(-40, 40), d * 0.95, n-1);
   drawBranch(px2, py2, siz * 0.85, ang + random(-40, 40), d * 0.95, n-1);
   drawBranch(px2, py2, siz * 0.85, ang + random(-40, 40), d * 0.95, n-1);
 }
}

画像8

おー!なんか面白そうな雰囲気が出てますね。

ちょっと重なりすぎてると見にくくなるので、枝の数を調整します。
ついでに、枝同士が接している点が見にくいので、その位置に円を描くようにしてみます。

function drawBranch(x, y, siz, ang, d, n) {
 ...
 
 /** 変更 **/
  if (n > 0) {
   // 左右対称な方向に伸ばす
   let d_ang = random(10, 50);
   drawBranch(px1, py1, siz * 0.85, ang + d_ang, d * 0.95, n-1);
   drawBranch(px2, py2, siz * 0.85, ang - d_ang, d * 0.95, n-1);
 }
 
 // 2頂点(枝同士の接点)に円を描く
 strokeWeight(siz * 3e-2);  stroke(bgColor);  fill(color(pal[n%4]));
 circle(px1, py1, siz * 0.15);
 circle(px2, py2, siz * 0.15);
}

画像9

だいぶキレイにまとまってきた...!ベースはこれで良さそうです。

...

さて、あとは全体構図の調整です。
まず、木が1本だけだとサミシイ...やはり木は生い茂ってなくては。
なので、増やしてみましょうか。横に並べたり、円形に配置してみます。

画像10

画像11

お、横並びもイイですが、円形の方が華やかですね...。枝が三角形だからでしょうか、角度基準で並べた方がバランス感が良さげです。
よし、今回は円形の配置を採用します!

...

次に、配色を考えましょう。
単色で塗りつぶしてもイイですし、輪郭だけでもイイですが...せっかくなら少しリッチにしたい。

なので、グラデーションで塗りつぶされた三角形を描く関数を作り、枝の描画をその関数で行うように変えます。
”グラデーションな三角形" は、色を少しずつ変化させた直線を重ねることで描けます。

/**
* 関数: グラデーションな三角形を描画
*/
function drawGradTriangle(x1, y1, x2, y2, x3, y3, c1, c2) {
 /**
 * 入力,
 *   x1, y1, x2, y2, x3, y3: 三角形の頂点座標
 *   c1, c2: グラデーションする2色
 */

 // 直線を重ねる
 for (let i = 0; i < 100; i++) {
   let px1 = map(i, 0, 100-1, x1, x2);
   let py1 = map(i, 0, 100-1, y1, y2);
   let px2 = map(i, 0, 100-1, x1, x3);
   let py2 = map(i, 0, 100-1, y1, y3);

   // 色を徐々に変化させる
   let rat = map(i, 0, 100-1, 0, 1);
   let c = lerpColor(c1, c2, rat);  c.setAlpha(80);

   strokeWeight(2);  stroke(c);
   line(px1, py1, px2, py2);
 }
}

枝の描画を "グラデーションな三角形” に変えると、こんな感じ。
ある2色を選び、一番深い層から先端まで変化するようにしてあげます。

画像12

わ...これはイイ感じ!放射状なのが相まって、キレイな結晶みたいに見えますね。

...

最後は、並べる木の数を色々と変えてみながら調整します。
今回は最終的に、大きい木と小さい木を交互に並べるようにしました。
また、配置の基準となっている円も描画するようにしました。

function draw() {
 randomSeed(seed);
 background(bgColor);
 
 let phase = random(360);
 
 // 配置の基準となっている円を描画
 strokeWeight(5);  stroke(pal[0]);  noFill();
 circle(300, 300, 50);
 
 // 大きい木を並べる
 for (let i = 0; i < 3; i++) {
   let ang = 360 / 3 * i + 60 + phase;
   drawBranch(sin(ang) * 25 + 300, cos(ang) * 25 + 300, 45, ang, 15, 6);
   circle(sin(ang) * 25 + 300, cos(ang) * 25 + 300, 10);
 }

 // 小さい木を並べる
 for (let i = 0; i < 3; i++) {
   let ang = 360 / 3 * i + phase;
   drawBranch(sin(ang) * 25 + 300, cos(ang) * 25 + 300, 15, ang, 15, 4);
   circle(sin(ang) * 25 + 300, cos(ang) * 25 + 300, 10);
 }
}

画像13

できたー!これで【#102 : Crystalized trees on planet】は完成です!

画像1

全体のコード

/**
* color pallet
*/
let pal = ["#1f2421","#216869","#49a078","#9cc5a1","#dce1de"];

let bgColor;
let seed;

function setup() {
 createCanvas(600, 600);
 angleMode(DEGREES);
 background(255);
 noLoop();

 let cid = int(random(pal.length));
 bgColor = pal[cid];
 pal.splice(cid, 1);
 pal = shuffle(pal);

 seed = random(1e+4);

 background(bgColor);
}

function draw() {
 randomSeed(seed);
 background(bgColor);
 
 let phase = random(360);

 // 配置の基準となっている円を描画
 strokeWeight(5);  stroke(pal[0]);  noFill();
 circle(300, 300, 50);

 // 大きい木を並べる
 for (let i = 0; i < 3; i++) {
   let ang = 360 / 3 * i + 60 + phase;
   drawBranch(sin(ang) * 25 + 300, cos(ang) * 25 + 300, 45, ang, 15, 6);
   circle(sin(ang) * 25 + 300, cos(ang) * 25 + 300, 10);
 }

 // 小さい木を並べる
 for (let i = 0; i < 3; i++) {
   let ang = 360 / 3 * i + phase;
   drawBranch(sin(ang) * 25 + 300, cos(ang) * 25 + 300, 15, ang, 15, 4);
   circle(sin(ang) * 25 + 300, cos(ang) * 25 + 300, 10);
 }
}

/**
* 関数: 再帰的に枝を描画(三角形バージョン)
*/
function drawBranch(x, y, siz, ang, d, n) {
 // 三角形の2頂点を求める
 let px1 = x + sin(ang + d) * siz, py1 = y + cos(ang + d) * siz;
 let px2 = x + sin(ang - d) * siz, py2 = y + cos(ang - d) * siz;

 // 枝を描画
 drawGradTriangle(x, y, px1, py1, px2, py2,
                  color(pal[(n+1)%4]), color(pal[n%4]));
 
 // 深さが0以上の場合,新たな枝を描画
 if (n > 0) {
   let d_ang = random(10, 50);
   drawBranch(px1, py1, siz * 0.85, ang + d_ang, d * 0.95, n-1);
   drawBranch(px2, py2, siz * 0.85, ang - d_ang, d * 0.95, n-1);
 }
 
 // 2頂点(枝同士の接点)に円を描く
 strokeWeight(siz * 4e-2);  stroke(bgColor);  fill(color(pal[n%4]));
 circle(px1, py1, siz * 0.15);
 circle(px2, py2, siz * 0.15);
}

/**
* 関数: グラデーションな三角形を描画
*/
function drawGradTriangle(x1, y1, x2, y2, x3, y3, c1, c2) {
 // 直線を重ねる
 for (let i = 0; i < 100; i++) {
   let px1 = map(i, 0, 100-1, x1, x2);
   let py1 = map(i, 0, 100-1, y1, y2);
   let px2 = map(i, 0, 100-1, x1, x3);
   let py2 = map(i, 0, 100-1, y1, y3);

   // 色を徐々に変化させる
   let rat = map(i, 0, 100-1, 0, 1);
   let c = lerpColor(c1, c2, rat);  c.setAlpha(80);

   strokeWeight(2);  stroke(c);
   line(px1, py1, px2, py2);
 }
}

画像2

おわりに

さて、作品の解説は以上になります。いかがだったでしょうか?
(ちゃんとご要望に応えられているか不安です...。)

今回、作品のタネ自体は非常にシンプルなので、明かしてしまえば特に詳しい解説もいらない程度だったかもしれないですね...。

なお、細かな工夫点については、あくまで自分流です。
人によって好きなやり方もあると思うので、ぜひ色々なやり方に変えてみてください。アレンジ作品お待ちしております🙇‍♂️

また今回は試しに、”制作プロセスをなぞる形式” での解説にしてみました。いかがでしたか?
前回の記事と同じく、こういうのは追体験してもらうのが一番だと思うので、ぜひ一度手を動かして作ってみてくださいね!

...

さて、次回も未定です...。
一応、今回のような解説系や、雑談系でも書こうかな〜と考えてます。

また今回のように、コメントでご要望等いただいたものについては、お応えしていきたいと考えています。
なので、また何かご要望があればコメントください。お待ちしております。

...

以上、ここまで読んでくださってありがとうございました!
ご指摘やコメント等、ぜひお待ちしております。

ではでは、ごきげんよう〜。

この記事が気に入ったらサポートをしてみませんか?