Minie, RigidBodyControl, object mass update issues

Hi everyone,

im having issues updating the mass of a RigidBodyControl object from 0.0f to >0.0f.
The object stays inactive and does not respond to linear or angular impulse.
I have written a little test program to reproduce the issue using minie-5.0.0 (see below).

Things i have tried so far:

  1. Calling activate(true) on the object, does not resolve the issue.
  2. Calling setEnabled(false) and then setEnabled(true), does resolve the issue in my application but not with the test.
  3. Removing and re-adding the object to the physics space, makes the object respond to linear impulse but not to angular impulse.

Observations:

  1. Setting a new CompoundCollisionShape shape resolved the issue in my application, but not with the test.
  2. Updating the mass from 1.0f to 10.0f does not seem to update the angular inerta.

As far as i can tell the issue seems to be related to this https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=13398 something about the inertia tensor not beeing updated.

Additional question:
Setting a margin on a CompoundCollisionShape seems to affect its angular inertia, is this intended?

Big thanks to everyone for your continuous effort here!

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.math.Vector3f;

public class Main extends SimpleApplication implements PhysicsTickListener {
    float timer = 3.0f;
    boolean isUpdate = true;
    boolean isPush = true;

    PhysicsSpace physicsSpace;
    RigidBodyControl rigidBodyControl;

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

    @Override
    public void simpleInitApp() {
        BulletAppState bulletAppState = new BulletAppState();
        bulletAppState.setDebugEnabled(true);
        stateManager.attach(bulletAppState);

        physicsSpace = bulletAppState.getPhysicsSpace();
        physicsSpace.setGravity(Vector3f.ZERO);
        physicsSpace.addTickListener(this);

        rigidBodyControl = new RigidBodyControl(0.0f);
        rigidBodyControl.setCollisionShape(createCollisionShape());
        physicsSpace.addCollisionObject(rigidBodyControl);

        Geometry box = new Geometry("boxGeometry", new Box(0.4f, 0.4f, 0.4f));
        box.addControl(rigidBodyControl);
        box.setMaterial(getAssetManager().loadMaterial("Common/Materials/RedColor.j3m"));
        getRootNode().attachChild(box);
    }

    @Override
    public void simpleUpdate(float tpf) {
        if (timer >= 0.0f) {
            timer -= tpf;
        } else {
            if (isUpdate) {
                isUpdate = false;

                System.out.println("update");

                // updating the mass from 1.0f to 10.0f shows no issues with linear motion but angular inertia does not seem to get updated
                rigidBodyControl.setMass(1.0f);

                // doesnt work in test but works to enable linear motion in application
//                rigidBodyControl.setEnabled(false);
//                rigidBodyControl.setEnabled(true);
                // works to re-enable torque in application but not in test
//                rigidBodyControl.setCollisionShape(createCollisionShape());

                // doesnt work
//                rigidBodyControl.activate(true);

                // works to enable linear motion but still no torque
                physicsSpace.removeCollisionObject(rigidBodyControl);
                physicsSpace.addCollisionObject(rigidBodyControl);
            }
        }
    }

    @Override
    public void prePhysicsTick(PhysicsSpace physicsSpace, float v) {
        if (!isUpdate && isPush) {
            System.out.println("push");
            isPush = false;
            rigidBodyControl.applyCentralImpulse(Vector3f.UNIT_Y);
            rigidBodyControl.applyTorqueImpulse(Vector3f.UNIT_Y);
        }
    }

    static CollisionShape createCollisionShape(){
        CompoundCollisionShape result = new CompoundCollisionShape();
        result.addChildShape(new BoxCollisionShape(0.5f));
        // why does this affect torque when a compound collision shape is used?
//        result.setMargin(1.0f);
        return result;
    }

    @Override
    public void physicsTick(PhysicsSpace physicsSpace, float v) {

    }
}

2 Likes

I don’t see where the RigidBodyControl gets added to the scene graph.

the control isnt added to the scene graph at all. only

bulletAppState.setDebugEnabled(true);

is used for visualization. shall i fix that?

1 Like

i updated the example but it did not seem to have an effect.

shall i fix that?

Not necessary. It seemed an roundabout way of creating the physics object, so I wanted to mention it.

I’ve run some experiments, and I’m convinced there’s a bug when a rigid body is converted from static to dynamic. I’ve written a patch, but I’d like to test it a bit before releasing it.

5 Likes

