Moving physics platform

Hello!

I have a question regarding physics, and it is very close to what happened here:

I also got allot of info on my previous post but it seemed like a good idea to start from scratch:

I am using the TestBetterCharacter Jme example to try and get a working scenario with working physics on a moving platform before I scale it up.

This information is copied from http://wiki.jmonkeyengine.org/doku.php/jme3:advanced:physics

“Bullet always uses world coordinates, there is no such concept as nodes so the object will be moved into a world location with no regard to its parent spatial.
You can configure this behavior using the setApplyPhysicsLocal() method on physics controls but remember the physics space still runs in world coordinates so you can visually detach things that will actually still collide in the physics space.
To use the local applying to simulate e.g. the internal physics system of a train passing by, simply create another BulletAppState and add all models with physics controls in local mode to a node. When you move the node the physics will happen all the same but the objects will move along with the node.”

So my question is:

-How do I configure the physics to use this “setApplyPhysicsLocal()” and what is meant by “simply create another BulletAppState and add all models with physics controls in local mode to a node”?

Hey,
just to make sure: are you trying to make a platform (that has no roof) or trying to make a space ship (where everything inside is enclosed by the ship’s hull)?

These two things are different:

  1. The platform could be realized by Kinematic movement.
  2. The physics space ship could be realized by second BulletAppState.

And I would add my solution proposed in the other thread: make all calculations relative to world’s (0,0,0) and then move the visual representation to the world position (LocalTranslation) and world rotation (LocalRotation) of the ship’s (0,0,0) - that’s what I would try to do first.

I feel that I or someone else should help you, but I’m currently doing more important things for the engine. :chimpanzee_sad:

But maybe I will find the time to make a small example of this (I’m curious myself). :chimpanzee_smile:

Hey Ogli,

The end goal is for the space ship.

I like that idea allot. It would be hard to debug since I would need to check physics at 0,0,0 all the time but if you think this is the best (or only) way to do it I will attempt it.

So to seperate the physics from the visual nodes I just do a “SetApplyPhysicsLocal()” correct?

I will try it in a small example myself and get back to you

Thanks

I did it!!! Damn, it seems so easy now that it works…

Simply create a new class in the jme3test.bullet and copy this code in and run it. I think you guys should add this to the test examples

Let me know what you think

P.S. How to you insert code the easy way again? I had to add the 4 spaces infront of code sooo many times for this:

