I'm currently using D3DXMatrixLookAtLH to implement a fixed view of a 3d scene, and having done some digging around, there seems to be a few schools of thought about which is the better way to update the viewmatrix (read "cameramatrix") when rotation in multiple axes is desired. I don't want to talk about Quaternions here. When we rotate the view around an axis, it messes up the other axes, so obviously it's craptarded to manipulate the vectors used in a call to D3DXMatrixLookAtLH, this is a cheesy solution and it leads to gimbal lock, which is a mathematical effect seen when we rotate 90 degrees on an axis, we have effectively switched some axes around, and everything goes haywire.
A better solution is to build the viewmatrix ourselves, taking into account the combined rotations and / or translations of the camera (us).
Here is a c++ function which is supposed to do it, followed by my asm translation of the same. Unfortunately, mine isn't working, and I'm not sure why.
And so I'm asking for your opinions on this issue, your comments, and your impressions on this solution and if possible, your eagle eye to spot the error in my translation...
My personal first impression was that since we are entirely building the matrix, there seems to be no need for the initial call to create an IdentityMatrix :)
Someone please call me an idiot and tell me that this is EXACTLY what D3DXMatrixLookAtLH does internally, and put me out of my misery :rolleyes:

//-- ---------------------------------------------------------------------------
// Name : updateViewMatrix()
// Desc : Builds a view matrix suitable for Direct3D.
// Here's what the final matrix should look like:
// | rx ux lx 0 |
// | ry uy ly 0 |
// | rz uz lz 0 |
// | -(r.e) -(u.e) -(l.e) 1 |
// Where r = Right vector
// u = Up vector
// l = Look vector
// e = Eye position in world space
// . = Dot-product operation
void updateViewMatrix( void )
D3DXMatrixIdentity( &view );

D3DXVec3Normalize( &g_vLook, &g_vLook );
D3DXVec3Cross( &g_vRight, &g_vUp, &g_vLook );
D3DXVec3Normalize( &g_vRight, &g_vRight );
D3DXVec3Cross( &g_vUp, &g_vLook, &g_vRight );
D3DXVec3Normalize( &g_vUp, &g_vUp );

view._11 = g_vRight.x;
view._12 = g_vUp.x;
view._13 = g_vLook.x;
view._14 = 0.0f;

view._21 = g_vRight.y;
view._22 = g_vUp.y;
view._23 = g_vLook.y;
view._24 = 0.0f;

view._31 = g_vRight.z;
view._32 = g_vUp.z;
view._33 = g_vLook.z;
view._34 = 0.0f;

view._41 = -D3DXVec3Dot( &g_vEye, &g_vRight );
view._42 = -D3DXVec3Dot( &g_vEye, &g_vUp );
view._43 = -D3DXVec3Dot( &g_vEye, &g_vLook );
view._44 = 1.0f;

g_pd3dDevice->SetTransform( D3DTS_VIEW, &view );


Now for my version..

updateViewMatrix proc
local view:D3DXMATRIX
local fptemp:FLOAT
; invoke D3DXMatrixIdentity ,addr view ;unnecessary, since we are filling the entire Matrix

invoke D3DXVec3Normalize, addr g_vLook, addr g_vLook
invoke D3DXVec3Cross, addr g_vRight, addr g_vUp, addr g_vLook
invoke D3DXVec3Normalize, addr g_vRight, addr g_vRight
invoke D3DXVec3Cross, addr g_vUp, addr g_vLook, addr g_vRight
invoke D3DXVec3Normalize, addr g_vUp, addr g_vUp

m2m view._11 , g_vRight.x
m2m view._12 , g_vUp.x;
m2m view._13 , g_vLook.x;
m2m view._14 , fp0

m2m view._21 , g_vRight.y;
m2m view._22 , g_vUp.y;
m2m view._23 , g_vLook.y;
m2m view._24 , fp0

m2m view._31 , g_vRight.z;
m2m view._32 , g_vUp.z;
m2m view._33 , g_vLook.z;
m2m view._34 , fp0

invoke D3DXVec3Dot, addr g_vEye, addr g_vRight
mov fptemp,eax
fld fptemp
fstp view._41

invoke D3DXVec3Dot, addr g_vEye, addr g_vUp
mov fptemp,eax
fld fptemp
fstp view._42

invoke D3DXVec3Dot, addr g_vEye, addr g_vLook
mov fptemp,eax
fld fptemp
fstp view._43
m2m view._44 , fp1

mov ecx,CApp_OnlyInstance
mcall .CApp.g_pd3dDevice,IDirect3DDevice8_SetTransform, D3DTS_VIEW, addr view
updateViewMatrix endp

