GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

snowflake generator 9種 — DLAで育つ雪結晶

snowflake generatorとは、雪片の絵を貼り付ける作業ではなく、粒子が結晶に触れて凍りつく規則を何千回も繰り返して形を育てる計算のことです。ライブラリ·WebGL なしで、外から飛んできた粒子1つが結晶に触れて付く瞬間を繰り返すだけで、六角の雪の結晶がひとりでに育ちます

デモ

自動再生 · タイルを押すとその項目へ · すべて1つのzip

付着確率ひとつで九枚の雪片を分けた順序

並び順の基準は派手さではなく付着確率が生む結晶の性格です。01の基本六角凝集はSTICK確率1.0で触れれば必ず付く原形で、02は先端ほど付きやすい偏りを持ち込んで樹枝状の枝を伸ばし、03は同じ規則のまま偏りの強さを変えて針とシダの間を行き来します。04は1セクタだけ計算して残りを回転コピーする対称の近道、05と06はそれぞれ温度場の縁凍結と年齢の彩色で「どこで」「いつ」付いたかを可視化へ進み、07は2つの種が競い合う相互成長、08は溶けと再凍結の呼吸、09は暗い舞台と被写界深度で締めくくります。snowflake generator onlineで検索してすぐ試せる9盤の中でも、この並びを貫くのは付着確率という1つの軸だけです。同じ乱数の種から山を盛り上げるノイズで山を作る9盤や模様がひとりでに育つチューリングパターンの9盤と同じく、複雑な形は1つの単純な規則の繰り返しから生まれます。

付着確率という1つの軸が9盤を貫く

01から09まで、粒子が結晶に付くかどうかを決めるstickProb(またはSTICK)という1つの値が、丸い塊になるか、鋭い枝になるか、針になるかを分けます。付着確率を先端ほど高くすると尖り、内側でも均等に付けば丸くなる——この1つのつまみの上に9つの見た目が並びます。

項目別コード

項目ごとに、成長を左右する定数の名前をそのまま示し、実際に動いているデモの関数を要約せずに引用します。省略した断片ではなく本物のコードなので、この画面のまま自分のページに貼って試せます。

01基本の六角凝集 — ランダムウォーク粒子が触れた場所に付くDLAひと層

結晶の種を1つ置き、外周から出た粒子が上下左右にランダムに歩き、結晶に触れた瞬間に付く規則を数千回繰り返すと、六角の尖ったクラスタが自ら育ちます。STICK確率を1.0に固定した最も単純な形で、24フレーム中23フレームが動き続け、累積面積は43.511%まで育ちます。

基本の六角凝集 — ランダムウォーク粒子が触れた場所に付くDLAひと層
新しいタブで開く

01 · 基本の六角凝集 — ランダムウォーク粒子が触れた場所に付くDLAひと層 — 計測 累積面積 43.511% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

触れたら必ず付くSTICK=1.0が丸い塊を作る

付着確率STICKを常に1.0にすると、粒子は結晶に触れた瞬間ためらわず凍りつくため、先端も側面も同じ確率で埋まり、輪郭の尖りが均されて丸みを帯びたクラスタになります。

  function step(t) {
    for (var s = 0; s < 20; s++) {          // フレームごとのステップ数 — 歩く速さ
      for (var w = walk.length - 1; w >= 0; w--) {
        var p = walk[w];
        if (rand() < 0.25) p[2] = Math.floor(rand() * 6);   // たまに方向を引き直す — 等方性
        var nb = nbr(p[0], p[1], p[2]);
        var i = nb[0], j = nb[1];
        if (!inGrid(i, j)) { walk.splice(w, 1); continue; }  // 格子の外に出たら捨てて撃ち直す
        if (rand() < 0.2) p[3] += (rand() - 0.5) * 0.7;
        p[0] = i; p[1] = j;
        if (touching(i, j)) {
          if (rand() < STICK) {              // 触れた — 確率でそのセルに凍りつく
            set(i, j, t);
            stuck++;
            var dc = Math.hypot(i - cx, (j - cy) * 0.92);
            if (dc + 2 > crystalR) crystalR = Math.min(N * 0.46, dc + 2);
          }
          walk.splice(w, 1);
        } else if (Math.hypot(i - cx, j - cy) > crystalR * 1.7 + 8) {
          walk.splice(w, 1);                 // 遠く離れすぎた — 戻ってくる見込みが薄い
        }
      }
    }
    while (walk.length < PARTICLES) launch();
  }

