🤭

Next.js + WebAssembly (Rust) で3D回転変換ツールを作った

に公開

作ったもの

3D回転表現を相互変換できるWebツール「Attitude Converter」を作りました。

https://attitude-converter.tompython.com/

GitHub: https://github.com/chantakan/attitude-converter

なぜ作ったか

3D回転の表現方法は複数あります。

表現 用途例
Quaternion Unity、ゲームエンジン、航空宇宙
Euler角 航空宇宙、ロボティクス
MRP 学術論文、姿勢制御
Axis-Angle 物理シミュレーション
回転行列 画像処理、コンピュータビジョン

異なるライブラリやツール間でデータをやり取りするとき、表現の変換が必要になります。手計算は面倒だし、ミスも起きやすい。そこでブラウザで動く変換ツールを作りました。

機能

5種類の回転表現に対応

  • Quaternion (w, x, y, z)
  • Euler Angles (12種類の回転順序)
  • MRP (Modified Rodrigues Parameters)
  • Axis-Angle
  • Rotation Matrix

どれか1つを変更すると、他の表現がリアルタイムで更新されます。

12種類のオイラー角順序

オイラー角は回転順序で結果が変わります。よく使われるZYX(Yaw-Pitch-Roll)だけでなく、12種類すべてに対応しました。

Tait-Bryan角: XYZ, XZY, YXZ, YZX, ZXY, ZYX
固有オイラー角: XYX, XZX, YXY, YZY, ZXZ, ZYZ

ジンバルロック検出

オイラー角の第2角が±90°に近づくと警告を表示します。

MRPシャドウセット

MRPは |σ|² > 1 で不安定になります。Auto Shadowモードで自動的にシャドウセットに切り替え、常に |σ|² ≤ 1 を保証します。

3Dビュー

React Three Fiberで回転を可視化。リングをドラッグして直接操作もできます。

技術スタック

Frontend: Next.js 14 + TypeScript + React Three Fiber
Core:     Rust → WebAssembly

なぜRust + WASM?

正直、この規模ならJavaScriptで十分です。ただ:

  1. 型安全性: 回転計算は符号ミスが致命的。Rustで防げる
  2. 学習目的: Rust + WASMの実践として
  3. 将来性: 重い計算(姿勢推定等)を追加するかも

ハマったポイント

wasm-optで動かなくなる

wasm-pack build --releaseで最適化すると、ブラウザで動かなくなりました。

WebAssembly.Table.grow(): failed to grow table by 4

原因はwasm-optとexternrefの互換性問題。解決策:

Cargo.toml
[package.metadata.wasm-pack.profile.release]
wasm-opt = false

パフォーマンスへの影響は体感ゼロです。

Next.jsでWASMを読み込む

Webpackとの相性が難しかったです。最終的にpublic/に置いて動的インポート:

const wasm = await import(/* webpackIgnore: true */ '/wasm/attitude_wasm.js');
const wasmBuffer = await (await fetch('/wasm/attitude_wasm_bg.wasm')).arrayBuffer();
await wasm.default(wasmBuffer);

コード抜粋

Rust側:クォータニオンからオイラー角

fn quaternion_to_euler(q: &Quaternion, order: EulerOrder) -> (f64, f64, f64) {
    let m = q.to_rotation_matrix();
    
    match order {
        EulerOrder::ZYX => {
            let sin_a2 = -m[2][0];
            if sin_a2.abs() >= 1.0 - EPSILON {
                // ジンバルロック
                let a1 = (-m[0][1]).atan2(m[0][0]);
                (a1, sin_a2.signum() * PI / 2.0, 0.0)
            } else {
                (m[1][0].atan2(m[0][0]), sin_a2.asin(), m[2][1].atan2(m[2][2]))
            }
        }
        // 他の11種類も同様に実装
    }
}

TypeScript側:WASM呼び出し

const convertFromQuaternion = useCallback(
  (w: number, x: number, y: number, z: number) => {
    if (!wasm) return null;
    const json = wasm.convert_from_quaternion(w, x, y, z, 'ZYX', true);
    return JSON.parse(json);
  },
  [wasm]
);

RustからはJSON文字列を返し、TypeScript側でパースしています。serde-wasm-bindgenより安定しました。

まとめ

  • 3D回転表現の変換ツールを作った
  • Rust + WASMで計算、Next.js + R3Fでフロントエンド
  • wasm-optとNext.jsのWASM読み込みでハマった

回転表現で困ったときに使ってください。

https://attitude-converter.tompython.com/

バグ報告・機能要望はGitHubへ:
https://github.com/chantakan/attitude-converter

Discussion