Problem with Hinge Joint axis (bodies fall as if not joined with non-unit axis)

Hi,

I have a problem with HingeJoint in the bullet physics implementation (v.3.2.1-stable-sdk1, jme-bullet-native lib).

If I create a hinge joint with a rotation axis that is a unit vector (e.g. Vector3f.UNIT_X) everything works as expected.

But if the axis is even slightly off (like Vector3f( 0.99f, 0, 0.01f ).normalize()), the joint has no effect so that the attached non-static rigid body just falls down.

Am I missing something? Could this be a bug?

Slightly related: How do I revert back to the non-native bullet lib to check if the problem still occurs? If I remove “jme-bullet-native” from my projects libraries, do clean and build, then the console output still says “Bullet-Native: Initializing java classes” :sweat:

The code looks ok from here, so no clue…
And my magic crystal ball is currently out of order :wink:

Oops, the code snippet in my previous post somehow got shrunk down to zero pixels :stuck_out_tongue:

So here it is:

// ...

Vector3f axis;

// This works, rigid body revolutes around x axis
axis = Vector3f.UNIT_X;

// This does not work! Body should rotate around an axis slightly off the global x axis.
axis = new Vector3f( 0.99f, 0, 0.01f );
axis.normalizeLocal();

physicsJoint = new HingeJoint
(
	rigidBodyPosA, // Rigid Body A is static (mass = 0)
	rigidBodyPosB, // Rigid Body B is dynamic (mass > 0)
	jointPos.subtract( rigidBodyPosA ),
	jointPos.subtract( rigidBodyPosB ),
	axis,
	axis
);

// ...

physicsSpace.add( physicsJoint );

// ...

There is actually more stuff involved, but I edited the code for clarity. If required I may put together a stand-alone example soon.

What happens on my machine if I run the code with a non-unit axis (e.g. UNIT_X) is that the dynamic body behaves as if there was no joint at all.

Does new Vector3f(1, 0, 0) work?!
I mean hmmmm… I guess it does, but it is not 100% the same like UNIT_X but almost :smile:

All the samples I found use either UNIT_X or UNIT_Z for the DoF, but your one should also work, except it is restricted to pure X or Y or Z… strange.

Yes, as expected this works just the same as Vector3f.UNIT_X.

Here is an isolated code example that shows the problem:

public class PhysicsJointTest extends SimpleApplication
{
    public static void main( String[] args )
    {
        // Settings
	AppSettings settings = new AppSettings( true );
	settings.setTitle( "Test" );
        settings.setResolution( 800, 600 );
	settings.setFullscreen( false );
	
	// App
	PhysicsJointTest app = new PhysicsJointTest();
	app.setShowSettings( false );
	app.setSettings( settings );
	app.start();
    }

    @Override
    public void simpleInitApp()
    {
        // Camera
        cam.setLocation( new Vector3f( -20, 20, -20 ) );
        cam.lookAt( Vector3f.ZERO, Vector3f.UNIT_Y );
        
        flyCam.setDragToRotate( true );
        flyCam.setMoveSpeed( 60 );
        flyCam.setRotationSpeed( 10 );
		
	// Physics
	BulletAppState physics = new BulletAppState();
	stateManager.attach( physics );
		
	physics.setDebugEnabled( true );
		
	// Rigid Body A		
	RigidBodyControl bodyA = new RigidBodyControl( new BoxCollisionShape( Vector3f.UNIT_XYZ ), 0.0f );
	physics.getPhysicsSpace().add( bodyA );
		
	Vector3f posA = new Vector3f( 0, 10, 0 );
	bodyA.setPhysicsLocation( posA );
		
	// Rigid Body B
	RigidBodyControl bodyB = new RigidBodyControl( new BoxCollisionShape( Vector3f.UNIT_XYZ ), 1.0f );
	physics.getPhysicsSpace().add( bodyB );
		
	Vector3f posB = new Vector3f( 0, 10, 10 );
	bodyB.setPhysicsLocation( posB );
		
	// Joint
	Vector3f jointPos = new Vector3f( 0, 10, 1 );
	Vector3f jointAxis;

	// This works:
	jointAxis = new Vector3f( Vector3f.UNIT_X );
		
	// This works too:
	jointAxis = new Vector3f( 1.0f, 0.0f, 0.0f );
		
	// This does not work:
	jointAxis = new Vector3f( 0.99f, 0.0f, 0.01f );
	jointAxis.normalizeLocal();
		
	HingeJoint joint = new HingeJoint
	(
		bodyA,
		bodyB,
		jointPos.subtract( posA ),
		jointPos.subtract( posB ),
		jointAxis,
		jointAxis
	);
		
	physics.getPhysicsSpace().add( joint );
    }

    @Override
    public void simpleUpdate( float tpf )
    {
        //TODO: add update code
    }

    @Override
    public void simpleRender( RenderManager rm )
    {
        //TODO: add render code
    }
}

Comment out the various joint axis settings to see the difference.
Only the unit X vector let’s the dynamic body B swing around the static body A :chimpanzee_woot:

A hinge joint can only rotate on one axis. I think it is more of “what axis” than “what direction”. So just set it to 1 for x,y or z.

So that means it is effectively not possible to have a hinge joint rotate around any axis that is not one of the three world axis XYZ?

Sounds like a missing feature to me…

Yes, just rotate your object first, and then rotate it in a hing-like fashion.

That is to say: position your object in its desired rotation, and then use it as a hinge that rotates on an single axis.

Ok, thanks for the clarification.

So I better set up my joints with everything being axis aligned and then rotate the whole compound I guess.

I believe the Spatial worldToLocal and localToWorld methods come in quite handy here as they do pretty much what you want.

In the case of your models, the question is “what is center” - and what you define as center in your model, and I believe the correct position of center in your model is the point of its rotation. For a person it would be the bottom of their feet in the middle. For a wheel it would be the middle of the wheel. For a hinge it would be at the hinges rotational axis. For a tree, maybe just above the root center, so scaling would work well, too, when I position it.

Now when I rotate it along its desired axis, it behaves as one expects.

When I made the Ragdoll control I remember switching to 6DofJoints instead of hingeJoints for this kind of reason.

OK, thanks for the hints guys.

I will take a closer look at those SixDofJoints, as I haven’t checked them out yet.
Until then I can work with axis aligned joints.

Maybe the HingeJoint constructor should raise an exception when a not supported axis is used (or at least it should be mentioned in the docs).