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

ローレンツアトラクター 9種 — 蝶の形のカオス軌道を1歩ずつ数値積分して積む

3つの微分方程式を1歩ずつ積分すると、蝶の形をした軌道がひとりでに積み上がります。ライブラリなしのコピペで持ち帰れるカオス可視化です。ローレンツアトラクターとは、蝶の絵を持ってきた画像ではなく、速度式3行に点の位置を従わせた計算の結果です。

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

デモ

九つの盤はそれぞれ index.html と style.css の2ファイルで自立し、開いた瞬間から動きます。24フレームの録画区間で九つのうち八つが23フレーム動き続け、04だけ19フレームです。04は3つの色チャンネルを順番に現す構造なので、チャンネルの切れ目に静止するフレームがあるのが正常です。08でポインタを動かすとσとρが変わって蝶の形がその場で生まれ変わります。

項目 焦点 累積面積(%) フレーム最大(%) 強度平均 フレーム
01 蝶の基本 3つの速度式をオイラー法で1歩ずつ積む基本画面 4.882 3.886 51.6 23/24
02 3D回転投影像 斜め投影で蝶が立体に回る最大面積の盤 28.715 11.488 102.4 23/24
03 RK4積分 オイラーと4次ルンゲ・クッタの並走比較 11.656 10.342 51.9 23/24
04 アトラクター図鑑 クリフォード・ド・ジョンの2次元漸化式6組 8.677 8.644 15.5 19/24
05 アイザワ・トーマス 球面と糸巻き螺旋、別の方程式との比較 0.768 0.444 60.7 23/24
06 密度の雲 2,000点の訪問回数を霧のように積む 1.04 0.934 72.7 23/24
07 ポアンカレ断面 z=27 を横切る瞬間だけに点を打つ地図 3.425 2.651 25.9 23/24
08 パラメータのつまみ ポインタでσ・ρをひねる係数探検 9.233 5.117 37.6 23/24
09 蝶のヒーローシーン 密度の雲に銀河の腕と文字を重ねた表紙 10.849 3.231 67.5 23/24

累積面積が最も広いのは02の28.715%で、強度平均102.4も九つで最も明るく、03の11.656%と09の10.849%が続きます。逆に05の0.768%・06の1.04%・07の3.425%が小さいのは欠陥ではなく素材の性質です。05は細い軌道線1本で形を見せ、06は訪問回数を霧のように薄く積むヒストグラム、07は断面を横切る瞬間だけに点を打つ点の雲なので、もともと覆う面積が小さく、線を太くして数値を膨らませると各項目の正体が消えます。01の4.882%も軌道線が1本ずつ積まれる画面として同じ性質です。

オイラー積分一枚からローレンツの蝶を九層で読み直す順序

並び順の基準は派手さではなく、同じ3行の式をどこまで信じられるかです。01はオイラー法の1歩で蝶を積む最小構成、02はその軌道を回して奥行きを読める形に引き伸ばします。03で初めて積分器そのものを疑い、04と05は式を入れ替えて漸化式と別の微分方程式の世界を見せます。06は1点の軌道を2,000点の統計に読み替え、07は3次元の流れを2次元の地図へ折りたたみ、08で初めて手で係数をひねります。09がその計算画面に文字とビネットを重ねてサービスの表紙へ組み立てて締めくくります。重力で星同士が引く銀河のn体シミュレーションや反復で境界を掘るマンデルブロのズームと違うのは、この9種がすべてたった1点の連続時間を積んでいる点です。

項目別コード

ここからは各盤の定数と、動いている関数をそのまま引用します。切り詰めた断片ではなく動いているコードそのものなので、この画面から自分のページへ貼って動かせます。コメントだけこの記事の言葉に置き換えてあり、変数名と関数名はzipの中身と同じ英語のままです。

01蝶の基本 — 3つの微分方程式をオイラー法で1歩ずつ

速度3つで位置を毎フレーム少しずつ動かして線を結ぶと、2つの羽を行き来する蝶がひとりでに積み上がります。オイラー法 dt 0.01 で3,000歩を前もって積分しておき、2秒周期で軌道を順番に公開します。公開が終わる位相0.82から画面がゆっくり消え、ループがつながります。累積面積4.882%は軌道線1本が積まれる画面として妥当な値です。