/*
 * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. <p/>
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  * 
  * * Redistributions of source code must retain the above copyright notice,
  * this list of conditions and the following disclaimer. <p/> * Redistributions
  * in binary form must reproduce the above copyright notice, this list of
  * conditions and the following disclaimer in the documentation and/or other
  * materials provided with the distribution. <p/> * Neither the name of
  * 'jMonkeyEngine' nor the names of its contributors may be used to endorse or
  * promote products derived from this software without specific prior written
  * permission. <p/> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package jme3test.bullet;

 import com.jme3.app.SimpleApplication;
 import com.jme3.bullet.BulletAppState;
 import com.jme3.bullet.PhysicsSpace;
 import com.jme3.bullet.control.BetterCharacterControl;
 import com.jme3.bullet.control.RigidBodyControl;
 import com.jme3.font.BitmapFont;
 import com.jme3.font.BitmapText;
 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.renderer.RenderManager;
 import com.jme3.scene.CameraNode;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Node;
 import com.jme3.scene.control.CameraControl.ControlDirection;
 import com.jme3.scene.shape.Box;
 import com.jme3.scene.shape.Sphere;
 import com.jme3.system.AppSettings;

 /**
  * A walking physical character followed by a 3rd person camera. (No animation.)
  *
  * @author normenhansen, zathras, Eric_Bourque
  */
 public class TestMovingPlatform extends SimpleApplication implements ActionListener {

private BulletAppState bulletAppState;
private BulletAppState movingBulletAppState;
private BetterCharacterControl physicsCharacter;
private Node characterNode;
private Node movingPhysicsNode;
private CameraNode camNode;
private boolean platformMotion = true;
private boolean playerIsOnPlatform = false;
private boolean lockView = false;

boolean rotate = false;
private Vector3f walkDirection = new Vector3f(0, 0, 0);
private Vector3f viewDirection = new Vector3f(0, 0, 1);
boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false,
        leftRotate = false, rightRotate = false;
private Vector3f normalGravity = new Vector3f(0, -9.81f, 0);
private Geometry planet;
private Geometry movingPlatform;

public static void main(String[] args) {
    TestMovingPlatform app = new TestMovingPlatform();
    AppSettings settings = new AppSettings(true);
    settings.setRenderer(AppSettings.LWJGL_OPENGL2);
    settings.setAudioRenderer(AppSettings.LWJGL_OPENAL);
    app.setSettings(settings);
    app.start();
}

@Override
public void simpleInitApp() {
    //setup keyboard mapping
    setupKeys();

    BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt");
    BitmapText txt2 = new BitmapText(fnt, false);
    txt2.setSize(fnt.getPreferredSize() * 1f);
    txt2.setText("Enter : lock view\n"
                +"Up/Down/Left/Right : Move\n"
                +"F8  : Apply impulse to cube\n"
                +"F9  : Transfer to moving platform\n"
                +"F10 : Transfer back to static floor\n"
                +"F11 : Toggle platform motion / rotation\n"
                +"F12 : Toggle debug physics");
    txt2.setColor(ColorRGBA.Green);
    txt2.setLocalTranslation(this.settings.getWidth()-txt2.getLineWidth(),this.settings.getHeight()/* - txt2.getHeight()*/, 0);
    guiNode.attachChild(txt2);
    
    // activate physics
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    bulletAppState.setDebugEnabled(true);
    
    movingBulletAppState = new BulletAppState();
    stateManager.attach(movingBulletAppState);
    movingBulletAppState.setDebugEnabled(true);
    movingPhysicsNode = new Node();
    rootNode.attachChild(movingPhysicsNode);
    
    // init a physics test scene
    PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace());
    PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace());
    setupPlanet();
    
    setupMovingPlatform();

    // Create a node for the character model
    characterNode = new Node("character node");
    characterNode.setLocalTranslation(new Vector3f(4,10, 4));
          

    // Add a character control to the node so we can add other things and
    // control the model rotation
    physicsCharacter = new BetterCharacterControl(0.3f, 2.5f, 8f);
    characterNode.addControl(physicsCharacter);
    getPhysicsSpace().add(physicsCharacter);           
    
    // Load model, attach to character node
    Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o");
    model.setLocalScale(1.50f);
    characterNode.attachChild(model);

    // Add character node to the rootNode
    rootNode.attachChild(characterNode);      
    
    // Set forward camera node that follows the character, only used when
    // view is "locked"
    camNode = new CameraNode("CamNode", cam);
    camNode.setControlDir(ControlDirection.SpatialToCamera);
    camNode.setLocalTranslation(new Vector3f(0, 2, -6));
    Quaternion quat = new Quaternion();
    // These coordinates are local, the camNode is attached to the character node!
    quat.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
    camNode.setLocalRotation(quat);
    characterNode.attachChild(camNode);
    // Disable by default, can be enabled via keyboard shortcut
    camNode.setEnabled(false);
}

@Override
public void simpleUpdate(float tpf) {
    // Apply planet gravity to character if close enough (see below)
    checkPlanetGravity();

    // Get current forward and left vectors of model by using its rotation
    // to rotate the unit vectors
    Vector3f modelForwardDir;
    Vector3f modelLeftDir;
    if(playerIsOnPlatform){            
        modelForwardDir = characterNode.getLocalRotation().mult(Vector3f.UNIT_Z);
        modelLeftDir = characterNode.getLocalRotation().mult(Vector3f.UNIT_X);
    }else{
        modelForwardDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_Z);
        modelLeftDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_X);
    }

    // WalkDirection is global!
    // You *can* make your character fly with this.
    walkDirection.set(0, 0, 0);
    if (leftStrafe) {
        walkDirection.addLocal(modelLeftDir.mult(5));
    } else if (rightStrafe) {
        walkDirection.addLocal(modelLeftDir.negate().multLocal(5));
    }
    if (forward) {
        walkDirection.addLocal(modelForwardDir.mult(5));
    } else if (backward) {
        walkDirection.addLocal(modelForwardDir.negate().multLocal(5));
    }
    physicsCharacter.setWalkDirection(walkDirection);

    // ViewDirection is local to characters physics system!
    // The final world rotation depends on the gravity and on the state of
    // setApplyPhysicsLocal()
    if (leftRotate) {
        Quaternion rotateL = new Quaternion().fromAngleAxis(FastMath.PI * tpf, Vector3f.UNIT_Y);
        rotateL.multLocal(viewDirection);
    } else if (rightRotate) {
        Quaternion rotateR = new Quaternion().fromAngleAxis(-FastMath.PI * tpf, Vector3f.UNIT_Y);
        rotateR.multLocal(viewDirection);
    }
    physicsCharacter.setViewDirection(viewDirection);
    fpsText.setText("Touch da ground = " + physicsCharacter.isOnGround());
    if (!lockView) {
        cam.lookAt(characterNode.getWorldTranslation().add(new Vector3f(0, 2, 0)), Vector3f.UNIT_Y);
    }        
    
    if(platformMotion){
        processPlatformMotion(tpf);           
    }
}

