|

Quaternion クォータニオン

Quaternions are peculiar. They are something I’m happy to use if a tool offers them, and are fascinating to explore conceptually, but I wouldn’t normally implement them myself except for learning purposes. They are like complex numbers in 4 dimensions that solve rotation by delving into imaginary dimensions. When available in your tool, they are actually very useful. They are free from gimbal lock, and can simplify the complex handling of rotation, or even let you blend multiple rotations. If all this sounds interesting, please proceed.

クォータニオンは奇妙なものです。ツールがサポートしていれば喜んで使うし、概念的にも非常に魅力的ですが、学習目的以外では自分で実装することはあまりありません。クォータニオンは4次元の複素数のようなもので、仮想の次元に潜り込むことで回転の問題を解くような代物です。使っているツールでサポートされているなら、クォータニオンは実際にとても便利です。ジンバルロックもなく、回転の複雑な処理を単純化したり、複数の回転をブレンドしたりもできます。これらが興味をそそるようなら、先に進んでください。

History of numbers

数の歴史

This might seem like a detour, but let’s take a look back at history. Humans have been expanding the concept of numbers for a long time. It probably began with natural numbers like 1, 2, 3, which naturally correspond to the number of items such as fruits or animals. The concept of zero, representing “nothing”, revolutionized the world, eventually leading to negative numbers that can represent debts or deficits. Ratios and rational numbers likely emerged from the need to measure things like weight, volume, length, etc. The discovery of irrational numbers, like the square root of 2, came as a big surprise to the Greeks, and they were afraid to reveal their findings.

遠回りに思えるかもしれませんが、歴史を振り返ってみましょう。人は長い間をかけて数の概念を広げてきました。おそらく始まりは1、2、3といった、果物や動物の数などに対応する自然数でしょう。「何もない」ことを表すゼロの概念は世界を革新し、次第に借金や赤字を表すことができる負の数も生まれました。比や有理数は、重量、容量、長さなどを測定する必要から生まれたのでしょう。ギリシャ人は2の平方根のような無理数に驚き、その発見が広まることを恐れました。

Mathematicians eventually encountered the square root of -1 in the 16th century, which led to the concept of imaginary and complex numbers. Every time new kind of numbers were discovered, they must have appeared abstract and unreal, as we can imagine from the names like ‘irrational’ and ‘imaginary’ (Though ‘irrational’ doesn’t mean a lack of rationality, it means ‘can’t be expressed as a ratio’.). But these numbers have proven to be very powerful and interesting, both practically and conceptually. They are necessary for describing our world too, as in general relativity or quantum physics, and in that sense, they are quite substantial too.

やがて数学者たちは16世紀に-1の平方根を見つけ、虚数と複素数の概念を導き出します。新しい種類の数が発見されるたび、それらは抽象的で非現実的に見えたことでしょう。それは、Irrational やI maginary のような名前からも想像できます(ただし、Irrational は理性のないといった意味ではなく、比として表現できないという意味です)。しかし、これらの数は、実用的にも概念としても非常に強力で興味深いことが示されました。一般相対性理論や量子物理学のように、私たちの世界を説明するためにも必要なこれらの数は、その意味で非常に重要かつ実質的だと言えるでしょう。

Quaternions are a relatively recent addition to these different kinds of numbers in the 19th century. To understand them, it helps to start from complex numbers, a direct ancestor of quaternions.

クォータニオンは比較的新しく、19世紀にこれら様々な数の仲間に加わりました。これを理解するには、クォータニオンの直接の祖先である複素数から始めるのが良いでしょう。

Complex numbers

複素数

The square root of a number is a value that, when squared, returns the original number, such as 4=2\sqrt{4} = 2. The square root of a positive number can be located on the number line as another real number (Usually, there are two real numbers that can be squared to give the same real number. They are distinguished as n\sqrt{n} and n-\sqrt{n}.) But the square root of a negative number cannot be placed on a number line. For instance, there isn’t a real number that squares to -1. This could have been a dead end, but mathematicians did what they do, to think ‘what if’. What if such a number exists? How would it behave?

数の平方根は、2乗すると元の数になる値で、例えば4=2\sqrt{4} = 2のようなものです。正の数の平方根は、実数として数直線上に配置することができます(通常、2乗すると同じ数になる実数は2つあり、n\sqrt{n}n-\sqrt{n}として区別されます)。しかし、負の数の平方根は数直線上に配置することはできません。例えば、2乗すると-1になる実数は存在しません。行き止まりのように思えますが、数学者たちはいつものように考えました。 もしそんな数が存在するとしたら、それはどのように振る舞うのでしょう。

