図1:黄金比がつくる向日葵の種のシミュレーション
放送大学の自然科学の授業で,岸根順一郎さんが向日葵の種の配列のシミュレーションを見せていた。自然は黄金比を選んでいるというわけだ。バラメタを少し変えるといろいろな模様が出現する。
自分でも試してみたくて,バイブコーディングに走る。ChatGPT Auto で試したところ,Juliaに慣れていないせいか,なかなかエラーが消えない。しょうがないので,ChatGPT Thinking-5.4 でリセットしてからお願いする。プロンプトは以下のとおり。
Q(koshix):Jupyter環境で,次のJulia プログラムをつくってください。 向日葵の種(塗りつぶした色つき小円でよい)の分布が 1粒ずつ内側から充足されていくインラインアニメーション。 これで意味が分かりますか? なるべく簡素で高速なものにしてください。
種の大きさや色相,パラメタの変更機能などを注文すると,「Julia で毎回 GIF や mp4 を作るより、Jupyter セル内に HTML/JavaScript の小さな UI を埋め込む 方がずっと軽くて速いです。」ということで,修正版ができた。饅頭の薄皮だけJuliaで中身の餡子はすべてJavascriptというよくわからないけど,よくあるパターンらしい。
最後に,Gemini Pro に投げてコード全体を簡素化してもらったところ,少しスッキリした。黄金比の部分を変えると,様々なパターンが現われる。
この中からJavascriptのコードを含むhtmlを取り出して,独立で動くように修正し,角度係数αを,1.60〜1.83まで0.01刻みで変化させた結果を合成した画像も,ほとんどそのまま出力してくれた。プロンプトを0.1刻みと間違えて入力しても,正しく解釈してくれる。
using UUIDs
function sunflower_simulator()
uid = "sf_" * replace(string(uuid4()), "-" => "")
html = """
<div id="$uid" style="font-family: sans-serif; line-height: 1.4;">
<div style="margin-bottom: 0.6em;">
<label>角度係数 α (2π/α): <input id="$(uid)_alpha" type="number" value="1.61803398875" step="0.0001" style="width: 8em;"></label>
<span style="margin-left:1em;"></span>
<label>粒数 N: <input id="$(uid)_N" type="number" value="500" step="50" style="width: 5em;"></label>
<span style="margin-left:1em;"></span>
<label>速度: <input id="$(uid)_step" type="number" value="5" step="1" min="1" style="width: 4em;"></label>
<span style="margin-left:1em;"></span>
<button id="$(uid)_run">実行</button>
</div>
<div style="margin-bottom: 0.5em; color: #444; font-size: 0.95em;">
α を黄金比 φ ≈ 1.618 にすると、回転角 2π/α ≈ 222.5° になります。<br>
ひまわり配列でよく使う黄金角 137.5° とは、向きが逆なだけで本質的に同じです。
</div>
<canvas id="$(uid)_canvas" width="520" height="520" style="border:1px solid #ddd; background:white;"></canvas>
<div id="$(uid)_info" style="margin-top:0.5em; color:#333;"></div>
</div>
<script>
(function() {
// DOM要素の取得を簡素化
const get = id => document.getElementById(`$(uid)_\${id}`);
const [alphaIn, nIn, stepIn, runBtn, canvas, info] = ["alpha", "N", "step", "run", "canvas", "info"].map(get);
const ctx = canvas.getContext("2d");
let animId = null;
function runOnce() {
if (animId) cancelAnimationFrame(animId);
const alpha = parseFloat(alphaIn.value);
const N = Math.max(1, parseInt(nIn.value));
const step = Math.max(1, parseInt(stepIn.value));
if (!isFinite(alpha) || alpha === 0) {
info.textContent = "α は 0 でない数を入れてください。";
return;
}
info.textContent = `α = \${alpha.toFixed(6)}, 回転角 = \${(360 / alpha).toFixed(3)}°, 粒数 = \${N}, 速度 = \${step}`;
// シード配列の生成 (Array.from でスッキリ記述)
const angle = 2 * Math.PI / alpha;
const seeds = Array.from({length: N}, (_, i) => {
const k = i + 1;
const r = Math.sqrt(k);
return {
x: r * Math.cos(k * angle),
y: r * Math.sin(k * angle),
color: `hsl(\${25 + 55 * (k / N)}, 90%, 59%)` // HSV(h, 78%, 96%) とほぼ同等のHSL色
};
});
const W = canvas.width, H = canvas.height;
const scale = 0.48 * Math.min(W, H) / (Math.sqrt(N) + 1);
const rad = Math.max(0.9, 140 / Math.sqrt(N + 20));
let k = 0;
// 描画とアニメーションループを統合
function tick() {
k = Math.min(k + step, N);
ctx.clearRect(0, 0, W, H);
ctx.save();
ctx.translate(W / 2, H / 2);
for (let i = 0; i < k; i++) {
ctx.beginPath();
ctx.arc(seeds[i].x * scale, seeds[i].y * scale, rad, 0, 2 * Math.PI);
ctx.fillStyle = seeds[i].color;
ctx.fill();
}
ctx.restore();
if (k < N) animId = requestAnimationFrame(tick);
}
tick();
}
runBtn.onclick = runOnce;
// Enterキーでの実行イベントをまとめる
[alphaIn, nIn, stepIn].forEach(el =>
el.addEventListener("keydown", e => e.key === "Enter" && runOnce())
);
})();
</script>
"""
display("text/html", html)
end
sunflower_simulator()
0 件のコメント:
コメントを投稿