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

Started by
19 comments, last by lifebane 21 years, 5 months ago
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]
Advertisement
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

Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site

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!
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]
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

Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site

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);
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

Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site

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!
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

Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site

>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!

This topic is closed to new replies.

Advertisement