Not understanding camera rotation

Hey everyone:

I am having trouble understanding how to rotate the camera.

I wrote this test code:

package jme3;

import com.jme3.app.SimpleApplication;
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.Mesh;
import com.jme3.scene.debug.Arrow;

public class CameraTest extends SimpleApplication {
  @Override
  public void simpleInitApp() {
    Mesh m = new Arrow(Vector3f.UNIT_X);
    Geometry geom = new Geometry("X",m);
    geom.scale(20);
    Material mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Red);
    geom.setMaterial(mat);
    rootNode.attachChild(geom);
    m = new Arrow(Vector3f.UNIT_Y);
    geom = new Geometry("Y",m);
    geom.scale(20);
    mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Green);
    geom.setMaterial(mat);
    rootNode.attachChild(geom);
    m = new Arrow(Vector3f.UNIT_Z);
    geom = new Geometry("Z",m);
    geom.scale(20);
    mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Blue);
    geom.setMaterial(mat);
    rootNode.attachChild(geom);
    
    cam.setLocation(new Vector3f(-10,-10,50));
    cam.setRotation(new Quaternion().fromAngleAxis(1*FastMath.DEG_TO_RAD,Vector3f.UNIT_X));
  }
  
  public static void main(String[] args) {
    CameraTest app = new CameraTest();
    app.start();
  }
}

If I don’t call cam.setRotation, I see this screen:


which makes sense to me.

If I call cam.setRotation, the screen becomes completely black. It should have only rotated 1 degree which should have been a very small change.

I must be misunderstanding something. I have searched the forum and read the pages about Quaternion, but it is not making sense to me.

I think the issue is you’re assuming the cameras default rotation is zero degrees, but it isn’t, if I add this to the application (before you change the camera)

    Vector3f axis = new Vector3f();
    float angle = getCamera().getRotation().toAngleAxis(axis);

    System.out.println( angle + " about " + axis);

Then that prints out 3.1415927 about (0.0, 1.0, 0.0). So the default rotation is Pi radians (aka 180 degrees) about the Y axis. So you are applying a very large relative rotation by moving it to 1 degree about the X axis.

1 Like

btw. You dont really “rotate”, but “set rotation”. so if camera had for example 180 degree, and suddenly you setup 1 degree, ofc it will move much.

1 Like

So, what you guys are saying is setting the camera rotation sets it absolute where I was making the assumption that it was relative to the current position. I was also making the assumption about zero initial rotation. Thank you for clarifying those for me!

I tried to do a relative change using this code:

    cam.setLocation(new Vector3f(-10,-10,50));
    Quaternion x = new Quaternion().fromAngleAxis(1*FastMath.DEG_TO_RAD,Vector3f.UNIT_X);
    cam.setRotation(cam.getRotation().mult(x));

And I see the axes again. Thank you!

1 Like

this code still looks bad if you use in same place as before.

“simpleInitApp()” is one time use to init app.

and you probably want to use “update()” one. or one that depend on mouse/keyboard input.

there are much more things to say, but you Never told what you want to achieve in general. IMO you do it totally wrong way. Try first read all wiki i assume. Or just try first tell us what you need to create.

Glad to be of help!

Be careful combining rotations, the order matters! Sometimes A.mult(B) and B.mult(A) end up the same, but not always.

A.mult(B) means: first rotate by B then by A¹. It’s likely what you wanted was x.mult(cam.getRotation())

¹ Assuming A and B are expressed in rotations in the global coordinate system. Alternatively you can think of A.mult(B) as being first rotate by A, then rotate by B where B is expressed in the rotated coordinate system, but that is harder to get your head around and I’m already regretting mentioning it

No, if you want to rotate one degree from the current rotation then it’s
currentRotation.mult(oneDegreeRotation).

The convention of A.mult(B) versus B.mult(A) is scene graph specific. In JME, it’s parent.mult(child) = world

So this:

…is the correct solution for pitching the camera one degree from current rotation.

Edit: to add quotation because even your own post seems contradictory… which may mean you are building assumptions into the phrase “rotate first”.

If I have a ball sitting on my desk and I want to rotate it 45 degrees left/right (yaw) and then 45 degrees up/down (pitch), in JME that would be:
yaw.mult(pitch)

I think you are talking in rotated coordinate systems, not the world coordinate system. Words like pitch and yaw imply local coordinates (Which is fine, but highlights my point about “be careful”)

Take this example application

public class RotationTest extends SimpleApplication{
    
    public static void main(String[] args){
        RotationTest app = new RotationTest();
        app.start(); // start the game
    }

