ブログ

PROMPT → WEB
#008
KINTSUGI
CLAUDE CODE × CANVAS 2D / VORONOI

そのWeb演出、Claude Codeで作れます。クリエイティブWeb表現のプロンプト集 #08 Kintsugi

株式会社グルーヴサインの米沢です。

AIコーディングでクリエイティブなWeb表現を実際に作り、そのプロンプトごと公開していく「PROMPT → WEB」の第8回です。

PROMPT → WEB #008

Kintsugi

INTRODUCTION

「ボロノイ分割で何か作って」と言うと、AIはたいてい割れた破片を吹き飛ばします。クリックすると画面がガラスのように砕けて、破片が回転しながら散っていく。よく見る演出です。

今回はその逆をやります。破片を飛ばしません。割れ目だけを見せます。

金継ぎ(きんつぎ)という修復技法があります。割れた器を漆で接ぎ、継ぎ目に金を蒔く。壊れた跡を消さずに、むしろ金の線として見せる。割れる前より価値が上がることさえあります。

私の本業は他社が作ったシステムの保守・引き継ぎで、20年ずっとそれをやっています。動いているものを作り直さず、傷んだところに手を入れて使い続ける仕事です。金継ぎの話を最初に聞いたとき、これは自分の仕事の絵だと思いました。割れ目を隠さないというのが、特に。

なので今回の作品は、割れて終わりにしません。

貫入(もともと入っている細かい罅)→ 割れ(一点から走る) → 継ぎ(金を流す)

この3つの段が順に進みます。技術としてはボロノイ分割ですが、主題は分割された結果の境界線のほうです。

RESULT

まず現物です。下のボタンを押してみてください。

クリックすると、この画面の上で Kintsugi が再生されます(何度でも再生可能)。ESC または CLOSE で終了します。

青磁の面に、すでに細かな貫入が入っています。そこへ一点から罅(ひび)が走り、しばらくして金が流し込まれます。 操作はひとつです。

放っておくと、13秒ごとに別の器が別の場所から割れます。左下の箱書きに、いまの釉薬・破片の数・罅の本数・どの段にいるか(罅/割/継/了)が出ています。ESC または CLOSE で終了します。

近づいて見ると、罅の線は直線ではありません。そして金の両側には暗い縁が残っています。金は面の上に描かれているのではなく、窪みに流し込まれているように見えるはずです。

WHAT WE MADE

技術的に分解すると、次のようになります。

HOW IT WORKS

1. ボロノイ分割は、定義をそのまま書けばいい

ボロノイ図を作ると聞くと、ドロネー三角形分割を計算してその双対を取る、という手順を思い浮かべます。正攻法ですが、実装は重いです。

定義に戻ります。母点 i の領域とは「他のどの母点よりも i に近い点の集まり」です。ということは、画面全体の四角形から出発して、他の母点 j との垂直二等分線で「j 側」を切り落としていけば、残ったものがそのまま領域になります。

// 母点 i と j の垂直二等分線で、j 側を捨てる
const dx = sx[j] - px, dy = sy[j] - py;              // i から j への向き
const mx = (sx[j] + px) * 0.5, my = (sy[j] + py) * 0.5; // 2点の中点
// 各頂点について (頂点 - 中点)・(向き) の符号を見る。正なら j 側なので捨てる
const d = (ax - mx) * dx + (ay - my) * dy;

あとは多角形を線で切る処理(Sutherland–Hodgman)を繰り返すだけです。定義を読めば書けるという手触りが、この作品を作っていていちばん気持ちよかったところでした。

もちろん全母点と比べる必要はありません。2300個の母点で総当たりすると530万回の比較になります。粗い格子に母点を配っておき、5×5マスの近傍だけを見ます。実測すると1母点あたり平均19〜25個で足りていました。

2. ロイド緩和という一手間

母点を完全な乱数で置くと、偏って細長い破片ができます。陶器の割れ方には見えません。

対策は2段階です。まず粗い格子の中で位置を揺らして置く。そのうえでロイド緩和をかけます。各母点を、自分の領域の重心へ移動させる。それだけを数回繰り返すと、粒が驚くほど揃います。

const [cx, cy, area] = centroid(poly);   // 自分の領域の重心へ
if (area > 0) { sx[i] = cx; sy[i] = cy; }

今回は割れに3回、貫入に1回かけています。回数が多いほど整然として、少ないほど自然に散らばります。 貫入をあまり整えないのは、器の罅が規則正しく入っていたら気味が悪いからです。

3. 同じコードを、2つの尺度で使う

この作品の見た目を支えているのは、密度の違う分割を2枚重ねていることです。

どちらもまったく同じ Tessellation クラスで作っています。違うのは母点の数と緩和の回数だけ。尺度を2つ持つと、一気に「物」に見えます。 片方だけだと、ただの図形の網です。

4. 辺は、必ず2回出てくる

領域ごとに輪郭を描くと、隣り合う領域が共有している辺を2度描くことになります。線が太くなり、半透明にすると重なった部分だけ濃くなります。

