Rotating CameraNode around a point

Here is an example of a camera I made a long time ago.

import com.jme3.asset.AssetManager;
import com.jme3.bounding.BoundingBox;
import com.jme3.collision.CollisionResults;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.input.InputManager;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.scene.shape.Box;
import com.jme3.util.TempVars;
import java.io.IOException;

/**
 * @deprecated 
 * @author Stomrage
 */
public class ZeldaCam implements Control, RawInputListener {

    //The target of your camera
    private Spatial target;
    private Camera cam;
    //The minimum distance that the camera can reach
    private final float minDistance = 20;
    //The maximum distance that the camera can reach
    private final float maxDistance = 50;
    //The distance the camera has to reach
    private float desiredDistance = 30;
    //It's the size of the target
    private float minHauteur = 0;
    //The distance that the character need to reach when there is to indicate
    //the distance he has to reach
    private float perfectDistance = 30;
    //Needed for the camera.lookat
    private final Vector3f initialUpVec;
    //The node used for camera collision
    private Node collides;
    //The stick current x rotation
    private float xRotation;
    //The stick current y rotation
    private float yRotation;
    //The current rotation speed for the interpolateLinear more greater this
    //value is more smoother the rotation will be
    private float roationSpeed = 2;
    private int borderSize = 30;
    private int borderIncrement = 25;
    private boolean lockedCamera = false;
    //Indicate the current distance for the stick value
    private float stickDistance = 0;
    private Node topBorder = new Node();
    private Node botBorder = new Node();
    private Vector3f topDefaultPosition = new Vector3f(0, 0, 0);
    private Vector3f botDefaultPosition = new Vector3f(0, 0, 0);
    private Vector3f finalTopLocation = new Vector3f(0, 0, 0);
    private Vector3f finalBotLocation = new Vector3f(0, 0, 0);
    private Spatial locked;
    private Vector3f finalTarget = new Vector3f(0,0,0);
    private Vector3f rotation = new Vector3f(0,0,0);
    private Vector3f targetLastPosition = new Vector3f(0,0,0);

    public ZeldaCam(Camera cam, Spatial target) {
        this.cam = cam;
        this.target = target;
        this.target.addControl(this);
        initialUpVec = cam.getUp().clone();
        minHauteur = ((BoundingBox) target.getWorldBound()).getYExtent() * 2;
        targetLastPosition.set(target.getLocalTranslation());
    }

    public void initiateBorder(Node guiNode, AssetManager assetManager, int screenWidth, int screenHeight) {

        borderIncrement = screenHeight / borderIncrement;
        Box b = new Box(new Vector3f(0, (screenHeight / borderSize), 0), screenWidth, screenHeight / borderSize + borderIncrement, 1);
        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Black);
        geom.setMaterial(mat);
        topBorder.attachChild(geom);

