[SOLVED] Meaning of parameter in PhysicsRigidBody.applyTorqueImpulse()

Hello everybody!

I wonder what the physical meaning of the Vector3f parameter in the function com.jme3.bullet.objects.PhysicsRigidBody.applyTorqueImpulse(Vector3f vec) might be.
I thought the direction of this vector should represent the direction of the rotation axis (through the center of mass of the body) and its length should represent the absolute value of the torque impulse.

But now in my tests I see, that a RigidBody’s rotation axis is NOT the same in the following two cases:

vehicle.setAngularVelocity(Vector3f.UNIT_Z)
vehicle.applyTorqueImpulse(Vector3f.UNIT_Z)

After I got suspicious about that point, I tried it out with a box with attached local coordinate-arrows (Gravity switched off):
First I give the Box a translation and rotation by using setPhysicsLocation and setPhysicsRotation to have a difference between local and global coordinates.
In some very simple Positions (eg. 90° turn around UNIT_Y) of the Box everything behaves like I would expect. But when the box is placed in a more complex way, the following problem occurs:

SetAngularVelocity results in a Rotation around the center of mass and and with the given vector as direction of the rotation axis (as I assumed).
But applyTorqueImpulse results in a Rotation around a different axis. The box seams to rotate neither around the UNIT_Z in world coordinates nor in local coordinates.

By the way: With applyTorque() its the same axis as whith applyTorqueImpulse.

So what is the physical meaning of the vector parameter in applyTorque and applyTorqueImpulse? It’s not described in JavaDoc. Could anybody explain it, please?
Thanks a lot for helping!

Here’s my test code:


import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;

public class TestPhysicsCar extends SimpleApplication implements ActionListener {

    private BulletAppState bulletAppState;
    private VehicleControl vehicle;
    Material matRed;
    Material matGreen;
    Material matBlue;
    Material matBlack;
    boolean capital = false;
    Vector3f rotationAxis;

    public static void main(String[] args) {
        TestPhysicsCar app = new TestPhysicsCar();
        app.start();
    }

    @Override
    public void simpleInitApp() {        
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(true);
        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
        matRed = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        matRed.getAdditionalRenderState().setWireframe(true);
        matRed.setColor("Color", ColorRGBA.Red);
        matGreen = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        matGreen.getAdditionalRenderState().setWireframe(true);
        matGreen.setColor("Color", ColorRGBA.Green);
        matBlue = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        matBlue.getAdditionalRenderState().setWireframe(true);
        matBlue.setColor("Color", ColorRGBA.Blue);
        matBlack = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        matBlack.getAdditionalRenderState().setWireframe(true);
        matBlack.setColor("Color", ColorRGBA.Black);
        setupKeys();
        buildPlayer();
        buildGlobalCoordSys();
        cam.setLocation(new Vector3f(5, 10, 20));
        
    }

    private PhysicsSpace getPhysicsSpace(){
        return bulletAppState.getPhysicsSpace();
    }