02樹枝状結晶(dendrite) — 6方向の先端が密に分岐する木の成長

付着確率を最外側の先端へ偏らせると、丸い塊ではなく本物の雪の結晶の枝(樹枝)が伸びていきます。TIP_BIAS=2.6で先端ほど付きやすくすると、累積面積は44.129%、強度平均は65.4まで上がります。

樹枝状結晶(dendrite) — 6方向の先端が密に分岐する木の成長
新しいタブで開く

02 · 樹枝状結晶(dendrite) — 6方向の先端が密に分岐する木の成長 — 計測 累積面積 44.129% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

TIP_BIAS指数が先端への偏りの強さを決める

stickProbは結晶の中心からの距離を0〜1に正規化したkのTIP_BIAS乗を主成分として返すため、指数を上げるほど中心付近より先端付近の付着確率が急激に高くなり、枝が伸びる方向に成長が集中します。

  function touching(i, j) {
    for (var k = 0; k < 6; k++) {
      var nb = nbr(i, j, k);
      if (inGrid(nb[0], nb[1]) && grid[gridIdx(nb[0], nb[1])]) return true;
    }
    return false;
  }

  // ── 先端加重の付着 — 半径が大きいほど(先端に近いほど)付着確率を高める ──
  function stickProb(i, j) {
    var dc = Math.hypot(i - cx, (j - cy) * 0.92);
    var k = Math.min(1, dc / Math.max(1, crystalR));
    return Math.pow(k, 1) * 0.15 + Math.pow(k, TIP_BIAS) * 0.85 + 0.05;
  }

03針・シダ結晶 — 湿度帯を変えると葉から針になる

同じ凝集規則に温度・湿度パラメータを1つ変えるだけで、広がるシダ型から細い針型へ結晶の形が分かれます——本物の雪の形成条件の地図です。BIAS_FERN=1.6とBIAS_NEEDLE=4.6という2つの偏りを切り替えるだけで、累積面積41.614%・強度平均75.9という9種の中で最も強いコントラストが出ます。

針・シダ結晶 — 湿度帯を変えると葉から針になる
新しいタブで開く

03 · 針・シダ結晶 — 湿度帯を変えると葉から針になる — 計測 累積面積 41.614% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

biasの値ひとつでシダと針を行き来する

stickProbのc.bias(偏りの指数)をBIAS_FERN寄りに下げると広い扇状のシダに、BIAS_NEEDLE寄りに上げると細く鋭い針に変わり、実際の雪結晶が気温と湿度で形を変える現象と同じ軸をなぞります。

  function touching(c, i, j) {
    for (var k = 0; k < 6; k++) {
      var nb = nbr(c, i, j, k);
      if (inGrid(c, nb[0], nb[1]) && c.grid[gridIdx(c, nb[0], nb[1])]) return true;
    }
    return false;
  }

  // 先端加重の付着 — biasが結晶の習性(幅)を決める
  function stickProb(c, i, j) {
    var dc = Math.hypot(i - c.cx, (j - c.cy) * 0.92);
    var k = Math.min(1, dc / Math.max(1, c.r));
    return Math.pow(k, 1) * 0.15 + Math.pow(k, c.bias) * 0.85 + 0.05;
  }

04六重対称ミラー — 60度ごとに折りたたみ6倍速で育つ

1つのセクタ(60度扇)だけシミュレートし残り5つを回転コピーすると、計算量は1/6で対称の狂わない完璧な結晶になります。SECTOR_DEG=60で計算し、累積面積は9種中5番目に大きい40.29%に達します。

六重対称ミラー — 60度ごとに折りたたみ6倍速で育つ
新しいタブで開く

04 · 六重対称ミラー — 60度ごとに折りたたみ6倍速で育つ — 計測 累積面積 40.29% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

rotateImageDatarequestAnimationFrame

fold関数が角度を60度幅に折りたたむ