From report to solution in <4h, thank you Stephen! :+1:
A scarce experience…

4 Likes

that’s how sgold rolls :muscle: (⌐▀͡ ̯ʖ▀)

Also welcome to the community! @Gaius

2 Likes

thank you Stephen!

And thank you @Gaius, for bringing the issue to my attention!

2 Likes

@Gaius: Please upgrade your project to use Minie v5.0.1 and let me know whether that solves the issue for you.

its a pleasure! its the least one can do for such a beautiful project like minie…
the initial issue is resolved with 5.0.1.
only the issue with the angular motion on updating the mass from 1.0f to 10.0f remains.

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication implements PhysicsTickListener {
    float timer = 3.0f;
    boolean isUpdate = true;
    boolean isPush = true;

    PhysicsSpace physicsSpace;
    RigidBodyControl updatedRigidBodyControl;
    RigidBodyControl referenceRigidBodyControl;

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

    @Override
    public void simpleInitApp() {
        BulletAppState bulletAppState = new BulletAppState();
        bulletAppState.setDebugEnabled(true);
        stateManager.attach(bulletAppState);

        physicsSpace = bulletAppState.getPhysicsSpace();
        physicsSpace.setGravity(Vector3f.ZERO);
        physicsSpace.addTickListener(this);

        updatedRigidBodyControl = new RigidBodyControl(1.0f);
        updatedRigidBodyControl.setCollisionShape(createCollisionShape());
        physicsSpace.addCollisionObject(updatedRigidBodyControl);

        referenceRigidBodyControl = new RigidBodyControl(10.0f);
        referenceRigidBodyControl.setCollisionShape(createCollisionShape());
        physicsSpace.addCollisionObject(referenceRigidBodyControl);

        Geometry updatedBox = new Geometry("boxGeometry", new Box(0.4f, 0.4f, 0.4f));
        updatedBox.setLocalTranslation(2.0f, 0.0f, 0.0f);
        updatedBox.addControl(updatedRigidBodyControl);
        updatedBox.setMaterial(getAssetManager().loadMaterial("Common/Materials/RedColor.j3m"));
        getRootNode().attachChild(updatedBox);

        Geometry referenceBox = new Geometry("boxGeometry", new Box(0.4f, 0.4f, 0.4f));
        referenceBox.setLocalTranslation(-2.0f, 0.0f, 0.0f);
        referenceBox.addControl(referenceRigidBodyControl);
        referenceBox.setMaterial(getAssetManager().loadMaterial("Common/Materials/RedColor.j3m"));
        getRootNode().attachChild(referenceBox);
    }

    @Override
    public void simpleUpdate(float tpf) {
        if (timer >= 0.0f) {
            timer -= tpf;
        } else {
            if (isUpdate) {
                isUpdate = false;

                System.out.println("update");

                // updating the mass from 1.0f to 10.0f shows no issues with linear motion but angular inertia does not seem to get updated
                updatedRigidBodyControl.setMass(10.0f);
            }
        }
    }

    @Override
    public void prePhysicsTick(PhysicsSpace physicsSpace, float v) {
        if (!isUpdate && isPush) {
            System.out.println("push");
            isPush = false;
            updatedRigidBodyControl.applyCentralImpulse(Vector3f.UNIT_Y);
            updatedRigidBodyControl.applyTorqueImpulse(Vector3f.UNIT_Y);
            referenceRigidBodyControl.applyCentralImpulse(Vector3f.UNIT_Y);
            referenceRigidBodyControl.applyTorqueImpulse(Vector3f.UNIT_Y);
        }
    }

    static CollisionShape createCollisionShape(){
        CompoundCollisionShape result = new CompoundCollisionShape();
        result.addChildShape(new BoxCollisionShape(0.5f));
        // why does this affect torque when a compound collision shape is used?
//        result.setMargin(1.0f);
        return result;
    }

    @Override
    public void physicsTick(PhysicsSpace physicsSpace, float v) {

    }
}

2 Likes

thank you for the welcome! have been reading the forums for quite some time now, but until now all questions have already been answered… :wink:

2 Likes

the issue with the angular motion on updating the mass from 1.0f to 10.0f remains.

Another good catch!

This appears to be a bug in the native portion of Minie. setMass() updates the body’s local inertia vector but not its world inertia tensor. Ordinarily this isn’t an issue because the tensor gets updated during the next simulation step. In the “Main” app, however, the torque impulse is applied before the next simulation step, so it sees the old value of the tensor.

