オブジェクトを滑らかに回転させる魔法の関数!Qt GUIにおけるQQuaternion::slerp()徹底解説


QQuaternion::slerp()関数は、2つの回転を表す四元数間を球面線形補間(Spherical Linear Interpolation)と呼ばれる手法で滑らかに補間する関数です。これは、3Dグラフィックスやアニメーションなどで、オブジェクトを滑らかに回転させるために広く使用されます。

解説

QQuaternion::slerp()関数は、以下の引数を取ります。

  • t: 補間の度合いを制御するパラメータ。0.0の場合はq1、1.0の場合はq2が返されます。
  • q2: 最後の回転を表す四元数
  • q1: 最初の回転を表す四元数

この関数は、以下の手順で動作します。

  1. q1とq2の間に存在する最短球面経路を計算します。
  2. パラメータtに基づいて、最短球面経路上の点を計算します。
  3. 計算された点を表す四元数を返します。

コード例

#include <QQuaternion>

int main() {
  // 最初の回転を表す四元数
  QQuaternion q1(0.5, 0.5, 0.5, 0.5);

  // 最後の回転を表す四元数
  QQuaternion q2(-0.5, -0.5, -0.5, -0.5);

  // 補間の度合い
  float t = 0.5;

  // 補間された四元数
  QQuaternion q = QQuaternion::slerp(q1, q2, t);

  // qの内容を出力
  std::cout << q.x() << ", " << q.y() << ", " << q.z() << ", " << q.w() << std::endl;

  return 0;
}
  • QQuaternion::slerp()関数は、q1とq2が同じ方向を向いている場合、q1を返します。
  • 補間の度合いtは、0.0と1.0の間の値である必要があります。
  • QQuaternion::slerp()関数は、単位四元数に対してのみ有効です。
  • カメラを滑らかに移動させる
  • アニメーションを作成する
  • オブジェクトを滑らかに回転させる


オブジェクトを滑らかに回転させる

このコードは、立方体を 1 秒かけて X 軸方向に 90 度回転させるものです。

#include <QCoreApplication>
#include <QQuaternion>
#include <QObject>
#include <QTimer>

class MyObject : public QObject {
public:
  MyObject() {
    rotation = QQuaternion(1, 0, 0, 0);
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &MyObject::update);
    timer->start(10);
  }

  void update() {
    // 回転角を更新
    float angle = M_PI / 2 * timer->elapsed() / 1000;
    QQuaternion newRotation = QQuaternion::fromAxisAngle(QVector3D(1, 0, 0), angle);

    // slerp を使って滑らかに回転
    rotation = QQuaternion::slerp(rotation, newRotation, 0.1);

    // オブジェクトを回転
    transform.setRotation(rotation);
  }

private:
  QQuaternion rotation;
  QTransform transform;
  QTimer *timer;
};

int main(int argc, char *argv[]) {
  QCoreApplication app(argc, argv);

  // MyObject インスタンスを作成
  MyObject object;

  // イベントループを実行
  return app.exec();
}

アニメーションを作成する

このコードは、球を 5 秒かけて円を描くようにアニメーションさせるものです。

#include <QCoreApplication>
#include <QQuaternion>
#include <QObject>
#include <QTimer>

class MyObject : public QObject {
public:
  MyObject() {
    position = QVector3D(0, 0, 0);
    rotation = QQuaternion(1, 0, 0, 0);
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &MyObject::update);
    timer->start(10);
  }

  void update() {
    // 回転角を更新
    float angle = M_PI / 2 * timer->elapsed() / 5000;
    QQuaternion newRotation = QQuaternion::fromAxisAngle(QVector3D(0, 1, 0), angle);

    // slerp を使って滑らかに回転
    rotation = QQuaternion::slerp(rotation, newRotation, 0.1);

    // 球の位置を更新
    position += QVector3D(0, 0.1, 0);

    // オブジェクトを更新
    transform.setTranslation(position);
    transform.setRotation(rotation);
  }

private:
  QVector3D position;
  QQuaternion rotation;
  QTransform transform;
  QTimer *timer;
};

int main(int argc, char *argv[]) {
  QCoreApplication app(argc, argv);

  // MyObject インスタンスを作成
  MyObject object;

  // イベントループを実行
  return app.exec();
}

カメラを滑らかに移動させる

このコードは、カメラを 2 秒かけてターゲット位置に移動させるものです。

#include <QCoreApplication>
#include <QQuaternion>
#include <QObject>
#include <QTimer>

class MyCamera : public QObject {
public:
  MyCamera() {
    position = QVector3D(0, 0, 10);
    target = QVector3D(0, 0, 0);
    rotation = QQuaternion::fromLookAt(position - target, QVector3D(0, 1, 0));
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &MyCamera::update);
    timer->start(10);
  }

  void update() {
    // ターゲット方向を更新
    QVector3D direction = target - position;
    direction.normalize();

    // 新しい回転を計算
    QQuaternion newRotation = QQuaternion::fromLookAt(direction, QVector3D(0, 1, 0));

    // slerp を使って滑らかに回転


Lerp (線形補間)

Lerp (Linear Interpolation) は、2 つの値の間を直線で補間する最も単純な方法です。 計算コストが低く、実装も簡単ですが、回転の補間には球面距離を考慮しないため、ぎくしゃくした動きになる可能性があります。

QQuaternion lerp(const QQuaternion& q1, const QQuaternion& q2, float t) {
  return QQuaternion::fromEulerAngles(
      q1.eulerAngles().x() * (1.0f - t) + q2.eulerAngles().x() * t,
      q1.eulerAngles().y() * (1.0f - t) + q2.eulerAngles().y() * t,
      q1.eulerAngles().z() * (1.0f - t) + q2.eulerAngles().z() * t);
}

長所

  • 実装が簡単
  • 計算コストが低い

短所

  • 球面距離を考慮しないため、ぎくしゃくした動きになる可能性がある

Squad (二次曲線補間)

Squad (Quadratic Interpolation) は、3 つの値を使用して 2 つの値の間を二次曲線で補間する方法です。 Lerp よりも滑らかな動きを生成できますが、計算コストが少し高くなります。

QQuaternion squad(const QQuaternion& q0, const QQuaternion& q1,
                 const QQuaternion& q2, float t) {
  float s = t * t;
  float t2 = t * s;
  return q0 * (2.0f * t2 - 3.0f * t + 1.0f) +
         q1 * (t3 - 2.0f * t2 + t) + q2 * s;
}

長所

  • Lerp よりも滑らかな動きを生成できる

短所

  • 計算コストが Lerp よりも少し高い

Cardinal Spline (カーディナルスプライン曲線)

Cardinal Spline は、4 つの値を使用して 2 つの値の間を曲線で補間する方法です。 Squad よりも柔軟な制御が可能で、より複雑な動きを生成できますが、計算コストが高くなります。

QQuaternion cardinalSpline(const QQuaternion& q0, const QQuaternion& q1,
                          const QQuaternion& q2, const QQuaternion& q3,
                          float t) {
  // ... (実装は省略)
}

長所

  • より複雑な動きを生成できる
  • Squad よりも柔軟な制御が可能

短所

  • 計算コストが最も高い

上記以外にも、双曲線補間 (Hyperbolic Interpolation) や射影補間 (Projective Interpolation) などの方法があります。 これらの方法は、より高度な数学的基盤に基づいており、より複雑な動きを生成するために使用できますが、実装と理解がより困難になります。