蝶の基本 — 3つの微分方程式をオイラー法で1歩ずつ
新しいタブで開く

01 · 蝶の基本 — 3つの微分方程式をオイラー法で1歩ずつ — 計測 累積面積 4.882% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CanvasRenderingContext2DrequestAnimationFramelineTo

3行の速度式と過渡のスキップ

deriv がローレンツ系の核心で、返り値はその瞬間の速度 dx・dy・dz です。出発点 (0.6, 0.7, 21) からアトラクターに乗るまでの200歩を捨ててから記録を始めるのが、絵をにじませない唯一の調整です。

  function deriv(x, y, z) {
    return [SIG * (y - x), x * (RHO - z) - y, x * y - BETA * z];
  }

  var xs = new Float32Array(STEPS), ys = new Float32Array(STEPS), zs = new Float32Array(STEPS);
  var x = 0.6, y = 0.7, z = 21, i;
  for (i = 0; i < 200; i++) {
    var d0 = deriv(x, y, z);
    x += d0[0] * DT; y += d0[1] * DT; z += d0[2] * DT;
  }
  for (i = 0; i < STEPS; i++) {
    xs[i] = x; ys[i] = y; zs[i] = z;
    var d1 = deriv(x, y, z);
    x += d1[0] * DT; y += d1[1] * DT; z += d1[2] * DT;
  }

区間ごとの平均yで奥行きを色づける

軌道を24区間に割り、区間ごとの平均 y を (y+27)/57 で正規化してアルファと明るさに乗せています。y は観察方向の座標なので、遠い区間は薄く・近い区間は濃く染まり、線1本の絵に奥行きの筋が入ります。

023D回転投影像 — 軌道を斜め投影でくるくる回す

xyz を回転角で画面座標に落とすと、蝶が立体で回って奥行きを感じます。高速の z 軸回転(2秒周期)の後に、14秒周期で0.10〜1.00rad を往復する視線の傾きで z を横にも混ぜる斜め投影です。横成分が2つの軸にまたがるので、どの角度でも軌道が糸1本に崩れません。累積面積28.715%・強度平均102.4は九つで最も広く明るい結果です。

3D回転投影像 — 軌道を斜め投影でくるくる回す
新しいタブで開く

02 · 3D回転投影像 — 軌道を斜め投影でくるくる回す — 計測 累積面積 28.715% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFrameMath.sinMath.cos

異方性スケールと斜め投影の核心

キャンバスは2.4:1の横長なのにアトラクターは1:1に近く、一辺 min(W, H) だけで括ると横の半分以上が空きます。横を W/57・縦を H/37.5 と別々に割って両辺を埋めるのが、この項目の面積の鍵です。

    var a1 = tSec / SPIN_S * 6.2832;    // z軸回転角 — 蝶が回る速い軸(2秒)
    var ca1 = Math.cos(a1), sa1 = Math.sin(a1);
    var ca2 = Math.cos(a2), sa2 = Math.sin(a2);

    var sx = W / 57, sy = H / 37.5;
    var ox = W / 2, oy = H / 2;

    // 斜め投影 — z軸回転(u, w)の後、傾き a2 で z を横にも混ぜる。
    // 横成分が2軸にまたがるので、どの回転角でも軌道が糸1本に崩れない。
    function pr(i) {
      var u = xs[i] * ca1 - ys[i] * sa1;
      var w = xs[i] * sa1 + ys[i] * ca1;
      var zv = zs[i] - 25;
      return [ox + (u * ca2 + zv * sa2) * sx, oy - (zv * ca2 * 0.66 + w * 0.40) * sy];
    }

遅い傾き軸が0を通らない理由

傾き角 a2 は正弦で0.10〜1.00rad を往復し、0 を通りません。傾きがちょうど0の瞬間は z が横に混ざらず、軌道が平面に潰れて糸1本に見えるからです。

03RK4積分 — オイラーから4次ルンゲ・クッタへ精度を上げる

1歩を4回の傾き平均で計算すると、オイラーよりはるかに正確で長く崩れない軌道になります。同じ dt 0.02 で2,400歩ずつ積分すると、左のオイラー盤は数周後に形が崩れ、右の RK4 盤は形を保ちます。オイラーの誤差が歩幅に比例(1次)するのに対し RK4 は4乗に比例するので、同じ歩幅でも誤差の桁が違います。累積面積11.656%は左右2盤を合わせた値です。

