雰囲気のあるサークル・パッキングを作るテクニック

2021年6月25日金曜日

p5.js サークル・パッキング テクニック 静止画 中級者向け

t f B! P L

👉 Read this article in English.

ノイズ・フィールド上のサークル・パッキング

印象の異なるサークル・パッキング

おかずさんのこのツイートを見て、先日作った作品 'Under the Waterfall' でのサークル・パッキングのことを思い出しました。


これがその作品です。この2つ、似てるけどちょっと受ける印象が違いませんか?

サークル・パッキングを利用したアート例 サークル・パッキングを利用したアート例


サークル・パッキングとは?

サークル・パッキングとは「パッキング問題」の一種で、ある領域に円を充填するというものです。

パッキング問題:Wikipedia

クリエイティブ・コーディングでサークル・パッキングというと、厳密に数学的問題として解くよりも、ほどほどに充填された結果を生むアルゴリズムを実装することが多いように思います。

そのアルゴリズムとして、円の配置と成長を繰り返すというものがあります。

  1. 領域内にランダムな位置を取り、そこに既に円がなければ小さな円を置く。(配置)
  2. 領域内のすべての円に対して下記を実行
  3. 他の円と接していなければちょっと大きくする。(成長)
  4. 1. へ戻る

シフマン先生のコーディング・チャレンジでも詳しい解説と具体例が出ています。


成長無しで配置するだけでもそれなりに充填されます。


【こんなのも】
円じゃなくて、不定形のパッキングを takawo shunsuke(@takawo)さんがやってらっしゃいました。これすごい!

元となった Jeff++(@ippsketch)さんの "Non Circular Packing" の解説記事


成長なしで配置するだけのサークル・パッキングのコード例がこちら。
コードは p5.js です。


const w = 640;
const h = 640;
const num = 2000; // 試行回数

function setup() {
  createCanvas(w, h);

  // サークル・パッキング
  const circles = getRandomCircles(num, w * 0.4, h * 0.4);

  // 描画
  translate(w * 0.5, h * 0.5);
  background(100);
  circles.forEach((c) => circle(c.x, c.y, c.z));
}

function getRandomCircles(_num, _w, _h) {
  let circles = [];
  for (let i = 0; i < _num; i++) {
    let x = random(-1, 1) * _w;
    let y = random(-1, 1) * _h;
    let z = random(10, 50); // z軸の値を円の大きさとして使用
    if (circles.every((c) => dist(x, y, c.x, c.y) > (z + c.z) * 0.5)) {
      circles.push(createVector(x, y, z));
    }
  }
  return circles;
}

成長なしだと、試行回数が少ない場合、だいぶ隙間が空きます。

試行回数を多くするとそれらしくなります。


グリッド配置のサークル・パッキング

先の例では配置位置を完全ランダムにしていました。

let x = random(-1, 1) * _w;
let y = random(-1, 1) * _h;

これをグリッド上のどこかランダムというコードにしてみます。

const gridDiv = 15;
let x = floor(random(-gridDiv, gridDiv)) / gridDiv * _w;
let y = floor(random(-gridDiv, gridDiv)) / gridDiv * _h;

最初に紹介した印象の違いは完全ランダムか、グリッド上のランダムかの違いでした。


グリッドの間隔や円の大きさでも雰囲気が変わります。

サークル・パッキングが無い例10x10のグリッド、円の大きさに差がない例。
サークル・パッキング感が小さくなります。

グリッド感が無い例60x60のグリッドになるとグリッド感が無くなってきます。

いろんな配置でサークル・パッキング

同じような考えでグリッド配置の他にもいろいろできます。

let rnd = random(-1, 1);
let x = rnd * _w;
let y = rnd * _h;

let rnd = random(TWO_PI);
let x = cos(rnd) * _w;
let y = sin(rnd) * _h;

for (let i = 0; i < _num; i++) {
  let rnd = 3 * TWO_PI * i / _num;
  let x = cos(rnd) * _w * i / _num;
  let y = sin(rnd) * _h * i / _num;

作例

ノイズ・フィールド(ベクター・フィールド、フロー・フィールドとも)上でサークル・パッキングしてみた例です。

ノイズ・フィールド上でサークル・パッキング

こちらはノイズ・フィールドを線で描画したもの。

ノイズ・フィールド

両方合わせてもいいですね。

ノイズ・フィールドの線とサークル・パッキング

作例の p5.js でのサンプルコード

GPL で公開します。どうぞご自由にお使いください。このコードを利用して何か作品を作ってもらえるととても嬉しいです。


const w = 570;
const h = 570;

function setup() {
  createCanvas(w, h);
  translate(w * 0.5, h * 0.5);
  background(100);

  // サークル・パッキングと同時にノイズ・フィールド描画
  noFill();
  stroke(250);
  strokeWeight(3);
  const circles = getNoiseField(100, w * 0.4, h * 0.4, min(w, h) * 0.15, 5);

  // 円の描画
  fill(250);
  stroke(0);
  strokeWeight(1);
  circles.forEach((c) => {
    circle(c.x, c.y, c.z);
    if (c.z > 10) {
      circle(c.x, c.y, c.z * 0.5);
    }
  });
}

function getNoiseField(_num, _w, _h, _iDiv, _step) {
  const circles = [];
  for (let iX = -_w; iX < _w; iX += _iDiv) {
    for (let iY = -_h; iY < _h; iY += _iDiv) {
      let x = iX;
      let y = iY;
      beginShape();
      for (let i = 0; i < _num; i++) {
        let nVal = noise(x * 0.001, y * 0.001) * 2;
        x += _step * cos(nVal * TWO_PI);
        y += _step * sin(nVal * TWO_PI);
        vertex(x, y);
        let z = random(_step, _step * 5);
        if (circles.every((c) => dist(x, y, c.x, c.y) > (z + c.z) * 0.5)) {
          circles.push(createVector(x, y, z));
        }
      }
      endShape();
    }
  }
  return circles;
}

/*
Copyright (C) 2021- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/


「// サークル・パッキングと同時にノイズ・フィールド描画」のところはちょっとダサくて、circles を2次元配列にしてフロー一本ごとのサークル・パッキング結果を取得できるようにすると、サークル・パッキングと描画を完全に分けられて綺麗に収まると思います。

でもそれは、(やるのが面倒くさいから)皆さんの課題にとっておきましょう。なんつって。😌

QooQ