jmonkeyengine icon indicating copy to clipboard operation
jmonkeyengine copied to clipboard

physics joints don't work unless both bodies are dynamic

Open DanielMartensson opened this issue 7 years ago • 14 comments
trafficstars

Hi!

I was following this tutorial https://wiki.jmonkeyengine.org/jme3/advanced/hinges_and_joints.html and I'm doing a cradle with 6 joints and 6 balls.

The problem is that if I have more than 1 joint, the other joints is going to fall down.

Here is the code. Try to run it and add some hinge joints by removing the comments down there.

Right now the code bulletAppState.getPhysicsSpace().add(Wire1Joint1);

Is activaded and you should see a pendelum with a square.

Try to activate

bulletAppState.getPhysicsSpace().add(Wire1Joint2);

package mygame;


import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import static com.jme3.bullet.PhysicsSpace.getPhysicsSpace;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.joints.HingeJoint;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;


/**
 * This is the Main Class of your Game. You should only do initialization here.
 * Move your Logic into AppStates or Controls
 * @author normenhansen
 */
public class Main extends SimpleApplication {

    private BulletAppState bulletAppState;
    
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        flyCam.setMoveSpeed(20);
        
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(true);
        
        // Load Cradle
        //Spatial Frame = assetManager.loadModel("Scenes/CradleScene.j3o");
        // Create node for that Cradle
        Node FrameNode = new Node();
        // Attach the cradle at the node
        //FrameNode.attachChild(Frame);
        rootNode.attachChild(FrameNode);
        
        // Create the static nodes for left wire
        //Node LeftWire1StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node LeftWire2StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node LeftWire3StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node LeftWire4StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node LeftWire5StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node LeftWire6StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        
        Node Wire1StaticNode = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        Node Wire2StaticNode = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        Node Wire3StaticNode = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        Node Wire4StaticNode = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        Node Wire5StaticNode = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        Node Wire6StaticNode = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        
        // Create the static nodes for rigth wire
        //Node RigthWire1StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node RigthWire2StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node RigthWire3StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node RigthWire4StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node RigthWire5StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        //Node RigthWire6StaticNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
        
