← 記事一覧へ

パーティクルフローフィールド — 粒子と力場のシミュレーション

数百の粒子が時間変化するベクトル場に沿って流れる Canvas シミュレーションアートです。クリックで風向きを変えられます。

作品

380 個の粒子が、sin / cos を重ね合わせた「ベクトル場」の流れに沿ってキャンバス上を漂います。 粒子の軌跡は半透明の暗いオーバーレイで徐々に消え、絶えず変化する模様を作り出します。

Flow Field — Canvas particle simulationクリック / タップで風向きを変える

クリック / タップするとフィールドの時間軸がジャンプし、風向きが急変します。


フローフィールドとは

フローフィールド(Flow Field)は平面上のすべての点に「向き(角度)」を持ったベクトルを定義したものです。 粒子はそのベクトルに従って進み、多数の軌跡が重なることで複雑な模様が生まれます。

 ↗ → ↘ ↓ ↙ ← ↖    ← 各格子点にベクトルが定義されている
 ↑ ↗ → ↘ ↓ ↙ ←
 ↖ ↑ ↗ → ↘ ↓ ↙

ベクトル場の定義

このアートでは sin / cos の重ね合わせで角度を決める数式を使っています。 時間 t を変数に含めることで、フィールドが時間とともに変化し続けます。

function fieldAngle(x: number, y: number, t: number): number {
  const nx = x / SIZE  // 0〜1 に正規化
  const ny = y / SIZE

  return (
    Math.sin(nx * 4.5 + t * 0.35) * Math.cos(ny * 3.2 + t * 0.22) +
    Math.cos(nx * 2.1 - ny * 1.8 + t * 0.14)
  ) * Math.PI
}

各係数を変えると模様が変わります。係数の比が整数に近いほど規則的な渦が生まれ、 無理数比に近いほど複雑でカオス的なパターンになります。


パーティクルの移動

粒子は毎フレーム、自分の現在地から fieldAngle を求めて速度を更新します。 速度をいきなり変えず 0.88 の慣性係数を掛けることで、動きが滑らかになります。

const angle = fieldAngle(p.x, p.y, t)

// 慣性あり:急に向きが変わらず、流れるような動きになる
p.vx = p.vx * 0.88 + Math.cos(angle) * speed * 0.12
p.vy = p.vy * 0.88 + Math.sin(angle) * speed * 0.12

p.x += p.vx
p.y += p.vy

画面外に出るか寿命(life)が尽きた粒子は、ランダムな位置で新生します。


残像(トレイル)効果

スピログラフではキャンバスを clearRect で完全に消去してから描き直していました。 フローフィールドでは消去しないことが重要です。

代わりに毎フレームの最初に「極めて薄い暗い矩形」を重ねます。

ctx.fillStyle = 'rgba(13, 17, 23, 0.06)'  // 不透明度 6% の暗いオーバーレイ
ctx.fillRect(0, 0, SIZE, SIZE)

これにより古い軌跡は 1 フレームごとに 6% ずつ暗くなり、約 16 フレームで消えます。 結果として「長い尾を引きながら流れる」ように見えます。

✓ TIP

オーバーレイの不透明度が高いほど尾が短くなり、低いほど長く残ります。0.01〜0.15 の範囲で試すと印象が大きく変わります。


クリックで風向きを変える

クリック時は tOffset を大きくジャンプさせることで、 フィールドの時間軸を飛ばして「急な風向き変化」を表現しています。

function shiftField() {
  tOffset += 4 + Math.random() * 3  // 4〜7 秒分ジャンプ
}

滑らかに変化するフィールドの流れが突然乱れ、 渦や吹き返しが生まれる瞬間を楽しめます。


手法まとめ

技術性質本作での役割
requestAnimationFrame60fps ループフレームごとに粒子を更新
sin / cos 合成連続・周期的時間変化するベクトル場
半透明オーバーレイ積算描画残像・トレイル効果
慣性係数低域フィルタ動きの滑らかさ
⚠ WARNING

粒子数を増やすと描画コストが上がります。モバイルでは COUNT を 200 程度に下げるとフレームレートが安定します。