Archived

This topic is now archived and is closed to further replies.

lifebane

<SOLVED> Rotating a mesh based on 2 vertices <was: based on a vertex normal>

Recommended Posts

lifebane    122
Howdy all! I've a character mesh with 16 frames(meshes) of walk animation and a single sword mesh translated to a vertex on the character meshes hand. I'd like to rotate the sword mesh based on the normal of the hand vertex I've attached to so that the sword always stays in the grip of the character meshes hand as its arms swing. So far, I've tried the following, but it doesn't seem to rotate right. It's attached to the hand, but the rotation is way off. double distX=pow( pVert->x - pVert->nx, 2.0f ); double distY=pow( pVert->y - pVert->ny, 2.0f ); double distZ=pow( pVert->z - pVert->nz, 2.0f ); double distance=sqrt( distX + distY + distZ ); handRotation.x=(float)asin( (pVert->x - pVert->nx)/distance ); handRotation.y=(float)asin( (pVert->y - pVert->ny)/distance ); handRotation.z=(float)asin( (pVert->z - pVert->nz)/distance ); Any ideas? Thanks! LB [edited by - Lifebane on October 23, 2002 5:54:39 PM] [edited by - Lifebane on October 23, 2002 5:56:42 PM] [edited by - Lifebane on October 23, 2002 5:57:23 PM]

Share this post


Link to post
Share on other sites
S1CA    1418

const float THRESHOLD = 0.8f;

vec3 dir = vHandNormal;
dir.normalise();

vec3 approxup( 0.0f, 1.0f, 0.0f );
if (DotProduct(approxup,dir) < THRESHOLD)
{
approxup = vec3(0.0f, 0.0f, 1.0f);
}

vec3 right = CrossProduct( approxup, dir );
vec3 up = CrossProduct( dir, right );
up.normalise();

mtx mSwordWorld;
mSwordWorld.identity();
mSwordWorld.m00 = right.x;
mSwordWorld.m10 = right.y;
mSwordWorld.m20 = right.z;
mSwordWorld.m01 = up.x;
mSwordWorld.m11 = up.y;
mSwordWorld.m21 = up.z;
mSwordWorld.m02 = dir.x;
mSwordWorld.m12 = dir.y;
mSwordWorld.m22 = dir.z;


Set mSwordWorld as the world transformation matrix for the sword.

That should work (exact code untested) as long as the length of the sword points down the Z axis (in sword object space).
The only issue you may see is the sword rotated 90 degrees in that axis around the hand because really you need a second vector (a tangent) as well as the normal of the hand.
You may be able to get a vector to use for the tangent from say the normal of the thumb.


--
Simon O''Connor
Creative Asylum Ltd
www.creative-asylum.com

Share this post


Link to post
Share on other sites
lifebane    122
I''ll give that a go. I can snag a second vector easily, allthough I''m not sure how to implement it as I''m fairly new to vector math.

Thanks again!

Share this post


Link to post
Share on other sites
lifebane    122
I'm using the DirectX 8.1 SDK, so the following is your code in DirectX.

---------------------------------
const float THRESHOLD = 0.8f;
D3DXVECTOR3 dir = handRotation;
D3DXVec3Normalize(&dir,&dir);
D3DXVECTOR3 approxup( 0.0f, 1.0f, 0.0f );
if(D3DXVec3Dot(&approxup,&dir) < THRESHOLD)
{
approxup = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
// D3DXVec3Dot(&approxup,&dir); //Needed?
}

D3DXVECTOR3 right;
D3DXVECTOR3 up;
D3DXVec3Cross( &right, &approxup, &dir );
D3DXVec3Cross( &up, &dir, &right );
D3DXVec3Normalize(&up,&up);
//D3DXVec3Normalize(&right,&right); //Needed?

D3DXMatrixIdentity(&matWorld);
matWorld._11 = right.x;
matWorld._12 = up.x;
matWorld._13 = dir.x;
matWorld._21 = right.y;
matWorld._22 = up.y;
matWorld._23 = dir.y;
matWorld._31 = right.z;
matWorld._32 = up.z;
matWorld._33 = dir.z;
---------------------------------

