▍AI Dev Lab
DoodleDrop

DoodleDrop ができるまで — 描いた線が落ちるお絵描き物理

お絵描き物理サンドボックスの開発ログ。matter.jsとCanvasで、手描きのストロークを凸包アルゴリズムで物理ボディに変換する仕組みと、黒板風の画面設計を記録しています。

·decision改善·stage公開中

DoodleDrop は、 matter.js + Canvas で動く お絵描き物理サンドボックス。 黒板にチョークで自由曲線を描くと、 ストロークが凸包 polygon の剛体になって即座に重力で落ち、 既存ストロークと衝突する。 ラボ 28本目、 シミュレーター ジャンル 2本目 (PilePark = 物理サンドボックス に続く)。

#なぜこの形にしたか

直近 5本は (side-tax: 計算 / atlas-quest: 学習 / hue-deck: ジェネレーター / astro-cast: データ可視化 / arcana-flip: 占い)。 直近 5本にシミュレーターゼロ → OK。

候補比較:

  • お絵描き物理 (採用)
  • 面接シミュ オフライン (server-side AI なしだと弱い)
  • 飲み会幹事シミュ (ルールベース、 弱)
  • 物理パズル (matter.js 拡張、 ステージ作成コスト大)

選んだ理由:

  • SNS シェア性が極めて高い (TikTok / Reels / Shorts ネタ: 描いたものが動く)
  • 既存大手は重い (Procreate / 3D) or 簡素すぎ (シンプル落書き) で間がない
  • matter.js を pile-park から再利用 = 新規 lib コストゼロ
  • 純 JS で軽量、 モバイルでも快適

#見た目をどう決めたか

直近 5本の 見た目 を書き出す:

side-tax — ledger green + cream + stamp red (帳簿 / 確定申告)
atlas-quest — dark navy + amber departure (空港 board)
hue-deck — warm gray + ink + lipstick (Pantone)
astro-cast — midnight space + 惑星レインボー (天文台)
arcana-flip — midnight black + plum + antique gold (中世大聖堂)

題材 「お絵描き / 黒板 / 落書き」 → モチーフ:

  • 学校の黒板 (chalkboard) ← 採用
  • 方眼ノート (既存 hue-deck や pile-park 系と近い)
  • ホワイトボード (light theme で被るリスク)
  • 砂浜 (niche)

採用 3 要素:

  • 配色: blackboard green #1a3c2e + worn black + chalk pastels 7 色 (白 #fff5e6 / 桃 #f48aaa / 黄 #f5d35e / 青 #6dafe0 / 緑 #7ed09e / 橙 #ff9b6b / 紫 #b78ad8) + worn wood frame #5a3f23 + chalk dust
  • side-tax の ledger green と同じ緑系だが、 モチーフ で完全別 (帳簿事務所 vs 学校黒板)
  • 配色 内の主役は chalk pastels、 side-tax は ink graphite + stamp red が主役
  • モチーフ: 学校の黒板 (12px wood frame で canvas を囲む + 木目 4 隅 dot + 横の薄い罫線 23px loop + chalk dust 散布 + dashed border 多用 = チョーク感)
  • typography: Space Grotesk 800 (clear classroom) + JetBrains Mono labels + 漢字を含む見出しを 「黒板のチョーク」 風に rotate(-1.5deg / 1deg)

#技術スタック

#凸包 (Andrew's monotone chain) でストロークを polygon body 化

ts
function convexHull(points) {
 if (points.length < 3) return [...points]
 const pts = [...points].sort((a, b) => a.x - b.x || a.y - b.y)
 const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x)
 // lower hull

 const lower = []
 for (const p of pts) {
 while (lower.length >= 2 && cross(lower[L-2], lower[L-1], p) <= 0) lower.pop
 lower.push(p)
 }
 // upper hull

 ...
 return lower.slice(0, -1).concat(upper.slice(0, -1))
}

O(N log N) で凸包を求める標準アルゴリズム。 描いたストロークが凹みのある形状 (☆ / C / 螺旋等) でも、 外側の凸包になる仕様。 v2 で凹み対応 (concave decomposition) は 改善候補。

#Canvas Pointer Events + Pointer Capture

