Mesh with offset center of mass

MalcolmB
Posts: 8
Joined: Tue May 03, 2011 9:04 pm

Mesh with offset center of mass

Post by MalcolmB »

I know this question has been asked a few times (one by myself also). I've read the various threads about it and although they all talk about the same general tools to get a solution, there isn't a full answer/example available it seems. I've tried lots of different combinations of answers but haven't been able to get a working solution for all cases yet. So here's an example that I hope I can come to a solution for.
I have a convex mesh centered around (1,1,1). This mesh has been further positioned at an arbitrary initial position by the user (called xform in the below code). So I create a btConvexHullShape with the points from the mesh directly, no change applied to them. The points are added using addPoint() to an empty btConvexHullShape, and initializePolyhedralFeatures() is called on the shape after that is done.

Then I use a btCompoundShape to apply the center of mass offset like this:

Code: Select all

btTransform centerOfMass;
centerOfMass.setIdentity();
centerOfMass.setOrigin(btVector3(center.x(), center.y(), center.z()));
btCompoundShape *colShape = new btCompoundShape();
colShape->addChildShape(centerOfMass, convexHullShape);
Then for the btCompoundShape I apply the initial position transform like this

Code: Select all

btTransform startTransform;
startTransform.setIdentity();
startTransform.setFromOpenGLMatrix(xform); // xform contains an arbitrary transform for the initial position of the object
centerOfMass.setIdentity();
colShape->calculateLocalInertia(mass,localInertia);

btDefaultMotionState* motionState = new btDefaultMotionState(startTransform, centerOfMass);
btRigidBody::btRigidBodyConstructionInfo rbInfo(m,motionState,colShape,localInertia);
body = new btRigidBody(rbinfo);
Finally the body is put into the physics solver, which is initialized like this

Code: Select all

myCollisionConfiguration = new btDefaultCollisionConfiguration();
myDispatcher = new btCollisionDispatcher(myCollisionConfiguration);
myBroadphase = new btDbvtBroadphase();
mySolver = new btSequentialImpulseConstraintSolver();
myWorld = new btDiscreteDynamicsWorld(myDispatcher,
                            myBroadphase,
                            mySolver,
                            myCollisionConfiguration);
The other posts seem to say I need to apply the inverse transform of the original offset ((-1,-1,-1) in this case), however I tried this various ways but things still don't look correct.

Can anyone give code to solve this problem? Thanks!
Flix
Posts: 456
Joined: Tue Dec 25, 2007 1:06 pm

Re: Mesh with offset center of mass

Post by Flix »

MalcolmB wrote:The other posts seem to say I need to apply the inverse transform of the original offset ((-1,-1,-1) in this case), however I tried this various ways but things still don't look correct.Can anyone give code to solve this problem?
No code in my case, just some quick considerations.
I have a convex mesh centered around (1,1,1)
You can just pass the original points (centered in 1,1,1) without using a btCompoundShape at all. (Of course in this case when you position the object through a transform, you have to take into account that you're transforming it from the the origin (0,0,0) point of the shape).

Alternatively you can center your hull in (0,0,0) (through centering the hull point cloud) and create a btCompoundShape to wrap it with a child shape transform.

"The inverse transform of the original offset" means that if you have a single shape (centered in (0,0,0)) and want to shift its COM (center of mass) down to 1 unit, you have to wrap it in a compound shape with child transform origin of (0,1,0).

Hope what I wrote is correct (I didn't double checked it).
MalcolmB
Posts: 8
Joined: Tue May 03, 2011 9:04 pm

Re: Mesh with offset center of mass

Post by MalcolmB »

Hey Flix,
Thanks for the response. I guess I'm being dense because I still can't see what I need to do from your answer. When you say
you have to take into account that you're transforming it from the the origin (0,0,0) point of the shape
.

What do you mean by 'take into account'? Can you be specific?
Flix
Posts: 456
Joined: Tue Dec 25, 2007 1:06 pm

Re: Mesh with offset center of mass

Post by Flix »

MalcolmB wrote:What do you mean by 'take into account'? Can you be specific?
Well, there's a lot of stuff related to this topic (e.g. motion states, graphic offset, etc.), and I still have not understood your issue exactly.

I would like to know:
1) Do you have problems matching the collision shape/graphical representation, but apart from this the physics is OK ?
2) The physics is OK, the graphical representation is OK, but you would like to assign/get transforms from outside the center of mass (i.e. relative to the center of the mesh). So this is only a "position" problem (for example, you may want to have 3 bodies with the same "mesh", but with 3 different center of mass, and you want to use the same exact transform to place each of the 3 bodies in the same place regardless of its COM, and the same for getting the transform back).
3) The physic is not OK. You would like to have a different center of mass for your rigid body.

