Rotating the Camera around a Point/Object [Solved]

I needed my Camera to rotate around an Object, so I searched the Internet for a good solution.
It seemed that many had the same issue, but there were few good explenations or examples.
For this reason I decided to share my Code.
I have written a class that controls the Camera. You can define a Point or Object which will be focused by the Camera.
When you touch the corners of the screen/window with your cursor, the camera moves around the object.
By scrolling you can zoom in and out.

Hope you guys like it…

package mygame;

import com.jme3.input.InputManager;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;

/**
 * StrategyCam
 * Implements an AnalogListener that rotates the camera around a given object or Coordinate (Vector3f)
 * when you touch the corners of the screen with your cursor.
 * 
 * @author NKappler
 */
public class StrategyCam implements AnalogListener {

    private Float movementSpeed = 1f; //rad per second
    private AppSettings settings;
    private InputManager inputManager;
    private Camera cam;
    private float camDistance = 10f;
    private float camMinDistance = 2f;
    private Vector3f camFocus = new Vector3f(0f, 0f, 0f);
    private Boolean isEnabled;
    private Boolean isMovingUp = false;
    private Boolean isMovingDown = false;
    private Boolean isMovingLeft = false;
    private Boolean isMovingRight = false;

    public StrategyCam(InputManager inputManager, AppSettings settings, Camera cam) {
        this.inputManager = inputManager;
        this.settings = settings;
        this.cam = cam;
        setFocus(camFocus, true);
        this.isEnabled = true;
    }

