視点の回転(クォータニオン)

視点を回転させます。
回転方法にはいろいろとあります。
ロールピッチヨー、オイラー角、クォータニオンなどです。
ロールピッチヨーはZ軸、Y軸、X軸を順番に回転させて目的の角度にする方法です。
飛行機の姿勢を表す時によく使われます。最も解り易く素直な方法です。
オイラー角はZ軸、X軸(またはY軸)、Z軸と回転させて目的の角度にする方法です。
3つの軸を使わなくても2つの軸があれば目的の角度にたどり着きます。
しかし、ロールピッチヨーとオイラー角には欠点があります。ジンバルロックと呼ばれるものです。
たとえば、X軸を90度傾けると、Y軸とZ軸が同軸となってしまい、その姿勢を保存して
また新たに回転をしようとしたときに問題がおきます。
X軸とY軸をそれぞれ90度傾けると全ての軸が同軸となり一つの方向にしか回せなくなります。
それを解消する方法がクォータニオンです。
クォータニオンは軸を任意に作成して回転します。つまりジンバルロックは起きません。
今回はクォータニオンから回転行列を作成して適用するプログラムを作りました。
演算子のオーバーロードも使っています。プログラムは、かなり短くしたつもりです。
ちなみに管理人はクォータニオンの計算が何をしているのか理解していません。
公式(ブラックボックス)を使っているのみです。
ウインドウ内でマウスボタンを押しながらマウスを移動させると自由回転します。

 

ファイル
main.cpp

main.cpp

#pragma comment(linker, "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
#include <GL/freeglut/freeglut.h>
#include <math.h>

#define WIDTH 320
#define HEIGHT 240

#define PAI 3.14159

//オレンジ
GLfloat orange[] = { 1.0f, 0.6f, 0.0f, 1.0f };
//ライトの位置
GLfloat lightpos[] = { 200.0, 150.0, -500.0, 1.0 };

int Mouse_X, Mouse_Y;

//クォータニオン構造体
struct Quaternion
{
 double w;
 double x;
 double y;
 double z;
};
//回転マトリックス
double Rotate[16];

Quaternion Target;
Quaternion current={ 1.0, 0.0, 0.0, 0.0 };

//演算子のオーバーロード Quaternionの積
Quaternion & operator *( Quaternion &q1, Quaternion &q2 )
{
 Quaternion q0={
  q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z,
  q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
  q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x,
  q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w,
 };
 q1=q0;
    return q1;
}

//クォータニオンから回転行列を算出
void qtor(double *r , Quaternion q)
{
 double xx = q.x * q.x * 2.0;
 double yy = q.y * q.y * 2.0;
 double zz = q.z * q.z * 2.0;
 double xy = q.x * q.y * 2.0;
 double yz = q.y * q.z * 2.0;
 double zx = q.z * q.x * 2.0;
 double xw = q.x * q.w * 2.0;
 double yw = q.y * q.w * 2.0;
 double zw = q.z * q.w * 2.0;
 double r1[16]={ 1.0 - yy - zz, xy + zw, zx - yw, 0.0,
  xy - zw, 1.0 - zz - xx, yz + xw, 0.0,
  zx + yw, yz - xw, 1.0 - xx - yy, 0.0,
  0.0, 0.0, 0.0, 1.0};
 for (int i = 0;i < 16;i++) {
  r[i]=r1[i];
 }
}
void display(void)
{

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 glViewport(0, 0, WIDTH, HEIGHT);
 glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 //視野角,アスペクト比(ウィンドウの幅/高さ),描画する範囲(最も近い距離,最も遠い距離)
 gluPerspective(30.0, (double)WIDTH / (double)HEIGHT, 1.0, 1000.0);
 glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
 //視点の設定
 gluLookAt(150.0,100.0,-200.0, //カメラの座標
      0.0,0.0,0.0, // 注視点の座標
     0.0,1.0,0.0); // 画面の上方向を指すベクトル
 //クォータニオンによる回転
 glMultMatrixd(Rotate);
 //ライトの設定
 glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
 //マテリアルの設定
 glMaterialfv(GL_FRONT, GL_DIFFUSE, orange);

 glutSolidTorus(20.0,40.0,16,16);

 glutSwapBuffers();
}
void idle(void)
{
 glutPostRedisplay();
}
void Init(){
 glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
 glEnable(GL_DEPTH_TEST);
 glEnable(GL_LIGHTING);
 glEnable(GL_LIGHT0);
 qtor(Rotate, current);

}
void mousemove(int x, int y)
{
  //移動量を計算
  double dx = (x - Mouse_X) * 1.33/WIDTH;
  double dy = (y - Mouse_Y) * 1.0/HEIGHT;

  //クォータニオンの長さ
  double length = sqrt(dx * dx + dy * dy);

  if (length != 0.0) {
    double radian = length * PAI;
    double theta = sin(radian) / length;
 Quaternion after={ cos(radian), dy * theta, dx * theta, 0.0};//回転後の姿勢

 Target = after * current;

    qtor(Rotate, Target);
  }
}
void mouse(int button, int state, int x, int y)
{
 if(button){
    switch(state){
    case GLUT_DOWN://マウスボタンを押した位置を記憶
      Mouse_X = x;
      Mouse_Y = y;
      break;
    case GLUT_UP://姿勢を保存
  current=Target;
      break;
    default:
      break;
    }
  }
}
int main(int argc, char *argv[])
{
 glutInitWindowPosition(100, 100);
 glutInitWindowSize(WIDTH, HEIGHT);
 glutInit(&argc, argv);
 glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
 glutCreateWindow("クォータニオンで自由軸回転");
 glutDisplayFunc(display);
 glutMouseFunc(mouse);
 glutMotionFunc(mousemove);
 glutIdleFunc(idle);
 Init();
 glutMainLoop();
 return 0;
}

 

 

 

 

 

 

最終更新:2015年02月09日 00:23
添付ファイル