Skip to main content
GameDev.net gamedev.net
🔒 Locked

Inertial Tensor

Started by jammy5202 Apr 12, 2006 at 2:40 PM 10 replies 3k views
Original Post
jammy5202
jammy5202
I have calculated this Inertial Tensor from a solid sphere mesh with a diameter of 21.83. Is this Inertial Tensor matrix correct: 39.057720, -5.2135115, -1.7545298 -5.2135115, 28.517597, -1.8240093 -1.7545298, -1.8240093, 67.575317 I used the vertexs and indexs to produce the Inertial Tensor matrix is this method correct. The ball is a bowling ball so the are some finger holes in the mesh. I have got the bowling pin to calculate the inertial tensor aswell. Do these Inertial Tensor matrix values seem correct? thanks james
Sneftel
Sneftel
No, they don't. The ZZ component is more than twice as large as the YY component? That doesn't sound like a sphere to me. The correct inertial tensor matrix will depend on the units in use and the density of the material, but the inertial tensor shouldn't be nearly this eccentric no matter how big the finger holes are.
jammy5202
jammy5202
Tried with a complete sphere with a 20 radius got

170.378, 0.002, 14.881
0.002, 54.12815, 0.01007
14.881, 0.01007, 224.499

The sphere is off center when loaded into directx but in the center in max
MaxX = 20.000999 and MinX = -19.999001.

This is the code i am using to get inertial tensor to simplify the problem could i use the tensors for a cylinder for bowling pin as it is close to the same shape:

D3DXMATRIX CalculateInertia(LPD3DXMESH mesh)
{
D3DXVECTOR3 *pVertices = NULL;
WORD *index = NULL;

int numvert = mesh->GetNumVertices();
int numindex = mesh->GetNumFaces();

float fVolume = 0;

float fXX = 0;
float fYY = 0;
float fZZ = 0;
float fYZ = 0;
float fXZ = 0;
float fXY = 0;

mesh->LockVertexBuffer(0, (void**)&pVertices);
mesh->LockIndexBuffer(0, (void**)&index);

for(int i=0; i < numindex;) // for each triangle
{
D3DXMATRIX mA(pVertices[index].x, pVertices[index].y, pVertices[index].z, 0,
pVertices[index[i+1]].x, pVertices[index[i+1]].y, pVertices[index[i+1]].z, 0,
pVertices[index[i+2]].x, pVertices[index[i+2]].y, pVertices[index[i+2]].z, 0,
0,0,0,0);

float fDet = Determinant(mA);
fVolume += fDet;

fXX += ( mA._11 * mA._21 +
mA._21 * mA._31 +
mA._31 * mA._11 +
(mA._11 * mA._11) +
(mA._21 * mA._21) +
(mA._31 * mA._31)) * fDet;

fYY += ( mA._12 * mA._22 +
mA._22 * mA._32 +
mA._31 * mA._12 +
(mA._12 * mA._12) +
(mA._22 * mA._22) +
(mA._32 * mA._32)) * fDet;

fYY += ( mA._13 * mA._23 +
mA._23 * mA._33 +
mA._33 * mA._13 +
(mA._13 * mA._13) +
(mA._23 * mA._23) +
(mA._33 * mA._33)) * fDet;

fYZ += (mA._12 * mA._23 +
mA._22 * mA._33 +
mA._32 * mA._13 +
mA._12 * mA._33 +
mA._22 * mA._13 +
mA._32 * mA._23 +
mA._12 * mA._13 * 2 +
mA._22 * mA._23 * 2 +
mA._32 * mA._33 * 2 ) * fDet;

fXZ += (mA._11 * mA._23 +
mA._21 * mA._33 +
mA._31 * mA._13 +
mA._11 * mA._33 +
mA._21 * mA._13 +
mA._31 * mA._23 +
mA._11 * mA._13 * 2 +
mA._21 * mA._23 * 2 +
mA._31 * mA._33 * 2 ) * fDet;

fXY += (mA._11 * mA._22 +
mA._21 * mA._32 +
mA._31 * mA._12 +
mA._11 * mA._32 +
mA._21 * mA._12 +
mA._31 * mA._22 +
mA._11 * mA._12 * 2 +
mA._21 * mA._22 * 2 +
mA._31 * mA._32 * 2 ) * fDet;

i+=3;
}

fXX /= fVolume * 10;
fYY /= fVolume * 10;
fZZ /= fVolume * 10;
fYZ /= fVolume * 20;
fXZ /= fVolume * 20;
fXY /= fVolume * 20;

mesh->UnlockVertexBuffer();
mesh->UnlockVertexBuffer();

return D3DXMATRIX( fYY + fZZ, -fXY, -fXZ, 0,
-fXY, fXX + fZZ, -fYZ, 0,
-fXZ, -fYZ, fXX + fYY, 0,
0, 0, 0, 0);
}

