Need help with camera math

Hello

I need help figuring out how to make the camera rotate around a quad. The code works but the camera rotates it's own axis (Having problems with the up vector I think).



I want to do it so that when you press left (or right) it circles around the quad keeping it's Z value and without tilting/twisting.



I think the problem lies with the camera lookat operation but im not quite sure. I thought I might have to compute left,up,dir vectors too to get that result but I have no idea how to do that.



This is my code so far:





Left key action


import com.jme.input.action.InputActionEvent;
import com.jme.input.action.KeyInputAction;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;

public class kleftaction extends KeyInputAction {
   
    private float campos;  //campos is the camera position around the quad, using 2*pi as a complete turn so campos is from 0 to 6.28
    private float cercania; //cercania is how close to the board the camera should be
    private Camera cam;
    private Vector3f gpos; //this is the quad position, used 0,0,0 for testing
    private keyboardcam kc; //InputHandler, holds all the actions
   
    public kleftaction(Camera cam, Vector3f gpos, keyboardcam kc) {
        this.cam = cam;
        this.gpos = gpos;
        this.kc = kc;
    }

    public void performAction(InputActionEvent evt) {
        Vector3f cpos = cam.getLocation(); //placeholder for the new camera position
        campos = kc.getCampos() + .001f; //moves around the circle
        cercania = kc.getCercania();

        if( campos > 6.28f ) campos = 0; //restart to 0 to avoid computing big numbers
        float cx = (float) (cercania*Math.cos(campos)); //calculates Camera X within the new capos range
        float cy = (float) (cercania*Math.sin(campos)); //calculates Camera Y within the new capos range
        cpos.setX(cx);
        cpos.setY(cy);
        // Z value of the camera should stay the same.
        kc.setCampos(campos); //returns the new value of campos to the InputHandler so that other actions can move from the same position.
        cam.setLocation(cpos);
       
        cam.lookAt(gpos, new Vector3f(0, 1, 0)); //This is where I think it doesn't work
        cam.update();
    }

}




Input handler in case you need it for testing


import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;


public class keyboardcam extends InputHandler {

    private float campos = 3.14f;
    private float cercania = 50f;
    private Vector3f gpos;
    private Camera cam;
    private kleftaction left;
   
    public keyboardcam(Vector3f gpos, Camera cam) {
        this.gpos = gpos;
        this.cam = cam;
        KeyBindingManager keyboard = KeyBindingManager.getKeyBindingManager();
        keyboard.set("cleft", KeyInput.KEY_LEFT);
        left = new kleftaction(cam, gpos, this);
        addAction(left, "cleft", true);
    }

    public float getCampos() {
        return campos;
    }

    public void setCampos(float campos) {
        this.campos = campos;
    }

    public float getCercania() {
        return cercania;
    }

    public void setCercania(float cercania) {
        this.cercania = cercania;
    }
}




You could create a new Node at the quads location, then attach a CameraNode to the Node and translate it on the X Axis away from the quad (and a bit up along the Y axis to meet your drawing).

Now if you rotate the new node around the Y Axis, the Camera will orbit nicely around the quad.

I looked at some solutions like that on the forum. I just thought that my approach could be possible.





Wouldn't I have to rotate the node around the Y and X axis? mhmm I don't get it yet. Ill try it later today.

i had to try it :wink:

one thing to keep in mind when using camera node is that cam.lookAt() has no effect anymore, the camera takes the direction of the CameraNode as its view direction.



import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.CameraNode;
import com.jme.scene.Node;
import com.jme.scene.shape.Quad;


public class TestRotate extends SimpleGame {
   Node rotNode;
   CameraNode camNode;
   Quaternion rotQ;
   
   @Override
   protected void simpleInitGame() {
      lightState.setEnabled(false);

      // create the Quad
      Quad quad = new Quad("q", 80, 80);
      quad.setModelBound(new BoundingBox());
      quad.updateModelBound();
      quad.setRandomColors();
      quad.getLocalRotation().fromAngleAxis(FastMath.DEG_TO_RAD*90,
            Vector3f.UNIT_X);
      rootNode.attachChild(quad);
      
      // a node to attach our camera node to it
      rotNode = new Node("rotNode");
      rootNode.attachChild(rotNode);
      
      // create the camera node
      camNode = new CameraNode("camNode", display.getRenderer().getCamera());
      // translate the Camera backwards and a bit above the quad
      camNode.setLocalTranslation(0, 2f, -50);
      rotNode.attachChild(camNode);
      
      // rotate the node around the Y Axis
      rotQ = new Quaternion();
      rotQ.fromAngleAxis(0.01f, Vector3f.UNIT_Y);
   }
   
   @Override
   protected void simpleUpdate() {
      // rotate the node, where the camerNode is attached to around the Y axis
      rotNode.getLocalRotation().multLocal(rotQ);
   }
   
   public static void main(String[] args) {
      new TestRotate().start();
   }
}

Thanks a lot for the help.



I still can't figure out how to make the camera look at the quad, the code you posted works but it missed the quad when the camera is moved. Ill try to work on this and post results.





Edit





D'oh…





My code works like a charm now that I used Z as the world up vector… I assumed it was Y so the camera was doing the right thing while rotating around the center. Changing the up vector to Z has the correct result.



Ill post example code since I saw a couple posts about this:



LeftKeyAction - kleftaction





import com.jme.input.action.InputActionEvent;
import com.jme.input.action.KeyInputAction;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;


public class kleftaction extends KeyInputAction {
   
    private float campos;
    private float distance;
    private Camera cam;
    private Vector3f gpos;
    private keyboardcam kc;
   
    public kleftaction(Camera cam, Vector3f gpos, keyboardcam kc) {
        this.cam = cam;
        this.gpos = gpos;
        this.kc = kc;
    }

    public void performAction(InputActionEvent evt) {
        Vector3f cpos = cam.getLocation();
        campos = kc.getCampos() + .001f;
        distance = kc.getDistance();

        if( campos > 6.28f ) campos = 0;
        float cx = (float) (distance*Math.cos(campos));
        float cy = (float) (distance*Math.sin(campos));
        cpos.setX(cx);
        cpos.setY(cy);
        kc.setCampos(campos);
        cam.setLocation(cpos);
       
        //cam.lookAt(gpos, cam.getUp());
        cam.lookAt(gpos, Vector3f.UNIT_Z.clone());
        cam.update();
    }

}




keyboardcam


import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;

public class keyboardcam extends InputHandler {

    private float campos = 1.57f;
    private float distance= 50f;
    private Vector3f gpos;
    private Camera cam;
    private kleftaction left;
    //private krightaction right;
   
    public keyboardcam(Vector3f gpos, Camera cam) {
        this.gpos = gpos;
        this.cam = cam;
        KeyBindingManager keyboard = KeyBindingManager.getKeyBindingManager();
        keyboard.set("cleft", KeyInput.KEY_LEFT);
        left = new kleftaction(cam, gpos, this);
        addAction(left, "cleft", true);
       
        /*keyboard.set("cright", KeyInput.KEY_RIGHT);
        right = new krightaction(cam, gpos, this);
        addAction(right, "cright", true);*/
    }

    public float getCampos() {
        return campos;
    }

    public void setCampos(float campos) {
        this.campos = campos;
    }

    public float getDistance() {
        return distance;
    }

    public void setDistance(float distance) {
        this.distance = distance;
    }
}





Test class basetest


import com.jme.app.BaseGame;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.AxisRods;
import com.jme.scene.shape.Quad;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.Timer;

public class basetest extends BaseGame {
   
    protected Timer timer;
    private Camera cam;
    private Node scene;
    private int width,height, depth, freq;
    private boolean fullscreen;
   
    protected InputHandler input;
           

    public static void main(String[] args) {
        basetest app = new basetest();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }

   
    protected void update(float interpolation) {
        timer.update();
        interpolation = timer.getTimePerFrame();
        if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
            finished = true;
        }
        input.update(interpolation);
    }

    protected void render(float interpolation) {
        display.getRenderer().clearBuffers();
        display.getRenderer().draw(scene);
    }

    protected void initSystem() {
        width = properties.getWidth();
        height = properties.getHeight();
        depth = properties.getDepth();
        freq = properties.getFreq();
        fullscreen = properties.getFullscreen();
       
        try {
            display = DisplaySystem.getDisplaySystem(properties.getRenderer());
            display.createWindow(width, height, depth, freq, fullscreen);
            cam = display.getRenderer().createCamera(width, height);
        } catch (JmeException exception) {
            System.out.println("***Error creating display***");
            exception.printStackTrace();
        }
       
        display.getRenderer().setBackgroundColor(ColorRGBA.black.clone());
       
        cam.setFrustumPerspective(45f, (float) width / (float) height, 1, 1000);
        Vector3f loc = new Vector3f(0f, 0f, 50f);
        Vector3f left = new Vector3f(-1f, 0f, 0);
        Vector3f up = new Vector3f(0f, 1f, 0);
        Vector3f dir = new Vector3f(0f, 0f, -1f);
       
        cam.setFrame(loc, left, up, dir);
        cam.update();
       
        timer = Timer.getTimer();
       
        display.getRenderer().setCamera(cam);
       
        input = new keyboardcam(new Vector3f(), cam);
       
        KeyBindingManager.getKeyBindingManager().set("exit", KeyInput.KEY_ESCAPE);

    }

    protected void initGame() {
        scene = new Node("Scene Node");
        Quad q = new Quad("q", 60, 60);
        scene.attachChild(q);
       
        AxisRods ar = new AxisRods("axis", true, 5f);
       
        scene.attachChild(ar);
       
        cam.update();
        scene.updateGeometricState(0f, true);
        scene.updateRenderState();
    }

    protected void reinit() {
        display.recreateWindow(width, width, depth, freq, fullscreen);
    }
   
    protected void quit() {
        super.quit();
        System.exit(0);
    }

    protected void cleanup() {
        //Clean textures if needed
    }

}




This was made for a spatial with center on the origin. If the spatial is somewhere else you need to add the x and y values to the camera coordinates after calculation.

***Added to wiki: http://www.jmonkeyengine.com/wiki/doku.php?id=rotate_camera_around_spatial_using_keyboard