Building a physics ship

Ok, I think I found a good way for you to pass knowledge along to me. My idea is to make a test case that we could add to jme3.1 test examples.

This test example would demonstrate the following:

  1. How to create a moving physics node
  2. How to transfer an object/character from one space to the another.

Quote from advancedPhysics:

“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.”

I used TestBetterCharacter from jme3test.bullet to make a small testcase to demonstrate what I am trying to do.

I added a new platform in a new BulletAppState and added a cube ontop of that platform. These two objects are attached to a node which is attached to the rootnode. The quote says that moving the node will move the objects with it but I can’t achieve this. For now, the physics are not following the meshes.

The following is runnable and does not use any special librairies:

 /*
 * 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.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
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
 */
public class TestBetterCharacter extends SimpleApplication implements ActionListener {

private BulletAppState bulletAppState;
private BulletAppState movingBulletAppState;
private BetterCharacterControl physicsCharacter;
private Node characterNode;
private Node movingPhysicsNode;
private CameraNode camNode;

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) {
    TestBetterCharacter app = new TestBetterCharacter();
    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();

    // 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, 5, 2));

    // 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 = characterNode.getWorldRotation().mult(Vector3f.UNIT_Z);
    Vector3f 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);
    }
    processPlatform(tpf);
}

private void processPlatform(float tpf){
    movingPhysicsNode.move(0.1f*tpf,0,0);
}

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 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).setKinematic(true);
    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(1000));        
    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);
    }
}
private boolean lockView = false;

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.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");
}

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

P.S. What do I need to put at start and end of code sections again? I did this putting 4 spaces infront of all lines of code :slight_smile: