パーティクルフローフィールド — 粒子と力場のシミュレーション
数百の粒子が時間変化するベクトル場に沿って流れる Canvas シミュレーションアートです。クリックで風向きを変えられます。
作品
380 個の粒子が、sin / cos を重ね合わせた「ベクトル場」の流れに沿ってキャンバス上を漂います。 粒子の軌跡は半透明の暗いオーバーレイで徐々に消え、絶えず変化する模様を作り出します。
クリック / タップするとフィールドの時間軸がジャンプし、風向きが急変します。
フローフィールドとは
フローフィールド(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 フレームで消えます。 結果として「長い尾を引きながら流れる」ように見えます。
オーバーレイの不透明度が高いほど尾が短くなり、低いほど長く残ります。0.01〜0.15 の範囲で試すと印象が大きく変わります。
クリックで風向きを変える
クリック時は tOffset を大きくジャンプさせることで、
フィールドの時間軸を飛ばして「急な風向き変化」を表現しています。
function shiftField() {
tOffset += 4 + Math.random() * 3 // 4〜7 秒分ジャンプ
}
滑らかに変化するフィールドの流れが突然乱れ、 渦や吹き返しが生まれる瞬間を楽しめます。
手法まとめ
| 技術 | 性質 | 本作での役割 |
|---|---|---|
requestAnimationFrame | 60fps ループ | フレームごとに粒子を更新 |
| sin / cos 合成 | 連続・周期的 | 時間変化するベクトル場 |
| 半透明オーバーレイ | 積算描画 | 残像・トレイル効果 |
| 慣性係数 | 低域フィルタ | 動きの滑らかさ |
粒子数を増やすと描画コストが上がります。モバイルでは COUNT を 200 程度に下げるとフレームレートが安定します。