RK4積分 — オイラーから4次ルンゲ・クッタへ精度を上げる
新しいタブで開く

03 · RK4積分 — オイラーから4次ルンゲ・クッタへ精度を上げる — 計測 累積面積 11.656% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFramelineToarc(

euler と rk4 — 同じ1歩の2つの読み方

euler は出発点の傾き1回で位置を動かし、rk4 は k1〜k4 の4回の傾きを 1/6・1/3・1/3・1/6 の重みで平均します。どちらも同じ DT を扱うので、差は積分器だけから出ます。

  function euler(p) {
    var d = deriv(p[0], p[1], p[2]);
    return [p[0] + d[0] * DT, p[1] + d[1] * DT, p[2] + d[2] * DT];
  }
  function rk4(p) {
    var k1 = deriv(p[0], p[1], p[2]);
    var k2 = deriv(p[0] + k1[0] * DT / 2, p[1] + k1[1] * DT / 2, p[2] + k1[2] * DT / 2);
    var k3 = deriv(p[0] + k2[0] * DT / 2, p[1] + k2[1] * DT / 2, p[2] + k2[2] * DT / 2);
    var k4 = deriv(p[0] + k3[0] * DT, p[1] + k3[1] * DT, p[2] + k3[2] * DT);
    return [
      p[0] + DT / 6 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]),
      p[1] + DT / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]),
      p[2] + DT / 6 * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2])
    ];
  }

同じ種から同じ歩数だけ歩かせる

両盤は同じ初期値 (0.6, 0.7, 21) から同じ2,400歩を積分し、記録も同じ位相で公開します。並べて初めて、崩れる方が歩幅の誤差で壊れていることが読み取れます。

04アトラクター図鑑 — クリフォード・ド・ジョン、数百万点が描く確率の花

微分方程式ではない2次元漸化式に乱数点を通し、訪問回数を明るさとして積むと、係数ごとに違う文様が滝のように開きます。クリフォード x′=sin(a·y)+c·cos(a·x)・ド・ジョン x′=sin(a·y)−b·cos(x) の6組の係数を、チャンネルごとに2万回ずつ反復して3色を順番に現します。強度平均15.5は薄いパステルの確率の絵として最も落ち着いた値です。

アトラクター図鑑 — クリフォード・ド・ジョン、数百万点が描く確率の花
新しいタブで開く

04 · アトラクター図鑑 — クリフォード・ド・ジョン、数百万点が描く確率の花 — 計測 累積面積 8.677% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFrameMath.sinfillRect

シード固定乱数と過渡の破棄

乱数は Math.random ではなくシード固定の rng(mulberry32系) から作り、同じ時刻のフレームはいつも同じ絵にします。accumulate は最初の400歩を捨ててアトラクターに乗ってから記録するので、文様の縁がにじみません。

  function accumulate(set, upto) {
    var rand = rng(set.kind * 1000 + 7);
    var x = rand() * 2 - 1, y = rand() * 2 - 1;
    var n = Math.floor(ITERS * upto);
    var a = set.p[0], b = set.p[1], c = set.p[2], d = set.p[3];
    var i, px, py;
    for (i = 0; i < n + 400; i++) {
      if (set.kind === 0) {
        var nx = Math.sin(a * y) + c * Math.cos(a * x);
        y = Math.sin(b * x) + d * Math.cos(b * y);
        x = nx;
      } else {
        var nx2 = Math.sin(a * y) - b * Math.cos(x);
        y = Math.sin(c * x) - d * Math.cos(y);
        x = nx2;
      }
      if (i < 400) continue;              // 過渡を捨てる
      px = (x + 3) / 6 * (RES - 1);
      py = (y + 3) / 6 * (RES - 1);
      if (px >= 0 && px < RES && py >= 0 && py < RES) {
        acc[(py | 0) * RES + (px | 0)] += 1;
      }
    }
  }

19/24フレームが正常である理由