The number was given a name, “imaginary number” and a symbol ii. i=1i = \sqrt{-1}, that’s the definition. Since it doesn’t fit on the real number line, we imagine it on a separate, perpendicular imaginary number line. This lets us place square roots of negative numbers on this new axis. For example, 2\sqrt{-2} is 2i\sqrt{2}i, and 16\sqrt{-16} is 4i4i. This extends the number system one step further, literally to a new dimension.

その数には「虚数」という名前と ii という記号が与えられました。i=1i = \sqrt{-1} というのが定義です。これは実数の数直線上にはおけないので、もう1つ、垂直に交わる虚数の数直線を思い浮かべます。これで、負の平方根をこの新しい軸に配置できるようになりました。例えば、2\sqrt{-2}2i\sqrt{2}i で、16\sqrt{-16}4i4i です。これで数のシステムが文字通り新たな次元に拡張されます。

Let’s consider these two number lines as axes on a plane. This plane, which has the real axis and the imaginary axis, is called the “complex plane”. A point on this plane, represented by a combination of a real number and an imaginary number, is a “complex number”. Examples of complex numbers are 2+1i2 + 1i, 22i.\sqrt{2} - 2i. 00, 3-3, or 1i1i are also considered complex numbers. They are just on either or both axes. Or in general, a complex number is of the form a+bia+bi, where a and b are any real numbers.

この2つの数直線を平面上の軸と考えましょう。実数軸と虚数軸を持つこの平面は、「複素平面」と呼ばれます。この平面上の点は、実数と虚数の組み合わせで表される「複素数」です。例えば 2+1i2 + 1i22i\sqrt{2} - 2i などは複素数です。003-31i1i なども複素数と考えられ、どちらかまたは両方の軸の上に置かれます。一般に、複素数は a+bia+ bi の形をしていて、a abb は任意の実数です。

Below is the graph plotting these numbers.

下はこれらの数字をプロットしたグラフです。

At this point, you might be wondering where this is leading. Surprisingly, complex numbers can actually represent rotation. Let’s start from 11. Then multiply it by ii, which is 1i1i or just ii. Multiply it again by ii, which is 1-1. If we repeat this, the next is i-i, then it comes back to 11… Doesn’t this look like rotating a point by 90 degrees each step?

ここまできましたが、話はどこへ向かっているのでしょう。なんと複素数は実際に回転を表すことができます。11から始めて ii を掛けると 1i1i または単に ii になります。さらにもう一度 ii を掛けてみると、1-1 になります。これを繰り返すと、次は i-i、そして 11 に戻ります。これはステップごとに90度、点を回転させているように見えないでしょうか。

Here is one more example with a smaller step. If we start from 1 and continue multiplying by 3/2+1/2i=cos(30)+isin(30)\sqrt{3} / 2 + 1/2i = \cos(30^\circ) + i \sin(30^\circ), we get this graph (Try calculating by yourself!)

もっと小さなステップの例をもう1つ挙げてみましょう。1から始めて、3/2+1/2i=cos(30)+isin(30)\sqrt{3} / 2 + 1/2i = \cos(30^\circ) + i \sin(30^\circ)を掛け続けると、次のグラフができます(自分でも計算して見ましょう)。

In general, a complex number that represents a rotation is cos(θ)+isin(θ)\cos(\theta) + i \sin(\theta). By multiplying this by a complex number, you can rotate it by θ\theta on the complex plane. This is just one step away from Euler’s identity, eiπ+1=0e^{i\pi} + 1 = 0, which some people claim is the most beautiful formula. We could easily spend more time and words on this, but instead now we’re finally moving onto quaternion!

一般的に、回転を表す複素数は cos(θ)+isin(θ)\cos(\theta) + i \sin(\theta) です。これを別の複素数に掛けると、その数を複素平面上で θ\theta だけ回転させることができます。ここから Eulerの恒等式、最も美しい公式だと主張する人もいる eiπ+1=0e^{i\pi} + 1 = 0 まではほんの一歩です。この式についてはいくらでも深掘りできますが今は置いておいて、ついにクォータニオンに進むことにしましょう。

These graphs are made with Python. You can find the code here on Google Colab.
これらのグラフはPythonで作成しました。コードはGoogle Colabにあります

Quaternion

To recap, a complex number is of the form a+bia+bi, where ii is the imaginary unit defined as i2=1i^2=-1. A complex number can be seen as a point on a 2D plane called the complex plane.

振り返ると複素数は a+bia+bi の形で、この iii2=1i^2=-1 と定義される虚数単位です。複素数は、複素平面と呼ばれる2D平面上の点として考えることができます。

