👉 Read this article in English.
印象の異なるサークル・パッキング
おかずさんのこのツイートを見て、先日作った作品 'Under the Waterfall' でのサークル・パッキングのことを思い出しました。
みちみち#p5js #creativecoding #generativehttps://t.co/I7EqSB74Th pic.twitter.com/4FyAgW3TMP
— おかず Okazz (@okazz_) June 21, 2021
これがその作品です。この2つ、似てるけどちょっと受ける印象が違いませんか?
サークル・パッキングとは?
サークル・パッキングとは「パッキング問題」の一種で、ある領域に円を充填するというものです。
パッキング問題:Wikipedia
クリエイティブ・コーディングでサークル・パッキングというと、厳密に数学的問題として解くよりも、ほどほどに充填された結果を生むアルゴリズムを実装することが多いように思います。
そのアルゴリズムとして、円の配置と成長を繰り返すというものがあります。
- 領域内にランダムな位置を取り、そこに既に円がなければ小さな円を置く。(配置)
- 領域内のすべての円に対して下記を実行
- 他の円と接していなければちょっと大きくする。(成長)
- 1. へ戻る
シフマン先生のコーディング・チャレンジでも詳しい解説と具体例が出ています。
成長無しで配置するだけでもそれなりに充填されます。
【こんなのも】
円じゃなくて、不定形のパッキングを takawo shunsuke(@takawo)さんがやってらっしゃいました。これすごい!画面内の領域に重ならないようにグラフィックを配置していく.https://t.co/7Fz4pqjehH#p5js #creativecoding #dailycoding pic.twitter.com/MS95b0v8BP
— takawo shunsuke (@takawo) May 16, 2021
成長なしで配置するだけのサークル・パッキングのコード例がこちら。
コードは 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;
最初に紹介した印象の違いは完全ランダムか、グリッド上のランダムかの違いでした。
グリッドの間隔や円の大きさでも雰囲気が変わります。
いろんな配置でサークル・パッキング
同じような考えでグリッド配置の他にもいろいろできます。
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 で公開します。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次元配列にしてフロー一本ごとのサークル・パッキング結果を取得できるようにすると、サークル・パッキングと描画を完全に分けられて綺麗に収まると思います。
でもそれは、(やるのが面倒くさいから)皆さんの課題にとっておきましょう。なんつって。😌