辺の中点を鍵にして重複を落とします。

const key = (Math.round(mx * 2) << 16) ^ Math.round(my * 2);
if (seen.has(key)) continue;
seen.add(key);

同じ辺は同じ中点を持つので、これで一意になります。96片の割れから、重複を落として249本の罅が残りました。

5. 罅は直線では走らない

取り出した辺をそのまま線で結ぶと、いかにも計算された多角形になります。罅に見せるには折らないといけません。

辺を3等分し、中間の点だけを法線方向へ少しずらします。ずらす量は辺の長さに応じて頭打ちにしてあります(長い辺が大きく波打つと、罅ではなく蛇行した川に見えます)。

走る順番は、衝撃点から辺の中点までの距離で決めています。ただしその距離に少し揺らぎを掛けています。素直に距離だけで並べると、罅がきれいな真円に広がって嘘っぽくなるためです。

6. 金は、罅より細く置く

継ぎの段で、最初に書いた実装は「罅の上に、罅より太い金の線を引く」でした。結果はオレンジ色の輪郭線で、器には見えませんでした。

直したのは順番と太さです。

  1. まず暗い罅を太めに引く
  2. その上に金の下地を、罅より細く引く
  3. さらに明るい金を、もっと細く引く

こうすると両側に暗い縁が残り、金が窪みに収まっているように見えます。**金を「描く」のではなく、罅を「埋める」**という順番でした。

7. 太さでまとめて描く

罅を1本ずつ beginPathstroke すると、継ぎの段では249本 × 3パス + 先端で毎フレーム748回の描画呼び出しになります。

太さを0.35px刻みに丸めて、同じ太さの罅を1本のパスにまとめました。

// 太さの近い罅を同じ入れ物へ。塗りは入れ物ごとに1回だけ
buckets[bucketOf((0.9 + 2.4 * age) * e.weight)].push(i);

実測すると1フレームあたり20.3回。見た目の差は、太さが0.35px単位に丸まることだけです。

THE PROMPT

ここが本題です。上のデモを作るためにClaude Codeへ渡せる、汎用プロンプト全文を掲載します。右上の COPY ボタンでそのままコピーできます。釉薬の色や破片の数を置き換えれば、自分用になります。

あなたはCreative Codingと計算幾何に精通したフロントエンドエンジニアです。
「Kintsugi」= 釉薬の器が一点から割れ、その割れ目を金で継ぐWeb表現を、
Canvas 2D で実装してください。ボロノイ分割のライブラリは使わず、自前で求めること。

# 最重要の方針
- 【破片は飛ばさない】。割れた欠片を回転させて散らす演出にはしない。
  主役は破片ではなく「割れ目そのもの」。
- 段は3つ。貫入(もともと入っている細かい罅)→ 割れ(一点から走る)→ 継ぎ(金を流す)。
- 分割の計算は起動時に1回だけ。毎フレームやるのは「どこまで割れたか」の線引きだけ。

# 置き換える変数
- GLAZES = 4種類。釉薬の色と、継ぎに使う金属を組にする:
    青磁 SEIJI  面 "#9fc4b8"→"#6d9a8e"  継ぎ 金 "#8a6526"/"#f2dda2"
    白磁 HAKUJI 面 "#eee8dc"→"#c9c0af"  継ぎ 金 "#96702a"/"#f7e9b8"
    鉄釉 TETSU  面 "#3b332c"→"#191512"  継ぎ 銀 "#6f787d"/"#e9f0f4"
    粉引 KOHIKI 面 "#ded5c6"→"#b0a492"  継ぎ 漆 "#5e2a20"/"#b96b52"
- LABEL = "KINTSUGI"

# ボロノイ分割(自前で書く)
- 【重要】ドロネー三角形分割を経由しない。定義どおり半平面クリッピングで求める:
  画面より少し大きい四角形から始め、近傍の母点との垂直二等分線で
  「相手側」を切り落としていく(Sutherland–Hodgman)。残った多角形が領域。
- 全母点と総当たりしない。粗い格子に母点を配り、5x5マスの近傍だけを見る
- 母点は完全な乱数で置かない。粗い格子の中で揺らして置き、そのうえで
  【ロイド緩和】(母点を自分の領域の重心へ寄せる)を数回かける。
  これをやらないと細長い破片ができて、陶器の割れ方に見えない
- 【重要】密度の違う分割を2枚重ねる:
    貫入 … 面積900px²に1片、緩和1回。細く薄く、起動時に面へ焼き込む
    割れ … 面積14,000px²に1片、緩和3回。太く濃く、時間をかけて走らせる
  尺度が2つあると、図形の網ではなく「物」に見える
- 領域の輪郭をそのまま描くと、隣り合う領域が共有する辺を2度描いてしまう。
  辺の中点を鍵にして重複を落とすこと

# 割れの走り方
- 辺ごとに「衝撃点から辺の中点までの距離」を持たせ、その順に現れさせる
- 【重要】距離に少し揺らぎ(±15%程度)を掛ける。素直に距離順だと
  罅がきれいな真円に広がって嘘っぽくなる
