How to make my spaceship tilt at left and right turns?

I have a pretty decent steering of my spaceship but I want a more realistic effect when it turns right and left. I think it would look better if the spaceship tilted right when it turned right and vice versa when turning left. But I don’t know how to achieve it. Can you tell me how to proceed? The control code is:

[java] public void onAnalog(String name, float value, float tpf) {
// computing the normalized direction of the cam to move the node
int speed = 80000;
int rotation = 1;
direction.set(cam.getDirection()).normalizeLocal();
if (name.equals(“moveForward”)) {
direction.multLocal(speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“moveBackward”)) {
direction.multLocal(-speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“moveRight”)) {
direction.set(cam.getLeft()).multLocal(-speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“moveLeft”)) {
direction.set(cam.getLeft().multLocal(speed * tpf));
ufoNode.move(direction);
}
if (name.equals(“moveUp”)) {
direction.crossLocal(cam.getLeft()).multLocal(speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“moveDown”)) {
direction.crossLocal(cam.getLeft()).multLocal(-speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“rotateRight”) && rotate) {
ufoNode.rotate(0, 1 * tpf, 0);
}
if (name.equals(“rotateLeft”) && rotate) {
ufoNode.rotate(0, -1 * tpf, 0);
}
if (name.equals(“rotateUp”) && rotate) {
ufoNode.rotate(0, 0, -1 * tpf);
}
if (name.equals(“rotateDown”) && rotate) {
ufoNode.rotate(0, 0, 1 * tpf);
}
}[/java]

[video]- YouTube
How can I make the graphics more realistic?

I would keep the rotation angles separate, and form a new Quaternion from them and set the local rotation. So for instance you could keep a separate angle variable for z rotation (or w/e you want), and adjust this, cap it at ±45 degrees for instance and create a quaternion from it, multiply by any other ones, and then set it as the local rotation. That way you can tilt a little bit when turning a little, or vice versa and it is easy to limit it as you have angles to begin with, rather than trying to extract them from a Quaternion later.

1 Like

Thanks, but why doesn’t it update? I added a 45 degree rotation.

[java] public void onAnalog(String name, float value, float tpf) {
// computing the normalized direction of the cam to move the node
int speed = 80000;
int rotation = 1;
direction.set(cam.getDirection()).normalizeLocal();
if (name.equals(“moveForward”)) {
direction.multLocal(speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“moveBackward”)) {
direction.multLocal(-speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“moveRight”)) {
direction.set(cam.getLeft()).multLocal(-speed * tpf);
System.out.println(“MOVERIGHT”);
/* This quaternion stores a 45 degree rolling rotation /
Quaternion roll45 = new Quaternion();
roll45.fromAngleAxis(FastMath.PI/4, new Vector3f(0, 0, 1));
/
The rotation is applied: The object rolls by 45 degrees. */
ufoNode.setLocalRotation(roll45);
ufoNode.updateGeometricState();
//ufoNode.move(direction);
}
if (name.equals(“moveLeft”)) {
direction.set(cam.getLeft().multLocal(speed * tpf));
ufoNode.move(direction);
}
if (name.equals(“moveUp”)) {
direction.crossLocal(cam.getLeft()).multLocal(speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“moveDown”)) {
direction.crossLocal(cam.getLeft()).multLocal(-speed * tpf);
ufoNode.move(direction);
}
if (name.equals(“rotateRight”) && rotate) {
ufoNode.rotate(0, 1 * tpf, 0);
}
if (name.equals(“rotateLeft”) && rotate) {
ufoNode.rotate(0, -1 * tpf, 0);
}
if (name.equals(“rotateUp”) && rotate) {
ufoNode.rotate(0, 0, -1 * tpf);
}
if (name.equals(“rotateDown”) && rotate) {
ufoNode.rotate(0, 0, 1 * tpf);
}
}[/java]

does calling the “moveRight” block of code not rotate it?

also, not sure why you are calling [java]ufoNode.updateGeometricState();[/java] is ufoNode not a relative of the rootNode?

This doesn’t do anything:

[java]	Quaternion roll45 = new Quaternion();
    	roll45.fromAngleAxis(FastMath.PI/4, new Vector3f(0, 0, 1));
	ufoNode.setLocalRotation(roll45)[/java]

When it didn’t I added the updateGeometricState and still no effect. I got this at the initialization: rootNode.attachChild(ufoNode);

And I can fly around with the UFO but I can’t make it tilt even though I can apply a local rotation if I do it in simpleInitApp before the game starts, for instance turning the UFO upside down is possible if I do it with a roll180 in the simpleInitApp method. So I don’t understand why the roll45 in the update won’t do anything. My complete game is

[java]package spaceworld;

import adventure.GameCharControl;

import com.jme3.animation.AnimControl;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.system.AppSettings;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue;
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.Sphere;
import com.jme3.water.SimpleWaterProcessor;

import spaceworld.FractalDataSource;
import spaceworld.Planet;
import spaceworld.PlanetAppState;

/* mer realistiskt inträde i atmosfären

  • hus och städer på planeter

  • beama grejer mellan rymdskeppet och planeten med den vita konstruktionen

  • Svart hål någonstans i spelet(?)

  • Multiplayer så att man kan spela flera och mötas i rymden.

  • En jumpgate ska teleportera en från en plats i rymden till en annan jumpgate.

  • Landa på planeter och månar.

  • Hus på planeter och ev. på månar.

  • Ufot är just nu helt still när man flyger. Det vore intressant att se det tilta lite och så när man svänger. Kanske man skall se det lite snett uppifrån också plus att det kan rotera, som alla UFOs gör. :slight_smile: Då kommer du att få lite mer liv i det. Speciellt om du har några färgade prickar på det - som flygplanslanternor.

  • Skall du bygga ett spel så kan det vara vettigt att specificera ett eller flera mål som spelaren skall uppnå samt ett eller flera sätt att misslyckas så att man har en klar och tydlig utmaning.

  • Rymdskeppet ska kunna wobbla och vicka vid acceleration för mer realistisk effekt.

  • Sätta i aliens som jag har grafiken till men inte lagt in i spelet.

  • Man borde vara en marsian med basen i en krater på Mars
    */
    public class SpaceStation extends SimpleApplication implements AnalogListener,
    ActionListener {

    private PlanetAppState planetAppState;
    private Geometry mark;
    private Node ufoNode;
    private Node spaceStationNode;
    private Node jumpgateNode;
    private Node jumpgateNode2;
    private BetterCharacterControl ufoControl;
    CameraNode camNode;
    ChaseCamera chaseCam;
    boolean rotate = false;
    Vector3f direction = new Vector3f();
    private BulletAppState bulletAppState;

    private void setupChaseCamera() {
    flyCam.setEnabled(false);
    chaseCam = new ChaseCamera(cam, ufoNode, inputManager);
    chaseCam.setDefaultDistance(37);
    }

    public static void main(String[] args) {
    AppSettings settings = new AppSettings(true);
    settings.setResolution(1024, 768);
    SpaceStation app = new SpaceStation();
    app.setSettings(settings);
    // app.showSettings = true;
    app.start();
    }

    @Override
    public void simpleInitApp() {
    // Only show severe errors in log
    java.util.logging.Logger.getLogger(“com.jme3”).setLevel(
    java.util.logging.Level.SEVERE);
    bulletAppState = new BulletAppState();
    bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
    stateManager.attach(bulletAppState);
    bulletAppState.setDebugEnabled(false);
    DirectionalLight sun = new DirectionalLight();
    sun.setDirection(new Vector3f(-.1f, 0f, -1f));
    sun.setColor(new ColorRGBA(0.75f, 0.75f, 0.75f, 1.0f));
    rootNode.addLight(sun);
    // Add sky
    Node sceneNode = new Node(“Scene”);
    sceneNode.attachChild(Utility.createSkyBox(this.getAssetManager(),
    “Textures/blue-glow-1024.dds”));
    rootNode.attachChild(sceneNode);
    // Create collision test mark
    Sphere sphere = new Sphere(30, 30, 5f);
    mark = new Geometry(“mark”, sphere);
    Material mark_mat = new Material(assetManager,
    “Common/MatDefs/Misc/Unshaded.j3md”);
    mark_mat.setColor(“Color”, ColorRGBA.Red);
    mark.setMaterial(mark_mat);
    // Add planet app state
    planetAppState = new PlanetAppState(rootNode, sun);
    stateManager.attach(planetAppState);

     // Add planet
     FractalDataSource planetDataSource = new FractalDataSource(4);
     planetDataSource.setHeightScale(900f);
     Planet planet = Utility.createEarthLikePlanet(getAssetManager(),
     		293710.0f, null, planetDataSource);
     planetAppState.addPlanet(planet);
     rootNode.attachChild(planet);
    
     // Add moon
     FractalDataSource moonDataSource = new FractalDataSource(5);
     moonDataSource.setHeightScale(300f);
     Planet moon = Utility.createMoonLikePlanet(getAssetManager(), 50000,
     		moonDataSource);
     planetAppState.addPlanet(moon);
     rootNode.attachChild(moon);
     moon.setLocalTranslation(new Vector3f(10f, 10f, 705000f));// -950000f,
     															// 0f, 0f);
     // add saucer
     ufoNode = (Node) assetManager.loadModel("usaucer_v01.j3o");
     ufoNode.setLocalScale(100f);
     ufoNode.setLocalTranslation((new Vector3f(1000f, -1000f, 328000f)));
     jumpgateNode = (Node) assetManager.loadModel("JumpGate.j3o");
     jumpgateNode.setLocalScale(10000f);
     jumpgateNode.setLocalTranslation((new Vector3f(10f, 10f, 908000f)));
    
     spaceStationNode = (Node) assetManager.loadModel("SpaceStation.j3o");
     spaceStationNode.setLocalScale(4000f);
     spaceStationNode.setLocalTranslation((new Vector3f(10000f, -10f,
     		625000f)));
    
     jumpgateNode2 = (Node) assetManager.loadModel("JumpGate.j3o");
     jumpgateNode2.setLocalScale(10000f);
     jumpgateNode2.setLocalTranslation((new Vector3f(10f, 10f, 998300f)));
    
     /* This quaternion stores a 180 degree rolling rotation */
      //Quaternion roll180 = new Quaternion();
      //roll180.fromAngleAxis(FastMath.PI, new Vector3f(0, 0, 1));
     /* The rotation is applied: The object rolls by 180 degrees. */
      //ufoNode.setLocalRotation(roll180);
     rootNode.attachChild(jumpgateNode);
     rootNode.attachChild(jumpgateNode2);
     rootNode.attachChild(spaceStationNode);
    
     // creating the camera Node
     camNode = new CameraNode("CamNode", cam);
     // Setting the direction to Spatial to camera, this means the camera
     // will copy the movements of the Node
     camNode.setControlDir(ControlDirection.SpatialToCamera);
     // attaching the camNode to the teaNode
     ufoNode.attachChild(camNode);
     // setting the local translation of the cam node to move it away a bit
     camNode.setLocalTranslation(new Vector3f(-30, 0, 0));
     // setting the camNode to look at the teaNode
     camNode.lookAt(ufoNode.getLocalTranslation(), Vector3f.UNIT_Y);
     // disable the default 1st-person flyCam (don't forget this!!)
     ufoControl = new BetterCharacterControl(100000f, 80000f, 5000f);// (2,
     																// 4,
     																// 0.5f);
     // radius (meters), height (meters), gravity (mass)
     // ufoNode.addControl(ufoControl);
     // rootNode.attachChild(ninjaNode);
     // bulletAppState.getPhysicsSpace().add(ufoControl);
     // getPhysicsSpace().add(ufoControl);
     rootNode.attachChild(ufoNode);
     flyCam.setEnabled(false);
     registerInput();
    

    }

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

    public void registerInput() {
    inputManager.addMapping(“moveForward”, new KeyTrigger(keyInput.KEY_UP),
    new KeyTrigger(keyInput.KEY_W));
    inputManager.addMapping(“moveBackward”, new KeyTrigger(
    keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S));
    inputManager.addMapping(“moveRight”,
    new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(
    keyInput.KEY_D));
    inputManager.addMapping(“moveLeft”, new KeyTrigger(keyInput.KEY_LEFT),
    new KeyTrigger(keyInput.KEY_A));
    inputManager.addMapping(“moveUp”, new KeyTrigger(keyInput.KEY_E));
    inputManager.addMapping(“moveDown”, new KeyTrigger(keyInput.KEY_Q));
    inputManager.addMapping(“toggleRotate”, new MouseButtonTrigger(
    MouseInput.BUTTON_LEFT));
    inputManager.addMapping(“rotateRight”, new MouseAxisTrigger(
    MouseInput.AXIS_X, true));
    inputManager.addMapping(“rotateLeft”, new MouseAxisTrigger(
    MouseInput.AXIS_X, false));
    inputManager.addMapping(“rotateUp”, new MouseAxisTrigger(
    MouseInput.AXIS_Y, true));
    inputManager.addMapping(“rotateDown”, new MouseAxisTrigger(
    MouseInput.AXIS_Y, false));
    inputManager.addListener(this, “moveForward”, “moveBackward”,
    “moveRight”, “moveLeft”, “moveUp”, “moveDown”);
    inputManager.addListener(this, “rotateRight”, “rotateLeft”, “rotateUp”,
    “rotateDown”, “toggleRotate”);
    // Toggle mouse cursor
    inputManager.addMapping(“TOGGLE_CURSOR”, new MouseButtonTrigger(
    MouseInput.BUTTON_LEFT), new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addListener(actionListener, “TOGGLE_CURSOR”);
    // Toggle wireframe
    inputManager.addMapping(“TOGGLE_WIREFRAME”, new KeyTrigger(
    KeyInput.KEY_T));
    inputManager.addListener(actionListener, “TOGGLE_WIREFRAME”);
    // Collision test
    inputManager.addMapping(“COLLISION_TEST”, new MouseButtonTrigger(
    MouseInput.BUTTON_RIGHT));
    inputManager.addListener(actionListener, “COLLISION_TEST”);
    }

    public void onAnalog(String name, float value, float tpf) {
    // computing the normalized direction of the cam to move the node
    int speed = 80000;
    int rotation = 1;
    direction.set(cam.getDirection()).normalizeLocal();
    if (name.equals(“moveForward”)) {
    direction.multLocal(speed * tpf);
    ufoNode.move(direction);
    }
    if (name.equals(“moveBackward”)) {
    direction.multLocal(-speed * tpf);
    ufoNode.move(direction);
    }
    if (name.equals(“moveRight”)) {
    direction.set(cam.getLeft()).multLocal(-speed * tpf);
    System.out.println(“MOVING RIGHT”);
    /* This quaternion stores a 45 degree rolling rotation /
    Quaternion roll45 = new Quaternion();
    roll45.fromAngleAxis(FastMath.PI/4, new Vector3f(0, 0, 1));
    /
    The rotation is applied: The object rolls by 45 degrees. */
    ufoNode.setLocalRotation(roll45);
    //ufoNode.updateGeometricState();
    //ufoNode.move(direction);
    }
    if (name.equals(“moveLeft”)) {
    direction.set(cam.getLeft().multLocal(speed * tpf));
    ufoNode.move(direction);
    }
    if (name.equals(“moveUp”)) {
    direction.crossLocal(cam.getLeft()).multLocal(speed * tpf);
    ufoNode.move(direction);
    }
    if (name.equals(“moveDown”)) {
    direction.crossLocal(cam.getLeft()).multLocal(-speed * tpf);
    ufoNode.move(direction);
    }
    if (name.equals(“rotateRight”) && rotate) {
    ufoNode.rotate(0, 1 * tpf, 0);
    }
    if (name.equals(“rotateLeft”) && rotate) {
    ufoNode.rotate(0, -1 * tpf, 0);
    }
    if (name.equals(“rotateUp”) && rotate) {
    ufoNode.rotate(0, 0, -1 * tpf);
    }
    if (name.equals(“rotateDown”) && rotate) {
    ufoNode.rotate(0, 0, 1 * tpf);
    }
    }

    public void onAction(String name, boolean keyPressed, float tpf) {
    // toggling rotation on or off
    if (name.equals(“toggleRotate”) && keyPressed) {
    rotate = true;
    inputManager.setCursorVisible(false);
    }
    if (name.equals(“toggleRotate”) && !keyPressed) {
    rotate = false;
    inputManager.setCursorVisible(true);
    }
    if (name.equals(“TOGGLE_CURSOR”) && !keyPressed) {
    if (inputManager.isCursorVisible()) {
    inputManager.setCursorVisible(false);
    } else {
    inputManager.setCursorVisible(true);
    }
    }
    if (name.equals(“TOGGLE_WIREFRAME”) && !keyPressed) {
    for (Planet planet : planetAppState.getPlanets()) {
    planet.toogleWireframe();
    }
    }
    if (name.equals(“COLLISION_TEST”) && !keyPressed) {
    CollisionResults results = new CollisionResults();
    Ray ray = new Ray(cam.getLocation(), cam.getDirection());
    // Test collision with closest planet’s terrain only
    planetAppState.getNearestPlanet().getTerrainNode()
    .collideWith(ray, results);
    System.out.println("----- Collisions? " + results.size() + “-----”);
    for (int i = 0; i < results.size(); i++) {
    // For each hit, we know distance, impact point, name of
    // geometry.
    float dist = results.getCollision(i).getDistance();
    Vector3f pt = results.getCollision(i).getContactPoint();
    String hit = results.getCollision(i).getGeometry().getName();
    System.out.println("* Collision #" + i);
    System.out.println(" You shot " + hit + " at " + pt + “, "
    + dist + " wu away.”);
    }
    if (results.size() > 0) {
    // The closest collision point is what was truly hit:
    CollisionResult closest = results.getClosestCollision();
    // Let’s interact - we mark the hit with a red dot.
    mark.setLocalTranslation(closest.getContactPoint());
    rootNode.attachChild(mark);
    } else {
    // No hits? Then remove the red mark.
    rootNode.detachChild(mark);
    }
    }
    }

    private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean pressed, float tpf) {
    if (name.equals(“TOGGLE_CURSOR”) && !pressed) {
    if (inputManager.isCursorVisible()) {
    inputManager.setCursorVisible(false);
    } else {
    inputManager.setCursorVisible(true);
    }
    }
    if (name.equals(“TOGGLE_WIREFRAME”) && !pressed) {
    for (Planet planet : planetAppState.getPlanets()) {
    planet.toogleWireframe();
    }
    }
    if (name.equals(“COLLISION_TEST”) && !pressed) {
    CollisionResults results = new CollisionResults();
    Ray ray = new Ray(cam.getLocation(), cam.getDirection());
    // Test collision with closest planet’s terrain only
    planetAppState.getNearestPlanet().getTerrainNode()
    .collideWith(ray, results);
    System.out.println("----- Collisions? " + results.size()
    + “-----”);
    for (int i = 0; i < results.size(); i++) {
    // For each hit, we know distance, impact point, name of
    // geometry.
    float dist = results.getCollision(i).getDistance();
    Vector3f pt = results.getCollision(i).getContactPoint();
    String hit = results.getCollision(i).getGeometry()
    .getName();
    System.out.println("* Collision #" + i);
    System.out.println(" You shot " + hit + " at " + pt + “, "
    + dist + " wu away.”);
    }
    if (results.size() > 0) {
    // The closest collision point is what was truly hit:
    CollisionResult closest = results.getClosestCollision();
    // Let’s interact - we mark the hit with a red dot.
    mark.setLocalTranslation(closest.getContactPoint());
    rootNode.attachChild(mark);
    } else {
    // No hits? Then remove the red mark.
    rootNode.detachChild(mark);
    }
    }
    }
    };
    }[/java]

as long as that is being called, that will set the rotation to be that, put a breakpoint in it, to see the effects before/after

1 Like
@niklasro said: This doesn't do anything:
[java]	Quaternion roll45 = new Quaternion();
    	roll45.fromAngleAxis(FastMath.PI/4, new Vector3f(0, 0, 1));
	ufoNode.setLocalRotation(roll45)[/java]

When it didn’t I added the updateGeometricState and still no effect. I got this at the initialization: rootNode.attachChild(ufoNode);

And I can fly around with the UFO but I can’t make it tilt even though I can apply a local rotation if I do it in simpleInitApp before the game starts, for instance turning the UFO upside down is possible if I do it with a roll180 in the simpleInitApp method. So I don’t understand why the roll45 in the update won’t do anything. My complete game is

It doesn’t do anything because ufoNode has a betterCharacterControl attached to it. Y always points up, so rolling along the z axis is pointless.

Another nit-picky thing: FastMath.PI/4 = FastMath.QUARTER_PI

1 Like

But the BetterCharacterControl is not used, I was only halfway trying to add it and it’s commented out. And how then should I code the tilting even if I manage to add the BetterCharacterControl for the UFO? I’m stuck at the code I posted earlier where I can rotate the UFO before the game starts but not in the code for the listeners that should generate the movement and tilting. Can you please advice me how to proceed since I’m stuck at this point both in adding the BetterCharacterControl and in generating the tilting.

@t0neg0d said: It doesn't do anything because ufoNode has a betterCharacterControl attached to it. Y always points up, so rolling along the z axis is pointless.

Another nit-picky thing: FastMath.PI/4 = FastMath.QUARTER_PI

Now it tilts and it’s almost perfect. I changed to a RigidBodyControl and then the moving and tilting works.

[java] @Override
public void simpleUpdate(float tpf) {
int speed = 50 * 80000;
Vector3f camDir = cam.getDirection().clone().multLocal(speed * tpf);
Vector3f camUp = cam.getUp().clone().mult(speed * tpf);
Quaternion roll = new Quaternion();
camDir.y = 0;
ufoDirection.set(0, 0, 0);
ufoControl.setLinearVelocity(Vector3f.ZERO);
if (left) {
roll.fromAngleAxis(-FastMath.QUARTER_PI/2, cam.getDirection());
ufoDirection.set(cam.getLeft().multLocal(speed * tpf));
}
if (right) {
roll.fromAngleAxis(FastMath.QUARTER_PI/2, cam.getDirection());
//ufoControl.setPhysicsRotation(roll);
ufoDirection.set(cam.getLeft()).multLocal(-speed * tpf);
}
if (up) {
roll.fromAngleAxis(0, cam.getDirection());
//ufoControl.setPhysicsRotation(roll);
ufoDirection.addLocal(camUp);
}
if (down) {
roll.fromAngleAxis(0, cam.getDirection());
//ufoControl.setPhysicsRotation(roll);
ufoDirection.addLocal(cam.getUp().multLocal(-speed * tpf));
}
if (forward) {
roll.fromAngleAxis(0, cam.getDirection());
//ufoControl.setPhysicsRotation(roll);
ufoDirection.set(camDir);
}
if (backward) {
roll.fromAngleAxis(0, cam.getDirection());
//ufoControl.setPhysicsRotation(roll);
ufoDirection.set(camDir.multLocal(-1f));
}
ufoControl.setPhysicsRotation(roll);
ufoControl.setLinearVelocity(ufoDirection);
}
[/java]

I’m manipulating the ufoControl now instead of the node in the update code. The complete rewritten app / game is

[java]public class UFOSpaceWorld extends SimpleApplication implements AnalogListener,
ActionListener {

private PlanetAppState planetAppState;
private Geometry mark;
private Node ufoNode;
private Node spaceStationNode;
private Node jumpgateNode;
private Node jumpgateNode2;
private RigidBodyControl ufoControl;
private BetterCharacterControl spaceStationControl;
CameraNode camNode;
ChaseCamera chaseCam;
boolean rotate = false;
static UFOSpaceWorld app;
Vector3f ufoDirection = new Vector3f();
private BulletAppState bulletAppState;
private boolean left = false, right = false, up = false, down = false,
		forward = false, backward = false, attack = false;

private void setupChaseCamera() {
	flyCam.setEnabled(false);
	chaseCam = new ChaseCamera(cam, ufoNode, inputManager);
	chaseCam.setDefaultDistance(2237);
}

public static void main(String[] args) {
	AppSettings settings = new AppSettings(true);
	settings.setResolution(1280, 1024);
	app = new UFOSpaceWorld();
	app.setSettings(settings);
	app.start();
}

@Override
public void simpleInitApp() {
	this.setDisplayStatView(false);
	setDisplayFps(false);
	// Only show severe errors in log
	java.util.logging.Logger.getLogger("com.jme3").setLevel(
			java.util.logging.Level.SEVERE);
	bulletAppState = new BulletAppState();
	bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
	stateManager.attach(bulletAppState);
	bulletAppState.setDebugEnabled(false);
	DirectionalLight sun = new DirectionalLight();
	sun.setDirection(new Vector3f(-.1f, 0f, -1f));
	sun.setColor(new ColorRGBA(0.75f, 0.75f, 0.75f, 1.0f));
	rootNode.addLight(sun);
	// Add sky
	Node sceneNode = new Node("Scene");
	sceneNode.attachChild(Utility.createSkyBox(this.getAssetManager(),
			"Textures/blue-glow-1024.dds"));
	rootNode.attachChild(sceneNode);
	// Create collision test mark
	Sphere sphere = new Sphere(30, 30, 5f);
	mark = new Geometry("mark", sphere);
	Material mark_mat = new Material(assetManager,
			"Common/MatDefs/Misc/Unshaded.j3md");
	mark_mat.setColor("Color", ColorRGBA.Red);
	mark.setMaterial(mark_mat);
	// Add planet app state
	planetAppState = new PlanetAppState(rootNode, sun);
	stateManager.attach(planetAppState);

	// Add planet
	FractalDataSource planetDataSource = new FractalDataSource(4);
	planetDataSource.setHeightScale(900f);
	Planet planet = Utility.createEarthLikePlanet(getAssetManager(),
			293710.0f, null, planetDataSource);
	planetAppState.addPlanet(planet);
	rootNode.attachChild(planet);

	// Add moon
	FractalDataSource moonDataSource = new FractalDataSource(5);
	moonDataSource.setHeightScale(300f);
	Planet moon = Utility.createMoonLikePlanet(getAssetManager(), 50000,
			moonDataSource);
	planetAppState.addPlanet(moon);
	rootNode.attachChild(moon);
	moon.setLocalTranslation(new Vector3f(10f, 10f, 705000f));
	// add saucer
	ufoNode = (Node) assetManager.loadModel("usaucer_v01.j3o");
	ufoNode.setLocalScale(100f);
	ufoNode.setLocalTranslation((new Vector3f(1000f, -1000f, 328000f)));
	jumpgateNode = (Node) assetManager.loadModel("JumpGate.j3o");
	jumpgateNode.setLocalScale(10000f);
	jumpgateNode.setLocalTranslation((new Vector3f(10f, 10f, 908000f)));

	spaceStationNode = (Node) assetManager.loadModel("SpaceStation.j3o");
	spaceStationNode.setLocalScale(2000f);
	spaceStationNode.setLocalTranslation((new Vector3f(10000f, -10f,
			625000f)));

	jumpgateNode2 = (Node) assetManager.loadModel("JumpGate.j3o");
	jumpgateNode2.setLocalScale(10000f);
	jumpgateNode2.setLocalTranslation((new Vector3f(10f, 10f, 998300f)));

	rootNode.attachChild(jumpgateNode);
	rootNode.attachChild(jumpgateNode2);
	//rootNode.attachChild(spaceStationNode);
	ufoControl = new RigidBodyControl( 50f);
	//new BetterCharacterControl100f, 80f,
	// radius (meters), height (meters), gravity (mass)
	System.out.println("space station local scale: "+spaceStationNode.getLocalScale());
	System.out.println("ufo local scale: "+ufoNode.getLocalScale());
	
	spaceStationControl = new BetterCharacterControl(4000f, 8000f, 900f);
	//ufoControl.setPhysicsDamping(0);
	ufoNode.addControl(ufoControl);
	spaceStationNode.addControl(spaceStationControl);
	//ufoControl.setGravity(new Vector3f(0,0,0));
	rootNode.attachChild(ufoNode);
	bulletAppState.getPhysicsSpace().add(ufoControl);
	getPhysicsSpace().add(ufoControl);		
	rootNode.attachChild(spaceStationNode);
	bulletAppState.getPhysicsSpace().add(spaceStationControl);
	getPhysicsSpace().add(spaceStationControl);
	bulletAppState.getPhysicsSpace().setGravity(new Vector3f(0, 0, 0));
	setupChaseCamera();
	registerInput();
}

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

public void registerInput() {
	inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP),
			new KeyTrigger(keyInput.KEY_W));
	inputManager.addMapping("moveBackward", new KeyTrigger(
			keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S));
	inputManager.addMapping("moveRight",
			new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(
					keyInput.KEY_D));
	inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT),
			new KeyTrigger(keyInput.KEY_A));
	inputManager.addMapping("moveUp", new KeyTrigger(keyInput.KEY_E));
	inputManager.addMapping("moveDown", new KeyTrigger(keyInput.KEY_Q));
	inputManager.addMapping("toggleRotate", new MouseButtonTrigger(
			MouseInput.BUTTON_LEFT));
	inputManager.addMapping("rotateRight", new MouseAxisTrigger(
			MouseInput.AXIS_X, true));
	inputManager.addMapping("rotateLeft", new MouseAxisTrigger(
			MouseInput.AXIS_X, false));
	inputManager.addMapping("rotateUp", new MouseAxisTrigger(
			MouseInput.AXIS_Y, true));
	inputManager.addMapping("rotateDown", new MouseAxisTrigger(
			MouseInput.AXIS_Y, false));
	inputManager.addListener(this, "moveForward", "moveBackward",
			"moveRight", "moveLeft", "moveUp", "moveDown");
	inputManager.addListener(this, "rotateRight", "rotateLeft", "rotateUp",
			"rotateDown", "toggleRotate");
	// Toggle mouse cursor
	inputManager.addMapping("TOGGLE_CURSOR", new MouseButtonTrigger(
			MouseInput.BUTTON_LEFT), new KeyTrigger(KeyInput.KEY_SPACE));
	inputManager.addListener(actionListener, "TOGGLE_CURSOR");
	// Toggle wireframe
	inputManager.addMapping("TOGGLE_WIREFRAME", new KeyTrigger(
			KeyInput.KEY_T));
	inputManager.addListener(actionListener, "TOGGLE_WIREFRAME");
	// Collision test
	inputManager.addMapping("COLLISION_TEST", new MouseButtonTrigger(
			MouseInput.BUTTON_RIGHT));
	inputManager.addListener(actionListener, "COLLISION_TEST");
}