I envision a one-line fix, but it might take a few days to release it.

3 Likes

the effect is persistent beyond the next simulation step i think. i updated the example with another timer to demonstrate this.
maybe its just an unusual use case for someone to update the mass of a body after creation.

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication implements PhysicsTickListener {
    float updateTimer = 3.0f;
    float pushTimer = 3.0f;
    boolean isUpdate = true;
    boolean isPush;
    boolean isDone;

    PhysicsSpace physicsSpace;
    RigidBodyControl updatedRigidBodyControl;
    RigidBodyControl referenceRigidBodyControl;

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

    @Override
    public void simpleInitApp() {
        BulletAppState bulletAppState = new BulletAppState();
        bulletAppState.setDebugEnabled(true);
        stateManager.attach(bulletAppState);

        physicsSpace = bulletAppState.getPhysicsSpace();
        physicsSpace.setGravity(Vector3f.ZERO);
        physicsSpace.addTickListener(this);

        updatedRigidBodyControl = new RigidBodyControl(1.0f);
        updatedRigidBodyControl.setCollisionShape(createCollisionShape());
        physicsSpace.addCollisionObject(updatedRigidBodyControl);

        referenceRigidBodyControl = new RigidBodyControl(10.0f);
        referenceRigidBodyControl.setCollisionShape(createCollisionShape());
        physicsSpace.addCollisionObject(referenceRigidBodyControl);

        Geometry updatedBox = new Geometry("boxGeometry", new Box(0.4f, 0.4f, 0.4f));
        updatedBox.setLocalTranslation(2.0f, 0.0f, 0.0f);
        updatedBox.addControl(updatedRigidBodyControl);
        updatedBox.setMaterial(getAssetManager().loadMaterial("Common/Materials/RedColor.j3m"));
        getRootNode().attachChild(updatedBox);

        Geometry referenceBox = new Geometry("boxGeometry", new Box(0.4f, 0.4f, 0.4f));
        referenceBox.setLocalTranslation(-2.0f, 0.0f, 0.0f);
        referenceBox.addControl(referenceRigidBodyControl);
        referenceBox.setMaterial(getAssetManager().loadMaterial("Common/Materials/RedColor.j3m"));
        getRootNode().attachChild(referenceBox);
    }

    @Override
    public void simpleUpdate(float tpf) {
        if (updateTimer >= 0.0f) {
            updateTimer -= tpf;
        } else {
            if (isUpdate) {
                isUpdate = false;
                System.out.println("update");
                // updating the mass from 1.0f to 10.0f shows no issues with linear motion but angular inertia does not seem to get updated
                updatedRigidBodyControl.setMass(10.0f);
            }
            if(pushTimer >= 0.0f){
                pushTimer -= tpf;
            } else {
                isPush = true;
            }
        }
    }

    @Override
    public void prePhysicsTick(PhysicsSpace physicsSpace, float v) {
        if (!isUpdate && isPush && !isDone) {
            System.out.println("push");
            isDone = true;
            updatedRigidBodyControl.applyCentralImpulse(Vector3f.UNIT_Y);
            updatedRigidBodyControl.applyTorqueImpulse(Vector3f.UNIT_Y);
            referenceRigidBodyControl.applyCentralImpulse(Vector3f.UNIT_Y);
            referenceRigidBodyControl.applyTorqueImpulse(Vector3f.UNIT_Y);
        }
    }

    static CollisionShape createCollisionShape(){
        CompoundCollisionShape result = new CompoundCollisionShape();
        result.addChildShape(new BoxCollisionShape(0.5f));
        // why does this affect torque when a compound collision shape is used?
//        result.setMargin(1.0f);
        return result;
    }

    @Override
    public void physicsTick(PhysicsSpace physicsSpace, float v) {

    }
}

3 Likes

setting a collision margin of 1.0f on the CompoundCollsisionShape also seems to influnce the angular inertia. so maybe there is a connection to the collision shape im using here.

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

public class Margin extends SimpleApplication implements PhysicsTickListener {
    float pushTimer = 3.0f;
    boolean isPush;
    boolean isDone;

    PhysicsSpace physicsSpace;
    RigidBodyControl rigidBodyControl;
    RigidBodyControl referenceRigidBodyControl;

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