The following is the D3DXMATRIX structure. It's a little different than your example, but I believe I aligned things properly.

typedef struct _D3DMATRIX {
union {
struct {
float _11, _12, _13, _14;
float _21, _22, _23, _24;
float _31, _32, _33, _34;
float _41, _42, _43, _44;

};
float m[4][4];
};
} D3DMATRIX;

The rotation is close, but the z axis seems to really jump around.

Thanks again! I'd appreciate any further help as it's getting close!


[edited by - Lifebane on October 20, 2002 7:57:42 PM]

Share this post


Link to post
Share on other sites
S1CA    1418
Ah, no - our matrices are binary compatible with D3D matrices so you''ve ended up transposing the matrix:

float m00, m01, m02, m03;
float m10, m11, m12, m13;
float m20, m21, m22, m23;
float m30, m31, m32, m33;

As for the Z axis, that is exactly your vertex normal, so there may be issues there - how are you transforming the vertex normal into world space for passing into the above?, does your world matrix have non uniform scales?, do you use the inverse transpose of the world matrix?

If you mean that the sword is spinning around the Z axis, then getting a tangent to that normal would fix it. (Either a perfect tangent stored with the normal for the hand or make one in the same way that my code makes the tangent and binormal by taking an approximate value and making it perpendicular to the other vectors with cross products).

The purpose of the dot product check is to ensure that the normal doesn''t point in roughly the same direction as the approximate up vector, if it does, a new up vector is chosen (if the normal is already ~0,1,0 then any other axis will work fine).

--
Simon O''Connor
Creative Asylum Ltd
www.creative-asylum.com

Share this post


Link to post
Share on other sites
lifebane    122
Ugh - I can''t believe I forgot to fix the matrix alignment before posting. *sigh* The symptoms in the post were from the properly aligned matrix. (I had moved the bits around just in case, to try all possibilities)

>how are you transforming the vertex normal into world space for passing into the above?, does your world matrix have non uniform scales?, do you use the inverse transpose of the world matrix?

handOffset is the hand location of the player model''s hand. standOffset is how far away from 0,0,0 the model is shifted.

matWorld._41=handOffset.x+standOffset.x;
matWorld._42=handOffset.y+standOffset.y;
matWorld._43=handOffset.z+standOffset.z;
m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
mesh->Render(m_pd3dDevice);

That''s everything I have done before the matrix transform you''ve helped me with. Currently, I''m performing the entire calculation in the render function every frame. Once things work well, I''ll offload the calc to a section where it''s only done once.

As for getting the hand vertex and normal, just after CloneMeshFVF(), I perform the following on each of the 16 frames I load:

if( m_pSysMemMesh )
D3DXComputeNormals( m_pSysMemMesh, NULL );
if( m_pLocalMesh )
D3DXComputeNormals( m_pLocalMesh, NULL );

if( m_pSysMemMesh )
{
LPDIRECT3DVERTEXBUFFER8 pVBuf;

if (SUCCEEDED(m_pSysMemMesh->GetVertexBuffer(&pVBuf)))
{
D3DVERTEX *pVert=NULL;
if (SUCCEEDED(pVBuf->Lock(0,0,(BYTE **) &pVert,D3DLOCK_READONLY) ) )
{
pVert+=3033;
handOffset=D3DXVECTOR3(pVert->x, pVert->y, pVert->z);
handRotation=D3DXVECTOR3(pVert->nx, pVert->ny, pVert->nz);
}
pVBuf->Unlock();
}
pVBuf->Release();
}

then when it''s time to render, I point the local handoffset and handrotation to the particular frames'' handoffset and handrotation.

To recap I''ll post all the code I''m performing

Load (once):
if( m_pSysMemMesh )
D3DXComputeNormals( m_pSysMemMesh, NULL );
if( m_pLocalMesh )
D3DXComputeNormals( m_pLocalMesh, NULL );