Instead of just one imaginary unit, quaternions introduce three units ii, jj, and kk (in addition to the real unit 11) They have the following relationships to each other.

クォータニオンは、ただ1つの虚数単位ではなく、(実数の単位1に加えて)iijjkkの3つの単位を導入します。これらには互いに以下の関係があります。

i2=j2=k2=ijk=1.i^2 = j^2 = k^2 = ijk = -1.

ij=ji=k,jk=kj=i,ki=ik=jij = -ji = k, jk = -kj = i, ki = -ik = j

And a quaternion is expressed as a+bi+cj+dka + bi + cj + dk, where aabbccdd, are real numbers.

クォータニオンは、a+bi+cj+dka + bi + cj + dkと表わされ、ここで aabbccdd は実数です。

This is very fascinating, as these rules indicate that multiplying quaternions can move a point across different regions in 4D space, just like multiplying complex numbers does so in 2D space. But how could we actually use it in practice?

これらのルールはとても面白く、クォータニオンを掛け合わせると、複素数を掛けて2D空間で行ったように、4D空間の異なる領域をに点で移動できることを示しています。しかし、実際のどのように使えるのでしょうか。

As you can see from ij=jiij = -ji and so on, quaternions are non-commutative, meaning the result will change if you change the order of operations.
ij=jiij = -ji などから分かるように、クォータニオンは非可換、つまり計算の順序を入れ替えると結果が変わります。

Below is the demo of a quaternion in action. This looks identical to the other demo with Rodrigues’ rotation formula, but it uses a quaternion behind the scenes. In fact, any rotation that can be expressed with Euler angles, Rodrigues’ formula (i.e., axis and angle), or a matrix can also be expressed by a quaternion.

下は、クォータニオンの動作のデモです。これはロドリゲスの回転公式を使った別のデモと全く同じに見えますが、裏ではクォータニオンを使っています。実際にオイラー角やロドリゲスの公式(つまり、軸と角度)、または行列で表現できる回転は全て、クォータニオンでも表現できます。

The key part of this demo is the Quaternion class, which implements the basic operations of a quaternion.

このデモの中心になるのは、クォータニオンの基本的な操作を実装したQuaternionクラスです。

Four coefficients

四つの係数

As you can see in the constructor, this class has four parameters, a, b, c, and d to capture the four coefficients for the real part, i, j, and k. A quaternion can express any rotation with just these four numbers.

コンストラクタを見れば分かるように、このクラスには実部、iijjkk に対する4つの係数を格納するための4つのパラメーター、aabbccdd があります。クォータニオンはこれら4つの数字だけで任意の回転を表現できます。

constructor(a = 1, b = 0, c = 0, d = 0) {
	this.a = a; // Real part
	this.b = b; // Coefficient of i
	this.c = c; // Coefficient of j
	this.d = d; // Coefficient of k
}

Axis and angle to Quaternion

軸と角度からクォータニオンを作る

For example, the rotation about a normalized vector u=(ux,uy,uz)\vec{u} = (u_x, u_y, u_z) as the axis by the angle θ\thetacan be converted to a quaternion qq using the formula below.

たとえば、正規化されたベクトル u=(ux,uy,uz)\vec{u} = (u_x, u_y, u_z) を軸とした角度 θ\theta の回転は、以下の式を使ってクォータニオン qq に変換できます。

α=θ2\alpha = \frac{\theta}{2} q=cos(α)+uxsin(α)i+uysin(α)j+uzsin(α)kq = \cos(\alpha) + u_x \cdot \sin(\alpha) \cdot i + u_y \cdot \sin(\alpha) \cdot j + u_z \cdot \sin(\alpha) \cdot k

This is implemented as the setFromAxisAngle() function.

これはsetFromAxisAngle()関数として実装されています。

setFromAxisAngle(axis, angle) {
    const normalizedAxis = axis.copy().normalize();
    const halfAngle = angle / 2;
    const sinHalfAngle = Math.sin(halfAngle);

    this.a = Math.cos(halfAngle);
    this.b = normalizedAxis.x * sinHalfAngle;
    this.c = normalizedAxis.y * sinHalfAngle;
    this.d = normalizedAxis.z * sinHalfAngle;
		return this;
}

Multiplication

掛け算

To understand how to apply a quaternion, we need to learn how to multiply two quaternions. This is essentially the same as ordinary polynomial multiplication, but the difference is in the rules for multiplying the imaginary units ii, kk, and jj. For example, if you multiply ii by jj, it becomes kk. With these rules in mind, try calculating step by step on your own to see if the following makes sense.

