2

I have looked at a ton of quaternion examples on several sites including this one, but found none that answer this, so here goes...

I want to orbit my camera around a large sphere, and have the camera always point at the center of the sphere. To make things easy, the sphere is located at {0,0,0} in the world. I am using a quaternion for camera orientation, and a vector for camera position. The problem is that the camera position orbits the sphere perfectly as it should, but always looks one constant direction straight forward instead of adjusting to point to the center as it orbits.

This must be something simple, but I am new to quaternions... what am I missing?

I'm using C++, DirectX9. Here is my code:

// Note: g_camRotAngleYawDir and g_camRotAnglePitchDir are set to either 1.0f, -1.0f according to keypresses, otherwise equal 0.0f
// Also, g_camOrientationQuat is just an identity quat to begin with.

float theta = 0.05f;
D3DXQUATERNION g_deltaQuat( 0.0f, 0.0f, 0.0f, 1.0f );
D3DXQuaternionRotationYawPitchRoll(&g_deltaQuat, g_camRotAngleYawDir * theta, g_camRotAnglePitchDir * theta, g_camRotAngleRollDir * theta);
D3DXQUATERNION tempQuat = g_camOrientationQuat;
D3DXQuaternionMultiply(&g_camOrientationQuat, &tempQuat, &g_deltaQuat);

D3DXMatrixRotationQuaternion(&g_viewMatrix, &g_camOrientationQuat);
g_viewMatrix._41 = g_camPosition.x;
g_viewMatrix._42 = g_camPosition.y;
g_viewMatrix._43 = g_camPosition.z;

g_direct3DDevice9->SetTransform( D3DTS_VIEW, &g_viewMatrix );

[EDIT - Feb 13, 2012]

Ok, here's my understanding so far:

move the camera using an angular delta each frame. Get a vector from center to camera-pos. Call quaternionRotationBetweenVectors with a z-facing unit vector and the target vector. Then use the result of that function for the orientation of the view matrix, and the camera-position goes in the translation portion of the view matrix.

Here's the new code (called every frame)...

//  This part orbits the position around the sphere according to deltas for yaw, pitch, roll
D3DXQuaternionRotationYawPitchRoll(&deltaQuat, yawDelta, pitchDelta, rollDelta);

D3DXMatrixRotationQuaternion(&mat1, &deltaQuat);

D3DXVec3Transform(&g_camPosition, &g_camPosition, &mat1);

// This part adjusts the orientation of the camera to point at the center of the sphere

dir1 = normalize(vec3(0.0f, 0.0f, 0.0f) - g_camPosition);

QuaternionRotationBetweenVectors(&g_camOrientationQuat, vec3(0.0f, 0.0f, 1.0f), &dir1);


D3DXMatrixRotationQuaternion(&g_viewMatrix, &g_camOrientationQuat);

g_viewMatrix._41 = g_camPosition.x;
g_viewMatrix._42 = g_camPosition.y;
g_viewMatrix._43 = g_camPosition.z;


g_direct3DDevice9->SetTransform( D3DTS_VIEW, &g_viewMatrix );

...I tried that solution out, without success. What am I doing wrong?

jessejuicer
  • 107
  • 3
  • 10
  • Do you know that the view matrix isn't the transform of the camera in world space, but the transform from world space into the cameras coordinate system (the inverse)? – rasmus Feb 14 '12 at 08:11
  • 1
    Try doing `D3DXMatrixInverse(&g_viewMatrix, NULL, &g_viewMatrix);` before `SetTransform`. – kloffy Feb 14 '12 at 16:59
  • That certainly fixed one issue I was having. Thanks! – jessejuicer Feb 15 '12 at 07:01

2 Answers2

2

It should be as easy as doing the following whenever you update the position (assuming the camera is pointing along the z-axis):

direction = normalize(center - position)
orientation = quaternionRotationBetweenVectors(vec3(0,0,1), direction)

It is fairly easy to find examples of how to implement quaternionRotationBetweenVectors. You could start with the question "Finding quaternion representing the rotation from one vector to another".

Here's an untested sketch of an implementation using the DirectX9 API:

D3DXQUATERNION* quaternionRotationBetweenVectors(__inout D3DXQUATERNION* result, __in const D3DXVECTOR3* v1, __in const D3DXVECTOR3* v2)
{
    D3DXVECTOR3 axis;

    D3DXVec3Cross(&axis, v1, v2);
    D3DXVec3Normalize(&axis, &axis);

    float angle = acos(D3DXVec3Dot(v1, v2));

    D3DXQuaternionRotationAxis(result, &axis, angle);

    return result;
}

This implementation expects v1, v2 to be normalized.

Community
  • 1
  • 1
kloffy
  • 2,928
  • 2
  • 25
  • 34
0

You need to show how you calculate the angles. Is there a reason why you aren't using D3DXMatrixLookAtLH?

rasmus
  • 3,136
  • 17
  • 22
  • Well, I was using D3DXMatrixLookAtLH, but since part of the formula I was using called for an "up" vector, the camera would freak out at the north and south poles of the sphere. I think this is known as Gimbal Lock? I read that using quaternions can avoid this problem. I had actually also tried rotating my up vector as the camera orbited the sphere, since I thought it might avoid the problem. That was just throwing a dart in the dark though, and it didn't work out. – jessejuicer Feb 14 '12 at 02:48
  • Regarding calculating the angles, I'm really just incrementing by an angle of 0.05f each frame a key is held down. Here are the details: the "a" key makes yawDir = 1.0f, and the "d" key makes yawDir -1.0f. If neither of those keys are pressed, then yawDir = 0.0f; Then, I call UpdateCameraMatrices() every frame, which contains the code in my first post. yawDir is then multiplied by theta (0.05f) to give an angle of either 0.05f, -0.05f, or 0.0f for yaw. I do the same thing for pitch. Roll never changes from 0.0f. – jessejuicer Feb 14 '12 at 02:59
  • The gimbal lock problem is easily avoided by calculating a suitable up-vector each frame. – rasmus Feb 14 '12 at 08:09
  • rasmus: Having tried for quite awhile now, I have to ask, how to do you calculate a suitable up-vector? I tried rotating my up vector as the camera orbited the sphere, so that the up vector matched the new orientation of the camera- meaning it was always straight up from the camera's perspective. That didn't work. Is that an incorrect approach? – jessejuicer Jun 05 '12 at 19:41
  • From the top of my head: `up = cross(camPos - centerOfSphere, camMatrixInverse * vec3(1,0,0))`, where `camMatrixInverse` is from the previous frame and `camPos` is the new position this frame. Don't forget to normalize `up`. Please accept my answer if this works for you. – rasmus Jun 06 '12 at 20:23