if( m_pSysMemMesh )
{
LPDIRECT3DVERTEXBUFFER8 pVBuf;

if (SUCCEEDED(m_pSysMemMesh->GetVertexBuffer(&pVBuf)))
{
D3DVERTEX *pVert=NULL;
if (SUCCEEDED(pVBuf->Lock(0,0,(BYTE **) &pVert,D3DLOCK_READONLY) ) )
{
pVert+=3033;
handOffset=D3DXVECTOR3(pVert->x, pVert->y, pVert->z);
handRotation=D3DXVECTOR3(pVert->nx, pVert->ny, pVert->nz);
}
pVBuf->Unlock();
}
pVBuf->Release();
}

Render:
const float THRESHOLD = 0.8f;
D3DXVECTOR3 dir = handRotation;
D3DXVECTOR3 right;
D3DXVECTOR3 up;

D3DXVec3Normalize(&dir,&dir);
D3DXVECTOR3 approxup( 0.0f, 1.0f, 0.0f );
if(D3DXVec3Dot(&approxup,&dir) < THRESHOLD)
approxup = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

D3DXVec3Cross( &right, &approxup, &dir );
D3DXVec3Cross( &up, &dir, &right );
D3DXVec3Normalize(&up,&up);

D3DXMatrixIdentity(&matWorld);
matWorld._11 = right.x;
matWorld._21 = up.x;
matWorld._31 = dir.x;
matWorld._12 = right.y;
matWorld._22 = up.y;
matWorld._32 = dir.y;
matWorld._13 = right.z;
matWorld._23 = up.z;
matWorld._33 = dir.z;

matWorld._41=handOffset.x+standOffset.x;
matWorld._42=handOffset.y+standOffset.y;
matWorld._43=handOffset.z+standOffset.z;

m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
mesh->Render(m_pd3dDevice);

Share this post


Link to post
Share on other sites
S1CA    1418
Ah, but the normal you calculate for the hand with D3DXComputeNormals, handRotation will be in "model space", i.e. the space which the hand was modelled in.

If any WORLD matrix is applied to the vertex positions of the hand (or the complete character), the top left 3x3 of the same matrix MUST be applied to the normals too. Though if that world matrix includes scales you must take the inverse transpose.

Alternatively you could concatenate the sword world matrix with the character world matrix.

Either:

1)
D3DXVec3TransformNormal(&handRotation, &mCharacter, &handRotation);
// then pass handRotation into the normal to matrix code
// (mCharacter is the world matrix applied to the main character)

OR

2)

matWorld = mSword * mCharacter;
// mSword is the sword matrix from the calculation
// mCharacter is whatever transform is applied to the character


--
Simon O''Connor
Creative Asylum Ltd
www.creative-asylum.com

Share this post


Link to post
Share on other sites
lifebane    122
Thanks again!

The following is what I now have in render(). ( Which still isn''t cooperating. )

------------------------------

D3DXMATRIX matPlayer;
D3DXMATRIX matSword;

D3DXMatrixIdentity(&matPlayer);
matPlayer._41=0.0f;
matPlayer._42=0.0f;
matPlayer._43=0.0f;
m_pd3dDevice->SetTransform( D3DTS_WORLD, &matPlayer );
playerMesh->Render(m_pd3dDevice);

D3DXVec3TransformNormal( &handRotation, &handRotation, &matPlayer );

const float THRESHOLD = 0.8f;
D3DXVECTOR3 dir = handRotation;
D3DXVECTOR3 right;
D3DXVECTOR3 up;

D3DXVec3Normalize(&dir,&dir);
D3DXVECTOR3 approxup( 0.0f, 1.0f, 0.0f );
if(D3DXVec3Dot(&approxup,&dir) < THRESHOLD)
approxup = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

D3DXVec3Cross( &right, &approxup, &dir );
D3DXVec3Cross( &up, &dir, &right );
D3DXVec3Normalize(&up,&up);