3つの色チャンネルを2秒周期で順番に現す構造上、チャンネルの切り替わりで蓄積が始まるまでの数フレームは絵が動きません。他の八つが23/24なのに対し04だけ19/24なのは、この待ちの設計であって詰まっているわけではありません。

05アイザワ・トーマス — 球面と螺旋、別の方程式がくれた別の彫刻

アトラクターはローレンツだけではありません。球をまとうアイザワ、糸巻き螺旋のトーマス。係数を少し変えるだけで形が一変することを見せる比較実験です。どちらも RK4 dt 0.02・1,800歩で積分し、12秒周期の観察軸回転で立体の形を見せます。累積面積0.768%は細い軌道線1本の比較盤として九つで最も小さい値です。

アイザワ・トーマス — 球面と螺旋、別の方程式がくれた別の彫刻
新しいタブで開く

05 · アイザワ・トーマス — 球面と螺旋、別の方程式がくれた別の彫刻 — 計測 累積面積 0.768% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFrameMath.sinlineTo

dAizawa と dThomas — 形を決める6+1個の係数

アイザワは a=0.95, b=0.7, c=0.6, d=3.5, e=0.25, f=0.1 の6係数、トーマスは b=0.19 の1係数です。rk4 は f を差し替えられる引数付きで、03のものと同じ4回の傾き平均をどちらの系にも適用します。

  function dAizawa(x, y, z) {
    var a = 0.95, b = 0.7, c = 0.6, d = 3.5, e = 0.25, f = 0.1;
    return [
      (z - b) * x - d * y,
      d * x + (z - b) * y,
      c + a * z - z * z * z / 3 - (x * x + y * y) * (1 + e * z) + f * z * x * x * x
    ];
  }
  function dThomas(x, y, z) {
    var b = 0.19;
    return [Math.sin(y) - b * x, Math.sin(z) - b * y, Math.sin(x) - b * z];
  }
  function rk4(p, f) {
    var k1 = f(p[0], p[1], p[2]);
    var k2 = f(p[0] + k1[0] * DT / 2, p[1] + k1[1] * DT / 2, p[2] + k1[2] * DT / 2);
    var k3 = f(p[0] + k2[0] * DT / 2, p[1] + k2[1] * DT / 2, p[2] + k2[2] * DT / 2);
    var k4 = f(p[0] + k3[0] * DT, p[1] + k3[1] * DT, p[2] + k3[2] * DT);
    return [
      p[0] + DT / 6 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]),
      p[1] + DT / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]),
      p[2] + DT / 6 * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2])
    ];
  }

出発点を選んでから記録する

アイザワは球の中心近く (0.1, 0, 0)、トーマスは (1.1, 1.2, −0.6) から出発し、100歩の過渡を捨ててから1,800歩を記録します。始点が悪いと軌道が薄く広がって彫刻の輪郭が読めません。

06密度の雲 — 訪問回数をピクセル輝度として積む霧の質感

1点ではなく数千点を同時に放ち、各ピクセルの通過回数を足すと、軌道の線が霧のような密度場に変わります。同じ式を統計として読むヒストグラム描画です。シード固定乱数でアトラクター中心付近に撒いた2,000点をオイラー dt 0.016・フレームごとに2歩ずつ進め、xz 投影の訪問回数を150px のヒストグラムに蓄積して、2秒周期でリセットしてまた積みます。

密度の雲 — 訪問回数をピクセル輝度として積む霧の質感
新しいタブで開く

06 · 密度の雲 — 訪問回数をピクセル輝度として積む霧の質感 — 計測 累積面積 1.04% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFramefillRectMath.min

seedPoints と stepAll — 2,000点の同時積分

点は (0.6, 0.7, 21) の周り ±0.01 に撒かれ、ごく近い出発点同士が時間とともに離れていく様子そのものがカオスの敏感性の可視化になっています。stepAll は毎フレーム2歩ずつ全点をオイラーで進めます。

  var rand = rng(42);
  function seedPoints() {
    var i;
    for (i = 0; i < NPTS; i++) {
      px[i] = 0.6 + (rand() - 0.5) * 0.02;
      py[i] = 0.7 + (rand() - 0.5) * 0.02;
      pz[i] = 21 + (rand() - 0.5) * 0.02;
    }
  }

  function stepAll() {
    var i, k, dx, dy, dz;
    for (k = 0; k < 2; k++) {
      for (i = 0; i < NPTS; i++) {
        var x = px[i], y = py[i], z = pz[i];
        dx = SIG * (y - x); dy = x * (RHO - z) - y; dz = x * y - BETA * z;
        px[i] = x + dx * DT; py[i] = y + dy * DT; pz[i] = z + dz * DT;
      }
    }
  }