    @Override
    public void simpleInitApp(){
        cam.setLocation(new Vector3f(0,0,0));
        box(ColorRGBA.Red, new Vector3f(-10, 0,0));
        box(ColorRGBA.Green, new Vector3f(10, 0,0));
        box(ColorRGBA.Yellow, new Vector3f(0, 0,10));
        box(ColorRGBA.Pink, new Vector3f(0, 0,-10));

        box(ColorRGBA.White, new Vector3f(0, 10, 0));

        //we start looking at the pink cube, with the white cube above us, and the red to the left and the green to the right

        //then the whole camera is rotated about its own look axis (the Z axis)
        Quaternion rotateToTurnHeadSideways = new Quaternion().fromAngleAxis(0.5f*FastMath.PI,Vector3f.UNIT_Z);
        cam.setRotation(rotateToTurnHeadSideways.mult(cam.getRotation()));

        //We are still looking at the pink cube, but the red cube is "local up"

        //we now want to look at the red cube. That is in the world coordinate system that remain a half PI turn about the Y axis
        Quaternion rotateToLookLeft = new Quaternion().fromAngleAxis(0.5f*FastMath.PI,Vector3f.UNIT_Y);
        cam.setRotation(rotateToLookLeft.mult(cam.getRotation()));
    }

    public void box(ColorRGBA colour, Vector3f position) {
        Box b = new Box(1, 3, 1);
        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", colour);
        geom.setMaterial(mat);
        geom.setLocalTranslation(position);
        rootNode.attachChild(geom);
    }
}

With the boxes set up like this (and the camera at zero, looking in the -Z direction)

    box(ColorRGBA.Red, new Vector3f(-10, 0,0));
    box(ColorRGBA.Green, new Vector3f(10, 0,0));
    box(ColorRGBA.Yellow, new Vector3f(0, 0,10));
    box(ColorRGBA.Pink, new Vector3f(0, 0,-10));

    box(ColorRGBA.White, new Vector3f(0, 10, 0));
    box(ColorRGBA.Gray, new Vector3f(0, -10,0));

You start looking at the pink cube

Then you make a 90 degree rotation about the world Z axis, you should still be looking at the pink cube, because you are looking along the Z axis

    Quaternion rotateToTurnHeadSideways = new Quaternion().fromAngleAxis(0.5f*FastMath.PI,Vector3f.UNIT_Z);
    cam.setRotation(rotateToTurnHeadSideways.mult(cam.getRotation()));

And indeed thats what happens

But now you are looking at it with your head rotated so that the red cube is seems to look like its above you (But thats only because you are holding your head weird).

Now say you want to look at the red box, that’s easy, you’re still looking the same direction you were to begin with, so its a 90 degree turn about the global Y axis

    Quaternion rotateToLookLeft = new Quaternion().fromAngleAxis(0.5f*FastMath.PI,Vector3f.UNIT_Y);
    cam.setRotation(rotateToLookLeft.mult(cam.getRotation()));

But if instead you make a rotation along the LOCAL Y axis ( cam.setRotation(cam.mult(rotateToLookLeft) )), you would end up looking at the grey cube, since that is to the left in the rotated coordinate system.

Overall

So I stick with my position that if your current rotation is A and you want to rotated by B where B is expressed in the global coordinate system then it’s

B.mult(A)

But if your current rotation is A and you want to rotated by B where B is expressed in the rotated coordinate system then it’s

A.mult(B)

The different between “expressed in the rotated coordinate system” and “expressed in the global coordinate system” being when you say “Rotated by 90 degrees about the X axis” what do you mean by the X axis.

All of which is overcomplicated, all I wanted to say was “be careful!”, order matters, and think about what coordinate system you are expressing things in

This is the best way to think in a scene graph… and especially from a camera. “I’m looking this way now I want to rotate a little to the right…” Always local to whatever rotations have happened already.

The other thinking will get folks in trouble. Quaternions are always “rotate relative to something” and that’s the best way to think of them.

But yes, be careful. (And note that in some scene graphs the Q1.mult(Q2) convention is reversed.)

I am not trying to write a game. I am using the engine to render an image to disk. My eventual goal is to end up with images like this:
3DMathPuzzle-Logo

I am using the init method to place all my objects, call ScreenshotAppState to take a screenshot, and then exiting the program.

then all you need is proper camera position and camera method like “lookAt()” at your text/model with Y as up

2 Likes

If you all are interested in the bigger “Why?” question, please take a look at the web site I am creating:
http://3dmathpuzzles.com