float Determinant(D3DXMATRIX m)
{
return (m._11 * m._22 * m._33 -
m._11 * m._32 * m._23 +
m._21 * m._32 * m._13 -
m._21 * m._12 * m._33 +
m._31 * m._12 * m._23 -
m._31 * m._22 * m._13);
}
melax
melax
For a freely rotating object the summation must be done wrt the center of mass.
It looks like the code assumes the center of mass is at the origin.
Easiest thing to do is calculate the center of mass first then translate the points, either once over the whole list or on the fly each time you use them.

Alternatively, one can translate the tensor. Of which the math is a bit tricky and the details I dont have handy.


Similar code to do a volume calcualtion, on the other hand, does not care where the origin is. The reason is that the inertia contribution per unit volume increases as you move away from the center of rotation.


I think I'll have to modify the sample code at

http://www.melax.com/volint/

to add a center-of-mass argument to the inertia function.
I did have a writeup that explains everything, but you know how sometimes the 'readme' comments get overlooked.




[Edited by - melax on April 19, 2006 1:50:16 PM]
John Schultz
John Schultz
Quote:
Original post by melax
I think I'll have to modify the sample code at
http://www.melax.com/volint/
to add a center-of-mass argument to the inertia function.


Stan, using an "ideal inertia tensor" where the geometry is (re)aligned in such a way so that the resulting inertia tensor is a diagonal matrix results in simpler, faster, and more accurate physics code. Might be something useful to add to your example. My method is iterative, and (mis)uses Ken Shoemake's decomp_affine(). There's probably a faster, cleaner, more efficient method (I have not seen a paper or description of this concept (finding the "ideal inertia tensor" for arbitrary objects); seems like it would be useful to physics/game developers).
melax
melax
Quote:
... using an "ideal inertia tensor" where the geometry is (re)aligned in such a way so that the resulting inertia tensor is a diagonal matrix results in simpler, faster, and more accurate physics code...


Absolutely true.

That snippet that produces the matrix was taken from some code that would first sum over a number of shapes of verying masses and compute the inertia of the aggregate object. After transforming (rotating and translating) the tensors and summing them, Diagonalization was the next step - since thats what most physics engines work best with. The diagonalization sits in a separate routine since its useful on its own for a variety of applications.


Ironically just yesterday I emailed someone some code to diagonalize a matrix that would return the rotation directly in the form of a quaternion. It uses the jacobi diagonalization method invented 100 years ago. I was thinking it would be a useful thing to post since the code (specific to 3D instead of general to N dimensions) is so compact and would be easy for someone to incorporate into their own game/math library.

When people are developing an application/game nowadays they end up putting together a mishmash of code all with different standards, coding styles and naming conventions. This tends to make debugging more painful since every other function you step into feels like its speaking a different language than the previous. Unfortunately industry convergence isn't going to happen in the near future. So, using reasonable conventions, if I can provide small simple code for people that someone can incorporate/adapt into their own code by only some simple changes such as renaming float3 to myvec3class then that does them a better service compared to having some bizarre complicated untouchable code that forces people to write a bunch of glue code or translation wrapper on top that ends up being more prone to introducing bugs.




John Schultz
John Schultz
Quote:
Original post by melax
Quote:
... using an "ideal inertia tensor" where the geometry is (re)aligned in such a way so that the resulting inertia tensor is a diagonal matrix results in simpler, faster, and more accurate physics code...


Ironically just yesterday I emailed someone some code to diagonalize a matrix that would return the rotation directly in the form of a quaternion. It uses the jacobi diagonalization method invented 100 years ago. I was thinking it would be a useful thing to post since the code (specific to 3D instead of general to N dimensions) is so compact and would be easy for someone to incorporate into their own game/math library.



Jacobi diagonalization makes sense. It would be cool to see your optimized 3D version.

melax
melax

I modified the reference code at http://www.melax.com/volint/ to add an center-of-mass parameter to the inertia function instead of assuming the origin:

  float3x3 Inertia(const float3 *vertices, const int3 *tris,                    const int count, const float3& com=float3(0,0,0))

See the link for the rest of the code.

I hate bloatware, but in this case i think the the addition of this single parameter is reasonable. Given that someone might copy and paste the code without reading the following paragraphs or the comments within the code, they will probably see the 'com' parameter and assume it has some relevance. Furthermore, it is more convenient than translating all the points initially, or doing that tricky tensor translation after the fact.


So that d3d version that appears earlier in this thread need only be modified to subtract the center of mass when it assigning the rows of the matrix using the input vertices for each triangle.

melax
melax
As requested I also posted the code to diagonalize a symmetric 3x3.
Obviously this can be used to diagonalize your inertial tensor, find principal axes etc...

The link, not mentioned anywhere yet but here on this very forum thread, is:

http://www.melax.com/diag/

John Schultz
John Schultz
Quote:
Original post by melax
As requested I also posted the code to diagonalize a symmetric 3x3.
Obviously this can be used to diagonalize your inertial tensor, find principal axes etc...

The link, not mentioned anywhere yet but here on this very forum thread, is:

http://www.melax.com/diag/


Thanks Stan. I tested your code against decomp_affine(): produces nearly identical results, but is likely faster/easier to integrate and use (internally decomp_affine() uses doubles, as well as generating DENORMAL exceptions (no biggie, unless you are debugging denormals in your own code[wink])).

