fractal snow フラクタルな図形の描画

カレンダー通りの勤務をされている皆様、お疲れ様でございます。穏やかな春の昼下がり、こもってPCとごにょごにょしている場合ではない様な気持ちのいい天気です。話は全く関連しませんが、皆様、Adobe Exchangeをご覧頂いたことはありますか?CS6のパネルからアクセスしないといけないので少々敷居は高いですが、ちょろちょろっとアイテムも増えてきています。しかしながらAI、おまえはオブジェクトモデルが中途半端でなにをするにしてもいちいち引っかかるよね。そんなだからパネルも作ってもらえないんだよ。QRコードメーカーが一番人気ってのは他に落とすものが無いという意味です。Adobe社におかれましては、そこのところ踏まえた上で更なる精進を期待したい次第でございます。
あ、CS5とか下位バージョンの方は当ブログよりダウンロード出来ます。コードジェネレーター系のパネルはまともな代物なので是非ご利用下さい。
本題です。今回はちょちょっとイラストレータがらみの小ネタを落としておきますね。
フラクタルな図形を見てて、ああ、こいつらってみんな再帰だよな…って思いました。少し調べてみるとやっぱり再帰なんですね。で、適当にコードを起こしてAIで描画できるようにしてみました。

ベースになるのはこんな変形です。

ご覧のように直線が凹んでいる状態です。これを多角形の辺に適用します。

5角形なんですが、こんな感じに変形します。更に内包する直線全てに適用します。

だんだんと雪の結晶の様に変形してきました。

どんどん階層を掘り下げます。

どんどん…

最後に細部を拡大したものを

こんな感じですね。親の形状を子・孫と継承しているのがお分かり頂けるでしょう。
コードを見てみましょう。

//preprocess
var dc =app.documents.add();clr = new CMYKColor;
clr.cyan = 0;
clr.magenta = 0;
clr.yellow = 0;
clr.black = 100;

var dp = 4; //recursive depth
var apex = 5; //base apex

//get points
var pt = new Array();
var x=250;
var y = 250;
var r = 200;
var step = 360 / apex;
for (var i=0;i<360;i=i+step){
    pt.push([Math.sin(i / 180 * Math.PI) * r + x,
                Math.cos(i / 180 * Math.PI) * r + y]);
    }

//open new document and set some parameters
var ln = app.activeDocument.pathItems.add();
ln.stroke = true;
ln.strokeColor = clr;
ln.strokeWidth = 0.3;
ln.filled =false;

var p = ln.pathPoints.add(); //start point
p.anchor = pt[0];
p.leftDirection = p.anchor;
p.rightDirection = p.anchor;

//apply fractal function to each side of polygon
for (i=0;i<pt.length;i++){
    if (i==pt.length-1){
        fractal(pt[i], pt[0], dp);
        }else{
            fractal(pt[i], pt[i+1], dp);
            }
    }

//postprocess
ln.closed = true;

//functions
function fractal(a, b, dpth){
    if (dpth==0) {
        addPoint(b);
        return;
        }
    var c = dv(ad(mlt(a, 2), b), 3);
    var d = dv(ad(mlt(b, 2), a), 3);
    var f = dv(ad(a, b), 2);

    var v0 = dv(sb(f, a), len(f, a));
    var v1 = [v0[1], -v0[0]];

    //var e = sb(f, mlt(v1, Math.sqrt(3) / 6 * len(b, a)));
    var e = ad(f, mlt(v1, Math.sqrt(3) / 6 * len(b, a)));

    fractal(a, c, dpth – 1);
    fractal(c, e, dpth – 1);
    fractal(e, d, dpth – 1);
    fractal(d, b, dpth – 1);
    }

function mlt(v, n){
    return [v[0]*n, v[1]*n];
    }

function dv(v, n){
    return [v[0]/n, v[1]/n];
    }
 
function ad(a, b){
    return [a[0]+b[0], a[1]+b[1]];
    }

function sb(a, b){
    return [a[0]-b[0], a[1]-b[1]];
    }