ts
canvas.addEventListener("pointerdown", (e) => {
 drawing = true
 currentStroke = [getXY(e)]
 canvas.setPointerCapture(e.pointerId) // ← マウスがキャンバス外に出ても捕捉継続

})

setPointerCapture で 「描いてる途中で canvas 外に出てもストロークが切れない」 を実現。 タッチ / マウス / ペン全て同じイベントで扱える。

#短すぎる線の代替処理

ts
const totalDist = pts.reduce((acc, p, i) => i === 0 ? 0 : acc + Math.hypot(p.x-pts[i-1].x, p.y-pts[i-1].y), 0)
if (totalDist < 12) {
 // 中心座標で小円 (Body.circle) として落とす

}
// hull.length < 3 (直線的) なら線分の length × width で rectangle 代替

「ペチペチとクリックして点を落とす」 「短い線分を引く」 もちゃんと物体化される。

#距離間引きで stroke 軽量化

ts
canvas.addEventListener("pointermove", (e) => {
 const p = getXY(e)
 const last = currentStroke[currentStroke.length - 1]
 if (!last || Math.hypot(p.x-last.x, p.y-last.y) >= 3) { // 3px 未満は捨てる

 currentStroke.push(p)
 }
})

ポインタイベントの大量発火でも、 3px 間隔まで間引き → 凸包計算と body 生成のコストを抑制。

#まだやっていないこと

  • 凹み対応 (concave decomposition で複数 body chain — ☆や C 型がそのまま物体化される)
  • PNG / WebM 出力 (Canvas toBlob / MediaRecorder)
  • chalk 質感シェーダー (ノイズ + ボケ + chalk grain texture)
  • 共有 URL で stroke 列を base64 シリアライズして リプレイ
  • 既存ストロークの ドラッグ / 削除
  • タイマー モード (描いた線が 10 秒後に消える)
  • PilePark との合流 (描く + 既存図形をクリックで落とす hybrid)
  • BGM toggle (ノスタルジック チョーク音)

[ ./next_action ]

読んだら、 DoodleDrop を実際に動かす。

この開発ログは DoodleDrop をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ShadowTwin

立体パズルの別解を作る:同じ影と最少の積み木

ShadowTwinの開発記録。3方向の図から別々の最少配置を2つ探す仕組み、5問の独立全探索、スマホの横並び比較、保存・再編集と入力検査を解説します。実照明の計算や能力診断ではありません。

read log →
SlitCard

スリットアニメーションを作る:4コマの合成と透明原稿の設計

SlitCardの開発記録。4コマの帯を交互に合成し、すき間と黒帯で別のコマを見せる仕組みを解説。同縮尺の台紙・透明シートSVG、50mm確認線、手動プレビュー・初期非表示、再編集と実出力検査を整理します。

read log →
WindowLight

日当たりシミュレーションを作る:窓の光を床へ投影する幾何モデル

WindowLightの開発記録。太陽位置近似から窓の4隅を床へ投影し、凸多角形を部屋で切り取る設計を解説。国立天文台の16時刻との限定照合、大気差・低高度・実際の採光との違い、SVGと設定の再編集を整理します。

read log →
GridLights

電源構成の比較を作る:割合と実量を100個の電球で読み解く

電源構成比較GridLightsの開発記録。12か国の年次発電データを最大剰余法で100個の電球に変換し、割合とTWhを比較する設計を解説。欠損を0補完しない7分類、予想と実値、出典付きSVGと実データ検証を整理します。

read log →
SoundLoom

音のお絵かきを作る:実WAVから絵を測り直す設計

音のお絵かきの開発記録。描画を正弦波のPCMとWAVへ変換し、保存音声の周波数をGoertzel法で測って比較する仕組みを解説。音量・測定窓・量子化の限界と、編集・離脱時の古い結果を抑止する検証を整理します。

read log →
MazeLoom

宝探し迷路を作る:寄り道まで含めた最短ルートの探索

宝探し迷路の開発記録。通路を木として生成し、現在地と3つの宝の取得状態を組み合わせて最短ルートを探す仕組みを解説。問題コードの版固定、画面と解答付きPDFの一致、手数と印刷の検証を整理します。

read log →