D3DXMatrixIdentity(&matSword);
matSword._11 = right.x;
matSword._21 = up.x;
matSword._31 = dir.x;
matSword._12 = right.y;
matSword._22 = up.y;
matSword._32 = dir.y;
matSword._13 = right.z;
matSword._23 = up.z;
matSword._33 = dir.z;

matSword._41=handOffset.x;
matSword._42=handOffset.y;
matSword._43=handOffset.z;

m_pd3dDevice->SetTransform( D3DTS_WORLD, &matSword );
swordMesh->Render(m_pd3dDevice);

------------------------------

Since the above still wasn''t looking right, I moved to the next vertex in the hand and its normal, expecting the animation to look the same, but it didn''t maintain the same effect the prior vertex did. Instead, the sword orientation jumped all over the place in different areas, which makes me suspect the normals. *sigh*

Could I easily convert the above to use another vertex (or 2) in the hand _instead_ of a normal?

If you''re interested in seeing more elaborate code, please lemmie know. This is the last step in my item editor. 8'')

Thanks again!

Share this post


Link to post
Share on other sites
S1CA    1418
quote:
Could I easily convert the above to use another vertex (or 2) in the hand _instead_ of a normal?


Yep, sure, as long as it''s normalised.

It''s actually very close to the method used when you have skinned characters - the bones are really just vectors each of which point in a direction specified by their world matrices.

To parent a sword to a hand bone is the same kind of job - although since the matrices are always stored for each part all you (usually) need to do is concatenate the matrices.


I''d actually suggest breaking things apart to try and isolate the problem - perhaps draw the vectors at each stage using lines so you can "see" what the maths is doing to them (it often helps quite a lot).
As well as this, instead of using anything from the vertex data try making an aribtrary vector as a constant (e.g. 0.5,0.5, 0), draw this vector on the screen with a line, normalise it and feed it in instead of the hand vector.

Also make doubly sure that your SystemMemMesh is actually valid and containing what you think it is!! - if that were corrupt or the Lock() call was failing, that''d account for the problems you''re having.

Debug output up, debug version of D3D and D3DX (d3dx8d .lib) and error code would be advised.

[I''ll check through the maths I posted later to ensure I''ve not made a silly mistake in what I''ve done too ]



--
Simon O''Connor
Creative Asylum Ltd
www.creative-asylum.com

Share this post


Link to post
Share on other sites
lifebane    122
>Also make doubly sure that your SystemMemMesh is actually valid and containing what you think it is!! - if that were corrupt or the Lock() call was failing, that''d account for the problems you''re having.

Since the sword stays at the hand vector, I''d assume it''s a valid mesh.

I''ll look into drawing some lines and see if I can spot what''s up. I appreciate all the help yer giving! I sure hope to move on from this problem soon... nothing is more discouraging than beating yer head against bricks when you thought it was going to be rice paper.

Thanks again!

Share this post


Link to post
Share on other sites
lifebane    122
When I set the normal to (5.0f,5.0f,5.0f), the sword always points to that location throughout all frames, which sounds right. This makes me think something is wrong with the normal?

The following is my D3DXVERTEX structure:

typedef struct _D3DVERTEX {
union {
float x;
float dvX;
};
union {
float y;
float dvY;
};
union {
float z;
float dvZ;
};
union {
float nx;
float dvNX;
};
union {
float ny;
float dvNY;
};
union {
float nz;
float dvNZ;
};
union {
float tu;
float dvTU;
};
union {
float tv;
float dvTV;
};
_D3DVERTEX() { }
_D3DVERTEX(const D3DVECTOR& v,const D3DVECTOR &n,float _tu, float _tv)
{ x = v.x; y = v.y; z = v.z;
nx = n.x; ny = n.y; nz = n.z;
tu = _tu; tv = _tv;
}
} D3DVERTEX, *LPD3DVERTEX;

See any problems?

Thanks!