        b = new Box(new Vector3f(0, screenHeight - (screenHeight / borderSize), 0), screenWidth, screenHeight / borderSize + borderIncrement, 1);
        geom = new Geometry("Box", b);
        mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Black);
        geom.setMaterial(mat);
        botBorder.attachChild(geom);

        guiNode.attachChild(topBorder);
        guiNode.attachChild(botBorder);
        topDefaultPosition.set(topBorder.getLocalTranslation());
        botDefaultPosition.set(botBorder.getLocalTranslation());
        finalTopLocation.set(topBorder.getLocalTranslation());
        finalBotLocation.set(botBorder.getLocalTranslation());
    }

    public void initKey(InputManager inputManager) {
        inputManager.addRawInputListener(this);
    }

    public Control cloneForSpatial(Spatial spatial) {
        ZeldaCam cc = new ZeldaCam(cam, spatial);
        return cc;
    }

    public void setSpatial(Spatial spatial) {
        this.target = spatial;
        minHauteur = ((BoundingBox) target.getWorldBound()).getYExtent() * 2;
    }

    public void update(float tpf) {
        updateCamera(tpf);
    }

    public void render(RenderManager rm, ViewPort vp) {
    }

    public void write(JmeExporter ex) throws IOException {
    }

    public void read(JmeImporter im) throws IOException {
    }

    private void updateCamera(float tpf) {
        computePosition(tpf);
    }

    public void setLockedSpatial(Spatial spatial) {
        this.locked = spatial;
    }

    private void computePosition(float tpf) {
 
        /*The stick distance is calculated here depending on the stick rotation given by yRotation this value is reached by the maxDistance and minDistance
         *so you can zoom between the max and min range.
         */
        stickDistance = Math.max(minDistance - perfectDistance, Math.min(maxDistance - perfectDistance, stickDistance + yRotation * tpf * 40));        
        finalTarget.set(Vector3f.ZERO);
        Vector3f positionRectification = new Vector3f(0,0,0);
        if(lockedCamera){
            if(locked!=null){
                Vector3f tempVector = target.getLocalTranslation().clone().addLocal(0,minHauteur/2,0);
                tempVector.addLocal(locked.getLocalTranslation());
                finalTarget = tempVector.divide(2);
                Vector3f tempDirection = locked.getLocalTranslation().subtract(target.getLocalTranslation());
                tempDirection.negateLocal();
                Quaternion tempQuat = new Quaternion();
                tempQuat.fromAngles(0, FastMath.atan2(tempDirection.getX(), tempDirection.getZ()), 0);
                target.setLocalRotation(tempQuat);
            }else{
                finalTarget = target.getLocalTranslation().clone();
                finalTarget.addLocal(0,minHauteur/2,0);
                positionRectification = target.getLocalTranslation().subtract(targetLastPosition);
            }
        }else{
            finalTarget = target.getLocalTranslation().clone();
            finalTarget.addLocal(0,minHauteur/2,0);
            positionRectification = target.getLocalTranslation().subtract(targetLastPosition);
        }
        
        //Calculate the height between the cam and the "top" of the target
        float hauteurCam = cam.getLocation().getY()-finalTarget.getY();
        //We copy the camera location into a temp variable 
        Vector3f finalLocation = cam.getLocation().clone();
        //Substract the current distance of the cam with the character height given by minHauteur
        
        finalLocation.subtractLocal(new Vector3f(0f, hauteurCam - FastMath.exp(desiredDistance / 20), 0f));
        
        //Make sure there is something to collide
        if (collides != null) {
            //If something collide then true or false if there is nothing in the eyes of the camera
            if (!cameraCollision(finalTarget)) {
                //If nothing collide in the path of the camera eyes it's ok we can reach the perfect distance (depending on the stick distance)
                desiredDistance = Math.max(minDistance, Math.min(maxDistance, perfectDistance + stickDistance));
                //Calculate the current distance between the target and the camera
                float currentDistance = cam.getLocation().distance(finalTarget);
                //currentDistance = Math.min(maxDistance, currentDistance);
                //We reach the desired distance and make sure to substract the currentDistance
                if(lockedCamera){
                    if(locked!=null){
                        finalLocation.addLocal(cam.getDirection().mult(currentDistance - desiredDistance));      
                    }else{
                        Vector3f tempDirection = cam.getDirection().add(rotation);
                        tempDirection.multLocal(50);
                        finalLocation.addLocal(tempDirection);
                        finalLocation.addLocal(cam.getDirection().mult(currentDistance - desiredDistance));     
                    }
                } else {
                    finalLocation.addLocal(cam.getDirection().mult(currentDistance - desiredDistance));
                }
            } else {
                if (lockedCamera) {
                    if (locked == null) {
                        Vector3f tempDirection = cam.getDirection().add(rotation);
                        tempDirection.multLocal(50);
                        finalLocation.addLocal(tempDirection);
                    }
                }
            }
        }
        //Rotate the camera left or right depending on the stick rotation
        finalLocation.addLocal(cam.getLeft().mult(xRotation * roationSpeed * desiredDistance / 5));
        Vector3f interpolateDistance = FastMath.interpolateLinear(tpf * 6, cam.getLocation().clone(), finalLocation);
        interpolateDistance.addLocal(positionRectification);
        //Change the camera location
        cam.setLocation(interpolateDistance);
        //Look at the head of the character
        cam.lookAt(finalTarget, initialUpVec);

        Vector3f interpolateTopLocation = FastMath.interpolateLinear(tpf * 6, topBorder.getLocalTranslation(), finalTopLocation);
        Vector3f interpolateBotLocation = FastMath.interpolateLinear(tpf * 6, botBorder.getLocalTranslation(), finalBotLocation);
        topBorder.setLocalTranslation(interpolateTopLocation);
        botBorder.setLocalTranslation(interpolateBotLocation);
        targetLastPosition.set(target.getLocalTranslation());
        
    }

    private boolean cameraCollision(Vector3f finalTarget) {

        //Here we rotate the camera direction to cast a ray and detect if something is in the eyes of the camera
        Vector3f direction = cam.clone().getDirection();
        Quaternion quat180 = new Quaternion();
        quat180.fromAngleAxis(FastMath.PI, cam.getUp());
        Vector3f inverseCamDirection = quat180.mult(direction);
        Ray ray = new Ray(finalTarget, inverseCamDirection);
        CollisionResults results = new CollisionResults();
        collides.collideWith(ray, results);
        if (results.size() > 0) {
            float resultDistance = results.getClosestCollision().getDistance();
            //If there is something we make sure it's not behind the camera max range
            if (resultDistance < maxDistance) {
                //If the resultDistance is greater than the desiredDistance+stickDistance there is no collision
                if (resultDistance < perfectDistance + stickDistance) {
                    //If there is really a collision we can set the desired distance to the resultDistance
                    //All the other case mean that there is no collision so we return false
                    float currentDistance = cam.getLocation().distance(finalTarget);
                    cam.getLocation().addLocal(cam.getDirection().clone().mult(currentDistance - resultDistance));
                    desiredDistance = resultDistance;
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    void setCollidesNode(Node collides) {
        this.collides = collides;
    }

    public void onJoyAxisEvent(JoyAxisEvent evt) {
        float value = evt.getValue();
        String joystickName = evt.getAxis().getName();
        if (joystickName.contains("Rotation")) {

            if (joystickName.contains("X")) {
                if (FastMath.abs(value) < 0.35) {
                    xRotation = 0;
                } else {
                    xRotation = value;
                }

            } else if (joystickName.contains("Y")) {
                if (FastMath.abs(value) < 0.25) {
                    yRotation = 0;
                } else {
                    yRotation = value;
                }
            }
        }
    }

    public void onJoyButtonEvent(JoyButtonEvent evt) {
        if (evt.getButtonIndex() == 5) {
            lockedCamera = !lockedCamera;
            if (lockedCamera) {
                finalTopLocation.set(topDefaultPosition.add(0, borderIncrement, 0));
                finalBotLocation.set(botDefaultPosition.add(0, -borderIncrement, 0));
                rotation = target.getLocalRotation().getRotationColumn(2);
            } else {
                finalTopLocation.set(topDefaultPosition);
                finalBotLocation.set(botDefaultPosition);
                rotation.set(new Vector3f(0,0,0));
            }
        }else if(evt.getButtonIndex() == 4){
            locked = null;
        }
    }

    public void onMouseMotionEvent(MouseMotionEvent evt) {
    }

    public void onMouseButtonEvent(MouseButtonEvent evt) {
    }

    public void onKeyEvent(KeyInputEvent evt) {
    }

    public void onTouchEvent(TouchEvent evt) {
    }

    public void beginInput() {
    }

    public void endInput() {
    }
}

The video where I use this system. Hope you find it usefull. (I’m using joystick for input)

4 Likes