public void toggleToFullscreen() {
	  GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
	  DisplayMode[] modes = device.getDisplayModes();
	  int i=0; // note: there are usually several, let's pick the first
	  settings.setResolution(modes[i].getWidth(),modes[i].getHeight());
	  settings.setFrequency(modes[i].getRefreshRate());
	  settings.setBitsPerPixel(modes[i].getBitDepth());
	  settings.setFullscreen(device.isFullScreenSupported());
	  app.setSettings(settings);
	  app.restart(); // restart the context to apply changes
	}




@Override
public void simpleUpdate(float tpf) {
	int speed = 50 * 80000;
	Vector3f camDir = cam.getDirection().clone().multLocal(speed * tpf);
	Vector3f camUp = cam.getUp().clone().mult(speed * tpf);
	Quaternion roll = new Quaternion();		
	camDir.y = 0;
	ufoDirection.set(0, 0, 0);		
	ufoControl.setLinearVelocity(Vector3f.ZERO);
	if (left) {
		roll.fromAngleAxis(-FastMath.QUARTER_PI/2, cam.getDirection());
		ufoDirection.set(cam.getLeft().multLocal(speed * tpf));
	}
	if (right) {			
		roll.fromAngleAxis(FastMath.QUARTER_PI/2, cam.getDirection());
		//ufoControl.setPhysicsRotation(roll);
		ufoDirection.set(cam.getLeft()).multLocal(-speed * tpf);
	}
	if (up) {
		roll.fromAngleAxis(0, cam.getDirection());	
		//ufoControl.setPhysicsRotation(roll);
		ufoDirection.addLocal(camUp);
	}
	if (down) {
		roll.fromAngleAxis(0, cam.getDirection());	
		//ufoControl.setPhysicsRotation(roll);			
		ufoDirection.addLocal(cam.getUp().multLocal(-speed * tpf));
	}
	if (forward) {
		roll.fromAngleAxis(0, cam.getDirection());	
		//ufoControl.setPhysicsRotation(roll);			
		ufoDirection.set(camDir);
	}
	if (backward) {
		roll.fromAngleAxis(0, cam.getDirection());	
		//ufoControl.setPhysicsRotation(roll);			
		ufoDirection.set(camDir.multLocal(-1f));	
	}
	ufoControl.setPhysicsRotation(roll);		
	ufoControl.setLinearVelocity(ufoDirection);
}