        // Create the dyncamical nodes for the balls
        Node Ball1Node = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),1f);
        Node Ball2Node = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),1);
        Node Ball3Node = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),1);
        Node Ball4Node = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),1);
        Node Ball5Node = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),1);
        Node Ball6Node = createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),1);
        
        // Place out the static nodes for left wire
        //LeftWire1StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(6f, 14.6214f,-7.56346f));
        //LeftWire2StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3.5f, 14.6214f,-7.56346f));
        //LeftWire3StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(1f, 14.6214f,-7.56346f));
        //LeftWire4StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-1.5f, 14.6214f,-7.56346f));
        //LeftWire5StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-4f, 14.6214f,-7.56346f));
        //LeftWire6StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-6.5f, 14.6214f,-7.56346f));
        
        Wire1StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(6f, 14.6214f,0f));
        Wire2StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3.5f, 14.6214f,0f));
        Wire3StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(1f, 14.6214f,0f));
        Wire4StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-1.5f, 14.6214f,0f));
        Wire5StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-4f, 14.6214f,0f));
        Wire6StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-6.5f, 14.6214f,0f));
        
        // Place out the static nodes for the rigth wire
        //RigthWire1StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(6f, 14.6214f,7.56346f));
        //RigthWire2StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3.5f, 14.6214f,7.56346f));
        //RigthWire3StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(1f, 14.6214f,7.56346f));
        //RigthWire4StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-1.5f, 14.6214f,7.56346f));
        //RigthWire5StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-4f, 14.6214f,7.56346f));
        //RigthWire6StaticNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-6.5f, 14.6214f,7.56346f));
        
        // Place out the dynamical nodes for the balls
        /*
        Ball1Node.getControl(RigidBodyControl.class).setPhysicsLocation(FrameNode.getChild("Ball1").getLocalTranslation());
        Ball2Node.getControl(RigidBodyControl.class).setPhysicsLocation(FrameNode.getChild("Ball2").getLocalTranslation());
        Ball3Node.getControl(RigidBodyControl.class).setPhysicsLocation(FrameNode.getChild("Ball3").getLocalTranslation());
        Ball4Node.getControl(RigidBodyControl.class).setPhysicsLocation(FrameNode.getChild("Ball4").getLocalTranslation());
        Ball5Node.getControl(RigidBodyControl.class).setPhysicsLocation(FrameNode.getChild("Ball5").getLocalTranslation());
        Ball6Node.getControl(RigidBodyControl.class).setPhysicsLocation(FrameNode.getChild("Ball6").getLocalTranslation());
        */
        
        Ball1Node.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(6, 3, 0));
        Ball2Node.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3.5f, 3, 0));
        Ball3Node.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(1, 3, 0));
        Ball4Node.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-1.5f, 3, 0));
        Ball5Node.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-4, 3, 0));
        Ball6Node.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(-6.5f, 3, 0));
        
        
        // Attach the nodes
        /*
        FrameNode.attachChild(LeftWire1StaticNode);
        FrameNode.attachChild(LeftWire2StaticNode);
        FrameNode.attachChild(LeftWire3StaticNode);
        FrameNode.attachChild(LeftWire4StaticNode);
        FrameNode.attachChild(LeftWire5StaticNode);
        FrameNode.attachChild(LeftWire6StaticNode);
        */
        
        FrameNode.attachChild(Wire1StaticNode);
        FrameNode.attachChild(Wire2StaticNode);
        FrameNode.attachChild(Wire3StaticNode);
        FrameNode.attachChild(Wire4StaticNode);
        FrameNode.attachChild(Wire5StaticNode);
        FrameNode.attachChild(Wire6StaticNode);
        
        /*
        FrameNode.attachChild(RigthWire1StaticNode);
        FrameNode.attachChild(RigthWire2StaticNode);
        FrameNode.attachChild(RigthWire3StaticNode);
        FrameNode.attachChild(RigthWire4StaticNode);
        FrameNode.attachChild(RigthWire5StaticNode);
        FrameNode.attachChild(RigthWire6StaticNode);
        */
        
        // Sett the physics at the nodes
        /*
        getPhysicsSpace().add(LeftWire1StaticNode);
        getPhysicsSpace().add(LeftWire2StaticNode);
        getPhysicsSpace().add(LeftWire3StaticNode);
        getPhysicsSpace().add(LeftWire4StaticNode);
        getPhysicsSpace().add(LeftWire5StaticNode);
        getPhysicsSpace().add(LeftWire6StaticNode);
        */
        
        getPhysicsSpace().add(Wire1StaticNode);
        getPhysicsSpace().add(Wire2StaticNode);
        getPhysicsSpace().add(Wire3StaticNode);
        getPhysicsSpace().add(Wire4StaticNode);
        getPhysicsSpace().add(Wire5StaticNode);
        getPhysicsSpace().add(Wire6StaticNode);
        
        /*
        getPhysicsSpace().add(RigthWire1StaticNode);
        getPhysicsSpace().add(RigthWire2StaticNode);
        getPhysicsSpace().add(RigthWire3StaticNode);
        getPhysicsSpace().add(RigthWire4StaticNode);
        getPhysicsSpace().add(RigthWire5StaticNode);
        getPhysicsSpace().add(RigthWire6StaticNode);
        */
        
        // Sett the nodes for the balls
        FrameNode.attachChild(Ball1Node);
        FrameNode.attachChild(Ball2Node);
        FrameNode.attachChild(Ball3Node);
        FrameNode.attachChild(Ball4Node);
        FrameNode.attachChild(Ball5Node);
        FrameNode.attachChild(Ball6Node);
        
        // Sett the physics for the balls
        getPhysicsSpace().add(Ball1Node);
        getPhysicsSpace().add(Ball2Node);
        getPhysicsSpace().add(Ball3Node);
        getPhysicsSpace().add(Ball4Node);
        getPhysicsSpace().add(Ball5Node);
        getPhysicsSpace().add(Ball6Node);
        
        
        
        // 14.6214f = The static node Y-heigth 
        // 3f = The balls Y-heigth
        // So the rod will be 14.6214 - 3
        
        HingeJoint Wire1Joint1 = new HingeJoint(Wire1StaticNode.getControl(RigidBodyControl.class),
                                                    Ball1Node.getControl(RigidBodyControl.class),
                                                    Wire1StaticNode.getLocalTranslation(),
                                                    new Vector3f(14.6214f - 3f, 0f, 0f),
                                                    Vector3f.UNIT_Z,
                                                    Vector3f.UNIT_Z);
       
        /*
        HingeJoint Wire1Joint2 = new HingeJoint(Wire2StaticNode.getControl(RigidBodyControl.class),
                                                    Ball2Node.getControl(RigidBodyControl.class),
                                                    Wire2StaticNode.getLocalTranslation(),
                                                    new Vector3f(14.6214f - 3f, 0f, 0f),
                                                    Vector3f.UNIT_Z,
                                                    Vector3f.UNIT_Z);
        
        
        HingeJoint Wire1Joint3 = new HingeJoint(Wire3StaticNode.getControl(RigidBodyControl.class),
                                                    Ball3Node.getControl(RigidBodyControl.class),
                                                    Wire3StaticNode.getLocalTranslation(),
                                                    new Vector3f(14.6214f - 3f, 0f, 0f),
                                                    Vector3f.UNIT_Z,
                                                    Vector3f.UNIT_Z);
        
        HingeJoint Wire1Joint4 = new HingeJoint(Wire4StaticNode.getControl(RigidBodyControl.class),
                                                    Ball4Node.getControl(RigidBodyControl.class),
                                                    Wire4StaticNode.getLocalTranslation(),
                                                    new Vector3f(14.6214f - 3f, 0f, 0f),
                                                    Vector3f.UNIT_Z,
                                                    Vector3f.UNIT_Z);
        
        HingeJoint Wire1Joint5 = new HingeJoint(Wire5StaticNode.getControl(RigidBodyControl.class),
                                                    Ball5Node.getControl(RigidBodyControl.class),
                                                    Wire5StaticNode.getLocalTranslation(),
                                                    new Vector3f(14.6214f - 3f, 0f, 0f),
                                                    Vector3f.UNIT_Z,
                                                    Vector3f.UNIT_Z);
        
        HingeJoint Wire1Joint6 = new HingeJoint(Wire6StaticNode.getControl(RigidBodyControl.class),
                                                    Ball6Node.getControl(RigidBodyControl.class),
                                                    Wire6StaticNode.getLocalTranslation(),
                                                    new Vector3f(14.6214f - 3f, 0f, 0f),
                                                    Vector3f.UNIT_Z,
                                                    Vector3f.UNIT_Z);
       */
       // Add joint
       
        // Activate the physics for every joints
        
        bulletAppState.getPhysicsSpace().add(Wire1Joint1);
        //bulletAppState.getPhysicsSpace().add(Wire1Joint2);
        //bulletAppState.getPhysicsSpace().add(Wire1Joint3);
        //bulletAppState.getPhysicsSpace().add(Wire1Joint4);
        //bulletAppState.getPhysicsSpace().add(Wire1Joint5);
        //bulletAppState.getPhysicsSpace().add(Wire1Joint6);
        
        
    }

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

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
    
    private Node createPhysicsTestNode(AssetManager manager, CollisionShape shape, float mass) {
        Node node = new Node("PhysicsNode");
        RigidBodyControl control = new RigidBodyControl(shape, mass);
        node.addControl(control);
        return node;
    }
}

