Rotating CameraNode around a point

Excluding the use of ChaseCamera, how can I rotate the CameraNode around the father node without rotating the latter?
In this case, the CameraNode is like a drone orbiting a circumference around the planet(Geometry at the center of the node) which rotates around him given the input. The camNode is already pointing at the center of the node with lookAt()
The methods I’m using are camNode.move() and/or camNode.setLocalTranslation().
Any formula(sin/cos or something like that), existing example, advice will be kindly accepted.

what I do is that I have a target node that is at the exact location of the geometry to orbit around.

The camera node is attached to this node and it’s position is offset.

When you want to orbit, you just have to rotate the target node and let the engine do the complicated math.

The is a ChaseCameraAppstate where I did this in core, you can check.

1 Like

That’s a great idea, don’t know why I didn’t think about it before!
Many thanks, I’ll test it right away

Still have a question,

If I want a smooth movement like I was applying a physics torque, how can I implement it?
I can’t create a RigidBodyControl on the node on which I’m rotating the camera node because it would collide with the RigidBodyControl of the planet.

You’ll have to apply some kind of ease in/out function to the tpf when rotating the target node I guess.

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

It does ray cast physic … Cool
Thanks for sharing. :heart:

Not what solves my problem, but really useful for future projects, many thanks :slight_smile: