mcpField
C
canvas-generative

canvas-generative

github.com/athevonクリエイティブ・メディア698994 回インストール3 週間前に更新
リポジトリ

canvas-generative について

クリエイティブコーディング向けの Canvas 2D 生成アートスキルです。ノイズ、パーティクル、フローフィールド、再帰構造の実装を案内し、鮮明な描画と効率的なアニメーションループにも配慮します。

主な機能

  1. 01DPR 対応のキャンバス設定
  2. 02ノイズと再現可能なパターン
  3. 03パーティクルプールとフローフィールド
  4. 04フラクタルと L-system
  5. 05オフスクリーン描画と軌跡効果

活用シーン

  1. 01高 DPI 画面で鮮明な生成アートを描画
  2. 02フローフィールドを使ったパーティクルシステムの作成
  3. 03L-system による再帰構造の構築

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/athevon/genjutsu --skill canvas-generative

インストール後、Claude Code で/canvas-generativeを入力して読み込まれたか確認します。

詳細

バージョン依存です。 以下のすべての API 名、SDK の対応状況、ブラウザーのサポートに関する記述は、一次情報に基づき 2026-09-08 に検証済みです。何をいつ検証したかは _jutsu/VERSIONS.md に記載されています。この日付が古い場合は、バージョン番号をもとに作業する前に再検証してください。

Canvas Generative

Canvas 2D によるアルゴリズムアートとジェネレーティブアートです。 ここでは簡潔なルールを示します。詳しい解説とリファレンス実装は references/ にあります。


Canvas 2D のセットアップ

DPR に対応したサイズ設定

すべてのキャンバスは、Retina/HiDPI ディスプレイで鮮明に表示される必要があります。バッファーサイズを物理ピクセルサイズに設定し、CSS で縮小してください。

function setupCanvas(canvas, width, height) {
  const dpr = window.devicePixelRatio || 1;
  canvas.width = width * dpr;
  canvas.height = height * dpr;
  canvas.style.width = `${width}px`;
  canvas.style.height = `${height}px`;
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);
  return ctx;
}
リサイズハンドラー
function handleResize(canvas, ctx, draw) {
  const ro = new ResizeObserver(([entry]) => {
    const { width, height } = entry.contentRect;
    const dpr = window.devicePixelRatio || 1;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    ctx.scale(dpr, dpr);
    draw(); // re-render after resize
  });
  ro.observe(canvas.parentElement);
  return () => ro.disconnect();
}
アニメーションループ(RAF)
let animId;
let prevTime = 0;

function loop(time) {
  const dt = Math.min((time - prevTime) / 1000, 0.1); // cap delta to avoid spiral of death
  prevTime = time;

  update(dt);
  render(ctx);

  animId = requestAnimationFrame(loop);
}

// Start
animId = requestAnimationFrame(loop);
// Stop
cancelAnimationFrame(animId);

ノイズ

種類特徴最適な用途
Perlin滑らかで、グリッドに沿った偏りがあり、低コスト地形、雲、穏やかな有機的テクスチャ
Simplexグリッド状のアーティファクトがなく、勾配に優れ、やや高コストフローフィールド、有機的な動き、シームレスタイリング
Worley(セルラー)最も近い点までの距離を表し、セル状ボロノイパターン、コースティクス、ひび割れ、セル状テクスチャ

使用ルール:

  • 入力座標は必ずスケーリングしてください(noiseScale 係数で割ります)。ピクセル座標をそのまま使うと、見た目がノイズ状になります
  • 細部を表現するにはオクターブ(フラクタルブラウン運動)を使います。複数のノイズ呼び出しを、周波数を上げながら振幅を下げて合算します
  • 再現性を確保するため、ノイズにシードを設定してください
// fBm pattern
function fbm(x, y, octaves = 4, lacunarity = 2, gain = 0.5) {
  let value = 0, amplitude = 1, frequency = 1, maxAmp = 0;
  for (let i = 0; i < octaves; i++) {
    value += amplitude * noise2D(x * frequency, y * frequency);
    maxAmp += amplitude;
    amplitude *= gain;
    frequency *= lacunarity;
  }
  return value / maxAmp; // normalize to [-1, 1]
}

パーティクルシステム

プールパターン(GC の負荷を回避)

固定サイズの配列を事前に確保してください。実行時に new や splice を使わないでください。

const POOL_SIZE = 10000;
const particles = new Array(POOL_SIZE);
let aliveCount = 0;

// Init pool
for (let i = 0; i < POOL_SIZE; i++) {
  particles[i] = { x: 0, y: 0, vx: 0, vy: 0, life: 0, maxLife: 0, active: false };
}

function spawn(x, y) {
  if (aliveCount >= POOL_SIZE) return;
  const p = particles[aliveCount++];
  p.x = x; p.y = y;
  p.vx = (Math.random() - 0.5) * 2;
  p.vy = (Math.random() - 0.5) * 2;
  p.life = 0; p.maxLife = 60 + Math.random() * 60;
  p.active = true;
}

function update() {
  for (let i = aliveCount - 1; i >= 0; i--) {
    const p = particles[i];
    p.x += p.vx; p.y += p.vy;
    p.life++;
    if (p.life >= p.maxLife) {
      // Swap with last alive, shrink pool
      particles[i] = particles[--aliveCount];
      particles[aliveCount] = p;
      p.active = false;
    }
  }
}

フローフィールド