public void onAnalog(String name, float value, float tpf) {

	if (name.equals("rotateRight") &amp;&amp; rotate) {
		System.out.println("rotateRight");	
		ufoNode.rotate(0, 1 * tpf, 0);
	}
	if (name.equals("rotateLeft") &amp;&amp; rotate) {
		System.out.println("rotateLeft");	
		ufoNode.rotate(0, -1 * tpf, 0);
	}
	if (name.equals("rotateUp") &amp;&amp; rotate) {
		System.out.println("rotateUp");	
		ufoNode.rotate(0, 0, -1 * tpf);
	}
	if (name.equals("rotateDown") &amp;&amp; rotate) {
		System.out.println("rotateDown");	
		ufoNode.rotate(0, 0, 1 * tpf);
	}	
}

public void onAction(String name, boolean keyPressed, float tpf) {

	if (name.equals("moveLeft")) {
		if (keyPressed) {
			left = true;
		} else {
			left = false;
		}
	} else if (name.equals("moveRight")) {
		if (keyPressed) {
			right = true;
		} else {
			right = false;
		}
	} else if (name.equals("moveUp")) {
		if (keyPressed) {
			up = true;
		} else {
			up = false;
		}
	} else if (name.equals("moveDown")) {
		if (keyPressed) {
			down = true;
		} else {
			down = false;
		}
	} else if (name.equals("moveForward")) {
		if (keyPressed) {
			forward = true;
		} else {
			forward = false;
		}
	} else if (name.equals("moveBackward")) {
		if (keyPressed) {
			backward = true;
		} else {
			backward = false;
		}
	}

	// toggling rotation on or off
	if (name.equals("toggleRotate") &amp;&amp; keyPressed) {
		rotate = true;
		inputManager.setCursorVisible(false);
	}
	if (name.equals("toggleRotate") &amp;&amp; !keyPressed) {
		rotate = false;
		inputManager.setCursorVisible(true);
	}
	if (name.equals("TOGGLE_CURSOR") &amp;&amp; !keyPressed) {
		if (inputManager.isCursorVisible()) {
			inputManager.setCursorVisible(false);
		} else {
			inputManager.setCursorVisible(true);
		}
	}
	if (name.equals("TOGGLE_WIREFRAME") &amp;&amp; !keyPressed) {
		for (Planet planet : planetAppState.getPlanets()) {
			planet.toogleWireframe();
		}
	}
	if (name.equals("COLLISION_TEST") &amp;&amp; !keyPressed) {
		CollisionResults results = new CollisionResults();
		Ray ray = new Ray(cam.getLocation(), cam.getDirection());
		// Test collision with closest planet's terrain only
		planetAppState.getNearestPlanet().getTerrainNode()
				.collideWith(ray, results);
		System.out.println("----- Collisions? " + results.size() + "-----");
		for (int i = 0; i &lt; results.size(); i++) {
			// For each hit, we know distance, impact point, name of
			// geometry.
			float dist = results.getCollision(i).getDistance();
			Vector3f pt = results.getCollision(i).getContactPoint();
			String hit = results.getCollision(i).getGeometry().getName();
			System.out.println("* Collision #" + i);
			System.out.println("  You shot " + hit + " at " + pt + ", "
					+ dist + " wu away.");
		}
		if (results.size() &gt; 0) {
			// The closest collision point is what was truly hit:
			CollisionResult closest = results.getClosestCollision();
			// Let's interact - we mark the hit with a red dot.
			mark.setLocalTranslation(closest.getContactPoint());
			rootNode.attachChild(mark);
		} else {
			// No hits? Then remove the red mark.
			rootNode.detachChild(mark);
		}
	}
}

