はじめに
Quaternion 入門講座 の三回目。
ほぼ個人用の学習記事です。
前回の記事:
[JavaScript] Quaternion 入門 #02 : なぜオイラー角は壊れるのか
JavaScript / Three.js において、オイラー角がなぜ壊れやすいのかを解説。回転順序の意味、ジンバルロックが起きる必然性、角度加算設計の限界を整理し、Quaternionに進む前の理解を固める。
https://humanxai.info/posts/javascript-quaternion-02-why-euler-breaks/Quaternionでやってはいけない実装集
Quaternion は便利だし安全だが、 「使い方を間違えた瞬間に破綻する」という、扱いにくさも持っている。 今回は、実務で頻発する “やりがちな失敗” を全部まとめて潰す回 だ。
Three.js / JavaScript を前提 として説明する。
Three.js – JavaScript 3D Library
Learn. examples · documentation. Tools. devtools · editor. Community. questions · discord · forum · twitter. Code. github · download. Resources. Three.js ...
https://threejs.org/JavaScript - MDN Web Docs - Mozilla.org
JavaScript(ジャバスクリプト)は、Webサイトに動きやインタラクティブ性(対話性)を追加するためのプログラミング言語で、主にWebブラウザ上で動作し、動的なコンテンツ(ポップアップ、アニメーション、フォームの補助など)を実現します。HTML(構造)とCSS(見た目)と並ぶWebのコア技術で、近年ではサーバーサイド(Node.js)、モバイルアプリ(React Native)、デスクトップアプリ開発など、ブラウザ以外でも広く利用されています。
https://developer.mozilla.org/ja/docs/Web/JavaScript1. Quaternion を “足して” しまうパターン
Quaternion を使い始めた直後、 かなりの確率で一度はやってしまう実装がこれだ。
q.x += dq.x;
q.y += dq.y;
q.z += dq.z;
q.w += dq.w;
見た目は「少しずつ回転を足している」ように見えるが、 これは必ず壊れる。例外はない。
何が問題なのか
Quaternion は 4次元ベクトルの形をしているが、4次元ベクトルではない。
ここが最大の罠だ。
Euler 角は、
- x に角度を足す
- y に角度を足す
- z に角度を足す
という設計が成り立つ。
Quaternion では、これが成り立たない。
Quaternion が表しているのは 「角度の集合」ではなく、 回転そのものを符号化した状態 だ。
そのため、
- 成分を足す
- 少しずつ加算する
という操作は、 回転として意味を持たない値 を作り出してしまう。
どう壊れるか
この実装を続けると、次のような症状が出る。
- 回転が徐々に歪む
- あるフレームで急に跳ねる
- 回転軸が意図せず変わる
- 最終的に正規化できなくなる
原因は単純で、 Quaternion として成立しない状態を作っている からだ。
正しい考え方
Quaternion における回転の合成は、 「足し算」ではなく 積 になる。
- 今の回転
- 追加したい回転
これらを 掛け合わせる ことで、 新しい回転が得られる。
さらに、 浮動小数点誤差は必ず蓄積するため、 正規化はセットで必須 になる。
正しい処理
q.multiply(dq); // 回転を合成する
q.normalize(); // 誤差の蓄積を防ぐ
この2行でやっているのは、
- 回転の意味を壊さずに合成し
- Quaternion として正しい形を保つ
という最低限の保証だ。
Quaternion を扱うときは、 「角度を足す」という発想を完全に捨てる。
更新方法は一つだけ。
回転は積で合成する
このルールを守るだけで、 Quaternion 周りの事故は一気に減る。
2. “lerp で補間”して破綻する例
Three.js には Quaternion.lerp() が用意されているが、
これを 回転の補間 に使うと高確率で破綻する。
q.lerp(target, t);
一見「値が近づいていくから良さそう」に見えるが、 Quaternion の補間に線形補間は向かない。
起きる問題
線形補間は、Quaternion が本来存在する “球面上の滑らかな経路” を無視するため、 回転として成立しにくい。
具体的には次のような症状が出る。
- 回転速度が一定にならず、途中で加速 / 減速する
- 微妙な角度差で回転軸が突然ねじれる
- t を 0→1 に動かすと、途中で破綻したような動きになる
回転というのは本来「丸い軌道」を描くものだ。 線形補間はその性質から外れてしまうため、 回転として不自然な動きを生成する。
正しい処理:slerp を使う
Quaternion の補間で使うべきメソッドはこちら。
q.slerp(target, t);
slerp(Spherical Linear Interpolation)は 球面上の最短経路を補間する アルゴリズムだ。
- 遠回りしない
- 回転軸がねじれない
- 回転速度が自然
- t の変化に対して破綻しない
回転の補間が破綻する問題の大半は、 lerp を slerp に置き換えるだけで解決する。
Quaternion を使うとき、 値を「線形」で動かすという発想は捨てたほうがいい。
補間するなら 必ず slerp。
これが安定して回転させるための基本ルールになる。
3. multiply の“順序”を間違える
Quaternion を使う上で、 最も頻発するトラブルが「順序ミス」だ。
q.multiply(delta); // OK(Three.js)
delta.multiply(q); // NG:全然違う回転になる
見た目は単純だが、 この1行の違いが 回転の意味を根本から変える。
なぜ順序が重要なのか
Quaternion の合成は 非交換 だ。
A × B と B × A は、別物の回転になる。
代数的にも、幾何的にも、 「回す順番が違えば、結果が変わる」のが Quaternion の性質。
この性質自体は問題ではない。 問題なのは、意図した順序で掛けていないことに気付きにくい点だ。
順序ミスで起きる事故
実務では、順序を逆にした瞬間、次のような崩れ方をする。
- カメラが 勝手に横方向へひねられる
- プレイヤーが 地面の上でバレルロール(横転) を始める
- 視点移動の上下・左右が 想定外の軸に混ざる
- 「微調整」したはずが 完全に意図しない方向を向く
これらはすべて同じ問題。
掛ける順番が1行ズレた だけで起きる。
Three.js における正しいルール
Three.js は
左に“現在の回転”、右に“後から適用する回転”
という構造で統一されている。
つまり、
新しい回転 delta を適用したいなら
q.multiply(delta);
これが正しい。
逆に、
delta.multiply(q);
は「delta の座標系から見た q」になるため、 思いもしない方向へ回り始める。
直感的に覚える方法
「今の姿勢にあとから回転を足す」 → multiply(あとから)
これだけ覚えれば十分だ。
Quaternion は順序で簡単に破綻するが、 一度このルールを体に染み込ませれば、 大半の事故は未然に防げるようになる。
4. “正規化し忘れ”て挙動が崩壊する
Quaternion を扱うとき、 最も見落とされやすいのが 正規化(normalize)忘れ だ。
Quaternion は数学的には「長さ1の4次元ベクトル」で定義される。 しかし実装上は浮動小数で計算するため、 multiply を繰り返すだけで、 本来1であるべき長さが少しずつズレていく。
このズレが積もると、 回転の挙動が微妙に歪み始め、 最後には目に見えておかしくなる。
NG例
q.multiply(delta); // このままだと長期的に破綻する
表面上は動いているので気づきにくいが、 何フレームも回転を積み重ねると状態が壊れていく。
発生する問題は次のようなものだ。
- 回転軸が徐々にズレる
- 回転の速度が不安定になる
- ある瞬間に急に跳ねる
- 行列へ変換したときに scale が歪む(重大)
歪んだ Quaternion は 見た目にも破綻が出るレベルで崩れる。
正しい処理
q.multiply(delta);
q.normalize(); // 必須
normalize を呼ぶだけで、 「Quaternion として正しい状態」に戻すことができる。
特に、
- カメラを少しずつ回す
- 回転アニメーションを毎フレーム積む
- ジョイスティック入力を継続して合成する
こういう「累積回転」が発生する場面では絶対に必要になる。
正規化を忘れると、 Quaternion を使っている意味が消えるほど破綻する。
逆に、 normalize を1行追加するだけで全てが安定する。
実務で Quaternion を扱うなら、 この1行はほぼ “儀式” として毎回書いていいレベルだ。
5. Quaternion と Euler を“混ぜて更新”してしまう
Quaternion のトラブルの中でも、 もっとも破壊力が高いのが Euler と Quaternion の混在 だ。
典型的な事故コードはこれ。
object.rotation.x += 0.1; // Euler で更新
object.quaternion.multiply(deltaQ); // Quaternion で更新
見た目は無害だが、 これをやった瞬間から 回転が壊れるカウントダウンが始まる。
なぜ破綻するのか
Three.js の回転は、
rotation(Euler表現)quaternion(Quaternion表現)
この2つを 内部で同期させている。
つまり、
- Euler を書き換える → quaternion が上書きされる
- quaternion を書き換える → Euler が上書きされる
という相互上書きが起こる。
そのため、 両方を同時に更新すると、
- どっちが正なのか
- どっちの状態を採用すべきか
という整合性が崩れ、 結果として回転が破綻する。
実際に起こる事故
この混在を続けると、次のような挙動が現れる。
- 角度が突然ジャンプする
- 回転軸が勝手に固定される
- なぜか特定方向に回しづらくなる
- いつか突然ジンバルロック状態に入る
- slerp も multiply も効かなくなる
特に “特定方向にだけ回せなくなる” という症状は 現場でもよく報告される。
解決策:更新はどちらか一方に統一する
これがすべて。
- 回転処理の主導権を持つのは Quaternion
- Euler は 参照専用(表示・デバッグ用途)
これだけで、 回転挙動が驚くほど安定する。
たとえば、カメラ操作でいうと、
- 実際の回転の更新 → Quaternion
- UIで角度を見たい → Euler(読み取りのみ)
この構造が最も壊れにくい。
Quaternion と Euler を混ぜて書くと、 コードは正しくても回転だけが壊れる。 この罠は Three.js で最初に潰すべきポイントだ。
「どちらか片方で更新する」 これを徹底するだけで、ほとんどの回転バグは消える。
6. “Quaternion を直接生成”して破綻する
Quaternion を理解し始めた頃にやりがちなミスがこれ。
new THREE.Quaternion(0, 0, 0, 0); // 死亡確定
一見「4つ値を入れれば Quaternion が作れる」と思いがちだが、 これは 使った瞬間に壊れる。
なぜダメなのか
Quaternion は 「長さ1の4次元ベクトル」 として定義されている。
しかし (0,0,0,0) は
- 長さ0
- 回転を表す情報ゼロ
- 正規化しても改善しない(0のまま)
つまり “回転の体裁を成していない”。
さらに厄介なのは、
- multiply
- slerp
- setFromEuler
- setFromAxisAngle
などの計算が 全て破綻する ことだ。
(0,0,0,0) の Quaternion を内部に混ぜた瞬間、 そのオブジェクトは「回転として存在しない状態」になる。
こういうコードも危険
new THREE.Quaternion(1, 2, 3, 4); // 実質ランダム回転
見た目は値が入っているが、 正規化しないとランダムに近い回転 になる。
意図しない挙動の原因ナンバーワン。
正しい生成法
Quaternion は 自分で値を入れる構造ではない。
次のいずれかで生成する。
軸+角度から作る
q.setFromAxisAngle(axis, angle);
Euler から作る
q.setFromEuler(euler);
既存の回転を合成(multiply)
q.multiply(delta);
補間(slerp)
q.slerp(target, t);
この4系統だけ使えば、 Quaternion を壊すことはまずない。
Quaternion の「4つの値」を直接触りたくなる気持ちはわかるが、 そこには 実務上メリットが全くない。
回転は「数学オブジェクト」ではなく、 Three.js が提供する変換APIを通して扱うもの と割り切った方が安全に運用できる。
まとめ
Quaternion が“難しい”と言われる理由の多くは、 数学そのものではなく 使い方の落とし穴 にある。
- 足し算で更新しようとすると 壊れる
- lerp で補間すると 破綻する
- multiply の順序を間違えると 崩れる
- 正規化を忘れると 長期的に歪む
- Euler と混在させると 即死する
- 4成分を直接 new すると 事故が確定する
これは「理解が足りないから失敗する」のではなく、 間違えたときの壊れ方が分かりづらい構造だから起こる。
実務レベルでは、このあたりの“やってはいけない操作”さえ避ければ、 Quaternion は安定して扱える。
💬 コメント