foldThは入力角度をSECTOR(60度)の幅に収まるよう剰余で折り返し、foldはその角度と半径から占有セルを1回だけ調べるので、残り5コピーは計算ではなく描画時の回転コピーで済みます。

  function foldTh(th) {
    var P = SECTOR, m = (th + P * 0.5) % P;
    if (m < 0) m += P;
    return m - P * 0.5;
  }
  // 相対座標 → 折りたたんだ(角度, 半径)
  function fold(fx, fy) {
    var r = Math.sqrt(fx * fx + fy * fy);
    var th = r < 1e-9 ? 0 : foldTh(Math.atan2(fx, fy));
    return [th, r];
  }
  function cellOcc(fx, fy) {
    var fo = fold(fx, fy);
    if (fo[1] > N) return 0;
    var gx = AX + Math.round(Math.sin(fo[0]) * fo[1]);
    var gy = AY + Math.round(Math.cos(fo[0]) * fo[1]);
    if (gx < 1 || gx >= WS - 1 || gy < 1 || gy >= HS - 1) return 0;
    return occ[gy * WS + gx];
  }

05霜の降りた窓 — 縁から内側へ這うフラクタル氷

種を中央ではなく窓枠の縁全体に敷き、温度場を内側へ下げると、凝集が窓の霜のように縁から内側へ伸びていきます。COLD=0.34で内側ほど凍りにくくすると、累積面積45.515%・面積の最大値35.31という9種で最も広い1枚になります。

霜の降りた窓 — 縁から内側へ這うフラクタル氷
新しいタブで開く

05 · 霜の降りた窓 — 縁から内側へ這うフラクタル氷 — 計測 累積面積 45.515% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

resetSimが窓枠全体に種を敷き詰める

resetSimは四辺のセルすべてをoccに1として書き込んでから中央へ向かう歩行者を配置するため、結晶は1点からではなく窓の縁という線から同時多発的に育ち始めます。

  function resetSim() {
    occ.fill(0);
    for (var x = 0; x < GW; x++) { occ[idx(x, 0)] = 1; occ[idx(x, GH - 1)] = 1; }
    for (var y = 0; y < GH; y++) { occ[idx(0, y)] = 1; occ[idx(GW - 1, y)] = 1; }
    filled = 2 * (GW + GH) - 4;
    phase = 0; shineT = 0;
    walkers = [];
    for (var i = 0; i < WALKERS; i++) { var w = { gx: 1, gy: 1 }; respawn(w); walkers.push(w); }
  }

06結晶格子の彩色 — 各セルの年齢をパステルの輪に刻む

粒子が付いた瞬間の時刻を記録し年齢ごとに色を変えると、結晶の成長の年輪が木のようにパステルの同心円で読めます。付着順をage配列に記録するだけの軽い処理で、累積面積は9種で最小の9.325%ながら強度平均は73.3まで上がります。

結晶格子の彩色 — 各セルの年齢をパステルの輪に刻む
新しいタブで開く

06 · 結晶格子の彩色 — 各セルの年齢をパステルの輪に刻む — 計測 累積面積 9.325% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

age配列が付着順を年輪として記録する

stepWalkerは粒子が付着した瞬間にorder.push(i)でセルの通し番号を並べ、age[i] = order.lengthでその時点の順番をそのまま年齢として刻むので、後の描画は年齢を読むだけで成長の年輪を色分けできます。

  function stepWalker(w) {
    var d = (rand() * 4) | 0;
    var nx = w.gx + DX[d], ny = w.gy + DY[d];
    var dx = nx - CX, dy = ny - CY;
    if (dx * dx + dy * dy > N * N) { respawn(w); return false; }   // 格子の外 → 雲へ戻す
    w.gx = nx; w.gy = ny;
    var i = idx(nx, ny);
    if (occ[i]) return false;
    var nc = (occ[idx(nx - 1, ny)] ? 1 : 0) + (occ[idx(nx + 1, ny)] ? 1 : 0) +
              (occ[idx(nx, ny - 1)] ? 1 : 0) + (occ[idx(nx, ny + 1)] ? 1 : 0);
    if (!nc) return false;
    // 分岐確率 — 隣接が少ない外側ほどよく付く(DLA樹枝状の偏り)
    var p = nc === 1 ? 1.0 : nc === 2 ? 0.62 : nc === 3 ? 0.22 : 0.10;
    if (rand() >= p) return false;
    occ[i] = 1;
    order.push(i);
    age[i] = order.length;                  // 年齢 = 付着順 — 色の唯一の情報源
    respawn(w);
    return true;
  }

