Camera controled by aircraft movement and by Mouse

I’m making simulator of aircraft, and I want pilot sitting in cockpit be able to look around by mouse. At the same time the movement of aircraft should also affect the direction of camera. Both rotations ( mouse_look + aircraft_rotation ) should add up ( resp. multiply as quaternions ).

The problem is that when I attach camera to aircraftNode and use
CameraNode cam_node = new CameraNode(“Camera Node”, cam);
wing_node.attachChild( cam_node );
the mouse_look functionality is lost :frowning: (the camera move properly with the aircraft, but is not affected by mouse any more)

Probably the solution is to make manually some quaternion rotation based on mouse inputs
MouseAxisTrigger(MouseInput.AXIS_X, false)
MouseAxisTrigger(MouseInput.AXIS_Y, true)
and then multiply it with the rotation quaternion of the aircraft, and set it to cameraNode

but I don’t want to reinvent the wheel if there is already such functionality in default camera’s mouse_Look

So is there any nicer / simpler solution, or is it necessary to make it manualy?

it is related to this topic, but I didn’t found solution there:

maybe this video is more explanatory what I want to achieve:

I would think that if the camera resides in a node (that is effected by the aircraft movement), then this shouldn’t be an issue at all.

Look at com.jme3.scene.CameraNode

And the test class

/sigh… I just noticed you are using this already… ignore!

Another thought… Have you tried nesting the camera node within another node (or second camera node) prior to attaching to the plane?

If this doesn’t produce the desired results, extending the existing camera node with the functionality you need should be a matter of cut & paste, really.

OK, this is my solution, if somebody would need the same in future:

package mygame;

// =================================================================
// Motivation:
// This Code Should ilustrate pilot camera in jMonkey 3
// camera orientation is controled both by mouse and by orientation of aircraft
// is should be useful for looking around from pilot cockpit
// The same is used in Il-2 Strurmovik simulator
// Technical:
// mouse rotation is stored in variables mouseX, mouseY set in onAnalog
// Camera is rotated manually in simpleUpdate()
// made by ProkopHapala email:
// ===================================================================

import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Dome;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.util.SkyFactory;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;

public class game extends SimpleApplication implements AnalogListener {

public static void main(String args[]) {
game app = new game();

private Node aircraft_node;
private Quaternion Qcam, qtemp;
private float mouseX, mouseY;

public void simpleInitApp() {
cam.setFrustumFar(100000.0f); // aircraft simulator needs big view distance
makeAircraft( new Vector3f(0,0,0) );
rootNode.attachChild(SkyFactory.createSky(assetManager, “Textures/Sky/Bright/”, false));
//flyCam.setEnabled(false); // flyCam should stay on, otherwise mouse is restricted to window
qtemp = new Quaternion(); Qcam = new Quaternion();

public void registerInput() {
inputManager.addMapping(“W”, new KeyTrigger(keyInput.KEY_W));
inputManager.addMapping(“S”, new KeyTrigger(keyInput.KEY_S));
inputManager.addMapping(“D”, new KeyTrigger(keyInput.KEY_D));
inputManager.addMapping(“A”, new KeyTrigger(keyInput.KEY_A));
inputManager.addMapping(“E”, new KeyTrigger(keyInput.KEY_E));
inputManager.addMapping(“Q”, new KeyTrigger(keyInput.KEY_Q));
inputManager.addListener(this, “W”, “S”, “D”, “A”, “E”, “Q” );
inputManager.addMapping(“mouseXt”, new MouseAxisTrigger(MouseInput.AXIS_X, true));
inputManager.addMapping(“mouseXf”, new MouseAxisTrigger(MouseInput.AXIS_X, false));
inputManager.addMapping(“mouseYt”, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
inputManager.addMapping(“mouseYf”, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
inputManager.addListener(this, “mouseXt”, “mouseXf”, “mouseYt”, “mouseYf” );

public Quaternion rotateByAxis( Quaternion q, int i, float value ){
if (i==0) { q.multLocal( qtemp.fromAngleNormalAxis( value, Vector3f.UNIT_X ) ); return q; }
if (i==1) { q.multLocal( qtemp.fromAngleNormalAxis( value, Vector3f.UNIT_Y ) ); return q; }
if (i==2) { q.multLocal( qtemp.fromAngleNormalAxis( value, Vector3f.UNIT_Z ) ); return q; }
return q;

public void onAnalog(String name, float value, float tpf) {
if ( aircraft_node!=null){
if ( name.equals(“W” )){ aircraft_node.setLocalRotation( rotateByAxis(aircraft_node.getLocalRotation(), 0, -0.01f )); return; }
if ( name.equals(“S” )){ aircraft_node.setLocalRotation( rotateByAxis(aircraft_node.getLocalRotation(), 0, +0.01f )); return; }
if ( name.equals(“D” )){ aircraft_node.setLocalRotation( rotateByAxis(aircraft_node.getLocalRotation(), 1, -0.01f )); return; }
if ( name.equals(“A” )){ aircraft_node.setLocalRotation( rotateByAxis(aircraft_node.getLocalRotation(), 1, +0.01f )); return; }
if ( name.equals(“E” )){ aircraft_node.setLocalRotation( rotateByAxis(aircraft_node.getLocalRotation(), 2, +0.01f )); return; }
if ( name.equals(“Q” )){ aircraft_node.setLocalRotation( rotateByAxis(aircraft_node.getLocalRotation(), 2, -0.01f )); return; }
if ( name.equals(“mouseXt” )){ mouseX+=value; return; }
if ( name.equals(“mouseXf” )){ mouseX-=value; return; }
if ( name.equals(“mouseYt” )){ mouseY+=value; return; }
if ( name.equals(“mouseYf” )){ mouseY-=value; return; }

private Geometry putShape( Geometry g, ColorRGBA color, Node node){
Material mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
mat.setColor(“Color”, color);
return g;

public void makeAircraft( Vector3f where ) {
aircraft_node = new Node(“aircraft_node”);
Mesh wing_mesh = new Dome(Vector3f.ZERO, 2, 3, 1f,false);
Geometry wing_geo = putShape( new Geometry(“wing”, wing_mesh ) , ColorRGBA.Green, aircraft_node);
wing_geo.rotate( 0, -FastMath.PI/6, 0 );
wing_geo.scale(1, 0.1f, 1);
aircraft_node.setLocalTranslation( where );

public void simpleUpdate(float tpf) {
if ((cam!=null) && (aircraft_node!=null) ){
Qcam.multLocal(qtemp.fromAngleNormalAxis( mouseX, Vector3f.UNIT_Y ));
Qcam.multLocal(qtemp.fromAngleNormalAxis( mouseY, Vector3f.UNIT_X ));
cam.setRotation( Qcam );
cam.setLocation( aircraft_node.getLocalTranslation().subtract( cam.getDirection().mult(0) ) );


The only thing which is a bit strange:
I need to keep flyCam enabled, because if I switch it off by flyCam.setEnabled(false); the mouse start to behave differently (cursor appear and mouse triggers are invoked only if the mouse curser is over the window of the game )… this could be set probably alos somehow.

a) if you don’t want fly cam then just remove it. There are numerous posts on that already.

b) if you don’t have flycam then you will have to turn the cursor off yourself on InputManager. (Better to remove flycam instead of disable it because otherwise you will fight with it on the cursor setting.)

c) design tip: consider turning your camera functionality into an AppState and then you won’t have to dirty up your main class with it.