訪問回数の蓄積 — xz 投影のヒストグラム

accumulate は各点の (x, z) を150px のマス目に落として +1 します。頻繁に通るマスは明るく育つので、軌道の密度がそのまま霧の濃さになり、1.04%という小さな累積面積は薄い霧を積むこの描画の正体です。

07ポアンカレ断面 — 軌道が平面を横切る瞬間だけを打つ

軌道が基準平面を横切る瞬間の座標だけに点を打つと、羽ごとに点の雲が帯になります。3次元の流れを2次元の地図へ折りたたむ古典的技法を画面に移した項目です。RK4 dt 0.005 で14,000歩(合計70時間単位)を密に積分し、前の z と今の z が基準面 z=27 をまたいだ瞬間を線形補間して、その (x, y) だけに打ちます。上昇通過はアイスブルー・下降通過はピンクに分かれます。

ポアンカレ断面 — 軌道が平面を横切る瞬間だけを打つ
新しいタブで開く

07 · ポアンカレ断面 — 軌道が平面を横切る瞬間だけを打つ — 計測 累積面積 3.425% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFramelineToMath.min

通過検出と線形補間

za・zb が異符号のとき、t2 = za / (za − zb) でまたぎの位置を補間します。この1行がないと点がまたぎの前後の離れた2箇所に割れて、地図の精度が失われます。

  var crX = [], crY = [], crUp = [];
  for (i = 1; i < STEPS; i++) {
    var za = zs[i - 1] - Z_PLANE, zb = zs[i] - Z_PLANE;
    if ((za < 0 && zb >= 0) || (za >= 0 && zb < 0)) {
      var t2 = za / (za - zb);
      crX.push(xs[i - 1] + (xs[i] - xs[i - 1]) * t2);
      crY.push(ys[i - 1] + (ys[i] - ys[i - 1]) * t2);
      crUp.push(zb > za ? 1 : 0);
    }
  }

3×3のガウス近似スプラット

打った点は1マスではなく、重み 0.30〜1.0 の3×3のスプラットで150px の地図に散らしています。疎な通過点でも帯が連続して見えるのはこの柔らかさのためで、3.425%という面積は点の雲の地図として自然な大きさです。

08パラメータのつまみ — ポインタでσ・ρをひねると形が変わる

3つの係数をポインタ位置に結ぶと、蝶が潰れたり裏返ったり新しい軌道に生まれ変わります。カオス研究の実際である係数探検をインタラクションにした操作レイヤーです。ポインタ x で σ(4〜24)・y で ρ(10〜45) を決め、アトラクターの両羽の軸を囲む円周12分割のシード軌道を毎フレーム積分し直します。

パラメータのつまみ — ポインタでシグマ・ローをひねると形が変わる
新しいタブで開く

08 · パラメータのつまみ — ポインタでシグマ・ローをひねると形が変わる — 計測 累積面積 9.233% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointermoverequestAnimationFrameCanvasRenderingContext2D

τ 60ms の追従遅れ