Some general considerations:
a) First of all, if you don't use a btCompoundShape to "wrap" the btConvexHullShape, you still have a center of mass different from the center of your mesh, because your point cloud is centered around the point (1,1,1) and you're not shifting it.
This is OK, you don't need any center of mass offset or graphical offset inside your motion state, and the transforms you get/set are not relative to the center of your mesh, but to the center of mass of the mesh (which is represented by the (0,0,0) point in the point cloud of your convex hull).
b) A possibly better solution is to use only meshes and convex hulls centered in the origin, and just use a "btCompoundShape wrapper" to apply an COM offset shift (you need a graphic offset in your motion state that will be equal in module to the center of mass of the body). So by using meshes centered in the origin, you can easily get the center of mass of the object back through its motionstate.

Now all that's left is how you implement (actually derive) your own motion state. I'm not sure if your problem is related to motion states or not.
Anyway, keep present that as far as I know btRigidBody::get/setWorldTransform() and btRigidBody::get/setCenterOfMassTransform() are equivalent (you won't set/get the transform "outside COM" in this way).
Also when you pass the initial transform to a (dynamic) body, I'm not sure if Bullet applies it to the COM directly, or calls btMotionState::getWorldTransform(...) after having initialized the btMotionSate with it (this might not end in what you're expecting anyway, depending on how you implement btMotionState::getWorldTransform(...), so it might be useful to adjust the initial transform in the MotionState ctr in some cases if you want it to be "outside COM").

Personally I don't use the rotational part of the COM offset inside the motion state (http://bulletphysics.org/Bullet/phpBB3/ ... f=9&t=6075); this makes all much simpler.
MalcolmB
Posts: 8
Joined: Tue May 03, 2011 9:04 pm

Re: Mesh with offset center of mass

Post by MalcolmB »

Thanks for the reply again. A general overview of my problem is that my application is an end-user tool. The users can bring in geometry modeled in whatever way they want, and I want to have the physics system be able to work with their models without having to tell them re-center the meshes around (0,0,0).
Flix wrote: 1) Do you have problems matching the collision shape/graphical representation, but apart from this the physics is OK ?
Hard to say for sure if the physics are ok, but things definitely look wrong in the graphical representation.
2) The physics is OK, the graphical representation is OK, but you would like to assign/get transforms from outside the center of mass (i.e. relative to the center of the mesh). So this is only a "position" problem (for example, you may want to have 3 bodies with the same "mesh", but with 3 different center of mass, and you want to use the same exact transform to place each of the 3 bodies in the same place regardless of its COM, and the same for getting the transform back).
I don't think this is my issue, since things look wrong.
3) The physic is not OK. You would like to have a different center of mass for your rigid body.
Maybe I'm thinking about the COM wrong? To me it seems like if I give Bullet a mesh centered around an arbitrary point, I need to tell Bullet somehow "This is where the center is", otherwise it assumes the center is at (0,0,0) and does calculations based on that. Am I wrong? Is the COM calculated as an offset by Bullet from the center of the mesh (which bullets calculates automatically from the point cloud)?
a) First of all, if you don't use a btCompoundShape to "wrap" the btConvexHullShape, you still have a center of mass different from the center of your mesh, because your point cloud is centered around the point (1,1,1) and you're not shifting it.
This is OK, you don't need any center of mass offset or graphical offset inside your motion state, and the transforms you get/set are not relative to the center of your mesh, but to the center of mass of the mesh (which is represented by the (0,0,0) point in the point cloud of your convex hull).
I think this is what I'm not understanding, because it seems like if I don't take into account the offset COM, then the physical calculations would be wrong, and for example the rotations I'd get would be pivoting around (0,0,0) instead of (1,1,1), resulting in strange graphical representations.
b) A possibly better solution is to use only meshes and convex hulls centered in the origin, and just use a "btCompoundShape wrapper" to apply an COM offset shift (you need a graphic offset in your motion state that will be equal in module to the center of mass of the body). So by using meshes centered in the origin, you can easily get the center of mass of the object back through its motionstate.
I have things working fine in cases where the mesh is centered in the origin, but I'd like to avoid this rule to allow my application to be easier to use.
Flix
Posts: 456
Joined: Tue Dec 25, 2007 1:06 pm

