Calculating the inertia for recursive compound shapes

kermado
Posts: 20
Joined: Tue Jan 12, 2016 11:20 am

Calculating the inertia for recursive compound shapes

Post by kermado »

Following the sample code provided in other threads I was able to simulate a simple compound shape properly. I now want to support recursive compound shapes, where one compound can be a child of another compound. My method is as follows:

1. Recursively transform the compound tree by calculating the principal transform for the compound, transforming the compound by the principal transform and then transforming the children by the inverse of the principal transform. Where the compound is the root collision shape in the tree, the rigid body is transformed by the principal transform instead.

2. Set the m_localInertia property in the btRigidBodyConstructionInfo struct and create the rigid body.

The relevant code is provided below:

Code: Select all

static btTransform RigidBody_TransformCompoundRecursive(btCompoundShape* compound, btScalar mass)
{
	btTransform principalTransform;
	btScalar* masses = RigidBody_CalculateMasses(compound, mass);
	btVector3 principalInertia;
	compound->calculatePrincipalAxisTransform(masses, principalTransform, principalInertia);

	for (int i = 0; i < compound->getNumChildShapes(); ++i)
	{
		btCollisionShape* childShape = compound->getChildShape(i);
		btTransform childTransform = principalTransform.inverse() * compound->getChildTransform(i);
		compound->updateChildTransform(i, childTransform);

		if (childShape->isCompound())
		{
			btTransform childPrincipalTransform = RigidBody_TransformCompoundRecursive(static_cast<btCompoundShape*>(childShape), masses[i]);
			compound->updateChildTransform(i, childTransform * childPrincipalTransform);
		}
	}

	delete[] masses;

	return principalTransform;
}

static btRigidBody* RigidBody_CreateRigidBody(btRigidBody::btRigidBodyConstructionInfo* constructionInfo)
{
	btCollisionShape* collisionShape = constructionInfo->m_collisionShape;
	const bool isCompound = collisionShape->isCompound();

	btTransform principalTransform;
	if (isCompound)
	{
		btCompoundShape* compound = static_cast<btCompoundShape*>(collisionShape);
		principalTransform = RigidBody_TransformCompoundRecursive(compound, constructionInfo->m_mass);
	}

	constructionInfo->m_collisionShape->calculateLocalInertia(native->m_mass, constructionInfo->m_localInertia);
	btRigidBody* body = new btRigidBody(*constructionInfo);

	if (isCompound)
	{
		body->setWorldTransform(body->getWorldTransform() * principalTransform);
	}

	return body;
}
This gives strange results when I create a rigid body with the following collision shape tree:

Code: Select all

    - Compound
        - Compound at local position (0, 0, 0)
            - Box Shape at local position (2, 2, 0)
Here is the result:
Image

As you can see, the box appears to be balancing in mid-air on its side.

However, if I configure the tree differently, then I get the result that I expected:

Code: Select all

    - Compound
        - Compound at local position (2, 2, 0)
            - Box Shape at local position (0, 0, 0)
Image

Any ideas as to where I have gone wrong?
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Calculating the inertia for recursive compound shapes

Post by drleviathan »

I don't think you need to update the transforms of the children of the btCompoundShape when making it a child of another. Instead you just set the transform of the btCompoundShape itself -- Bullet will evaluate the child transforms recursively. I have not tested this, but is how I would expect it to work.

When it comes to computing the inertia tensor of the object: btShape::calculateLocalInertia() is available. Personally I would assume that it Just Works well enough unless proven otherwise.

Note: btRigidBody stores the inverse local inertia tensor as a btVector3 which means it assumes that the shape's local axes align with the eigenvectors of the inertia tensor. This is not necessarily the case with arbitrary btCompoundShapes but it is clear that Bullet's calculateLocalInertia() makes the assumption. In general you need to either (a) supply an approximate diagonal inertia tensor or (b) back rotate the child transforms until the correct "local frame" inertia tensor's eivenvectors line up with the shape's local axes. Option (b) is probably too complicated for most game applications -- no users will be able to tell if the inertia tensor of a tumbling object is slightly off. However you should be aware that if the supplied inertia is Very Wrong the simulation may go unstable -- typically this would only happen for long thin objects that have one inertia eigenvalues that is much larger than the other two.
kermado
Posts: 20
Joined: Tue Jan 12, 2016 11:20 am

Re: Calculating the inertia for recursive compound shapes

Post by kermado »

Thanks for your insight.

I'm unclear as to what you mean about transforming the children. I am following the second method presented in another thread, where I shift the child shapes in place. The btCompoundShape::calculatePrincipalAxisTransform function calculates the principal transform. The origin of this principal transform is the centre of mass in local space. So we transform the collision object by the principal transform and the children by the inverse of the principal transform. This has the affect of maintaining the world position of the children whilst ensuring that the collision object is at the centre of mass and aligned with the axes of the principal transform.

However, when I configure the collision tree as detailed in my previous post, the centre of mass (the origin of the principal transform) calculated by btCompoundShape::calculatePrincipalAxisTransform is (0,0,0). Clearly, this is incorrect. The centre of mass should be at the centre of the box (2,2,0). Looking at the function, it appears that only the immediate children of the shape are considered. In my case, the immediate child is another compound shape with a local position of (0,0,0). Hence, the centre of mass returned is (0,0,0). It looks like I need to make the calculatePrincipalAxisTransform consider the entire tree and not just the immediate children.
kermado
Posts: 20
Joined: Tue Jan 12, 2016 11:20 am

Re: Calculating the inertia for recursive compound shapes

Post by kermado »

Ok, I think that I have a solution. We can use the btCompoundShape::calculatePrincipalAxisTransform function by traversing the tree from the bottom up. This way we ensure that the child compound is positioned at its centre of mass before we consider the parent compound. Here is the solution for anyone interested:

Code: Select all

static btTransform RigidBody_TransformCompoundRecursive(btCompoundShape* compound, btScalar mass)
{
	btScalar* masses = RigidBody_CalculateMasses(compound, mass);

	// Recurse down the compound tree, transforming each compound and their children so that the
	// compound is positioned at its centre of mass and with its axes aligned with those of the
	// principal inertia tensor.
	for (int i = 0; i < compound->getNumChildShapes(); ++i)
	{
		btCollisionShape* childShape = compound->getChildShape(i);
		if (childShape->isCompound())
		{
			btCompoundShape* childCompound = static_cast<btCompoundShape*>(childShape);
			btTransform childPrincipalTransform = RigidBody_TransformCompoundRecursive(childCompound, masses[i]);
			compound->updateChildTransform(i, childPrincipalTransform * compound->getChildTransform(i)); // Transform the compound so that it is positioned at its centre of mass.
		}
	}

	// Calculate the principal transform for the compound. This has its origin at the compound's
	// centre of mass and its axes aligned with those of the inertia tensor.
	btTransform principalTransform;
	btVector3 principalInertia;
	compound->calculatePrincipalAxisTransform(masses, principalTransform, principalInertia);

	// Transform all the child shapes by the inverse of the compound's principal transform, so
	// as to restore their world positions.
	for (int i = 0; i < compound->getNumChildShapes(); ++i)
	{
		btCollisionShape* childShape = compound->getChildShape(i);
		compound->updateChildTransform(i, principalTransform.inverse() * compound->getChildTransform(i));
	}

	return principalTransform;
}