    private void setupKeys() {
        inputManager.addMapping("LeftTorque", new KeyTrigger(KeyInput.KEY_LEFT));
        inputManager.addMapping("RightTorque", new KeyTrigger(KeyInput.KEY_RIGHT));
        inputManager.addMapping("LeftAngularV", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("RightAngularV", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("Stop", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
        inputManager.addListener(this, "LeftTorque");
        inputManager.addListener(this, "LeftAngularV");
        inputManager.addListener(this, "RightTorque");
        inputManager.addListener(this, "RightAngularV");
        inputManager.addListener(this, "Stop");  
        inputManager.addListener(this, "Reset"); 
    }

    private void buildPlayer() {

        //create a compound shape and attach the BoxCollisionShape for the car body
        CompoundCollisionShape compoundShape = new CompoundCollisionShape();
        BoxCollisionShape box = new BoxCollisionShape(new Vector3f(1, 0.5f, 3f));
        compoundShape.addChildShape(box, new Vector3f(0, 0, 0));

        //create vehicle node
        Node vehicleNode=new Node("vehicleNode");
        vehicle = new VehicleControl(compoundShape, 1);
        vehicleNode.addControl(vehicle);
        getPhysicsSpace().add(vehicle);
        vehicle.setGravity(new Vector3f(0,0,0));
        
        //give it a mesh
        Box vehicleMesh = new Box(1, 0.5f, 3);
        Geometry vehicleGeo = new Geometry("vehicle", vehicleMesh);
        vehicleGeo.setMaterial(matBlack);
        vehicleNode.attachChild(vehicleGeo);        
        
        //show a local CoordSys
        //X
        Arrow arrowXMesh = new Arrow(Vector3f.UNIT_X.mult(4));
        Geometry arrowXGeo = new Geometry("arrowXLocal", arrowXMesh);
        arrowXGeo.setMaterial(matRed);
        Node arrowXNode = new Node ("arrowX");
        arrowXNode.attachChild(arrowXGeo);
        vehicleNode.attachChild(arrowXNode);
        //Y
        Arrow arrowYMesh = new Arrow(Vector3f.UNIT_Y.mult(4));
        Geometry arrowYGeo = new Geometry("arrowYLocal", arrowYMesh);
        arrowYGeo.setMaterial(matGreen);
        Node arrowYNode = new Node ("arrowY");
        arrowYNode.attachChild(arrowYGeo);
        vehicleNode.attachChild(arrowYNode);
        //Z
        Arrow arrowZMesh = new Arrow(Vector3f.UNIT_Z.mult(4));
        Geometry arrowZGeo = new Geometry("arrowZLocal", arrowZMesh);
        arrowZGeo.setMaterial(matBlue);
        Node arrowZNode = new Node ("arrowZ");
        arrowZNode.attachChild(arrowZGeo);
        vehicleNode.attachChild(arrowZNode);        
        
        //place vehicle node
        placeVehicle();
        
        //set Rotation Axis
        rotationAxis = Vector3f.UNIT_Z;
        
        rootNode.attachChild(vehicleNode);
    }
    
    
    private void placeVehicle() {
        vehicle.setPhysicsLocation(new Vector3f(0, 5, 0));
        Quaternion quat1 = new Quaternion();
        quat1.fromAngleAxis((float)Math.PI/2, new Vector3f(0,1,1));
        vehicle.setPhysicsRotation(quat1);
    }
    
    
    private void buildGlobalCoordSys() {
        //show a global CoordSys
        //X
        Arrow arrowXMesh = new Arrow(Vector3f.UNIT_X.mult(6));
        Geometry arrowXGeo = new Geometry("arrowXGlobal", arrowXMesh);
        arrowXGeo.setMaterial(matRed);
        Node arrowXNode = new Node ("arrowX");
        arrowXNode.attachChild(arrowXGeo);
        rootNode.attachChild(arrowXNode);
        //Y
        Arrow arrowYMesh = new Arrow(Vector3f.UNIT_Y.mult(6));
        Geometry arrowYGeo = new Geometry("arrowYGlobal", arrowYMesh);
        arrowYGeo.setMaterial(matGreen);
        Node arrowYNode = new Node ("arrowY");
        arrowYNode.attachChild(arrowYGeo);
        rootNode.attachChild(arrowYNode);
        //Z
        Arrow arrowZMesh = new Arrow(Vector3f.UNIT_Z.mult(6));
        Geometry arrowZGeo = new Geometry("arrowZGlobal", arrowZMesh);
        arrowZGeo.setMaterial(matBlue);
        Node arrowZNode = new Node ("arrowZ");
        arrowZNode.attachChild(arrowZGeo);
        rootNode.attachChild(arrowZNode);   
    }

    @Override
    public void simpleUpdate(float tpf) {
        cam.setLocation(new Vector3f(5, 10, 20));
        cam.lookAt(vehicle.getPhysicsLocation(), Vector3f.UNIT_Y);
    }

    @Override
    public void onAction(String binding, boolean value, float tpf) {
        if (binding.equals("Stop")) {
            if (value) {
                vehicle.setAngularVelocity(Vector3f.ZERO);
            } else {                
            }
        } else if (binding.equals("LeftTorque")) {
            if (value) {
                vehicle.applyTorqueImpulse(rotationAxis.normalize());
            } else {                
            }
        } else if (binding.equals("LeftAngularV")) {
            if (value) {
                vehicle.setAngularVelocity(rotationAxis.normalize());
            } else {                
            }
        } else if (binding.equals("RightTorque")) {
            if (value) {
                vehicle.applyTorqueImpulse(rotationAxis.normalize().negate());
            } else {                
            }
        } else if (binding.equals("RightAngularV")) {
            if (value) {
                vehicle.setAngularVelocity(rotationAxis.normalize().negate());                
            } else {
            } 
        } else if (binding.equals("Reset")) {
            if (value) {
                vehicle.setAngularVelocity(Vector3f.ZERO);
                placeVehicle();
            }
        }
    }
}
1 Like

I don’t know much about bullet but a torque or angular momentum change for physics engine is usually not set as “an axis” because that would be direction only with no magnitude.

In many physics engines, it’s rotation about the x, y, and z-axes separately. So unit Z would be a 1-unit rotation around the Z axis so might “look” like you are passing in an axis… but it’s really separate values for each axis and they can be more than one for high values, etc…

@pspeed: Thank you for answering!

I think your interpretation (seperat rotations around x, y, and z-axes) makes no difference to mine:
In real physics, where torque impulse is a vector according to my interpretation, you can add the vectors of two torque impulses by vector addition. So applying two torque impulses (eg. 1.0 around x and 0.5 around y) would result in a Vector (1.0, 0.5, 0) in your interpretation and in (1.0, 0, 0) + (0, 0.5, 0) in mine. And that is the same.
With the magnitude there is no problem in my interpretation. The magnitude of the given vector would represent the magnitude of the torque impulse applied.

So in both Interpretations the problem of the “wrong” behaviour of the box remains.
If you run my code (above) you can see this odd behaviour at once: I use UNIT_Z as parameter for applyTorqueImpulse(). That should cause an angular velocity around z in both interpretations. But it does not do that.
Use arrow left or right for applying torque impulse and A/D for setting angular velocity directly. Reset the Situation by pressing return.

In the end, it’s a matter of terminology. An axis vector will always be of length 1. A torque vector will not.

Usually, I’d expect such vectors to be applied directly to the angular velocity (which is also effectively separate x-rotation, y-rotation, z-rotation values in vector form and not a unit vector axes). So the x, y, z components are effectively maintained separately until final application to orientation.

So while you can think of them kind of sort of like similar to axis vectors, they are not really.

As to your issue, I don’t know. It feels like a coordinate system problem but you already say that it seems not to make sense as either world or local… so I don’t know.

Yes, I agree. For myself I prefer the axis idea because this way I can imagine whats going on. Thank you very much so long!

I hope somebody else could help?

If there’s no JavaDoc, then you’re not using the Minie Physics Library.
And if you’re not using Minie Physics, you should!

Here’s what the Minie javadoc says about applyTorqueImpulse():

### applyTorqueImpulse

public void applyTorqueImpulse(com.jme3.math.Vector3f torqueImpulse)

Apply a torque impulse to the body.

Parameters:
torqueImpulse - the torque impulse vector (mass times physics-space units squared per second in physics-space coordinates, not null, unaffected)

It’s very likely the semantics are the same in jme3-jbullet: in other words, physics-space coordinates, not local coordinates.

@sgold: Thanks a lot for answering my question and for the hint to use the Minie javadoc!

So the meaning of the parameter in applyTorqueImpulse is what I had expected: Its like in real physics.

The problem with the odd behaviour of the box in my test remains.

Meanwhile I read some articles about the real physics of rotation and realized that torque impulse and rotation axis are not parallel in general. They only are if a body is rotated around one of its main inertia axis.
This could perhaps explain, why applying a torque impulse to a randomly placed box can result in a rotation around an axis that is not the direction of the torque impuls vector.

Conclusion: My Problem is solved with your help and the knowledge of real rotation physics.

3 Likes

I’m glad you figured out what was going on.