レベルエンター山本大のブログ

面白いプログラミング教育を若い人たちに

BLOCKVROCKリファレンス目次はこちら

クォータニオン(Quaternion)をAframeで基礎から学ぶ

クォータニオン(Quaternion)は3次元空間の回転を表現するものです。 回転軸(3次元のベクトル)と回転角で成り立っているので、4要素があるので四元数(Quaternion)といいます。

A-frameでのrotationはオイラー角という方法で表現されています。これはxyzの3つの軸で回転する方法でわかりやすいのですが、複雑な回転になると使いづらいシーンがあります。

たとえば、すでに傾きのある物体に横回転を加えようと思います。

以下の動画では、左側の直方体をクリックするとオイラー角でY軸を中心に10度づつ回転するようにしました。 右側の直方体をクリックすると、クオータニオンで物体の上下を軸に10度づつ回転します。

f:id:iad_otomamay:20210516220346g:plain
クオータニオン回転とオイラー回転

オイラー角での回転は、Y軸の回転を徐々に増やすという書き方をしました。 今回のサンプルのようにすでに角度の付いた物体への回転は軸のずれたような回転になります。

this.el.addEventListener("click",()=>{
    this.el.object3D.rotation.y += 10;
});

クオーターニオンでの回転は複雑に見えますが、あるクオータニオンと別のクオータニオン内積を取ることで、ある回転した状態(姿勢)に別の回転をくわえた姿勢への移行が簡単に表現できます。

// Y軸のベクトル({x:0 y:0 z:0}から{x:0 y:1 z:0}のベクトルを作り回転させる)
this.yAxis = new THREE.Vector3( 0, 1, 0 );
// クオータ二オンの枠
this.quaternion = new THREE.Quaternion();
// 軸を中心に10度回線させるクオータ二オンを生成。(パイはラジアン角で180度)
this.quaternion.setFromAxisAngle( this.yAxis, Math.PI / 18   );

this.el.addEventListener("click",()=>{
    // クオーターニオンの内積によって現在の回転から、引数の回転を加えた姿勢に移行する。
    this.el.object3D.quaternion.multiply(this.quaternion );
});

上記と同じY軸10度のクオータニオンの生成は、以下のようにも書くことができます。 {x:0 y:1 z:0}のベクトル軸を中心に10度回線させるクオータ二オンをコンストラクタで一発で生成しています。 そしてnormalizeによって単位ベクトルに変換しておきます。単位ベクトルとは方向だけを表して長さを持たないベクトルです。

this.quaternion = new THREE.Quaternion(0, 1, 0, Math.PI / 18).normalize();

他にも、クォータニオン表現ではオイラー角表現で生じるような特異点ジンバルロック)が存在しなかったりと、とても便利な機能をもっていて3D空間で回転を扱うには必須の知識です。

コード全文

<html>
  <head>
    <meta charset="utf-8" />
    <title>quaternion sample</title>
    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
  </head>
  <script type="text/javascript">
    AFRAME.registerComponent('quaterinon-rotation', {
      init: function () {
        // {x:0 y:0 z:0}から{x:0 y:1 z:0}のベクトル軸を中心に10度回線させるクオータ二オンを生成。(パイはラジアン角で180度)
        this.quaternion = new THREE.Quaternion(0, 1, 0, Math.PI / 18).normalize();

        this.el.addEventListener('click', () => {
          // クオーターニオンの内積によって現在の回転から、引数の回転を加えた姿勢に移行する。
          this.el.object3D.quaternion.multiply(this.quaternion);
        });
      },
    });
    AFRAME.registerComponent('euler-rotation', {
      init: function () {
        this.el.addEventListener('click', () => {
          this.el.object3D.rotation.y += 10;
        });
      },
    });
  </script>
  <body>
    <a-scene>
      <a-text value="click quaternion rotation" position="1 2 -4" color="black"></a-text>
      <a-box quaterinon-rotation position="2 1 -4" rotation="0 0 15" scale="1 1 2" init color="red"></a-box>

      <a-text value="click euler rotation" position="-2 2 -4" color="black"></a-text>
      <a-box euler-rotation position="-1 1 -4" rotation="0 0 15" scale="1 1 2" color="red"></a-box>

      <a-camera>
        <a-entity cursor="rayOrigin: mouse" />
      </a-camera>
    </a-scene>
  </body>
</html>