    @Override
    public void simpleInitApp() {
        BulletAppState bulletAppState = new BulletAppState();
        bulletAppState.setDebugEnabled(true);
        stateManager.attach(bulletAppState);

        physicsSpace = bulletAppState.getPhysicsSpace();
        physicsSpace.setGravity(Vector3f.ZERO);
        physicsSpace.addTickListener(this);

        rigidBodyControl = new RigidBodyControl(1.0f);
        CompoundCollisionShape compoundCollisionShape = new CompoundCollisionShape();
        BoxCollisionShape childShape = new BoxCollisionShape(0.5f);
        compoundCollisionShape.addChildShape(childShape);
        compoundCollisionShape.setMargin(1.0f);
        rigidBodyControl.setCollisionShape(compoundCollisionShape);
        physicsSpace.addCollisionObject(rigidBodyControl);

        referenceRigidBodyControl = new RigidBodyControl(1.0f);
        BoxCollisionShape referenceCollisionShape = new BoxCollisionShape(0.5f);
        referenceCollisionShape.setMargin(1.0f);
        referenceRigidBodyControl.setCollisionShape(referenceCollisionShape);
        physicsSpace.addCollisionObject(referenceRigidBodyControl);

        Geometry box = new Geometry("boxGeometry", new Box(0.4f, 0.4f, 0.4f));
        box.setLocalTranslation(3.0f, 0.0f, 0.0f);
        box.addControl(rigidBodyControl);
        box.setMaterial(getAssetManager().loadMaterial("Common/Materials/RedColor.j3m"));
        getRootNode().attachChild(box);

        Geometry referenceBox = new Geometry("boxGeometry", new Box(0.4f, 0.4f, 0.4f));
        referenceBox.setLocalTranslation(-3.0f, 0.0f, 0.0f);
        referenceBox.addControl(referenceRigidBodyControl);
        referenceBox.setMaterial(getAssetManager().loadMaterial("Common/Materials/RedColor.j3m"));
        getRootNode().attachChild(referenceBox);
    }

    @Override
    public void simpleUpdate(float tpf) {
        if (pushTimer >= 0.0f) {
            pushTimer -= tpf;
        } else {
            isPush = true;
        }
    }

    @Override
    public void prePhysicsTick(PhysicsSpace physicsSpace, float v) {
        if (isPush && !isDone) {
            System.out.println("push");
            isDone = true;
            rigidBodyControl.applyCentralImpulse(Vector3f.UNIT_Y);
            rigidBodyControl.applyTorqueImpulse(Vector3f.UNIT_Y);
            referenceRigidBodyControl.applyCentralImpulse(Vector3f.UNIT_Y);
            referenceRigidBodyControl.applyTorqueImpulse(Vector3f.UNIT_Y);
        }
    }

    @Override
    public void physicsTick(PhysicsSpace physicsSpace, float v) {

    }
}

2 Likes

the effect is persistent beyond the next simulation step i think

If you print out the inverse inertia after each timestep, you can see that it gets updated:

    @Override
    public void physicsTick(PhysicsSpace physicsSpace, float v) {
        Matrix3f iiw = updatedRigidBodyControl.getInverseInertiaWorld(null);
        System.out.println(iiw);
    }
Matrix3f
[
 5.1440325  0.0  0.0 
 0.0  5.1440325  0.0 
 0.0  0.0  5.1440325 
]
push
Matrix3f
[
 0.5144033  0.0  0.0 
 0.0  0.5144033  0.0 
 0.0  0.0  0.5144033 
]

The torque impulses are only applied once, right? At that time, the inertia was incorrect, so the angular rates got set wrong. Bullet doesn’t recalculate the rates after the inertia changes, and since there are no further forces/torques/impulses, the incorrect rates persist.

setting a collision margin of 1.0f on the CompoundCollsisionShape also seems to influnce the angular inertia

Bullet’s estimate of angular inertia for a compound shape uses a crude approximation based on the shape’s axis-aligned bounding box:

It seems likely (though I haven’t checked) that the AABB includes the collision margin. If so, that would explain why margin affects the angular inertia.

(By the way, Bullet’s author explicitly advises against changing collision margins from the default.)

If accuracy matters, you can override Bullet’s estimate using setInverseInertiaLocal(). Browsing the code, I just realized that setInverseInertiaLocal()—like setMass()—doesn’t immediately update the world inertia tensor, so I’ll fix that in the next release as well.

1 Like

yes, the torque impulse is only applied once.
i was just wondering as i was seeing the effect in my game as well where applyTorque() is used continously.

1 Like

that explains something! :+1:

1 Like