Re: Mesh with offset center of mass

Post by Flix »

MalcolmB wrote:Is the COM calculated as an offset by Bullet from the center of the mesh (which bullets calculates automatically from the point cloud)?
I don't know it exactly... I've always thought that Bullet always places the center of mass of a shape in the (0,0,0) point of it, regardless the shape type (compound or hull). This means that a hull with a point cloud centered in a point!=(0,0,0) (say (1,1,1)) will have a center of mass offset implicitly defined ((-1,-1,-1) in this case, because the (0,0,0) point in the point cloud stays (-1,-1,-1) units away from the center of the shape, which is (1,1,1)). When you "drive" rigid bodies through a transform, it should be relative to this same point. This is how I understand the situation, but I may be wrong...

Maybe you just want that the center of mesh supplied by the user is not used by default as a center of mass by Bullet, and want the COM to be the center of the mesh (or you want the user to insert a new COM for it, but I'm considering the simpler case now): in this case is probably better to center the mesh you feed Bullet with, AND to add a graphic offset through a motion state (actually the mesh won't have any COM offset applied (COM=real center of the mesh), but you still need a motion state with COM offset, since you "draw" your mesh from a point that is not used by Bullet as a center of mass (technically it's a "graphic offset"). Remember that by doing so, when you use the motion state to get/set transforms, they will be offset so that they are relative to the original geometric center of input mesh (that was not (0,0,0)); if you set/get them without using the motion states they refer to the center of mass of the body, which is (0,0,0)).
... I think it's very difficult to talk about these topics so that people can easily understand what one means...
Anyway in the example you provided you made a "double step": convex hull inside a compound shape... I suggest you simplify it into a single step to avoid confusion.

Please note that since BulletOpenGLSupport.lib can only draw collision shapes (and not the graphic meshes that "dress" them), you don't need to add motion states with a COM offset (i.e. graphic offset), even if you create shapes with a COM offset (and if you do you should experience a wrong graphic offset). A limitation of it is that you can't retrieve (or set) a "transform outside the COM" from COM offset bodies in the Bullet demos, because you don't pass the graphic offset to the motion state.

Here is a small example of what I meant above, with a little hack that allows users to specify a "transform outside COM" when positioning the bodies (warning: it lacks a bit of testing):

Code: Select all

// In BasicDemo::initPhysics():
	{
		btCollisionShape* shapeWithNoCOMOffset = new btBoxShape(btVector3(1,8,0.75));	// Half dimensions
		m_collisionShapes.push_back(shapeWithNoCOMOffset);
		
		const btScalar mass = 1;
		const btTransform baseT(btQuaternion::getIdentity(),btVector3(0,8,0));
		
		btRigidBody* body;
		btVector3 COMOffset(0,0,0);
		btTransform T;		// Transform that does NOT depend on the COM offset
		int cnt=0;
		for (int i = -2;i<3;i++)	{
			T = baseT;
			T.getOrigin().setX((btScalar)i*2);
			COMOffset.setY((btScalar)i*4);
			body = localCreateRigidBodyWithCOMOffset(mass,T,shapeWithNoCOMOffset,COMOffset);
			if (body->getCollisionShape()!=shapeWithNoCOMOffset) m_collisionShapes.push_back(body->getCollisionShape());
		
			// But the returned transforms are still relative to the center of mass, if we don't add it to the motion state...
			btTransform wt;
			((btDefaultMotionState*) body->getMotionState())->getWorldTransform(wt);
			const btVector3& origin = wt.getOrigin();
			printf("T.origin() %1d) (%1.4f,%1.4f,%1.4f)\n",cnt++,origin.x(),origin.y(),origin.z());
		}	
	}
// Added Class Method:
btRigidBody*	BasicDemo::localCreateRigidBodyWithCOMOffset(float mass, const btTransform& startTransform,btCollisionShape* shapeWithNoCOMOffset,const btVector3& COMoffset)
{
	btAssert((!shape || shape->getShapeType() != INVALID_SHAPE_PROXYTYPE));

	btCollisionShape* shape = shapeWithNoCOMOffset;
	const btTransform COMOffsetTransform(btTransform(btQuaternion::getIdentity(),COMoffset));	
	const btTransform inverseCOMOffsetTransform = COMOffsetTransform.inverse();//(btTransform(btQuaternion::getIdentity(),-COMoffset));
	if (COMoffset!=btVector3(0,0,0))	{
		//Wrap it inside a btCompoundShape (TODO WARNING for unsupported shapes)
		btCompoundShape* cs = new btCompoundShape();
		cs->addChildShape(inverseCOMOffsetTransform,shapeWithNoCOMOffset);
		shape = cs;
	}

	//rigidbody is dynamic if and only if mass is non zero, otherwise static
	bool isDynamic = (mass != 0.f);

	btVector3 localInertia(0,0,0);
	if (isDynamic)
		shape->calculateLocalInertia(mass,localInertia);

	//using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects
	btDefaultMotionState* myMotionState;
	if (COMoffset==btVector3(0,0,0))	myMotionState = new btDefaultMotionState(startTransform);
	else	{
		const btTransform transformedStartTransform(startTransform.getBasis(),startTransform.getOrigin()+startTransform.getBasis()*(COMoffset));	// To test: startTransform.getBasis()!=identity		
		myMotionState = new btDefaultMotionState(transformedStartTransform);	// Here we SHOULD add COMOffsetTransform (or inverseCOMOffsetTransform according on how it is used inside), but everything added here
											// seems to result in a wrong graphic offset (that does not happen if nothing is added).
											// We can always derive from btDefaultMotionState, and just add an additional transform or vector variable (that is never touched) just to store the COM offset, so that it can be used to get a transform outside COM for rigid bodies, but this is more like a hack than a solution...
	}	
	

	btRigidBody::btRigidBodyConstructionInfo cInfo(mass,myMotionState,shape,localInertia);

	btRigidBody* body = new btRigidBody(cInfo);
	body->setContactProcessingThreshold(m_defaultContactProcessingThreshold);


	m_dynamicsWorld->addRigidBody(body);

	return body;
}
MalcolmB wrote:I have things working fine in cases where the mesh is centered in the origin, but I'd like to avoid this rule to allow my application to be easier to use.
Well, I usually prefer centering input meshes (for both graphic rendering and physics) and then I can apply a COM offset together with a graphic offset (so that things are a bit simpler). I must admit that I've never tried to do what you're trying to achieve; that's why I'm not 100% sure that my answer is correct/helpful :oops: .