Local vs Global Rotation

I know that there was a recent post about local and global coordinates, which are no problems. However, I am trying to implement an orbit control without any reliance on a chase camera (to allow variety of spatial movements). I seem to be stuck on the mouse-propelled rotations. I have the following code:

pre type="java"

public void simpleUpdate(float tpf){

if ((mouseDragXDir != 0) || (mouseDragYDir != 0)) {

Quaternion pivotRot = pivot.getWorldRotation();

Quaternion mouseDrag = new Quaternion(new float [] {







Basically I am trying to translate mouse drag in X and Y coordinates into rotation of a complex object (pivot) to follow the mouse movement. The reasons tells me to modify the pivot rotation either in local or world coordinates, so here I decided to get pivot’s world rotation, add the rotation implied by the mouse drag, and apply it back to the pivot in its local coordinates.

It seems that regardless of whether I initially get the pivot’s local or world rotation, the behaviour is exactly the same. The spatials are rotating according to their own local coordinates irrespectively of the current position in the world (in this way, for example, I can never get the spatials to rotate around their original Z axis).

Does this help?


This is really great, thanks @normen! All mouse drags work perfectly! I had some problems with the loaded models which confused the coordinate system, however it is all good. The following code (EDITED since the original posting) is slightly different from yours as it allows free rotation of the controlled object:

pre type="java"

 Ironfrown @ Pineberry Pty Ltd

  Feel free to use this code



package com.pineberry.jmetute;

import com.jme3.app.SimpleApplication;

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.AmbientLight;

import com.jme3.light.DirectionalLight;

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.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Sphere;

public class HelloOrbit extends SimpleApplication {

protected Node pivot;

int mouseDragXDir=1;

int mouseDragYDir=0;

int walkForward = 0;

int walkRight = 0;

float walkSpeed = 0.9f;

float dragRotXSpeed = 1.0f;

float dragRotYSpeed = 0.8f;

float rotSpeedMagnif = 1000.0f;

    Vector3f lookAtVect = new Vector3f(0,0,1);

    Vector3f upVect = new Vector3f(0,1,0);

boolean mouseLeftButtonPressed = false;

public static void main(String[] args) {

HelloOrbit app = new HelloOrbit();




public void simpleInitApp() {

// INitialise the world



// Create a brick sphere

Sphere b1 = new Sphere(50, 50, 1);

Geometry bb = new Geometry(“Box”, b1);

Material m1 = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

m1.setTexture(“ColorMap”, assetManager.loadTexture(“Textures/Brick.jpg”));


bb.setLocalTranslation(new Vector3f(0, 0, 0));

// Create a red planet cube

Box b2 = new Box(Vector3f.ZERO, 1, 1, 1);

Geometry rb = new Geometry(“Box”, b2);

Material m2 = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

m2.setTexture(“ColorMap”, assetManager.loadTexture(“Textures/DustStorm.jpg”));



rb.setLocalTranslation(new Vector3f(-3, 0, 0));

// Create a pivot node at (0,0,0) and attach it to the root

pivot = new Node(“Pivot”);


// Attach both boxes to the pivot node



// Set the pivot

pivot.setLocalTranslation(new Vector3f(0, 0, 0));

// Init controls


// Add some light

        AmbientLight amb = new AmbientLight();



        DirectionalLight dl = new DirectionalLight();

        dl.setDirection(new Vector3f(5, -5, -5));



private void initControls() {

// Map several inputs, some could be to the same action

inputManager.addMapping(“Forward”, new KeyTrigger(KeyInput.KEY_UP));

inputManager.addMapping(“Back”, new KeyTrigger(KeyInput.KEY_DOWN));

inputManager.addMapping(“Left”, new KeyTrigger(KeyInput.KEY_LEFT));

inputManager.addMapping(“Right”, new KeyTrigger(KeyInput.KEY_RIGHT));

inputManager.addMapping(“Stop”, new KeyTrigger(KeyInput.KEY_SPACE));

inputManager.addMapping(“MouseLeftButton”, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

inputManager.addMapping(“RotRight”, new MouseAxisTrigger(MouseInput.AXIS_X, true));

inputManager.addMapping(“RotLeft”, new MouseAxisTrigger(MouseInput.AXIS_X, false));

inputManager.addMapping(“RotUp”, new MouseAxisTrigger(MouseInput.AXIS_Y, true));

inputManager.addMapping(“RotDown”, new MouseAxisTrigger(MouseInput.AXIS_Y, false));

// Adds names to action listener

inputManager.addListener(myActionListener, new String[]{

“Left”, “Right”, “Forward”, “Back”, “Stop”, “MouseLeftButton”});

inputManager.addListener(myAnalogListener, new String[]{

“RotLeft”, “RotRight”, “RotUp”, “RotDown”});


private ActionListener myActionListener = new ActionListener() {

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

if (name.equals(“MouseLeftButton”)) {

mouseLeftButtonPressed = keyPressed;

} else if (name.equals(“Right”)) {

walkRight = 1;

} else if (name.equals(“Left”)) {

walkRight = -1;

} else if (name.equals(“Forward”)) {

walkForward = 1;

} else if (name.equals(“Back”)) {

walkForward = -1;

} else if (name.equals(“Stop”)) {

walkForward = 0;

walkRight = 0;




private AnalogListener myAnalogListener = new AnalogListener() {

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

if (name.equals(“RotRight”)) {

if (mouseLeftButtonPressed) {

dragRotXSpeed = value

mouseDragXDir = -1;


} else if (name.equals(“RotLeft”)) {

if (mouseLeftButtonPressed) {

dragRotXSpeed = valuerotSpeedMagnif;

mouseDragXDir = 1;


} else if (name.equals(“RotUp”)) {

if (mouseLeftButtonPressed) {

dragRotYSpeed = value

mouseDragYDir = 1;


} else if (name.equals(“RotDown”)) {

if (mouseLeftButtonPressed) {

dragRotYSpeed = valuerotSpeedMagnif;

mouseDragYDir = -1;






public void simpleUpdate(float tpf){

if (walkForward != 0) {

Vector3f walkDirection = new Vector3f(0f, 0f, 0f);

    walkDirection = cam.getLocation().clone();

walkDirection.addLocal(0, 0, -walkForward



if (walkRight != 0) {

// If you stray to the left or right you may be in trouble

// As your camera direction vector has nothing to do with x, y, z anymore

Vector3f walkDirection = new Vector3f(0f, 0f, 0f);

    walkDirection = cam.getLocation().clone();

tpfwalkSpeed, 0, 0);



if ((mouseDragXDir != 0) || (mouseDragYDir != 0)) {

Quaternion addRot = new Quaternion(new float [] {






        pivot.lookAt(lookAtVect, upVect);



An interesting problem is happening in the above code when you move the rotating object (“pivot”) - it completely upsets the rotation. Try using the arrow keys to shift the object linearly. The objects starts wobbling. Any ideas?

I could simply move the camera…

Thats because lookAt practically works absolute in this case, you would have to use the spatials localToWorld() method.

Right, thank you!

I have modified the above code to correct the problems (so the example above has been EDITED). Note however that when your camera is moving around the object your x, y and z coordinates stay in place and so your camera may not be pointing at the object centrally and your up vector may be pointing in quite arbitrary direction in relation to you as well. If you do not correct this in respect of your object and your camera, your rotations can get quite unpredictable! The above code will work fine for movements in all directions. However when the distances are big enough for the side movement to cause visibly non-linear translations and the rotations will get distorted. In this case, either give up your lateral movements or go back and start working quaternions to adjust these linear translations.