private ActionListener actionListener = new ActionListener() {
	public void onAction(String name, boolean pressed, float tpf) {
		if (name.equals("TOGGLE_CURSOR") &amp;&amp; !pressed) {
			if (inputManager.isCursorVisible()) {
				inputManager.setCursorVisible(false);
			} else {
				inputManager.setCursorVisible(true);
			}
		}
		if (name.equals("TOGGLE_WIREFRAME") &amp;&amp; !pressed) {
			for (Planet planet : planetAppState.getPlanets()) {
				planet.toogleWireframe();
			}
		}
		if (name.equals("COLLISION_TEST") &amp;&amp; !pressed) {
			CollisionResults results = new CollisionResults();
			Ray ray = new Ray(cam.getLocation(), cam.getDirection());
			// Test collision with closest planet's terrain only
			planetAppState.getNearestPlanet().getTerrainNode()
					.collideWith(ray, results);
			System.out.println("----- Collisions? " + results.size()
					+ "-----");
			for (int i = 0; i &lt; results.size(); i++) {
				// For each hit, we know distance, impact point, name of
				// geometry.
				float dist = results.getCollision(i).getDistance();
				Vector3f pt = results.getCollision(i).getContactPoint();
				String hit = results.getCollision(i).getGeometry()
						.getName();
				System.out.println("* Collision #" + i);
				System.out.println("  You shot " + hit + " at " + pt + ", "
						+ dist + " wu away.");
			}
			if (results.size() &gt; 0) {
				// The closest collision point is what was truly hit:
				CollisionResult closest = results.getClosestCollision();
				// Let's interact - we mark the hit with a red dot.
				mark.setLocalTranslation(closest.getContactPoint());
				rootNode.attachChild(mark);
			} else {
				// No hits? Then remove the red mark.
				rootNode.detachChild(mark);
			}
		}
	}
};

}
[/java]

And having rewritten it to use a RigidBodyControl now the gameplay is somewhat different but it is rendering everything except the tilting that I want to achieve.THe UFO now does tilt.[video]http://youtu.be/wtP14tj-I4k[/video]