フーリエ周転円で文字を描く
フーリエ周転円で文字を描く
数日かけて、周転円と呼ばれる回転する円が私の名前の文字を描き出すインタラクティブなデモを作った。その背後にある数学、ハマった罠、そしてどうやってそこから抜け出したかを紹介する。
フーリエ周転円とは?
2D点の列の離散フーリエ変換(DFT)は、回転するベクトルの集合を与える。それらを先端から末尾に並べ、それぞれ固有の周波数と振幅で回転させると、最後のベクトルの先端が好きな閉曲線を描き出す。
N個の複素サンプル点 が与えられると:
各 は一つの周転円の振幅、位相、周波数をエンコードする複素数だ。レンダリング時、 における時刻tのペン位置は:
キャンバス的に言うと:各フレームで全ての回転するベクトルを合計し、チェーンを描き、軌跡をプロットする。
文字パスのサンプリング
最初の課題は文字の輪郭を追跡する、クリーンで順序付けられた2D点の集合を得ることだ。
最初の試み:文字をキャンバスにレンダリングし、ピクセルデータを読み返し、Moore近傍輪郭追跡でエッジピクセルを並べる。理論的には動作するが——細いストロークと鋭い角に対して、トレーサーは1ピクセル幅の先端で2ステップのループに囚われ、終了しない。
第二の試み:極角ソート——全てのエッジピクセルを集め、重心からの角度でソートする。エレガントに聞こえるが、非凸形状では即座に崩壊する。文字Yには3本の腕がある;極スイープは各腕を2回見て、Yではなく星形の混乱を生む。
実際に機能するもの:SVGのpath.getPointAtLength()。
全ての文字はSVGパス(またはキーポイントのポリライン)として定義できる。そして:
const N = 512
const total = path.getTotalLength()
const pts = Array.from({ length: N }, (_, i) => {
const p = path.getPointAtLength(i / N * total)
return { x: p.x, y: p.y }
})これはパス順序で等間隔に配置された点を与える——追跡なし、ソートなし、トポロジーの問題なし。DFTはクリーンに機能する。
Uの問題
文字Uは上部が開いている。しかしDFTは閉じたパスが必要だ——z(t)をからにループするので、始点と終点は同じ点でなければならない。
Uを上部の2つの角を接続して単純に閉じると、開口部に水平な棒が引かれる。それはUではなく、四角いブラケットだ。
試み1:左上から始め、再追跡。
パス:(左上) → 左に下る → 曲線 → 右に上がる → (右上) → 戻りながら再追跡 → (左上)
クロージャはゼロ長。キャップなし。しかしアニメーションは左上角から始まり、最初に描くのが_下向き_のストロークだ。各サイクルの終わりにペンは左上にあり、次のサイクルはすぐに下へ向かう——毎ループで角から底まで非常に目立つ直線ストロークだ。
試み2:底の中央から始める。
パス:
(底中央)→ 左腕を上へ →(左上)→ 戻りながら再追跡 →(底中央)(底中央)→ 右腕を上へ →(右上)→ 戻りながら再追跡 →(底中央)
始点 = 終点 = 曲線の底。サイクル間の「縫い目」は曲線の中央に埋まる——視覚的に見えない。各腕が独立して出たり入ったりして追跡される。
U: [
{x:.5, y:1},
{x:.35, y:.97}, {x:.2, y:.87}, {x:.15, y:.68}, {x:.15, y:0}, // 左腕を上へ
{x:.15, y:.68}, {x:.2, y:.87}, {x:.35, y:.97}, // 再追跡
{x:.5, y:1},
{x:.65, y:.97}, {x:.8, y:.87}, {x:.85, y:.68}, {x:.85, y:0}, // 右腕を上へ
{x:.85, y:.68}, {x:.8, y:.87}, {x:.65, y:.97}, // 再追跡
{x:.5, y:1}, // ゼロクロージャ
]クリーンなU、アーティファクトなし。
Math.minの罠
正規化のために数千の点をMath.min()/Math.max()にスプレッドするときの微妙な問題:
const minX = Math.min(...points.map(p => p.x)) // 💥 大きな配列でRangeErrorJavaScriptはスプレッドを関数の引数に展開する。数千の要素を過ぎると、これはコールスタックをオーバーフローさせる。reduceを使う:
const minX = points.reduce((m, p) => Math.min(m, p.x), Infinity)調和の数
いくつの周転円が必要か?各調和は一つの回転する円を追加する。少なすぎると文字が角を失う。多すぎるとサンプルパスのピクセルレベルのノイズを忠実に再現する。
512点の文字パスには、80〜100個の調和がノイズを増幅せずにクリーンな鋭い角を与える。振幅でソートされたレンダリング(最大の円が最初、最小が最後)も視覚をクリアに保つ——細部が現れる前に大きな構造が見える。
ライブデモ
フルデモはこちら——5つの文字が同時にアニメーション、各文字が独自の色で。
ソース:約200行のバニラJS、ライブラリなし、純粋なキャンバス。DFTはサンプル点のネストされたループだけだ。
次:同じ文字をシードとしたDLA(拡散制限凝集)結晶成長を実験中。粒子が結晶に触れるまでランダムウォークする——別の種類の創発。