private void processPlatformMotion(float tpf){
    //move away slowly
    movingPhysicsNode.move(0,0,-0.15f*tpf);  
    //and rotate
    Quaternion roll = new Quaternion(); 
    roll.fromAngleAxis( 0.1f*tpf , new Vector3f(0,0,1) );        
    movingPhysicsNode.rotate(roll);
}

private void setupPlanet() {
    Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
    //immovable sphere with mesh collision shape
    Sphere sphere = new Sphere(64, 64, 20);
    planet = new Geometry("Sphere", sphere);
    planet.setMaterial(material);
    planet.setLocalTranslation(30, -15, 30);
    planet.addControl(new RigidBodyControl(0));
    planet.getControl(RigidBodyControl.class).setKinematic(true);
    rootNode.attachChild(planet);
    getPhysicsSpace().add(planet);
}

private void transferToPlatform(){        
    if(playerIsOnPlatform) return;
    
    getPhysicsSpace().remove(physicsCharacter);        
    characterNode.removeFromParent();        
    
    physicsCharacter.warp(movingPlatform.getControl(RigidBodyControl.class).getPhysicsLocation().add(0,10,0));
    movingPhysicsNode.attachChild(characterNode);
    physicsCharacter.setApplyPhysicsLocal(true);
    
    this.getMovingPhysicsSpace().add(physicsCharacter);
    playerIsOnPlatform = true;
}

private void transferToFloor(){        
    if(!playerIsOnPlatform) return;
    
    getMovingPhysicsSpace().remove(physicsCharacter);        
    characterNode.removeFromParent();        
    
    physicsCharacter.warp(new Vector3f(0,10,0));
    rootNode.attachChild(characterNode);
    physicsCharacter.setApplyPhysicsLocal(false);
    
    this.getPhysicsSpace().add(physicsCharacter);
    
    playerIsOnPlatform = false;
}   

private void setupMovingPlatform(){
    Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
    
    //Platform for testing motion 
    Box platform = new Box(16, 0.3f, 8);
    movingPlatform = new Geometry("Platform", platform);
    movingPlatform.setMaterial(material);
    movingPlatform.setLocalTranslation(0, 1f, -20);
    movingPlatform.addControl(new RigidBodyControl(0));       
    movingPlatform.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
    movingPhysicsNode.attachChild(movingPlatform);
    getMovingPhysicsSpace().add(movingPlatform);
    
    //Box put ontop of platform for localPhysics test        
    Box box = new Box(1, 1, 1);
    Geometry cube = new Geometry("Cube", box);
    cube.setMaterial(material);
    cube.setLocalTranslation(0, 10f, -20);
    cube.addControl(new RigidBodyControl(100));        
    cube.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);        
    movingPhysicsNode.attachChild(cube);
    getMovingPhysicsSpace().add(cube);
}

private void checkPlanetGravity() {
    
    Vector3f planetDist = planet.getWorldTranslation().subtract(characterNode.getWorldTranslation());
    if (planetDist.length() < 24) {            
        physicsCharacter.setGravity(planetDist.normalizeLocal().multLocal(9.81f));            
    } else {
        physicsCharacter.setGravity(normalGravity);
    }
}

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

private PhysicsSpace getMovingPhysicsSpace() {
    return movingBulletAppState.getPhysicsSpace();
}