As always, I appreciate any and all feedback in respect to my humble postings.
Thanks in advance,
Posted on 2003-12-16 02:42:59 by Homer
This may seem a little crass, but would it be a workable solution to calculate the LookAt position vector by projecting a ray (from the Camera Position) an arbitrary distance in the LookIn direction, and then simply using D3DXMatrixLookAtLH to generate an acceptable viewmatrix? Could we use trig to calc this, and would it avoid gimbal lock, given that we can perform trig-based math on fixed axes?
Posted on 2003-12-16 03:11:43 by Homer
Afternoon, EvilHomer2k.

You are an idiot ;) .

The following code has been cut out of my "fly" example. The proggy had a "space-buggy" for the player, plus a spacebuggy for the enemy. The camera was set behind and above the players' spacebuggy (the code for placing the camera behind/above has been removed from the following code).

; the camera (view)

INVOKE D3DXVec3Normalize, ADDR lookat_vector,ADDR lookat_vector
INVOKE D3DXVec3Cross, ADDR right_vector,ADDR up_vector,ADDR lookat_vector
INVOKE D3DXVec3Normalize, ADDR right_vector,ADDR right_vector
INVOKE D3DXVec3Cross, ADDR up_vector, ADDR lookat_vector, ADDR right_vector
INVOKE D3DXVec3Normalize, ADDR up_vector, ADDR up_vector

INVOKE D3DXMatrixRotationAxis, addr rollmatrix, addr lookat_vector, Roll
invoke D3DXVec3TransformCoord, addr right_vector , addr right_vector, addr rollmatrix
invoke D3DXVec3TransformCoord, addr up_vector, addr up_vector, addr rollmatrix

INVOKE D3DXMatrixRotationAxis, addr yawmatrix, addr up_vector, Yaw
invoke D3DXVec3TransformCoord, addr lookat_vector , addr lookat_vector, addr yawmatrix
invoke D3DXVec3TransformCoord, addr right_vector, addr right_vector, addr yawmatrix

INVOKE D3DXMatrixRotationAxis, addr pitchmatrix, addr right_vector, Pitch
invoke D3DXVec3TransformCoord, addr lookat_vector , addr lookat_vector, addr pitchmatrix
invoke D3DXVec3TransformCoord, addr up_vector, addr up_vector, addr pitchmatrix

fst matView.D3DXMATRIX._14
fst matView.D3DXMATRIX._24
fstp matView.D3DXMATRIX._34
fstp matView.D3DXMATRIX._44

fld right_vector.x
fstp matView.D3DXMATRIX._11
fld up_vector.x
fstp matView.D3DXMATRIX._12
fld lookat_vector.x
fstp matView.D3DXMATRIX._13

fld right_vector.y
fstp matView.D3DXMATRIX._21
fld up_vector.y
fstp matView.D3DXMATRIX._22
fld lookat_vector.y
fstp matView.D3DXMATRIX._23

fld right_vector.z
fstp matView.D3DXMATRIX._31
fld up_vector.z
fstp matView.D3DXMATRIX._32
fld lookat_vector.z
fstp matView.D3DXMATRIX._33

INVOKE D3DXVec3Dot, addr position_vector, addr right_vector
fstp matView.D3DXMATRIX._41
INVOKE D3DXVec3Dot, addr position_vector, addr up_vector
fstp matView.D3DXMATRIX._42
INVOKE D3DXVec3Dot, addr position_vector, addr lookat_vector
fstp matView.D3DXMATRIX._43

Some things to note:

rollmatrix, yawmatrix and pitchmatrix are just:

LOCAL rollmatrix:D3DMATRIX
LOCAL pitchmatrix:D3DMATRIX

inside the same proc (since they're recreated each time they're used).

Roll, Yaw and Pitch are angles of the object in radians (meaning... 0 or 2*PI is no rotation, while PI would be a 180 degree rotation).

lookat_vector, right_vector, up_vector and position_vector are just:

lookat_vector D3DXVECTOR3 <0.0f, 0.0f, 1.0f>
up_vector D3DXVECTOR3 <0.0f, 1.0f, 0.0f>
right_vector D3DXVECTOR3 <1.0f, 0.0f, 0.0f>
position_vector D3DXVECTOR3 <0.0f, 10.0f, -30.0f>

inside the programs .data section.
Remember: the camera needs a postion in world space (position_vector) plus a direction (the other vectors).

matView is just your typical D3DMATRIX matrice. It can either be in your .data section or used as LOCALs.