係数は即時反映ではなく τ 0.06秒の遅れで目標に近づきます。手を動かすたびに軌道が跳ねると操作感が損なわれるので、この追従遅れが「ひねると形が流れる」感触を作ります。

  var lastSig = -1, lastRho = -1, lastT = 0;
  function draw(dt, tSec) {
    // τ追従 — 目標係数に60msの時定数で近づく
    var f = 1 - Math.exp(-dt / TAU);
    sig += (sigT - sig) * f;
    rho += (rhoT - rho) * f;

    // σ・ρの変化が閾値を超えたときだけ再積分 — 毎フレーム再計算の浪費を防ぐ
    var need = Math.abs(sig - lastSig) > 0.05 || Math.abs(rho - lastRho) > 0.1;

    for (var k2 = 0; k2 < ORBITS; k2++) {
      if (need || orbits[k2] === null) {
        n = traceOrbit(seeds[k2][0], seeds[k2][1], seeds[k2][2], sig, rho);
        orbits[k2] = [Array.prototype.slice.call(bufX, 0, n), Array.prototype.slice.call(bufZ, 0, n)];
      }
      var orbx = orbits[k2][0], orbz = orbits[k2][1];
      var L = orbx.length;
      if (L < 8) continue;

発散ガード — 軌道が画面外へ走るのを止める

係数を極端な領域へ持っていくと軌道は外へ発散します。traceOrbit は |x| か |z| が900を超えた時点で残り歩を同じ座標で埋めて打ち切り、12軌道のうち壊れたものだけが静かに沈むようにしています。

09蝶のヒーローシーン — 文字・粒子と重ねる表紙の場面

密度の雲を背景に敷き、ビネット・文字・ゆっくりした回転を重ねると、サイエンスチャンネルやデータサービスの表紙に使える場面が完成します。計算画面1つがサービスの最初の画面になる組立例です。2,400点の雲(オイラー dt 0.016)の上を、前もって積分した銀河の腕の明るい頭が12秒周期で流れ、観察角は30秒に1周回ります。3行の文字(CHAOS BUTTERFLY・ローレンツアトラクター シミュレーター・dz/dt = xy − βz)を重ねて表紙を締めます。

蝶のヒーローシーン — 文字・粒子と重ねる表紙の場面
新しいタブで開く

09 · 蝶のヒーローシーン — 文字・粒子と重ねる表紙の場面 — 計測 累積面積 10.849% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFramecreateRadialGradientMath.sin

銀河の腕 — 前もって積分した1本の軌道

腕は雲とは別に、dt 0.012で700点を前もって積分した1本の軌道から読み出します。armT = (tSec % 12) / 12 が12秒周期で頭の位置を決め、後ろの160点が尾として引かれます。

  // 銀河の腕用の事前積分軌道 (x, y, z)
  var armPts = [];
  (function () {
    var x = 0.6, y = 0.7, z = 21, i;
    var adt = 0.012;
    for (i = 0; i < 300; i++) {
      var dx = SIG * (y - x), dy = x * (RHO - z) - y, dz = x * y - BETA * z;
      x += dx * adt; y += dy * adt; z += dz * adt;
    }
    for (i = 0; i < 700; i++) {
      armPts.push([x, y, z]);
      var dx2 = SIG * (y - x), dy2 = x * (RHO - z) - y, dz2 = x * y - BETA * z;
      x += dx2 * adt; y += dy2 * adt; z += dz2 * adt;
    }
  })();

回転するヒストグラムとビネット

雲の accumulate は観察角 a で x・y を混ぜてからヒストグラムに落とすので、点群はそのままに見え方だけが30秒かけて回ります。縁はビネットで抑え、文字はクリーム色の HEAD トークンで3段の濃さに重ねました。

オイラーのdtを0.05以上にすると、そこで壊れる落とし穴

オイラーの dt を大きく取る

dt 0.05以上で1歩の誤差が歩ごとに積み重なり、軌道が外へ突き出て蝶が崩れます。03が dt 0.02 でオイラーと RK4 を比べるのはこのためです。逆に雲・つまみのように毎フレーム2,000点前後を積分し直す項目では、dt を小さくしたオイラーの方がフレーム予算に合います。RK4 は1歩あたり傾きを4回計算するからです。

02のスケールを一辺で括る

キャンバスは2.4:1なのにアトラクターは1:1に近いので、min(W, H) で括ると横の半分以上が空きます。横 W/57・縦 H/37.5 の異方性スケールこそ累積面積28.715%の鍵で、正方形スケールに戻すだけで画面が痩せます。

ポアンカレの通過をフレーム単位で検査する

dt 0.005 で密に積分しても、前の z と今の z の間のまたぎを線形補間しないと点が遠くにずれます。この補間こそが地図の精度を決めます。

zipを開くと九つの項目の index.html と style.css がフォルダーごと入っています。展開に使うパスワードは32hdtxzfです。本文に引用した関数がコメント込みでそのまま入っているので、行単位で突き合わせて確かめられます。

3つの変形のひとつが、そのままreduced-motionのアクセシビリティ対応になる

名前 変更する値 変わる感触
時間ステップ 01 DT 0.01 → 0.005 歩幅が半分になると軌道が滑らかになり、同じ時間を埋める歩数が2倍になる
係数ロー 08 ρ 28 → 35 蝶の羽がさらに開き、軌道が外へ走り去る周期が目に見えて短くなる
点の数 06 NPTS 2,000 → 8,000 霧が濃くなり、羽の内側の構造が夜空の銀河のように明るくなる

定数ブロックの1行を書き換えるだけ

3つとも書き換えるのはソース先頭の変数ブロックの1行だけです。時間ステップは精度、係数は世界そのもの、点の数は統計の密度という、それぞれ別の性格のつまみです。

動きを減らす設定で残る1枚

九つの盤すべてに prefers-reduced-motion: reduce の分岐が組み込まれており、ループを回さず積み上がった1場面を描いて .is-still クラスを付けます。静止していても蝶が見えるので、動きを止めた訪問者にも各盤の結論の場面が届きます。

触れる1枚と完結する8枚

08だけがポインタ入力を受け付け、ほかの8つは手を触れなくても完結します。08のポインタ移動は pointermove で受け、画面の下のメタ行で dt・歩数・周期を読めます。

この画面はどこで使う — 蝶のヒーロー部とパラメータ遊び場

九つの盤のうち、そのまま画面に載せられる組み合わせは3つに絞られます。09の蝶のヒーローシーンは文字と粒子を密度の霧に重ねる表紙の場面なので、製品紹介ページの最初の画面や講座サイトの導入にそのまま使え、背景が暗いヒーロー領域で特に映えます。

08のパラメータのつまみはポインタでσ・ρを直接ひねって形が変わる盤なので、インタラクティブなデモ枠や訪問者に係数を触らせたい教材ページに向きます。08自体の発散ガードがすでに効いているので、追加の例外処理なしでそのまま流用できます。

06の密度の雲は訪問回数を輝度として積む霧の質感なので、カードの背景やセクションの区切りの裏に薄く敷く用途に向きます。点の数を減らせば計算量が下がり、背景用途に見合う軽い盤になります。

ローレンツアトラクターを初めて触るときによくある質問 FAQ

ローレンツアトラクタとは 何ですか? — 簡単に説明できますか?

3つの速度式 dx/dt = σ(y−x)・dy/dt = x(ρ−z)−y・dz/dt = xy−βz に従って点を動かすと、2つの羽の間を行き来する軌道が勝手に積もります。その積もった軌道の形をローレンツアトラクターと呼びます。絵を持ってくるのではなく、式を積分すると画面に現れるところがポイントです。

計算をそのままコピーして自分のページに置けますか?

置けます。zipの中から好きな盤1つのフォルダーを取り出して、index.html と style.css の2ファイルをそのままアップロードすれば動きます。ライブラリも WebGL も CDN も使っていないので、読み込みの設定は何も要りません。

なぜ勝手に乱れているように見えるのですか?

式自体は決定論的で、同じ初期値と歩幅からは必ず同じ軌道が再現されます。乱れて見えるのは、初期値の下の桁に隠れた小さな差が時間とともに指数的に広がる敏感性のためで、06の2,000点の雲が広がる様子がそれを見せる場面です。

あらためて — なぜ同じ式で毎回違う道をたどるのか

初期値をどれだけ正確に覚えても、予測はずれます。ローレンツ系の速度式に微小な差を持つ2点を放つと、差は時間とともに指数的に広がり、しばらくすると2点は別々の羽にいます。06の2,000点がすべて (0.6, 0.7, 21) の周り ±0.01 から出発して夜空の銀河のように広がるのは、この敏感性を統計として眺めている場面です。決して乱数ではありません。式は決定論的で、同じ初期値から同じ歩幅で積分すれば同じ軌道が毎回現れます。それでも予測できないのは、初期値を有限の桁でしか書けないためで、桁の下に隠れた差がやがて全体の形を左右します。カオス アトラクター と呼ばれる絵の正体は、この予測不可能性と決定論が1本の軌道に同居していることです。03でオイラー盤が崩れるのも同族の現象で、歩幅の誤差が初期値の誤差と同じ役割を果たして数周後に形を入れ替えます。

もっと読む

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

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