クォータニオンの適用方法を理解するには、まず2つのクォータニオンの掛け合わせる方法を学ぶ必要があります。基本的は普通の多項式の掛け算と同じですが、虚数単位 iikkjj を掛けるルールに違いがあります。例えば、iijj に掛けると kk になります。これらのルールを頭に入れた上で、自分で手順を追って計算して下記が正しいか試してみましょう。

q×r=(qa+qbi+qcj+qdk)(ra+rbi+rcj+rdk)=(qaraqbrbqcrcqdrd)+(qarb+qbra+qcrdqdrc)i+(qarcqbrd+qcra+qdrb)j+(qard+qbrcqcrb+qdra)k\begin{aligned} q \times r &= (q_a + q_bi + q_cj + q_dk)(r_a + r_bi + r_cj + r_dk) \\ &= (q_a r_a - q_b r_b - q_c r_c - q_d r_d) \\ &\quad + (q_a r_b + q_b r_a + q_c r_d - q_d r_c)i \\ &\quad + (q_a r_c - q_b r_d + q_c r_a + q_d r_b)j \\ &\quad + (q_a r_d + q_b r_c - q_c r_b + q_d r_a)k \end{aligned}

This is what the multiply() function does.

これが multiply() 関数の行う内容です。

multiply(quaternion) {
    return new Quaternion(
        this.a * quaternion.a - this.b * quaternion.b - this.c * quaternion.c - this.d * quaternion.d,
        this.a * quaternion.b + this.b * quaternion.a + this.c * quaternion.d - this.d * quaternion.c,
        this.a * quaternion.c - this.b * quaternion.d + this.c * quaternion.a + this.d * quaternion.b,
        this.a * quaternion.d + this.b * quaternion.c - this.c * quaternion.b + this.d * quaternion.a
    );
}

Applying to a vector

ベクトルに適用する

To apply a quaternion to a vector to rotate it, we can follow the following steps.

ベクトルを回転させるためにクォータニオンを適用するには、下記の手順に従います。

Convert the Vector to a Quaternion

ベクトルをクォータニオンに変換する

First, convert the vector to rotate to a quaternion so that it can be multiplied with the quaternion that represents the rotation. If the vector to rotate is v\vec{v}, it becomes a quaternion vq=0+vxi+vyj+vzkv_q = 0 + \vec{v}_x i + \vec{v}_y j + \vec{v}_z k.

まず、回転させるベクトルをクォータニオンに変換し、回転を表すクォータニオンと掛け合わせられるようにします。回転させるベクトルが v\vec{v} の場合、クォータニオンは vq=0+vxi+vyj+vzkv_q = 0 + \vec{v}_x i + \vec{v}_y j + \vec{v}_z k になります。

Find the conjugate of the rotation quaternion

回転クォータニオンの共役数を求める

We need something called the “conjugate” of the rotation quaternion. If the original rotation quaternion is q=a+bi+cj+dkq = a + bi + cj + dk, its conjugate is q=abicjdkq* = a - bi - cj - dk, i.e., the original quaternion with the coefficients for ii, jj, k multiplied by -1.

回転クォータニオンの「共役数」と呼ばれる数が必要になります。元の回転クォータニオンが q=a+bi+cj+dkq = a + bi + cj + dk の場合、その共役数は q=abicjdkq* = a - bi - cj - dk 、つまり元のの i, j, k に -1をかけた物です。

Multiply the Quaternions

クォータニオンを掛け合わせる

Then we multiply these three quaternions we have. Qrotated=q×vq×qQ_{rotated} = q \times v_q \times q*

次に、これらの3つのクォータニオンを掛け合わせます。Qrotated=q×vq×qQ_{rotated} = q \times v_q \times q*

Put it back to a vector

ベクトルに戻す

Finally, we convert it back to a vector (Qrotatedx,Qrotatedy,Qrotatedz)(Q_{rotated}x, Q_{rotated}y, Q_{rotated}z). This is the desired rotated vector.

最後に、ベクトル (Qrotatedx,Qrotatedy,Qrotatedz)(Q_{rotated}x, Q_{rotated}y, Q_{rotated}z) に戻します。これが求めていた回転後のベクトルです。

And this function below is the code implementation of these steps.

これらのステップをコードで実装したのが下記の関数です。

applyToVector(vector) {
    const vectorQuat = new Quaternion(0, vector.x, vector.y, vector.z);
    const conjugateQuat = new Quaternion(this.a, -this.b, -this.c, -this.d);
    const rotatedQuat = this.multiply(vectorQuat).multiply(conjugateQuat);
    return createVector(rotatedQuat.b, rotatedQuat.c, rotatedQuat.d);
}

