My SixDofJoint behaves strangely on y-axis rotation.
In the game I am making I need to limit the movement of some objects to the XZ plane in such a way that Y-axis movement is prohibited and that rotation is only permitted along the Y axis. Slight deviations are ok, but should be minor and the objects should be returned to an allowed position/rotation as soon as possible in a stable way.
The way I went about this was to create a SixDofJoint that connected the object body with a invisible mass 0 RigidBodyControl.
The translation limits worked fine, by setting Upper/Lower linear limits like so:
planeJoint.setLinearLowerLimit(new Vector3f(Float.NEGATIVE_INFINITY, -0.01f, Float.NEGATIVE_INFINITY));
planeJoint.setLinearUpperLimit(new Vector3f(Float.POSITIVE_INFINITY, 0.01f, Float.POSITIVE_INFINITY));
I set the rotation similarity by setting
planeJoint.setAngularLowerLimit(new Vector3f(0, Float.NEGATIVE_INFINITY, 0));
planeJoint.setAngularUpperLimit(new Vector3f(0, Float.POSITIVE_INFINITY, 0));
This is when things started to get strange…
The joint works perfectly when the Y-axis-rotation is less than 1/2 PI (90 degrees) to both directions, but as soon as the object is rotated over that limit, it suddenly starts rotating wildly and is apparently affacted by a lot of strong opposing forces. Why is this happening?
I have tested to limit the other rotational axis instead, for instance so that only x-axis rotation is allowed, and this works fine, the object can rotate to any angle along that axis and the joint still behaves well, but for the y-axis something is different…?
If I disable all rotational limits and set them all to infinity, then the object can rotate just fine around the y-axis, but as soon as I prohibit either X or Z axis rotation, Y axis rotation starts behaving this way…
Any ideas?
Did you try this with native bullet too? It might be theres some quirks in parts of jbullet.
I did this a long time ago in c++ using Bullet (and Ogre3d), and I got it working then but I dont remember exactly how I did it.
But, I have found the problem, as it also says in the comments to the JBullet General6DofConstraint it will only accept angular limits in a certain range:
X: +/- PI
Y: +/- PI / 2
Z: +/- PI / 2
PI / 2 is the exact point where my joint started to misbehave…
I somewhat suck att linear algebra but my guess (motivated by nothing but intuition…) is that the algorithm that calculates if there is a violation of the limit or not uses euler angles that are unable to figure out what set of rotations that produced the position and thus believes that the y-rotation > PI / 2 is actually some combination of X and Z rotations, in which case they violate the limits, triggering a huge torque in an attempt to push the object back into position along some not so correct path.
Probably this is general bullet behaviour rather than a bug in jbullet… but it limits the uses of the 6-dof-constraint…
Here is a workaround solution, if there are others trying to do the same thing:
I used the SixDofJoint as above but only for the linear limits, thus keeping the object in the plane, but setting the angular limits to:
[java] planeJoint.setAngularLowerLimit(Vector3f.NEGATIVE_INFINITY);
planeJoint.setAngularUpperLimit(Vector3f.POSITIVE_INFINITY);[/java]
thus letting the object rotate freely.
Then I added this into the update method:
[java] // limiting the rotations to the XZ plane is all about keeping the y-axis pointing upwards
Vector3f[] axes = new Vector3f [] {new Vector3f(), new Vector3f(), new Vector3f()}; Vector3f devAxis = null;
ship.getLocalRotation().toAxes(axes);
// find how the local-y deviates from world-y
// taking the cross product gives the axis of the deviation,
// and the length of this vector also grows geometrically as the angle increases
// starting from 0
devAxis = axes[1].cross(Vector3f.UNIT_Y);
// if the local y does not align with world y…
if(devAxis.length() > 0) {
RigidBodyControl shipB = ship.getControl(RigidBodyControl.class);
// apply a corrective torque
shipB.applyTorque(devAxis.mult(5000.0f));
Vector3f av = shipB.getAngularVelocity();
// this prevents the object from going into a pendulum motion
shipB.setAngularVelocity(new Vector3f(0.9f * av.x, av.y, 0.9f * av.z));
}
[/java]
Actually, it also worked with only this:
[java] shipB.setAngularVelocity(new Vector3f(0, av.y, 0));
[/java]
but in my particular application it made the constraint too stiff, the above version using the cross-product to apply a corrective torque gave a more dynamic feel as it allows for the object to be pushed off slightly in a collision and then it will snap back smoothly.