07ポインタの種 — クリックした場所で2つ目の結晶が一緒に育つ

クリック(タップ)した場所にもう1つ種を植えると、粒子が2つの結晶の間で競いながら凝集します——出会った瞬間、枝が絡み合う相互成長です。累積面積34.032%、強度平均56.9で、ポインタ操作がなければ自動追加された種で同じ相互成長を再現します。

ポインタの種 — クリックした場所で2つ目の結晶が一緒に育つ
新しいタブで開く

07 · ポインタの種 — クリックした場所で2つ目の結晶が一緒に育つ — 計測 累積面積 34.032% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointermoveImageDatarequestAnimationFrame

stickが接した結晶の所有者をセルへ書き込む

filledNbで6方向の隣接凍結セル数を数えてから、stickはownerにどちらの結晶が付けたかを記録するため、2つの種が伸びて出会う境界では互いの所有セルが入り組み、枝が絡み合う模様になります。

  function filledNb(x, y) {
    var nx = hexNbr(y), ny = hexNbrY(y), n = 0;
    for (var k = 0; k < 6; k++) {
      var ax = x + nx[k], ay = y + ny[k];
      if (inside(ax, ay) && tStick[idx(ax, ay)] >= 0) n++;
    }
    return n;
  }

  function stick(x, y, own, now) {
    var id = idx(x, y);
    tStick[id] = now; owner[id] = own;
    if (sparkles.length > 96) sparkles.shift();
    sparkles.push({ x: x * CELL + 1, y: y * CELL + 1, t: now });
  }

08溶けてまた凍る — 最外側の粒子を剥がして再付着させる循環

一定時間ごとに表面の粒子を確率的に剥がして周囲に放つと、古い核は残り縁だけが育ち直す、息をする結晶になります。CYCLE_MS=2000ごとにMELT_FRAC=0.5の周期で溶かし、累積面積37.253%まで育ちながら面積の最大値は0.452と9種で最も小さく保たれます。

溶けてまた凍る — 最外側の粒子を剥がして再付着させる循環
新しいタブで開く

08 · 溶けてまた凍る — 最外側の粒子を剥がして再付着させる循環 — 計測 累積面積 37.253% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

pCoreが古い核ほど溶けにくくする

meltStepは隣接凍結数nが少なく年齢が浅い表面セルほど高い確率pで剥がし、pCoreで年齢が古いコアほど確率を下げるため、中心の骨格は残ったまま縁だけが溶けては再び凍りつきます。

  function meltStep(heat, now) {
    var tries = Math.round(MELT_RATE * heat * 10);
    for (var i = 0; i < tries; i++) {
      var x = (rand() * GW) | 0, y = (rand() * GH) | 0;
      var id = idx(x, y);
      if (!frozen[id]) continue;
      var n = frozenNb(x, y);
      if (n >= 6) continue;                                  // 完全に埋まったセルは溶けない
      var age = now - tStick[id];
      var pCore = Math.max(0.02, 1 - age / 5200);            // 古いコアほど溶ける確率が低い
      var p = (n / 6) * heat * pCore * 0.5;
      if (rand() < p) {
        frozen[id] = 0;
        var ang = rand() * Math.PI * 2, sp = 0.25 + rand() * 0.8;
        drifters.push({
          x: x * CELL + 1, y: y * CELL + 1,
          vx: Math.cos(ang) * sp, vy: Math.sin(ang) * sp,
          born: now, hot: true
        });
      }
    }
  }

09マクロ写真シーン — 暗い舞台に浅い被写界深度で浮かぶ結晶

黒い背景に中央照明(放射グラデーション)を敷き結晶を大きく育てると、顕微鏡写真のような1枚になります——残光と浮遊粒子で画面が動き続けます。DOF_CELLS=26を境に鮮明レイヤーとぼかしレイヤーへ分け、累積面積36.536%、強度平均38.3で9種の中では2番目に落ち着いた明るさです。

