はじめに
第3回までの流れを軽く振り返る:
- オイラー角は壊れる
- Quaternionは壊れにくい
- でも扱いを間違えると壊す
そして第4回からは “Quaternionが何をしているのか”を触って分かる回。
ここで初めて「回転=作用」という感覚が入ってくる。
[JavaScript] Quaternion 入門 #03 : やってはいけない実装集(JavaScript / Three.js)
Three.js / JavaScript でQuaternionを扱う際に頻発する“やってはいけない実装”をまとめた記事。足して壊れる例、lerpによる破綻、multiplyの順序ミス、正規化の欠落、Eulerとの混在など、実務者が陥りやすいミスを具体的に解 …
https://humanxai.info/posts/javascript-quaternion-03-common-mistakes/1. 回転は「足すもの」ではなく「掛けるもの」
オイラー角では、回転は「角度を足していく」ものとして扱う。
rotation.x += 0.1;
これは直感的で分かりやすいが、 回転が複数重なると軸そのものが変形してしまい、壊れる原因になる。
Quaternion はまったく別の考え方をしている。
- Euler → 足す(加算)
- Quaternion → 掛ける(作用)
ここを最初にしっかり分けておく。
1.1 「足す」ではなく「作用させる」
Quaternion の回転は、角度を加算しているのではなく、
「ある回転を、対象に“適用する”」
という動作になっている。
例えるなら:
A.applyRotation(B)
// 「B という回転の作用を A に与える」
というイメージ。
加算ではなく、“作用の適用” だと考えるほうが正しい。
1.2 multiply は「回転を掛ける」=作用の合成
Three.js の場合:
A.multiply(B);
は
- A という回転に
- B の回転という作用を追加で掛ける
ことを意味する。
このとき、
- 「先にAで軸が回っている」
- 「その後にBがその軸に従って作用する」
という流れになる。
足し算ではなく、作用の順番が存在する掛け算 だからこそ、 順序が変わると結果も変わる。
1.3 この章で伝えたいポイント
ここではまだ math(行列・四元数の数式)には触れない。
大事なのはただ一つ:
Quaternion は “回転を合成するための掛け算” をしている =「どちらの回転がどちらに作用するか」で結果が変わる
この感覚が掴めれば、 multiply の順序がなぜ重要なのかが自然に理解できる。
2. 絶対回転(world)と相対回転(local)の違い
Quaternion の multiply がなぜ難しいか? その理由の 95% は「どの軸で回っているのか」が混乱しているから だ。
回転には必ず、
- 世界(ワールド)全体から見た回転
- オブジェクト自身の軸に対する回転
の二つが存在する。
2.1 ワールド回転(absolute)
「世界座標の X/Y/Z の軸に対して回る」
たとえば、 世界の上方向(Y軸)に対して 90° 回す。 世界の前方向(Z軸)に対して回す。
回転の基準が 世界に固定 されている。
例:
カメラを “世界のY軸に沿って” 水平方向に回す。 キャラを “世界の上方向” に対して向きを揃える。
2.2 ローカル回転(relative)
「そのオブジェクトが今どんな向きを向いているかに応じて回る」
オブジェクト自身の軸(local X/Y/Z)が毎回回転するため、 回転するたびに軸そのものが更新される。
つまり、
- “自分の右方向に90°回る”
- “自分の前方向に傾く”
のように、基準は常に その物体自身。
2.3 Three.js の multiply が意味するもの
Three.js の Quaternion は 右から掛ける 形式になっている。
quaternion.multiply(rot);
これは、
rot の回転を「自分のローカル軸」で適用する
という意味。
つまり:
- multiply → ローカル回転
- (逆順で)rot.multiply(quaternion) → ワールド回転に見える挙動(ただし実務ではほぼ使わない)
2.4 なぜこれが大事なのか?
ローカル軸は、回転すればするほど “軸が変形していく”。 だから multiply の順序が変わるだけで結果が大きく変わる。
これが次につながるテーマ:
- multiply の順序が逆になると結果が変わる理由
3. multiply の順序が逆になると結果が変わる理由
ここが第4回の本丸。 Quaternion の multiply は「掛け算」だが、普通の掛け算のように順番を入れ替えても同じ結果にはならない。
理由はシンプルで、かつ本質的だ。
3.1 結論:回転は交換法則が成り立たない
A × B と B × A は必ず違う。
数学的に言えば“非可換(non-commutative)”。 でもここでは数学の話は不要。
大事なのはこれだけ:
回転を適用するたびに「軸そのものが回転する」から 順序を変えると結果が変わる。
3.2 どっちの回転軸が先に変形されるか
回転には「作用が適用される順番」が存在する。
たとえば、
- A = 先に適用される回転
- B = 後から適用される回転
だとすると、
後から掛けた回転 B は、
“A で既にグルっと回ってしまった軸” に沿って作用する
これが ローカル回転 の構造。
逆に、
- B を先に適用して
- A を後から適用すると
A は、
“まだ回っていない元の軸” を基準に作用する
これが ワールド回転に近い挙動。
3.3 イメージ図(文章で理解する)
● A × B
- まず A がオブジェクトの向きを変える
- 次に B を掛けると → その時点で「A によって回転した軸」に B が作用する
- = ローカル回転になる
● B × A
- まず B が世界基準で作用する
- 次に A が掛かると → まだ変形されていない“元の軸”に対する回転として作用する
- = ワールド回転に近い結果になる
読者はここで「ああ、軸が回るから順番で変わるのか」と一気に理解する。
3.4 だから multiply の順序は絶対に重要
結論として:
- 同じ2つの回転でも A×B と B×A では意味が違う
- multiply は「どっちの回転がどっちの軸に作用するか」を決めている
- その軸は、毎回回転によって変形していく
この“軸が変わる”という事実こそ、 Euler が壊れる理由でもあり、Quaternion の本質でもある。
4. Three.js での“右から掛ける”の実際
Three.js の Quaternion は、右から作用を掛ける(right-multiply) 形式になっている。 multiply の実装は次のような意味を持つ。
this.multiply(q);
// this ← this * q (右掛け)
つまり、
q の回転が「this のローカル軸」に対して適用される (=ローカル回転になる)
4.1 multiply は “ローカル軸での回転適用”
Three.js では基本的に、この書き方がほとんどの用途で使われる:
this.quaternion.multiply(rot);
これは
- “このオブジェクト自身の軸”
- “現在の向きで歪んだローカル軸”
に対して rot の回転を追加で与える、という意味。
✔ キャラの向きを変える
✔ カメラのピッチ/ヨーを適用
✔ モデルの傾き調整
全部この「ローカル軸の回転」で成立している。
4.2 逆順 multiply(左掛け)は基本使わない
一応こういう書き方も可能ではある:
rot.multiply(this.quaternion);
ただしこれは、
- rot の軸
- 世界の軸(world基準)
に対して回転を適用するような挙動になる。
Three.js の実務(ゲーム / WebXR / 3D UI)では
ほとんど使わない
理由は簡単で:
- キャラやカメラは常に「自分の軸」で回したい
- わざわざワールド基準で apply したいケースが少ない
- 逆順 multiply を使うと挙動が読みにくい
つまり通常用途では 右掛け(this.multiply(q))一択。
4.3 言い換えると?
- 右掛け → ローカル軸に作用
- 左掛け → ワールド軸に近い作用
Three.js が採用しているのは「右掛け」。
だから multiply の使い方を正しく理解すると、 “ローカル回転の挙動が直感的に読める” ようになる。
5. 「右から掛ける」「左から掛ける」を直感で理解する
multiply の順序が変わると結果が変わる理由は、 「どの回転がどの軸に作用するか」が変わるから。
それを直感で理解するために、 ロボットの腕を例にして考える。
5.1 ロボットの手を “上げる” 回転と “腕をひねる” 回転
たとえば 2 つの回転を用意する:
- A:手を上げる(前に倒す)
- B:腕全体をひねる(水平回転)
同じ2つの回転でも、掛ける順序が違うと全く違う結果になる。
5.2 A × B(手を上げてから腕をひねる)
手を上げる × 腕をひねる
- 先に「手を上げる」で腕が前に上がる
- その後「腕をひねる」をかけると → “上がった方向に対して” 水平回転が作用する
つまり、「上がった手」が左右に振られる。
5.3 B × A(腕をひねってから手を上げる)
腕をひねる × 手を上げる
- 先に「腕をひねる」(水平回転)
- 次に「手を上げる」をかけると → まだ垂直のままの軸に対して手が上がる
結果、
- 手の上がる方向が全く違う
5.4 順序で軸が変わる=結果が変わる
この例で重要なのはただ一つ:
先に作用した回転が“軸そのもの”を回してしまう。
そのため、
-
右から掛けた場合(local) → すでに曲がった軸に対して作用する
-
左から掛けた場合(world に近い) → まだ曲がっていない軸に対して作用する
結果は当然変わる。
5.5 だから multiply の順序には意味がある
この直感例だけで、
- なぜ回転が交換できないのか
- なぜ順序を変えると挙動が変わるのか
- なぜ multiply が重要なのか
全部説明できる。
Quaternion の本質は「軸そのものを回しながら回転を合成する」 そのため順序は本質的に意味を持つ。
6. よくある間違い(実例)
multiply の順序を誤ると、 Three.js / JavaScript の Quaternion はあっという間に“壊れたように”見える。
ここでは、初心者〜中級者が実際に踏む代表的な事故をまとめる。
6.1 「Yaw → Pitch」のつもりが、軸がズレて思わぬ方向に曲がる
よくある誤解:
Y軸まわりに水平回転 → X軸まわりに縦回転
こう書いているのに、
- なぜか頭が斜めに傾く
- カメラが変な方向に回る
- 上を向いたあとに左右に振れない
その原因はたった一つ:
Yaw をかけたあと、Pitch の“軸そのものが”回されているから
つまり、
- 正しく掛ければ「ローカルのX軸でPitch」
- 間違えると「世界のX軸でPitch」になり、方向が変わる
6.2 multiply の順序ミスでキャラが「変な方向に飛んでいく」
キャラクターの前方向に速度を与えているのに:
- 右に飛ぶ
- 上に飛ぶ
- 斜め上にすっ飛ぶ
- 回転の後に突然方向が乱れる
こういう“暴走挙動”が起きる。
原因は単純で、
“速度ベクトルをどの軸に変換するか” の順番を間違えている
- local 回転後に forward を取るべきところを
- world 回転前に forward を取ってしまう
ただそれだけで向きは完全に狂う。
6.3 初心者が「Quaternion怖い!」と思う理由の9割はこれ
Quaternion の数学が難しいわけではない。 壊れるわけでもない。
単に:
- 軸が回る
- multiply の順序でその“軸の状態”が変わる
この事実を知らずに触るから、挙動が読めずに“怖い”と感じるだけ。
この記事の内容が腑に落ちれば、 Quaternion の恐怖はほぼ消える。
7. 結論(第4回の着地点)
Quaternion の multiply は、ただの掛け算ではない。 回転という“作用”を、どの順序で合成するかを表している。
- multiply = 回転の“作用”の合成
- 右から掛ける → 自分の ローカル軸 に対する回転
- 左から掛ける → 外部(ワールド軸)に近い回転
- 順序で結果が変わるのは、回転によって軸そのものが回るから
ここを理解すると、
- キャラクターの向き制御
- カメラの yaw / pitch 処理
- WebXR の視線・姿勢制御
といった 「回転が絡む処理」が一気に安定する。
Quaternion が難しいのではない。 「回転=作用」「順序に意味がある」 この感覚を知らないだけだ。
💬 コメント