パーティクルを誘導する角度ベクトルのグリッドです。ジェネレーティブアートの定番の手法です。

  1. グリッドを構築: キャンバスをセルに分割し、ノイズから各セルの角度を計算します
  2. 参照: パーティクルの位置をグリッドセルに対応させ、角度を取得します
  3. 誘導: 角度を速度として適用し、フレームをまたいで累積します
const cols = Math.ceil(width / cellSize);
const rows = Math.ceil(height / cellSize);
const field = new Float32Array(cols * rows);

// Fill with noise-based angles
for (let y = 0; y < rows; y++) {
  for (let x = 0; x < cols; x++) {
    field[y * cols + x] = noise2D(x * 0.05, y * 0.05) * Math.PI * 2;
  }
}

// Particle follow
function followField(p) {
  const col = Math.floor(p.x / cellSize);
  const row = Math.floor(p.y / cellSize);
  if (col >= 0 && col < cols && row >= 0 && row < rows) {
    const angle = field[row * cols + col];
    p.vx += Math.cos(angle) * force;
    p.vy += Math.sin(angle) * force;
  }
  // Damping to prevent runaway velocity
  p.vx *= 0.98;
  p.vy *= 0.98;
}

フラクタルと L システム

L システムは、文字列の書き換えとタートルグラフィックスを組み合わせて、再帰構造を表現します。

構成要素役割
公理開始文字列(例:"F")
ルール生成ルール(例:"F" -> "F[+F]F[-F]F")
角度+/- ごとのタートルの回転角度
反復回数ルールを適用する回数
function lsystem(axiom, rules, iterations) {
  let current = axiom;
  for (let i = 0; i < iterations; i++) {
    current = current.split('').map(c => rules[c] || c).join('');
  }
  return current;
}

function drawLSystem(ctx, commands, len, angle) {
  const stack = [];
  for (const c of commands) {
    switch (c) {
      case 'F': ctx.lineTo(ctx._x += Math.cos(ctx._a) * len, ctx._y += Math.sin(ctx._a) * len); break;
      case '+': ctx._a += angle; break;
      case '-': ctx._a -= angle; break;
      case '[': stack.push({ x: ctx._x, y: ctx._y, a: ctx._a }); break;
      case ']': { const s = stack.pop(); ctx._x = s.x; ctx._y = s.y; ctx._a = s.a; ctx.moveTo(s.x, s.y); } break;
    }
  }
}

ダブルバッファーパターン

オフスクリーンキャンバスに描画し、表示用キャンバスに転送します。ちらつきをなくし、残像効果を実現できます。

const offscreen = document.createElement('canvas');
offscreen.width = canvas.width;
offscreen.height = canvas.height;
const offCtx = offscreen.getContext('2d');

function render() {
  // Draw to offscreen
  offCtx.fillStyle = 'rgba(0, 0, 0, 0.05)'; // trail fade
  offCtx.fillRect(0, 0, offscreen.width, offscreen.height);
  drawParticles(offCtx);

  // Blit to screen
  ctx.drawImage(offscreen, 0, 0);
}

禁止事項

1. 残像効果のために毎フレーム clearRect を呼び出さない

クリアすると残像が消えてしまいます。代わりに半透明の塗りつぶしを使ってください。

// BAD -- kills trails
ctx.clearRect(0, 0, w, h);

// GOOD -- fades previous frame
ctx.fillStyle = 'rgba(0, 0, 0, 0.02)';
ctx.fillRect(0, 0, w, h);
2. アニメーションループ内で getImageData を呼び出さない

getImageData は GPU からデータを読み戻すため、非常に低速です。絶対に必要な場合(例:一度だけ行う色のサンプリング)を除き、使用しないでください。

// BAD -- 60fps GPU readback
function loop() {
  const data = ctx.getImageData(0, 0, w, h); // blocks rendering pipeline
  processPixels(data);
  requestAnimationFrame(loop);
}

// GOOD -- sample once, cache
const colorMap = ctx.getImageData(0, 0, w, h);
function getColor(x, y) {
  const i = (y * w + x) * 4;
  return [colorMap.data[i], colorMap.data[i+1], colorMap.data[i+2]];
}
3. 鮮明さを保つため、必ず DPR を考慮する

DPR によるスケーリングを行わないキャンバスは、Retina ディスプレイでぼやけて表示されます。上記のセットアップセクションを参照してください。

// BAD
canvas.width = 800;
canvas.height = 600;

// GOOD
const dpr = window.devicePixelRatio || 1;
canvas.width = 800 * dpr;
canvas.height = 600 * dpr;
canvas.style.width = '800px';
canvas.style.height = '600px';
ctx.scale(dpr, dpr);
4. 処理負荷の高いループ内でメモリを確保しない

update() や render() 内では、new、オブジェクトスプレッド、配列の作成を行わないでください。すべて事前に確保してください。

// BAD
function update() {
  particles.forEach(p => {
    const force = { x: Math.cos(a), y: Math.sin(a) }; // new object every frame per particle
    p.vx += force.x;
  });
}

// GOOD
let fx = 0, fy = 0; // reuse
function update() {
  for (let i = 0; i < aliveCount; i++) {
    fx = Math.cos(a); fy = Math.sin(a);
    particles[i].vx += fx;
  }
}

クイックリファレンス:サブスキルの読み込み

必要なもの読み込むもの
ノイズの実装、パーティクルプール、フローフィールド、L システム、アトラクターreferences/algorithms.md
アニメーション遷移のタイミングとイージング../motion-principles/SKILL.md
3D ジェネレーティブアート(シェーダー、GPU パーティクル)../threejs-r3f/SKILL.md

コメント