public void onAction(String binding, boolean value, float tpf) {
    if (binding.equals("Strafe Left")) {
        if (value) {
            leftStrafe = true;
        } else {
            leftStrafe = false;
        }
    } else if (binding.equals("Strafe Right")) {
        if (value) {
            rightStrafe = true;
        } else {
            rightStrafe = false;
        }
    } else if (binding.equals("Rotate Left")) {
        if (value) {
            leftRotate = true;
        } else {
            leftRotate = false;
        }
    } else if (binding.equals("Rotate Right")) {
        if (value) {
            rightRotate = true;
        } else {
            rightRotate = false;
        }
    } else if (binding.equals("Walk Forward")) {
        if (value) {
            forward = true;
        } else {
            forward = false;
        }
    } else if (binding.equals("Walk Backward")) {
        if (value) {
            backward = true;
        } else {
            backward = false;
        }
    } else if (binding.equals("Jump")) {
        physicsCharacter.jump();
    } else if (binding.equals("Duck")) {
        if (value) {
            physicsCharacter.setDucked(true);
        } else {
            physicsCharacter.setDucked(false);
        }
    } else if (binding.equals("Lock View")) {
        if (value && lockView) {
            lockView = false;
        } else if (value && !lockView) {
            lockView = true;
        }
        flyCam.setEnabled(!lockView);
        camNode.setEnabled(lockView);
    } else if (binding.equals("Toggle Platform Motion") && !value){
        platformMotion =! platformMotion;
        System.out.println("Platform motion: "+String.valueOf(platformMotion));
    } else if (binding.equals("Add Impulse") && !value){
        movingPhysicsNode.getChild("Cube").getControl(RigidBodyControl.class).applyImpulse(new Vector3f(0,1000,0), new Vector3f(0,0,0));
        System.out.println("Added Impulse");
    } else if(binding.equals("Transfer to platfrom")&& !value){
        transferToPlatform();
    } else if(binding.equals("Transfer to floor")&& !value){
        transferToFloor();
    } else if(binding.equals("Debug Physics")&& !value){
        if(bulletAppState.isDebugEnabled()){
            bulletAppState.setDebugEnabled(false);
            movingBulletAppState.setDebugEnabled(false);
        }else{
            bulletAppState.setDebugEnabled(true);
            movingBulletAppState.setDebugEnabled(true);
        }
    } 
    
}    

private void setupKeys() {
    inputManager.addMapping("Strafe Left",
            new KeyTrigger(KeyInput.KEY_U),
            new KeyTrigger(KeyInput.KEY_Z));
    inputManager.addMapping("Strafe Right",
            new KeyTrigger(KeyInput.KEY_O),
            new KeyTrigger(KeyInput.KEY_X));
    inputManager.addMapping("Rotate Left",
            new KeyTrigger(KeyInput.KEY_J),
            new KeyTrigger(KeyInput.KEY_LEFT));
    inputManager.addMapping("Rotate Right",
            new KeyTrigger(KeyInput.KEY_L),
            new KeyTrigger(KeyInput.KEY_RIGHT));
    inputManager.addMapping("Walk Forward",
            new KeyTrigger(KeyInput.KEY_I),
            new KeyTrigger(KeyInput.KEY_UP));
    inputManager.addMapping("Walk Backward",
            new KeyTrigger(KeyInput.KEY_K),
            new KeyTrigger(KeyInput.KEY_DOWN));
    inputManager.addMapping("Jump",
            new KeyTrigger(KeyInput.KEY_F),
            new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping("Duck",
            new KeyTrigger(KeyInput.KEY_G),
            new KeyTrigger(KeyInput.KEY_LSHIFT),
            new KeyTrigger(KeyInput.KEY_RSHIFT));
    inputManager.addMapping("Lock View",
            new KeyTrigger(KeyInput.KEY_RETURN));
    inputManager.addMapping("Add Impulse",
            new KeyTrigger(KeyInput.KEY_F8));
    inputManager.addMapping("Transfer to platfrom",
            new KeyTrigger(KeyInput.KEY_F9));
     inputManager.addMapping("Transfer to floor",
            new KeyTrigger(KeyInput.KEY_F10));
    inputManager.addMapping("Toggle Platform Motion",                
            new KeyTrigger(KeyInput.KEY_F11));  
    inputManager.addMapping("Debug Physics",
            new KeyTrigger(KeyInput.KEY_F12));
    inputManager.addListener(this, "Strafe Left", "Strafe Right");
    inputManager.addListener(this, "Rotate Left", "Rotate Right");
    inputManager.addListener(this, "Walk Forward", "Walk Backward");
    inputManager.addListener(this, "Jump", "Duck", "Lock View");
    inputManager.addListener(this, "Toggle Platform Motion");
    inputManager.addListener(this, "Add Impulse");
    inputManager.addListener(this, "Transfer to platfrom");
    inputManager.addListener(this, "Transfer to floor");
    inputManager.addListener(this, "Debug Physics");
    
}

@Override
public void simpleRender(RenderManager rm) {
}
}
1 Like

Three back ticks “`” before the start of the code block and after it again.

1 Like