🤭
Next.js + WebAssembly (Rust) で3D回転変換ツールを作った
作ったもの
3D回転表現を相互変換できるWebツール「Attitude Converter」を作りました。

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で十分です。ただ:
- 型安全性: 回転計算は符号ミスが致命的。Rustで防げる
- 学習目的: Rust + WASMの実践として
- 将来性: 重い計算(姿勢推定等)を追加するかも
ハマったポイント
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読み込みでハマった
回転表現で困ったときに使ってください。
バグ報告・機能要望はGitHubへ:
Discussion