function len(a, b){
    return Math.sqrt(Math.pow(a[0] – b[0], 2)
                + Math.pow(a[1] – b[1], 2));
    }

function addPoint(p){
    var pts = ln.pathPoints.add();
    pts.anchor = p;
    pts.leftDirection = pts.anchor;
    pts.rightDirection = pts.anchor;
    }<pt.length;i++){ if="" (i="=pt.length-1){" fractal(pt[i],="" pt[0],="" dp);="" }else{="" pt[i+1],="" }="" postprocess="" ln.closed="true;" functions="" function="" fractal(a,="" b,="" dpth){="" (dpth="=0)" {="" addpoint(b);="" return;="" var="" c="dv(ad(mlt(a," 2),="" b),="" 3);="" d="dv(ad(mlt(b," a),="" f="dv(ad(a," 2);="" v0="dv(sb(f," len(f,="" a));="" v1="[v0[1]," -v0[0]];="" e="sb(f," mlt(v1,="" math.sqrt(3)="" 6="" *="" len(b,="" a)));="" c,="" dpth="" -="" 1);="" fractal(c,="" e,="" fractal(e,="" d,="" fractal(d,="" mlt(v,="" n){="" return="" [v[0]*n,="" v[1]*n];="" dv(v,="" [v[0]="" n,="" v[1]="" n];="" ad(a,="" b){="" [a[0]+b[0],="" a[1]+b[1]];="" sb(a,="" [a[0]-b[0],="" a[1]-b[1]];="" len(a,="" math.sqrt(math.pow(a[0]="" b[0],="" 2)="" +="" math.pow(a[1]="" b[1],="" 2));="" addpoint(p){="" pts="ln.pathPoints.add();" pts.anchor="p;" pts.leftdirection="pts.anchor;" pts.rightdirection="pts.anchor;" }

とりあえず全体像なのですが、要点だけ解説しておきましょう。

var dp = 4; //recursive depth
var apex = 5; //base apex

//get points
var pt = new Array();
var x=250;
var y = 250;
var r = 200;
var step = 360 / apex;
for (var i=0;i<360;i=i+step){
    pt.push([Math.sin(i / 180 * Math.PI) * r + x,
                Math.cos(i / 180 * Math.PI) * r + y]);
    }

dpは再帰の深さ、ようするにループの回数です。apexは多角形の頂点です。
半径rの仮想円周上に並ぶ頂点の座標をpt配列に収容します。

//apply fractal function to each side of polygon
for (i=0;i<pt.length;i++){
    if (i==pt.length-1){
        fractal(pt[i], pt[0], dp);
        }else{
            fractal(pt[i], pt[i+1], dp);
            }
    }<pt.length;i++){ if="" (i="=pt.length-1){" fractal(pt[i],="" pt[0],="" dp);="" }else{="" pt[i+1],="" }="" }

前述の頂点を結ぶ辺に対してファンクションを適用します。

function fractal(a, b, dpth){
    if (dpth==0) {
        addPoint(b);
        return;
        }
    var c = dv(ad(mlt(a, 2), b), 3);
    var d = dv(ad(mlt(b, 2), a), 3);
    var f = dv(ad(a, b), 2);

    var v0 = dv(sb(f, a), len(f, a));
    var v1 = [v0[1], -v0[0]];

    //var e = sb(f, mlt(v1, Math.sqrt(3) / 6 * len(b, a)));
        var e = ad(f, mlt(v1, Math.sqrt(3) / 6 * len(b, a)));

    fractal(a, c, dpth – 1);
    fractal(c, e, dpth – 1);
    fractal(e, d, dpth – 1);
    fractal(d, b, dpth – 1);
    }

こちらのファンクションは基本的にアンカーを追加してごにょごにょ計算の上で直線をとげとげに変身させるものです。線分が4つ発生しますので、それぞれに対して再帰的に関数を適用します。規定の深度までループするとアンカーポイントのプロットを行います。

ten_a

Graphic Designer, Scripter and Coder. Adobe Community Professional.

シェアする