Information

openjdk 10.0.1 2018-04-17
OpenJDK Runtime Environment (build 10.0.1+10-Ubuntu-3ubuntu1)
OpenJDK 64-Bit Server VM (build 10.0.1+10-Ubuntu-3ubuntu1, mixed mode)

jMonkeyEngine V3.2.1-Stable SDK 3 with JDK 1.8 as default

DanielMartensson avatar Aug 10 '18 21:08 DanielMartensson

The solution was:

Use jme3-jbullet library. Not the jme3-bullet and jme3-bullet-native library.

jme3-bullet and jme3-bullet-native is default in the library folder at SDK 3.2 version.

DanielMartensson avatar Aug 11 '18 20:08 DanielMartensson

I'm investigating this one, but don't have much of a handle on it yet.

stephengold avatar Aug 23 '18 20:08 stephengold

Well, I've made some progress.

  1. So far, the issue only shows up with ≥2 hinged balls and ≥1 free-falling balls:
  • Multiple hinged balls by themselves are fine.
  • 1 hinged ball plus multiple free-falling balls is fine, and the free-falling balls fall straight down.
  • But with 2 hinged balls plus a free-falling ball, not only do the hinged balls go crazy, but the free-falling ball doesn't fall straight down.
  1. The issue was apparently introduced into the jme3-bullet-native module between version 3.1.0-stable (which was based on bullet-2.82-r2704) and version 3.2.0-alpha1 (which was based on bullet3-2.86.1).
  • If I run Daniel's test case using jme3-bullet-native at version 3.1.0-stable and all other modules at version 3.2.1-stable, the test code works fine with 2 hinged balls plus a free-falling ball.
  • If I run Daniel's test case using jme3-bullet-native at version 3.2.0-alpha1 and all other modules at version 3.2.1-stable, 2 hinged balls plus a free-falling ball, the hinged balls go crazy.

I'm not sure why the native Bullet version was changed between JME 3.1 and JME 3.2 but it might be a good idea to revert it.

stephengold avatar Aug 24 '18 02:08 stephengold

The Bullet version was changed via pull request #698.

stephengold avatar Aug 24 '18 03:08 stephengold

Also changed by pull request #528.

stephengold avatar Aug 24 '18 06:08 stephengold