- 辺は直線で結ばない。3等分して中間の点を法線方向へずらす(折れ線にする)。
  ずらす量は辺の長さに応じて頭打ちにする(長い辺が波打つと川に見える)
- 先端(いま走っている罅)だけは途中まで描く。到達済みの罅は太く落ち着かせる

# 継ぎ(ここを間違えると輪郭線にしか見えない)
- 罅が走りきってから少し置いて、衝撃点の側から順に金が流れる
- 【重要】金は罅より【細く】置く。順番は
    ① 暗い罅を太めに ② 金の下地を細く ③ 明るい金をもっと細く
  こうすると両側に暗い縁が残り、金が窪みに収まって見える。
  金を「描く」のではなく、罅を「埋める」

# 性能
- 【重要】罅を1本ずつ beginPath/stroke しない。太さを0.35px刻みに丸めて
  同じ太さをまとめ、まとまりごとに1回だけ stroke する
  (そのままだと継ぎの段で毎フレーム700回以上の描画呼び出しになる)
- 面(釉薬+貫入)は1枚のオフスクリーンに焼いておき、毎フレームは drawImage 1回で敷く

# インタラクション
- クリック/タップした場所から割れる。同時に次の釉薬へ切り替える
- 放っておいたら13秒ほどで、別の器が別の場所から割れる
- 隅に箱書き(釉薬名・破片の数・罅の本数・いまどの段か)を明朝系で小さく置く
- ネオン・rainbow・glassmorphism は使わない。陶器の質感にする

# 品質要件(production)
- 全画面オーバーレイ。CLOSE ボタンと ESC で終了。多重起動を防ぐ
- レスポンシブ: リサイズで面を焼き直し、分割も作り直す
- devicePixelRatio は最大2でクランプし、以降の描画は CSS px で行う
- 狭い画面では、箱書きと操作ヒントが重ならないよう配置を組み替える
- prefers-reduced-motion: アニメーションせず、継ぎ終わった姿を1枚だけ描いて静止させる
- 終了時に requestAnimationFrame を停止し、全リスナを解除し、CanvasをDOMから除去する
- 乱数は種から作る(同じ器を再現できるように)

CUSTOMIZE

PERFORMANCE

分割は起動時と釉薬の切り替え時だけ。毎フレームやっているのは線を引くことだけです(1920×1080・Nodeで計算部分を実測)。

WHAT AI DID / WHAT HUMAN DIRECTION MATTERS

ボロノイ分割そのものは、AIはよく知っています。むしろ知りすぎていて、頼んでいないものまで出してきました。

最初に返ってきたのは、ライブラリを使ってドロネー三角形分割を計算し、その双対からボロノイ図を得て、クリックで破片を飛ばす実装でした。正しいし、速いし、よくある。しかしライブラリを外すと何も残りません。

半平面クリッピングで書くよう指定したのは人間側です。実装量は増えますが、コードが定義そのものになり、読めば分かるものになりました。 連載の目的が「渡せるプロンプトを公開すること」である以上、ここは譲れないところでした。

もうひとつ、AIが自分からは提案しなかったのがロイド緩和です。最初の版は母点を一様乱数で置いていて、細長い破片が混じった、いかにも計算機的な分割でした。「陶器の割れ方に見えない」という判断は、絵を見ないと下せません。

金の置き方も同じです。最初は罅の上に太い金の線を引いていて、オレンジ色の輪郭線にしか見えませんでした。罅より細く置いて暗い縁を残すという1行の変更で、ようやく金が窪みに収まりました。これは技術の話ではなく、実物の金継ぎを見たことがあるかどうかの話です。

人間の仕事として残ったのは、次のような判断でした。

最後の2つは、どちらも「正しく作ると嘘っぽくなる」類の調整です。距離順に正確に割れると真円になり、金を上から描くと輪郭線になる。正しさと、それらしさは違う。 その差を埋めるのは、いまのところ人の目です。

8回続けて、実装は一貫して速いままです。そして毎回、捨てる判断と、濁す判断に時間を使っています。

NEXT

粒子、文字、線、物理、計算、製版、音、そして今回の分割。

次回はまた別の仕掛け ── 影だけで形を見せる表現、幾何の証明が組み上がる線、スクロール連動あたりを考えています。

今回のプロンプトも丸ごとコピーして使えます。釉薬の色を自社のブランドカラーに置き換えれば、まったく別の器が割れます。割れ目を消さずに残すという考え方は、画面の中だけの話でもありません。私はそれを20年、他人の書いたコードでやっています。

レガシーPHPシステムの保守・引き継ぎのご相談を受け付けています。
初回のご相談・ヒアリングは無料です。

無料相談・お問い合わせはこちら

株式会社グルーヴサイン 代表取締役 米沢忠司。PHP開発歴約20年。他社開発システムの保守・引き継ぎを主力に、2007年の設立から19期目。