[JavaScript] Quaternion 入門 #04 : multiply の順序は何を意味しているのか

はじめに

第3回までの流れを軽く振り返る:

  • オイラー角は壊れる
  • Quaternionは壊れにくい
  • でも扱いを間違えると壊す

そして第4回からは “Quaternionが何をしているのか”を触って分かる回。

ここで初めて「回転=作用」という感覚が入ってくる。

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 の順序が変わるだけで結果が大きく変わる。

これが次につながるテーマ:

  1. 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 が難しいのではない。 「回転=作用」「順序に意味がある」 この感覚を知らないだけだ。