mov ecx,CApp_OnlyInstance
mcall [ecx].CApp.g_pd3dDevice,IDirect3DDevice8_SetTransform, D3DTS_VIEW, addr matView


Using movement/rotation code like this makes it *very* easy to move something around.
To move an object (camera/particle/pig) forward/backwards in the directions it's facing, you just:

fld lookat_vector.x
fmul fpSpeed
fadd position_vector.x
fstp position_vector.x

fld lookat_vector.y
fmul fpSpeed
fadd position_vector.y
fstp position_vector.y

fld lookat_vector.z
fmul fpSpeed
fadd position_vector.z
fstp position_vector.z

just before you build the matrice (i.e. just *after* the last D3DXVec3TransformCoord call).

The D3DXVec3Dot calculations have their signs changed (using fchs) because position_vector is in *world* coordinates, and we're wanting to change it (back) to *view* coordinates.

Posted on 2003-12-16 06:49:47 by Scronty
This is wonderful - I was wondering to myself earlier today how I might manage to combine a PitchYawRoll matrix with a viewmatrix, and you have answered a question which I hadn't even gotten around to answering yet :)
I am humbled by the simplicity of your answer. Thanks !!
I'm grateful as always for your input, and I do appreciate you taking the time to help me out. Every little thing I pick from ur brain brings me closer to realizing my dream of garage Indy, 80's fashion. Dare to dream :alright:
Posted on 2003-12-16 07:27:38 by Homer
Afternoon, EvilHomer2k.

Stop greasing. Ya making me hungry (grease == grilled lamb chops to me).

A few things I forgot to inlcude in my previous post:

The supplied code is used for all objects in a game (whether they be a camera or computer-controlled or the player/s themselves).
This means that it'd actually make sense to chuck that code into its own proc and just call it for every object.

There're no comments in that code as it was copy/pasted from the first commented code.
So I'll comment it now...;)

The D3DXVec3Normalize, D3DXVec3Cross, D3DXVec3Normalize, D3DXVec3Cross, D3DXVec3Normalize lines are to get rid of gimbal-lock. For people who don't know what this is: When you rotate objects on all three axis, a small error creeps into the calculations due to floating-point rounding errors. Recalculating the look/right/up vectors each time eliminates this error.

The D3DXMatrixRotationAxis, D3DXVec3TransformCoord, D3DXVec3TransformCoord, etc lines are for rotation about the X/Y/Z axis (yaw/pitch/roll). Looking at the code, you'll see that every calculation for each rotation matrice (roll/pitch/yaw) updates their opposing vectors. i.e. the Roll matrix updates the up/right vectors. This makes sure that the main vectors (look/up/right) are always at right-angles to each other.

This stuff:

fst matView.D3DXMATRIX._14
fst matView.D3DXMATRIX._24
fstp matView.D3DXMATRIX._34
fstp matView.D3DXMATRIX._44

.. etc

... loads up our matrix (whether a camera or any other object) with the correct values.

And finally, this bit:

just makes sure the position of the object is correct.

Note that the previous code-snippet creates a matrice in view space.
To get a normal objects real matrix (i.e. a normal object is everything that *isn't* a camera), I use immediately after the previous code:
INVOKE D3DXMatrixInverse, ADDR MatrixOfObject, NULL, ADDR MatrixOfObject

where MatrixOfObject is whatever mat*** you're using (i.e. the same matice that's used in place of matView above).
Just remember to use that last D3DXMatrixInverse call, otherwise the matrice will be totally stuffed up ;) .

Posted on 2003-12-16 19:42:34 by Scronty
Yep, that works.
Hey, that c++ code was pretty close imho to your version, as was my translation.
1000 ways to skin a cat, as they say.
I don't mean to piss in your pocket, but yes, u have more experience than me in this field and on this platform. I tend to call a spade a friggin shovel.

I'll be away for a few days at the Metal For The Brain gig in Canberra, as a pretence for buying a crapload of fireworks for New Year. Hopefully I'll finish implementing a cheapass version of ArcBall camera control before I leave, and if so I'll post again, if not, it can wait until I get back.
Have you ever tried animating a camera view using animation keyframe data?
Does Maya and/or 3DS export camera path matrix keys as a series of view matrices, or as separate position/rotation/scale keys? (I am guessing the latter, which seems redundant in respect to a single camera's viewpoint)
IE when u draw some curves and then attach a camera to the curve...
I'm obviously talking about cutscenes here :)
If as seems likely that I won't be around much for the rest of the Silly Season, have a great Christmas/New Year, and don't get too wasted daddy :)
(My son is about to start high school and I feel so old lol)
Posted on 2003-12-16 20:45:45 by Homer