[edited by - Lifebane on October 21, 2002 4:24:14 PM]

Share this post


Link to post
Share on other sites
lifebane    122
I''ve found something that may be a problem. Once the item is rotated in the x and y planes, it looks like the z rotation could be nullified. The same seems true for any axis since the rotations are applied to the world XYZ instead of the sword XYZ.

Share this post


Link to post
Share on other sites
S1CA    1418
Hmm - I''m back to suspecting the creation of the normals in the system memory mesh. The mesh is valid because as you say you get the position, but the normals may not be.

1) Link with d3dx8d.lib rather than d3dx8.lib and check every line of debug output you get.

2) Check any error codes from D3DXComputeNormals

3) The docs for D3DXComputeNormals do say that the mesh *must* have space in its vertices for normals, i.e. it''s vertex format MUST have D3DFVF_NORMAL. Out of interest try inserting this before you call D3DXComputeNormals()


if (m_pSysMemMesh->GetFVF() & D3DFVF_NORMAL)
{
OutputDebugString("****** AARGH, mesh doesn''t have normals in its FVF ******\n");
}


4) That would actually make a lot of sense actually. If your input mesh vertices were actually D3DLVERTEX rather than D3DVERTEX (both are 32bytes so the indexing lines up anyway), then the computed normals will be "clashing" with the predefined colours and texture coordinates in the system mesh.

5) Another way to verify this theory would be to call D3DXComputeNormals on the geometry used to render - if stuff is textured and starts looking weird, it could indicate the same.

--
Simon O''Connor
Creative Asylum Ltd
www.creative-asylum.com

Share this post


Link to post
Share on other sites
lifebane    122
Thanks again for getting back to me!

When I inserted your code, it spit out the debug string on every mesh, but then I realized you were saying spit it out if it DOES exist, so I modded the code to the following and it never gives me the message.

if( m_pSysMemMesh )
if ( !(m_pSysMemMesh->GetFVF() & D3DFVF_NORMAL) )
OutputDebugString("****** AARGH, mesh doesn''t have normals in its FVF ******\n");

>1) Link with d3dx8d.lib rather than d3dx8.lib and check every line of debug output you get.

I was already linking the following libraries. dsound.lib dinput8.lib dxerr8.lib d3dx8dt.lib d3d8.lib d3dxof.lib dxguid.lib winmm.lib

If I link d3dx8d.lib, I get an error at runtime stating that it cannot find d3dx8d.dll. Likely since I''m using Win2000 instead of WinME, which may use d3dx8d.dll instead of d3dx8dt.dll.

>2) Check any error codes from D3DXComputeNormals
if( m_pSysMemMesh )
if( D3DXComputeNormals( m_pSysMemMesh, NULL ) != D3D_OK )
OutputDebugString("m_pSysMemMesh->D3DXComputeNormals() failed\n");
if( m_pLocalMesh )
if( D3DXComputeNormals( m_pLocalMesh, NULL ) != D3D_OK )
OutputDebugString("m_pLocalMesh->D3DXComputeNormals() failed\n");

No errors to report.

5) Another way to verify this theory would be to call D3DXComputeNormals on the geometry used to render - if stuff is textured and starts looking weird, it could indicate the same.

Since the function needs a baseMesh structure to apply to, I''m not sure how to do this.

-----------------------------

I''m also completely baffled that this isn''t working. Could I possibly use a plane normal instead? (However, I''m not sure how to grab the plane normals.)

Thanks again!

Share this post


Link to post
Share on other sites
lifebane    122
I''m studying the following link so that I can better understand what you''ve shown me. Maybe then I can help figure out the problem.

http://www.makegames.com/3drotation/

Share this post


Link to post
Share on other sites
lifebane    122
There were 2 problems. Normals and exported mesh.

First, since I didn't trust the normals, I decided to change the code to use 2 vertices found on the hand. The second vertex is converted to a vector then normalized. There are a few code differences from what you had supplied, so I listed the entire section of code below.