Since the ideal inertia tensor is in the diagonal of D, I changed the code so that D is passed in as an argument (I also had to flip around the matrix mul order for my math lib). This will allow the user to validate D is (nearly) diagonal, then extract the values from the matrix. The rigid body can then be inverse rotated by the returned quaternion. This will save having to recompute the inertia tensor after RB rotation (should still be done when integrating the code for test and validation; afterwords can be ifdef'd out). Since D now contains diagonalized A, returning the reverse rotation might be helpful (to inverse rotate the mesh/RB that generated the non-diagonal inertia tensor).

Quaternion Diagonalizer(const float3x3 &A, float3x3 & D)  {	// A must be a symmetric matrix.	// returns quaternion q such that its corresponding matrix Q 	// can be used to Diagonalize A	// Diagonal matrix D = Q * A * Transpose(Q);  and  A = QT*D*Q	// The rows of q are the eigenvectors D's diagonal is the eigenvalues	// As per 'row' convention if float3x3 Q = q.getmatrix(); then v*Q = q*v*conj(q)	int maxsteps=24;  // certainly wont need that many.	int i;	Quaternion q(0,0,0,1);	for(i=0;i < maxsteps;i++)	{		float3x3 Q  = q.getmatrix(); // v*Q == q*v*conj(q)		D = Q * A * Transpose(Q);  // A = Q^T*D*Q		float3 offdiag(D[1][2],D[0][2],D[0][1]); // elements not on the diagonal		float3 om(fabsf(offdiag.x),fabsf(offdiag.y),fabsf(offdiag.z)); // mag of each offdiag elem		int k = (om.x > om.y && om.x > om.z)?0: (om.y > om.z)? 1 : 2; // index of largest element of offdiag		int k1 = (k+1)%3;		int k2 = (k+2)%3;		if(offdiag[k]==0.0f) break;  // diagonal already		float thet = (D[k2][k2]-D[k1][k1])/(2.0f*offdiag[k]);		float sgn = (thet > 0.0f)?1.0f:-1.0f;		thet    *= sgn; // make it positive		float t = sgn /(thet +((thet < 1.E6f)?sqrtf(sqr(thet)+1.0f):thet)) ; // sign(T)/(|T|+sqrt(T^2+1))		float c = 1.0f/sqrtf(sqr(t)+1.0f); //  c= 1/(t^2+1) , t=s/c 		if(c==1.0f) break;  // no room for improvement - reached machine precision.		Quaternion jr(0,0,0,0); // jacobi rotation for this iteration.		jr[k] = sgn*sqrtf((1.0f-c)/2.0f);  // using 1/2 angle identity sin(a/2) = sqrt((1-cos(a))/2)  		jr[k] *= -1.0f; // since our quat-to-matrix convention was for v*M instead of M*v		jr.w  = sqrtf(1.0f - sqr(jr[k]));		if(jr.w==1.0f) break; // reached limits of floating point precision		q =  q*jr;  		q.Normalize();	}//      return quaternion(-q.x,-q.y,-q.z,q.w); 	return q;  }


Along with a short test/validation demo (start with a rigid body (mesh) with non-diagonal inertia tensor, compute diagonalized inertia tensor, rotate rigid body (mesh), then recompute inertia tensor, then print error (recomputed inertia tensor diagonal compared to diagonal of D), etc.), this topic would make a good article for a future Game Programming Gems, etc.

For others converting Stan's code, here are the changes I made for my math lib:
// From Stan Melax: http://www.melax.com/diagQuat4 diagonalize(const Mat3 & A,Mat3 & D) {  // A must be a symmetric matrix.  // returns Quat4 q such that its corresponding matrix Q   // can be used to Diagonalize A  // Diagonal matrix D = Q * A * Transpose(Q);  and  A = QT*D*Q  // The rows of q are the eigenvectors D's diagonal is the eigenvalues  // As per 'row' convention if Mat3 Q = q.getmatrix(); then v*Q = q*v*conj(q)  const int maxsteps=24;  // certainly wont need that many.  int i;  Quat4 q(0,0,0,1);  for (i=0;i < maxsteps;i++) {    Mat3 Q = q; // v*Q == q*v*conj(q)//    Mat3 D = Q ^ A ^ Q.transposedCopy();   // A = Q^T*D*Q    D = Q.transposedCopy() ^ A ^ Q;  // A = Q^T*D*Q    Vec3 offdiag(D[1][2],D[0][2],D[0][1]); // elements not on the diagonal    Vec3 om(fabsf(offdiag.x),fabsf(offdiag.y),fabsf(offdiag.z)); // mag of each offdiag elem    int k = (om.x > om.y && om.x > om.z)?0: (om.y > om.z)? 1 : 2; // index of largest element of offdiag    int k1 = (k+1)%3;    int k2 = (k+2)%3;    if (offdiag[k] == 0.0f) break;  // diagonal already    float thet = (D[k2][k2]-D[k1][k1])/(2.0f*offdiag[k]);    float sgn = (thet > 0.0f)?1.0f:-1.0f;    thet    *= sgn; // make it positive    float t = sgn /(thet +((thet < 1.E6f) ? sqrtf(RSQR(thet)+1.0f):thet)) ; // sign(T)/(|T|+sqrt(T^2+1))    float c = 1.0f/sqrtf(RSQR(t)+1.0f); //  c= 1/(t^2+1) , t=s/c     if (c == 1.0f) break;  // no room for improvement - reached machine precision.    Quat4 jr(0,0,0,0); // Jacobi rotation for this iteration.    jr[k] = sgn*sqrtf((1.0f-c)/2.0f);  // using 1/2 angle identity sin(a/2) = sqrt((1-cos(a))/2)      jr[k] *= -1.0f; // since our quat-to-matrix convention was for v*M instead of M*v    jr.w  = sqrtf(1.0f - RSQR(jr[k]));    if (jr.w == 1.0f) break; // reached limits of floating point precision    q = q ^ jr;      q.normalize();  } // for  return !q; // Return reverse rotation (quat conjugate).} // diagonalize
melax
melax
Quote:
Original post by John Schultz
... I tested your code against decomp_affine(): produces nearly identical results, ...


Hey thanks for giving the code a whirl and providing some additional testing. Unless you have any objections, i'll credit you on the web page for the collaboration.

s

John Schultz
John Schultz
Quote:
Original post by melax
Quote:
Original post by John Schultz
... I tested your code against decomp_affine(): produces nearly identical results, ...


Hey thanks for giving the code a whirl and providing some additional testing. Unless you have any objections, i'll credit you on the web page for the collaboration.

s


That's cool(years ago I had added rotationArcMelax() to my code base ;)).
IIRC, when I originally (mis)used decomp_affine() to get the rotation, I used the extracted rotation to re-orient the RB, then repeated the process: this resulted in very small values for the off-diagonals. The same technique might work with diagonalizer() (if someone wanted to improve the accuracy for whatever reason (not needed for a game app)).

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.