マクロ写真シーン — 暗い舞台に浅い被写界深度で浮かぶ結晶
新しいタブで開く

09 · マクロ写真シーン — 暗い舞台に浅い被写界深度で浮かぶ結晶 — 計測 累積面積 36.536% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

createRadialGradientImageDatarequestAnimationFrame

distC<=DOF_CELLSが鮮明層とぼかし層を分ける

repaintGridsはセルの中心からの距離distCがDOF_CELLS以下なら鮮明レイヤーに、それを超えればぼかしレイヤーへ同じ色を書き込むので、後段のフィルタは焦点内と焦点外を別々のキャンバスとしてぼかすだけで済みます。

    for (var y = 0; y < GH; y++) {
      for (var x = 0; x < GW; x++) {
        var id = y * GW + x;
        var o = id * 4;
        if (!frozen[id]) { pxS[o + 3] = 0; pxF[o + 3] = 0; continue; }
        var age = now - tStick[id];
        var k = Math.min(1, age / 850);
        // 結晶軸の向きによる淡い色の分散 — 顕微鏡偏光風のパステル
        var ang = Math.atan2(y - CY, x - CX);
        var tint = (ang > -0.5 && ang <= 0.5) || (ang > Math.PI - 0.5 || ang <= -Math.PI + 0.5) ? C_LILAC
                 : ang > 0.5 && ang <= 2.1 ? C_MINT : C_PINK;
        var lift = frozenNb(x, y) <= 2 ? 24 : 0;
        var R = Math.min(255, C_CORE[0] + (tint[0] - C_CORE[0]) * k * 0.55 + lift);
        var G = Math.min(255, C_CORE[1] + (tint[1] - C_CORE[1]) * k * 0.55 + lift);
        var B = Math.min(255, C_CORE[2] + (tint[2] - C_CORE[2]) * k * 0.55 + lift);
        pxS[o] = R; pxS[o + 1] = G; pxS[o + 2] = B; pxS[o + 3] = 255;
        // 焦点面の内側 = 鮮明レイヤー、外側 = ソフトレイヤーへ振り分ける
        var dof = distC[id] <= DOF_CELLS;
        if (dof) { pxF[o + 3] = 0; }
        else {
          pxF[o] = R; pxF[o + 1] = G; pxF[o + 2] = B; pxF[o + 3] = 255;
          pxS[o + 3] = 0;
        }
      }
    }

写真調のヒーローとクリックで育つ結晶 — この9枚を実際どこで使うか

冬季キャンペーンや雪テーマのランディングページの表紙なら09 マクロ写真シーンが一番早い方法です。暗い舞台に浅い被写界深度で浮かぶ結晶一枚がすでに写真のように完成しているので、見出しを重ねるだけで済みます。訪問者に直接クリックしてほしいヒーロー領域には07 ポインタの種を置くと、クリックした場所で2つ目の結晶が競うように育つ反応がそのまま体験になります。

湿度と結晶の形の関係を説明する科学コンテンツには03 針・シダ結晶が向いています。湿度帯の値1つを動かすだけで葉の形から針の形へ移る過程が、別のグラフなしで画面上に見えます。3枚ともファイル1つで完結しているので、追加のフレームワークなしで静的セクションにそのまま組み込めます。

雪の結晶はなぜDLAで育つか — ランダムウォーク凝集と六角対称の計算

ランダムウォークは長さより衝突の回数が形を決める

粒子が結晶に触れるまで歩く距離そのものは形に関係せず、触れた瞬間に何%の確率で凍りつくかという1つの数だけが、丸い塊になるか枝分かれした樹枝になるかを分けます。

6方向の隣接判定が正六角形の骨格を作る

01・02・03・07・08・09は上下左右4方向ではなく6方向の隣接セルを調べるnbr(またはhexNbr・frozenNb)関数を使うことで、格子の歩行がそのまま正六角形の対称を持ち、四角格子を60度回転で近似する方法より結晶の角が自然に尖ります。04は同じ六方向の対称を保ったまま1セクタだけ計算して折りたたみ、05・06はあえて正方格子の4方向隣接を使うことで霜や年輪のような結晶らしくない不揃いな質感を出しています。snowflake generator svgの中にはベクターパスとして結晶を書き出すものもありますが、ここではセルの格子(ピクセル)を直接塗るラスター計算なので、境界の滑らかさより粒子1つ1つの凝集そのものを目で追えます。