The second problem was that my 3DS exported mesh was rotated -90, which caused the rotation to look strange, especially if I tried to straighten out the rotation using D3DXMatrixRotationX, Y, and Z. Once I saw that the mesh was off (I forced the 2 hand vertices to 0,0,0 and 0,1,0 and saw the model wasn't facing up), I used a properly rotated mesh export and magically, all was aligned and rotating as the player ran. *cheer* :D

I still need to create rotation functions to manually rotate each axis for fine tuning, but the major hurdle is far behind.

Thanks again for all your help! You might cut/paste this for the next fellah. I tried to make sure to insert working code so that someone could cut/paste and have similar results.

Thanks much!
Jack Harmon

---------------------------------------------------
const float THRESHOLD=0.000001f;
D3DXVECTOR3 axisZ( handVector.x-handOffset.x, handVector.y-handOffset.y, handVector.z-handOffset.z); //Direction
D3DXVECTOR3 axisX; // Right
D3DXVECTOR3 axisY; // Up
D3DXVECTOR3 worldUp( 1.0f, 0.0f, 0.0f );
float dotProduct;

D3DXVec3Normalize( &axisZ, &axisZ );
dotProduct=D3DXVec3Dot( &axisZ, &worldUp );

axisY.x=worldUp.x-dotProduct*axisZ.x;
axisY.y=worldUp.y-dotProduct*axisZ.y;
axisY.z=worldUp.z-dotProduct*axisZ.z;

if ( sqrt( (axisY.x*axisY.x) + (axisY.y*axisY.y) + (axisY.z*axisY.z) ) < THRESHOLD )
{
OutputDebugString("Too close to normalize - trying again\n");
axisY.x=-axisZ.y*axisZ.x;
axisY.y=1-axisZ.y*axisZ.y;
axisY.z=-axisZ.y*axisZ.z;

if ( sqrt( (axisY.x*axisY.x) + (axisY.y*axisY.y) + (axisY.z*axisY.z) ) < THRESHOLD )
{
OutputDebugString("Too close to normalize - trying again\n");
axisY.x=-axisZ.z*axisZ.x;
axisY.y=-axisZ.z*axisZ.y;
axisY.z=1-axisZ.z*axisZ.z;

if ( sqrt( (axisY.x*axisY.x) + (axisY.y*axisY.y) + (axisY.z*axisY.z) ) < THRESHOLD )
OutputDebugString("Too close to normalize - failing operation\n");
}
}

D3DXVec3Normalize( &axisY, &axisY );
D3DXVec3Cross( &axisX, &axisY, &axisZ );

D3DXMatrixIdentity(&matItem);

matItem._11 = axisX.x;
matItem._12 = axisX.y;
matItem._13 = axisX.z;

matItem._21 = axisY.x;
matItem._22 = axisY.y;
matItem._23 = axisY.z;

matItem._31 = axisZ.x;
matItem._32 = axisZ.y;
matItem._33 = axisZ.z;

matItem._41=handOffset.x;
matItem._42=handOffset.y;
matItem._43=handOffset.z;

m_pd3dDevice->SetTransform( D3DTS_WORLD, &matItem );
mesh->Render(m_pd3dDevice);
---------------------------------------------------

[edited by - Lifebane on October 23, 2002 9:44:38 PM]

Share this post


Link to post
Share on other sites
lifebane    122
Well...

Everything is working with the exception of the y axis of the weapon is rotating opposite what it should. The blade of an axe is rotating backward as the arm swings.

Any ideas?

Thanks!

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Time for a 3rd point to provide the forward vector I guess.

Share this post


Link to post
Share on other sites
lifebane    122
Care to elaborate? As it does currently rotate on all 3 axes (just the wrong direction for Y), how would a 3rd point help and how would that be implemented? Should I replace the fixed worldUp vector with a 3rd hand vector?

Jack Harmon

[edited by - Lifebane on October 24, 2002 10:45:04 AM]

Share this post


Link to post
Share on other sites