I'm not sure why the native Bullet version was changed between JME 3.1 and JME 3.2 but it might be a good idea to revert it.

I would suggest the opposite, see if a even newer version fixes the issue as well, if we go that path

empirephoenix avatar Aug 24 '18 06:08 empirephoenix

I've tested this with a newer build of bullet (from their master branch) and nothing changed. It seems that the issue occurs when you try to create a constraint between a dynamic and static rigidbody. Try to apply this diff to your test

@@ -107,7 +107,15 @@
         float size = 0.1f;
         Vector3f halfExtents = new Vector3f(size, size, size);
         CollisionShape shape = new BoxCollisionShape(halfExtents);
-        RigidBodyControl control = new RigidBodyControl(shape, mass);
+        RigidBodyControl control;
+        if(mass==0){
+            mass=1000;
+            control=new RigidBodyControl(shape,mass);
+            control.setAngularDamping(1);
+            control.setLinearDamping(1);
+        }else{
+            control= new RigidBodyControl(shape, mass);
+        }
         Node node = new Node();
         node.addControl(control);
         rootNode.attachChild(node);

EDIT: It works fine also if you replace the static nodes with kinematic ones. There might be another minor bug here because in the initial frames the hinged nodes seem to be "pulled in place" rather than starting the simulation at the correct location.

@@ -107,8 +107,16 @@
         float size = 0.1f;
         Vector3f halfExtents = new Vector3f(size, size, size);
         CollisionShape shape = new BoxCollisionShape(halfExtents);
-        RigidBodyControl control = new RigidBodyControl(shape, mass);
         Node node = new Node();
+        RigidBodyControl control;
+        if(mass==0){
+            mass=1;
+            control = new RigidBodyControl(shape,mass);
+            control.setKinematic(true);
+            node.setLocalTranslation(location);
+        }else{
+            control= new RigidBodyControl(shape, mass);
+        }
         node.addControl(control);
         rootNode.attachChild(node);
         bulletAppState.getPhysicsSpace().add(node);

riccardobl avatar Aug 24 '18 12:08 riccardobl

So... I haven't found any documentation that says if attaching a costraint to a static body is something supposed to work or not, but I've figured we could use the constructor that asks only for one rigidbody and use a fixed point+pivot for the static body.

This is what i've come up with https://github.com/riccardobl/jmonkeyengine/commit/8c4d96cefbd6dbff28d6e68aaf0fd2d212876d3b (that will have to be eventally ported to the other joints aswell)

I have 3 main concerns with it:

  1. The code change in PhysicsJoint.java may break some previous use cases that i'm not aware of
  2. I don't know what i am supposed to do with axisA, so for now i'm ignoring it.
  3. You should be able to move your static rb as long as you do it before attaching it to the physics space, but with this change, the joint won't follow, so you are allowed to create your constraints only after the rb is in place.

Neverless, this seems to work fine in my scene and in your test.

riccardobl avatar Aug 24 '18 15:08 riccardobl

3 is a non issue, as moving a static rb is not supported by bullet anyway and will make it explode when using non trivial math shapes. (eg box often works, but a meshshape will not at all)

empirephoenix avatar Aug 25 '18 22:08 empirephoenix

According to the Bullet Manual:

"Constraint act between two rigidbodies, where at least one of them needs to be dynamic."

However, the kernel of this issue seems to be that Bullet constraints don't work well unless ALL bodies connected to the constraint are dynamic. The number of constraints isn't the issue.

While I appreciate the ingenuity of riccardobl's proposed fix, a better solution, I think, would be to document this limitation and expose single-ended constraints in jme3-bullet. But that's still not ideal because it would introduce yet another incompatibility between jme3-bullet and jme3-jbullet.

It's also conceivable that the issue has been fixed in Bullet. I'll explore this possibility.

stephengold avatar Nov 29 '18 16:11 stephengold

I'm seeing evidence that simply upgrading to Bullet v2.87 will resolve the issue. I'll generate a pull request.

stephengold avatar Nov 29 '18 16:11 stephengold

The upgrade resolved the issue. The hard part was getting Travis and AppVeyor to build new native libraries.

Fixed in master branch.

stephengold avatar Dec 02 '18 07:12 stephengold

The issue is now resolved for native Bullet (jme3-bullet) in both the master and v3.2 branches. I just noticed, however, that the TestIssue877 app fails for JBullet (jme3-jbullet) in both master and v3.2. Yet DanielMartensson used JBullet as a workaround ... ??

stephengold avatar Dec 29 '18 10:12 stephengold

Lacking source code, I have no clue how to fix JBullet.

stephengold avatar Jan 13 '19 02:01 stephengold