    public void registerInput() {
        inputManager.addMapping("rotateRight", new MouseAxisTrigger(MouseInput.AXIS_X, false));
        inputManager.addMapping("rotateLeft", new MouseAxisTrigger(MouseInput.AXIS_X, true));
        inputManager.addMapping("rotateUp", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
        inputManager.addMapping("rotateDown", new MouseAxisTrigger(MouseInput.AXIS_Y, true));
        inputManager.addMapping("scrollUp", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
        inputManager.addMapping("scrollDown", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
        inputManager.addListener(this, "rotateRight", "rotateLeft", "rotateUp", "rotateDown", "scrollUp", "scrollDown");
    }

    public void onAnalog(String name, float value, float tpf) {
        if (!isEnabled) return;

        Vector2f cursorPosition = inputManager.getCursorPosition();

        if (name.equals("rotateRight")) {
            if (!isMovingRight && (cursorPosition.x >= settings.getWidth() - 2)) {
                isMovingRight = true;
            } else if (isMovingLeft && (cursorPosition.x > 3)) {
                isMovingLeft = false;
            }
        } 
        else if (name.equals("rotateLeft")) {
            if (isMovingRight && (cursorPosition.x < settings.getWidth() - 2)) {
                isMovingRight = false;
            } else if (!isMovingLeft && (cursorPosition.x <= 3)) {
                isMovingLeft = true;
            }
        } 
        if (name.equals("rotateUp")) {
            if (!isMovingUp && (cursorPosition.y >= settings.getHeight() - 2)) {
                isMovingUp = true;
            } else if (isMovingDown && (cursorPosition.y > 3)) {
                isMovingDown = false;
            }
        } 
        else if (name.equals("rotateDown")) {
            if (isMovingUp && (cursorPosition.y < settings.getHeight() - 2)) {
                isMovingUp = false;
            } else if (!isMovingDown && (cursorPosition.y <= 3)) {
                isMovingDown = true;
            }
        }
        
        if (name.equals("scrollUp")) {
            if ((camDistance-3) >= camMinDistance) {
                camDistance -= 3;
                refresh();
            }
        }  
        
        if (name.equals("scrollDown")) {
            camDistance += 3;
            refresh();
        }  
               
    }

    private Matrix3f rotateHorizontal(float tpf) {
        Matrix3f M = new Matrix3f();
        M.fromAngleAxis(movementSpeed * tpf, Vector3f.UNIT_Y);
        return M;
    }

    private Matrix3f rotateVertical(float tpf) {
        Matrix3f M = new Matrix3f();
        M.fromAngleAxis(movementSpeed * tpf, cam.getLeft());
        return M;
    }

    public void moveCam(float tpf) {
        if (!isEnabled) {
            return;
        }
        if (!(isMovingDown || isMovingLeft || isMovingRight || isMovingUp)) {
            return;
        }

        Vector3f camDirection = cam.getDirection().normalize();
        Vector3f camPosition = cam.getLocation();

        if (isMovingRight) {
            camDirection = rotateHorizontal(tpf).mult(camDirection).normalize();
            camPosition = camFocus.add(camDirection.negate().mult(camDistance));
        } else if (isMovingLeft) {
            camDirection = rotateHorizontal(tpf).invertLocal().mult(camDirection).normalize();
            camPosition = camFocus.add(camDirection.negate().mult(camDistance));
        }
        if (isMovingUp && (camDirection.y > -0.98f)) {
            camDirection = rotateVertical(tpf).mult(camDirection).normalize();
            camPosition = camFocus.add(camDirection.negate().mult(camDistance));
        } else if (isMovingDown && (camDirection.y < 0.98f)) {
            camDirection = rotateVertical(tpf).invertLocal().mult(camDirection).normalize();
            camPosition = camFocus.add(camDirection.negate().mult(camDistance));
        }

        cam.setLocation(camPosition);
        cam.lookAtDirection(camDirection, Vector3f.UNIT_Y);
    }

    public void setFocus(Vector3f focus, boolean centered) {
        camFocus = focus;

        if (centered) {
            cam.setLocation(new Vector3f(camFocus.x, camFocus.y, camFocus.y + camDistance));
        } else {
            cam.lookAt(camFocus, Vector3f.UNIT_Y);
            cam.setLocation(camFocus.add(cam.getDirection().negate().mult(camDistance)));
        }
    }

    public void setFocus(Node focus, boolean centered) {
        camFocus = focus.getWorldTranslation();

        if (centered) {
            cam.setLocation(new Vector3f(camFocus.x, camFocus.y, camFocus.y + camDistance));
        } else {
            cam.lookAt(camFocus, Vector3f.UNIT_Y);
            cam.setLocation(camFocus.add(cam.getDirection().negate().mult(camDistance)));
        }
    }
    
    private void refresh() {
        setFocus(camFocus, false);
        if (camDistance - camMinDistance <= 3) {
            movementSpeed = .5f;
        }
        else if (camDistance - camMinDistance > 10) {
            movementSpeed = 2f;
        } else {
            movementSpeed = 1f;
        }
    }
}
2 Likes

can you please explain how to use it with example?
Thanks

1 Like

Here’s a version that lets you set Z as the “up” axis.

import com.jme3.input.InputManager;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;

/**
 * StrategyCam
 * Implements an AnalogListener that rotates the camera around a given object or Coordinate (Vector3f)
 * when you touch the corners of the screen with your cursor.
 * 
 * @author NKappler from JMonkey forums, modified by Alweth to allow for a Z-Axis "up".
 */
public class StrategyCam implements AnalogListener {

    private Float movementSpeed = 1f; //rad per second
    private AppSettings settings;
    private InputManager inputManager;
    private Camera cam;
    private float camDistance = 10f;
    private float camMinDistance = 2f;
    private Vector3f camFocus = new Vector3f(0f, 0f, 0f);
    private Vector3f up = Vector3f.UNIT_Y;
    private Boolean isEnabled;
    private Boolean isMovingUp = false;
    private Boolean isMovingDown = false;
    private Boolean isMovingLeft = false;
    private Boolean isMovingRight = false;

    public StrategyCam(InputManager inputManager, AppSettings settings, Camera cam) {
        this.inputManager = inputManager;
        this.settings = settings;
        this.cam = cam;
        setFocus(camFocus, true);
        this.isEnabled = true;
    }
    
    /**
     * Added by Alweth
     * 
     * @param zUp true is you want to treat the positive Z axis as up. By default, y is treated as up.
     */
    public StrategyCam(InputManager inputManager, AppSettings settings, Camera cam, boolean zUp) {
        this(inputManager, settings, cam);
        if (zUp) this.up = Vector3f.UNIT_Z;
    }
    
   

    public void registerInput() {
        inputManager.addMapping("rotateRight", new MouseAxisTrigger(MouseInput.AXIS_X, false));
        inputManager.addMapping("rotateLeft", new MouseAxisTrigger(MouseInput.AXIS_X, true));
        inputManager.addMapping("rotateUp", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
        inputManager.addMapping("rotateDown", new MouseAxisTrigger(MouseInput.AXIS_Y, true));
        inputManager.addMapping("scrollUp", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
        inputManager.addMapping("scrollDown", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
        inputManager.addListener(this, "rotateRight", "rotateLeft", "rotateUp", "rotateDown", "scrollUp", "scrollDown");
    }

    public void onAnalog(String name, float value, float tpf) {
        if (!isEnabled) return;

        Vector2f cursorPosition = inputManager.getCursorPosition();

        if (name.equals("rotateRight")) {
            if (!isMovingRight && (cursorPosition.x >= settings.getWidth() - 2)) {
                isMovingRight = true;
            } else if (isMovingLeft && (cursorPosition.x > 3)) {
                isMovingLeft = false;
            }
        } 
        else if (name.equals("rotateLeft")) {
            if (isMovingRight && (cursorPosition.x < settings.getWidth() - 2)) {
                isMovingRight = false;
            } else if (!isMovingLeft && (cursorPosition.x <= 3)) {
                isMovingLeft = true;
            }
        } 
        if (name.equals("rotateUp")) {
            if (!isMovingUp && (cursorPosition.y >= settings.getHeight() - 2)) {
                isMovingUp = true;
            } else if (isMovingDown && (cursorPosition.y > 3)) {
                isMovingDown = false;
            }
        } 
        else if (name.equals("rotateDown")) {
            if (isMovingUp && (cursorPosition.y < settings.getHeight() - 2)) {
                isMovingUp = false;
            } else if (!isMovingDown && (cursorPosition.y <= 3)) {
                isMovingDown = true;
            }
        }
        
        if (name.equals("scrollUp")) {
            if ((camDistance-3) >= camMinDistance) {
                camDistance -= 3;
                refresh();
            }
        }  
        
        if (name.equals("scrollDown")) {
            camDistance += 3;
            refresh();
        }  
               
    }

    private Matrix3f rotateHorizontal(float tpf) {
        Matrix3f M = new Matrix3f();
        M.fromAngleAxis(movementSpeed * tpf, up);
        return M;
    }

    private Matrix3f rotateVertical(float tpf) {
        Matrix3f M = new Matrix3f();
        M.fromAngleAxis(movementSpeed * tpf, cam.getLeft());
        return M;
    }

    public void moveCam(float tpf) {
        if (!isEnabled) {
            return;
        }
        if (!(isMovingDown || isMovingLeft || isMovingRight || isMovingUp)) {
            return;
        }

        Vector3f camDirection = cam.getDirection().normalize();
        Vector3f camPosition = cam.getLocation();

        if (isMovingRight) {
            camDirection = rotateHorizontal(tpf).mult(camDirection).normalize();
            camPosition = camFocus.add(camDirection.negate().mult(camDistance));
        } else if (isMovingLeft) {
            camDirection = rotateHorizontal(tpf).invertLocal().mult(camDirection).normalize();
            camPosition = camFocus.add(camDirection.negate().mult(camDistance));
        }
        if (isMovingUp && (upScalar(camDirection) > -0.98f)) {
            camDirection = rotateVertical(tpf).mult(camDirection).normalize();
            camPosition = camFocus.add(camDirection.negate().mult(camDistance));
        } else if (isMovingDown && (upScalar(camDirection) < 0.98f)) {
            camDirection = rotateVertical(tpf).invertLocal().mult(camDirection).normalize();
            camPosition = camFocus.add(camDirection.negate().mult(camDistance));
        }

        cam.setLocation(camPosition);
        cam.lookAtDirection(camDirection, up);
    }

    /**
     * 
     * Added by Alweth
     * 
     * @return the value of the "up" axis in this Vector3f (Y-Axis by default, Z-Axis if set as such)
     */
    private float upScalar(Vector3f v) {
		if (up == Vector3f.UNIT_Z) {
			return v.z;
		} else {
			return v.y;
		}
	}

	public void setFocus(Vector3f focus, boolean centered) {
        camFocus = focus;

        if (centered) {
            cam.setLocation(new Vector3f(camFocus.x, camFocus.y, camFocus.y + camDistance));
        } else {
            cam.lookAt(camFocus, up);
            cam.setLocation(camFocus.add(cam.getDirection().negate().mult(camDistance)));
        }
    }

    public void setFocus(Node focus, boolean centered) {
        camFocus = focus.getWorldTranslation();

        if (centered) {
            cam.setLocation(new Vector3f(camFocus.x, camFocus.y, camFocus.y + camDistance));
        } else {
            cam.lookAt(camFocus, up);
            cam.setLocation(camFocus.add(cam.getDirection().negate().mult(camDistance)));
        }
    }
    
    private void refresh() {
        setFocus(camFocus, false);
        if (camDistance - camMinDistance <= 3) {
            movementSpeed = .5f;
        }
        else if (camDistance - camMinDistance > 10) {
            movementSpeed = 2f;
        } else {
            movementSpeed = 1f;
        }
    }
}

To use it, in your simpleInitApp() method add:

		//change "false" to "true" to set Z-axis as up
		strategyCam = new StrategyCam(inputManager, settings, cam, false);
		strategyCam.registerInput();
		strategyCam.setFocus(new Vector3f(0f,  0f, 0f), true);

If you don’t want the camera immediately being controlled this way, you can call the registerInput() method later.

EDIT: I forgot to mention that to use this "moveCam(float tpf) needs to be called from the update loop.

moveCam(tpf);

I fixed it for you… as documented in the sticky topic, the three back-ticks need to be on their own line.

1 Like

I was pretty sure I tried that without results.

You had them at the beginning of your first code line (not on a line by itself) and on the end of your last line (not on a line by itself)… so if you only changed one and not the other then it still wouldn’t have worked.

The proof is that all I did to fix it was to add some line feeds. You can see the edit. (Well, you could before your edit… :slight_smile: )

1 Like

Ah, it was the fact that the final backticks need to be on their own line that I missed.