私がしたいこと:
カメラを次のように機能させる方法を理解しようとしています。
- マウスの動き: カメラが回転します
- 上/下キー: カメラが前後に移動します。forward はカメラが向いている方向を意味します
- 左/右キー: カメラが横に動きます
- Q/E キー: カメラが上下に動きます
私はたくさんのコードを持っているので、あまり多くのコードを使わずに、どのようにそれを行ったかを説明するために最善を尽くします. 私が取り組んでいるプロジェクトは非常に大きく、多くのクラスと型を含む非常に大きなライブラリがあり、理解が困難です。
問題
私はこれをほとんど機能させることができましたが、少し動かした後、いくつかの角度で、物事が失敗し始めます.Upを押すと、カメラが横に動くなど.
私が考えたアルゴリズムを以下に詳しく説明します。
問題は、私が間違ったことをしているのでしょうか? 失敗の原因は何ですか?一日中このカメラのデバッグを試みましたが、何が失敗するのかわかりませんでした。
明確化
これが私が回転をどのように理解したかです: 3D ベクトル (不適切にベクトルと呼ばれる可能性があります)。ここで、各コンポーネントはオブジェクトが回転する軸を意味します。たとえば、X 値は、オブジェクトが X 軸を中心に回転する量です。私は OpenGL で作業しているため、回転値は(ラジアンではなく)度になります。
カメラをレンダリングするときは、カメラの位置を単純に変換しますが、反対の符号を使用します。
回転にも同じことが当てはまります。
glRotatef(-currentCamera->Rotation().X, 1.0f, 0, 0);
glRotatef(-currentCamera->Rotation().Y, 0, 1.0f, 0);
glRotatef(-currentCamera->Rotation().Z, 0, 0, 1.0f);
glTranslatef(-currentCamera->Position().X, -currentCamera->Position().Y, -currentCamera->Position().Z);
私が試したこと(そしてうまくいかなかった):
単純な幾何学と数学、ピタゴラスの定理、単純な三角法を使用してみましたが、惨めに失敗したので、これを機能させるのをやめました。(たとえば、回転座標のいずれかが 0 の場合、結果は NaN になります)。
私が試したこと(そしてうまくいきました...ほとんど):
変換行列の使用。
ユーザーがこれらのキーのいずれかを押すと、3d ベクトルが生成されます。
+X = right; -X = left
+Y = top; -Y = bottom
+Z = backward (towards camera); -Z = forward (away from camera)
次に、変換行列を生成します。3 つの座標 (X、Y、Z) のそれぞれについて、単位 (4x4 行列) に回転行列を 3 回掛けます。次に、作成したベクトルに行列を適用し、その結果をカメラの古い位置に追加します。
ただし、この方法には問題があるようです。最初は問題なく動作しますが、しばらくすると Up を押すと、本来の方向ではなく横に移動します。
実際のコード
上で述べたように、できるだけ少ないコードを使用しようとしました。ただし、これで十分でない場合は、実際のコードを次に示します。最も関連性の高いコードのみを選択するように最善を尽くしました。
// ... Many headers
// 'Camera' is a class, which, among other things, it has (things relevant here):
// * Position() getter, SetPosition() setter
// * Rotation() getter, SetRotation() setter
// The position and rotation are stored in another class (template), 'Vector3D <typename T>',
// which has X, Y and Z values. It also implements a '+' operator.
float angle; // this is for animating our little cubes
Camera* currentCamera;
// 'Matrix' is a template, which contains a 4x4 array of a generic type, which is public and
// called M. It also implements addition/subtraction operators, and multiplication. The
// constructor memset's the array to 0.
// Generates a matrix with 1.0 on the main diagonal
Matrix<float> IdentityMatrix()
{
Matrix<float> res;
for (int i = 0; i < 4; i++)
res.M[i][i] = 1.0f;
return res;
}
// I used the OpenGL documentation about glRotate() to write this
Matrix<float> RotationMatrix (float angle, float x, float y, float z)
{
Matrix<float> res;
// Normalize; x, y and z must be smaller than 1
if (abs(x) > 1 || abs(y) > 1 || abs(z) > 1)
{
// My own implementation of max which allows 3 parameters
float M = Math::Max(abs(x), abs(y), abs(z));
x /= M; y /= M; z /= M;
}
// Vars
float s = Math::SinD(angle); // SinD and CosD convert the angle to degrees
float c = Math::CosD(angle); // before calling the standard library sin and cos
// Vector
res.M[0][0] = x * x * (1 - c) + c;
res.M[0][1] = x * y * (1 - c) - z * s;
res.M[0][2] = x * z * (1 - c) + y * s;
res.M[1][0] = y * x * (1 - c) + z * s;
res.M[1][1] = y * y * (1 - c) + c;
res.M[1][2] = y * z * (1 - c) - x * s;
res.M[2][0] = x * z * (1 - c) - y * s;
res.M[2][1] = y * z * (1 - c) + x * s;
res.M[2][2] = z * z * (1 - c) + c;
res.M[3][3] = 1.0f;
return res;
}
// Used wikipedia for this one :)
Matrix<float> TranslationMatrix (float x, float y, float z)
{
Matrix<float> res = IdentityMatrix();
res.M[0][3] = x;
res.M[1][3] = y;
res.M[2][3] = z;
return res;
}
Vector3D<float> ApplyMatrix (Vector3D<float> v, const Matrix<float>& m)
{
Vector3D<float> res;
res.X = m.M[0][0] * v.X + m.M[0][1] * v.Y + m.M[0][2] * v.Z + m.M[0][3];
res.Y = m.M[1][0] * v.X + m.M[1][1] * v.Y + m.M[1][2] * v.Z + m.M[1][3];
res.Z = m.M[2][0] * v.X + m.M[2][1] * v.Y + m.M[2][2] * v.Z + m.M[2][3];
return res;
}
// Vector3D instead of x, y and z
inline Matrix<float> RotationMatrix (float angle, Vector3D<float> v)
{
return RotationMatrix (angle, v.X, v.Y, v.Z);
}
inline Matrix<float> TranslationMatrix (Vector3D<float> v)
{
return TranslationMatrix (v.X, v.Y, v.Z);
}
inline Matrix<float> ScaleMatrix (Vector3D<float> v)
{
return ScaleMatrix (v.X, v.Y, v.Z);
}
// This gets called after everything is initialized (SDL, OpenGL etc)
void OnStart()
{
currentCamera = new Camera("camera0");
angle = 0;
SDL_ShowCursor(0); // Hide cursor
}
// This gets called periodically
void OnLogicUpdate()
{
float delta = .02; // How much we move
Vector3D<float> rot = currentCamera->Rotation();
Vector3D<float> tr (0, 0, 0);
Uint8* keys = SDL_GetKeyState(0);
// Cube animation
angle += 0.05;
// Handle keyboard stuff
if (keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT]) delta = 0.1;
if (keys[SDLK_LCTRL] || keys[SDLK_RCTRL]) delta = 0.008;
if (keys[SDLK_UP] || keys[SDLK_w]) tr.Z += -delta;
if (keys[SDLK_DOWN] || keys[SDLK_s]) tr.Z += delta;
if (keys[SDLK_LEFT] || keys[SDLK_a]) tr.X += -delta;
if (keys[SDLK_RIGHT] || keys[SDLK_d]) tr.X += delta;
if (keys[SDLK_e]) tr.Y += -delta;
if (keys[SDLK_q]) tr.Y += delta;
if (tr != Vector3D<float>(0.0f, 0.0f, 0.0f))
{
Math::Matrix<float> r = Math::IdentityMatrix();
r *= Math::RotationMatrix(rot.X, 1.0f, 0, 0);
r *= Math::RotationMatrix(rot.Y, 0, 1.0f, 0);
r *= Math::RotationMatrix(rot.Z, 0, 0, 1.0f);
Vector3D<float> new_pos = Math::ApplyMatrix(tr, r);
currentCamera->SetPosition(currentCamera->Position() + new_pos);
}
}
// Event handler, handles mouse movement and ESCAPE exit
void OnEvent(SDL_Event* e)
{
const float factor = -.1f;
if (e->type == SDL_MOUSEMOTION)
{
// Is mouse in the center? If it is, we just moved it there, ignore
if (e->motion.x == surface->w / 2 && e->motion.y == surface->h / 2)
return;
// Get delta
float dx = e->motion.xrel;
float dy = e->motion.yrel;
// Make change
currentCamera->SetRotation(currentCamera->Rotation() + World::Vector3D<float>(dy * factor, dx * factor, 0));
// Move back to center
SDL_WarpMouse(surface->w / 2, surface->h / 2);
}
else if (e->type == SDL_KEYUP)
switch (e->key.keysym.sym)
{
case SDLK_ESCAPE:
Debug::Log("Escape key pressed, will exit.");
StopMainLoop(); // This tells the main loop to stop
break;
default: break;
}
}
// Draws a cube in 'origin', and rotated at angle 'angl'
void DrawCube (World::Vector3D<float> origin, float angl)
{
glPushMatrix();
glTranslatef(origin.X, origin.Y, origin.Z);
glRotatef(angl, 0.5f, 0.2f, 0.1f);
glBegin(GL_QUADS);
glColor3f(0.0f,1.0f,0.0f); // green
glVertex3f( 1.0f, 1.0f,-1.0f); // Top Right Of The Quad (Top)
glVertex3f(-1.0f, 1.0f,-1.0f); // Top Left Of The Quad (Top)
glVertex3f(-1.0f, 1.0f, 1.0f); // Bottom Left Of The Quad (Top)
glVertex3f( 1.0f, 1.0f, 1.0f); // Bottom Right Of The Quad (Top)
glColor3f(1.0f,0.5f,0.0f); // orange
glVertex3f( 1.0f,-1.0f, 1.0f); // Top Right Of The Quad (Bottom)
glVertex3f(-1.0f,-1.0f, 1.0f); // Top Left Of The Quad (Bottom)
glVertex3f(-1.0f,-1.0f,-1.0f); // Bottom Left Of The Quad (Bottom)
glVertex3f( 1.0f,-1.0f,-1.0f); // Bottom Right Of The Quad (Bottom)
glColor3f(1.0f,0.0f,0.0f); // red
glVertex3f( 1.0f, 1.0f, 1.0f); // Top Right Of The Quad (Front)
glVertex3f(-1.0f, 1.0f, 1.0f); // Top Left Of The Quad (Front)
glVertex3f(-1.0f,-1.0f, 1.0f); // Bottom Left Of The Quad (Front)
glVertex3f( 1.0f,-1.0f, 1.0f); // Bottom Right Of The Quad (Front)
glColor3f(1.0f,1.0f,0.0f); // yellow
glVertex3f( 1.0f,-1.0f,-1.0f); // Bottom Left Of The Quad (Back)
glVertex3f(-1.0f,-1.0f,-1.0f); // Bottom Right Of The Quad (Back)
glVertex3f(-1.0f, 1.0f,-1.0f); // Top Right Of The Quad (Back)
glVertex3f( 1.0f, 1.0f,-1.0f); // Top Left Of The Quad (Back)
glColor3f(0.0f,0.0f,1.0f); // blue
glVertex3f(-1.0f, 1.0f, 1.0f); // Top Right Of The Quad (Left)
glVertex3f(-1.0f, 1.0f,-1.0f); // Top Left Of The Quad (Left)
glVertex3f(-1.0f,-1.0f,-1.0f); // Bottom Left Of The Quad (Left)
glVertex3f(-1.0f,-1.0f, 1.0f); // Bottom Right Of The Quad (Left)
glColor3f(1.0f,0.0f,1.0f); // violet
glVertex3f( 1.0f, 1.0f,-1.0f); // Top Right Of The Quad (Right)
glVertex3f( 1.0f, 1.0f, 1.0f); // Top Left Of The Quad (Right)
glVertex3f( 1.0f,-1.0f, 1.0f); // Bottom Left Of The Quad (Right)
glVertex3f( 1.0f,-1.0f,-1.0f); // Bottom Right Of The Quad (Right)
glEnd();
glPopMatrix();
}
// Gets called periodically
void OnRender()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
// Camera movement
glRotatef(-currentCamera->Rotation().X, 1.0f, 0, 0);
glRotatef(-currentCamera->Rotation().Y, 0, 1.0f, 0);
glRotatef(-currentCamera->Rotation().Z, 0, 0, 1.0f);
glTranslatef(-currentCamera->Position().X, -currentCamera->Position().Y, -currentCamera->Position().Z);
// Draw some cubes
for (float i = -5; i <= 5; i++)
for (float j = -5; j <= 5; j++)
{
DrawCube(World::Vector3D<float>(i*3, j * 3, -5), angle + 5 * i + 5 * j);
}
SDL_GL_SwapBuffers();
}
ご覧のとおり、簡単な例を作成するのは非常に困難です。背後で非常に多くのことが起こっており、非常に多くのクラスとデータ型があるためです。
その他ボーナスアイテム
実行可能ファイルもアップロードしました(うまくいけば動作します)。これにより、私が話している問題を確認できます。