付着確率という1つのつまみが9つの性格を生む

STICK・TIP_BIAS・BIAS_FERN・BIAS_NEEDLE・COLDという名前は違っても、どれも「触れた瞬間に何%で凍るか」という同じ関数の引数で、この1つの軸を動かすだけで01から09までの見た目が変わります。

セクタ折りたたみが六角対称の計算量を1/6にする

60度のセクタ1つだけをシミュレートして残りを回転コピーする04の手法は、対称性がある形を計算するときの一般的な近道で、フルの格子を6回計算するより速く、しかも対称が数値誤差でずれない利点があります。

湿度帯と鏡映数を変える3つの変種とreduced-motion

定数ひとつで変わる3つのバリエーション

名前 変える値 手触り
枝分かれの確率 02 樹枝状結晶 分岐確率 0.3 → 0.6 枝が密になるほど本物の雪結晶に近いレース模様になる
凝集の速さ 01 基本の六角凝集 粒子220個/秒 → 700個/秒 速度を上げると結晶が目に見えて速く育ちデモが短くなる
対称の折りたたみ角 04 六重対称ミラー セクタ角60度 → 30度 セクタを半分にすると枝がさらに細く精巧になる

動きを減らす設定で残る静止フレーム

動きを減らす設定(prefers-reduced-motion: reduce)が検知されたとき、9盤すべてがループを止め、.is-stillクラスを付けてその盤が最も読める一場面だけを計算し直します。01・02・03は粒子が半分ほど付いた雪片の途中経過を、04は1セクタの計算が済んだ直後の対称形を、07は2つの結晶が触れ合う瞬間を、09は被写界深度が定まった1枚を静止画として描きます。

9つの全盤がタブで焦点を受け取る

9盤ともrole="application"とtabindex="0"を持つので、タブキーで焦点を移して盤に入れます。ポインター入力を受け取るのは07だけで、クリックした場所にもう1つの種を植える反応が盤の主題なので、動きを減らす設定の利用者でも操作した瞬間の凝集を計算し直して描きます。

四角格子のまま60度回転しても六角形にならない落とし穴

四角格子に60度回転を足すだけでは六角にならない

隣接判定を4方向のまま60度だけ回転させても、セルの並び自体は四角格子のままなので、育つ結晶は角が微妙にずれた擬似六角形になります。nbr関数で本物の6方向テーブルを使うのが唯一の回避策です。

STICKを1.0に固定したまま先端を尖らせようとすると詰まる

01のようにSTICKが常に1.0だと、先端だけでなく側面のあらゆるセルが同じ確率で埋まるため、TIP_BIASのような先端偏重の項を別に足さない限り、いくら粒子を増やしても丸いまま尖りません。

溶けと再凍結を自分の値で試すにはzipを開く

08のMELT_RATEやpCoreの重みを自分の値で試したくなったら、9つのソース一式が入ったzipを開くことになり、解凍のパスワードはfcyyjg6vです。

雪結晶DLAを組み込む前によくある質問

snowflake generatorはどうやって六角形の対称を保っていますか?

01・02・03・07・08・09は6方向の隣接テーブル(nbr・hexNbr・frozenNb)を使って格子上を歩かせているため、四角格子を回転させる近似と違い、粒子がどの方向に歩いても正六角形の対称が数値的に崩れません。04はその六方向対称を保ったまま1セクタだけ計算して折りたたみ、霜や年輪を描く05・06はあえて正方格子の4方向隣接で不揃いな質感を選んでいます。

9枚とも同じ乱数の種で毎回同じ結晶になりますか?

はい。mulberry32という1つの関数を各盤ごとに固定シードで初期化しているため、リロードしても同じ手順で同じ結晶が育ちます。粒子の数や確率の定数を変えれば別の結晶になります。

snowflake generator stlのように立体データを出力できますか?

このデモはcanvasの2Dピクセルに凝集した結果を描くだけで、3Dプリンタ向けのSTLのような立体データは生成しません。格子上のオン/オフ配列をそのまま読み取れば、自分でSTLへ変換する土台には使えます。

もっと読む

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。