But why?

なぜこんなことをするのか

This might seem too much for just rotating a vector. But once implemented, quaternions have some great advantages over other methods.

  • No Gimbal Lock: Quaternions do not have the gimbal lock problem like Euler angles.
  • Efficiency: Quaternions are more memory-efficient than a matrix (4 numbers vs 9 numbers), and operations are usually cheaper too. Once calculated, a quaternion can be applied to as many vectors as you want.
  • Interpolation and Concatenation: Quaternions can be interpolated. This is really useful for transitioning between two orientations. Quaternions can also be concatenated, or merged together. If you want to apply multiple rotations, you can simply multiply the corresponding quaternions. The result can be stored as a single quaternion and reused.

ベクトルを回転させるだけにしては大袈裟に見えますが、いったん実装するとクォータニオンは他の方法に比べてとても良い点がいくつかあります。

  • ジンバルロックがない:クォータニオンには、オイラー角のようなジンバルロックの問題がありません。
  • 効率性:クォータニオンは行列よりもメモリ効率が良く(数値4つ、数値9つ)、計算も大抵は少なく済みます。また一度クォータニオンを計算したら、同じ変換を多くのベクトルに適用することができます。
  • 補間と連結:クォータニオンは補間することができます。これは2つの向きの間を遷移するのにとても便利です。クォータニオンはまた、連結、または合体することもできます。複数の回転を適用したい場合は、単純に対応するクォータニオンを掛け合わせれば良いのです。掛け合わせた結果は1つのクォータニオンとして保存し、再利用することができます。

The last demo below shows the interpolation between quaternions. Move your mouse around to rotate the box between three different poses. The interpolation of quaternion, referred as slerp (spherical linear interpolation) is defined by the formula below. tt is the interpolation parameter between 0 and 1, and Ω\Omega is the angle between the quaternions, defined as cos(Ω)=q0q1\cos(\Omega) = q_0 \cdot q_1 (the dot product of the quaternions). Slerp is implemented as slerp() in the demo.

下はこのページの最後となる、クォータニオンの補間のデモです。マウスを動かすと、ボックスを3つの異なる姿勢の間で回転できます。クォータニオンの補間は、slerp(球面線形補間 spherical linear interpolation)と呼ばれ、下の式で定義されます。 tt は0と1の間の補間パラメータで、Ω\Omega はクォータニオの角度を表し、cos(Ω)=q0q1\cos(\Omega) = q_0 \cdot q_1(クォータニオンのドット積)と定義されます。slerp はデモの中でslerp()として実装されています。

q(t)=sin((1t)Ω)sin(Ω)q0+sin(tΩ)sin(Ω)q1q(t) = \frac{\sin((1 - t) \Omega)}{\sin(\Omega)} q_0 + \frac{\sin(t \Omega)}{\sin(\Omega)} q_1

const quatA = new Quaternion().setFromAxisAngle(createVector(0, 1, 0), 0);
const quatB = new Quaternion().setFromAxisAngle(createVector(0, 1, 0), PI);
const quatC = new Quaternion().setFromAxisAngle(createVector(0, 0, 1),  PI );

let quat = quatA;
quat = quat.slerp(quatB, mouseX / width);
quat = quat.slerp(quatC, mouseY / height);

points = points.map(p => quat.applyToVector(p));

To learn more

もっと学ぶために

That’s it. This was quite complicated, but quaternions are really useful. I hope this page can help understanding them when you encounter them elsewhere (For example, quaternions are a standard way to represent rotations in Unity and other gaming engines). Beyond that, the idea of handling rotation in 4D itself is very intriguing. To learn more, you can search and find many documents, but they are mostly math-heavy. For someone more visually oriented, I recommend the page below. They have amazing visualizations of various concepts without compromising mathematical accuracy.

以上です。かなり複雑でしたが、クォータニオンは本当に便利です。他の場所でクォータニオンを見かけた時に、このページが助けになることを願っています(例えば、クォータニオンはUnityで回転を表す標準的な方法で、他の多くのゲームエンジンでも同様です)。便利なだけではなく、四次元で回転の扱うという考え方自体もとても刺激的です。もっと詳しく知りたければ検索でたくさんのドキュメントを見つけられますが、多くのページは数学的に難解です。ビジュアル指向の方には、下のページをお勧めします。このページでは様々な概念が、数学的な正確性を損なうことなく見事に映像化されています。

It is also useful to look at a good implementation as an example. For instance, you can take a look at the ofQuaternion class in openFrameworks.

良い実装の例を見るのも役に立ちます。例えば、openFrameworksofQuaternionクラスを見て見ましょう。