Rotate a camera around Z axis

edited April 2018 in How To...

Hi everybody !

I'm having big problems to understand the maths behind camera rotation for short animations of terrain or planet flight by (like a kind of spatial vessel)

Following Daniel Shifman's lessons about vector I use vectors for position, velocity, acceleration and lookAt (and later implement applyForce to velocity to create some nice camera movements) I use the Processing camera() method

It's very basic but it works, except concerning rotations. I want my camera to rotate on Z axis and I really don't know how to do. It's not as simple as calling rotateZ(rad_angle)

I don't rotate on X or Y axis because I presume I should be able to achieve rotations by computing a new lookAt vector correctly.

Is the solution to play_with/compute/tweak the up vector ? And how ? I'm totally at loss...

Note : I've digged into quesycam, peasycam, OCD but that not helped me that much - and frankly I really want to code my own Camera class from a learning point of view.

Note 2 : sorry if this post is not very clear, that's very hard to formulate questions about a confusing subject ;-)

Tagged:

Answers

  • post your entire code

  • edited April 2018

    OK before we come to "Rotate a camera around Z axis":

    here is a standard version where the camera position rotates in the X/Z plane around a fixed center (a Lamp post). Camera Y position (its height) is fixed.

    The camera moves on a circle, calculated with cos and sin.

    It looks as if the bar (Lamp post) rotates, but in fact only the camera position rotates.

    The bar is fixed and the camera look at (Center) is fixed.

    The camera position itself moves. UP vector is also fixed.

    Forget the lamp post just look at function CheckCameraSinusLevel().

    https://www.processing.org/reference/camera_.html

    // Lamppost
    int lampPost_XValue = 250; // nach rechts
    int lampPost_YValue = 330; // nach unten
    int lampPost_ZValue = -150; // nach hinten
    
    String actualTextMessage = "Use 0,1,2,3 for Camera";
    String actualTextMessage2 = "Camera circles at constant height";
    
    // ==================================================
    // Kamerasteuerung:
    
    float Angle = 0.0; // Angle 
    float Height = 130;    // Height
    float Radius = 210.0;  // Radius
    
    // Camera
    int LookAtX;
    int LookAtY;
    int LookAtZ;
    
    // ==========================================================
    
    void setup() {
      size(600, 600, P3D);
    
      // LookAt
      LookAtX=lampPost_XValue;
      LookAtY= lampPost_YValue;
      LookAtZ= lampPost_ZValue;
    } // function
    
    void draw() {
      background(153);
      lights();
    
      // the scene 
      CheckCameraSinusLevel();
      fill(242);
      stroke(22);
      PaintLamppost();
      Angle=Angle+1.0;
    
      // HUD to display text 
      camera();
      noLights();
      hint(DISABLE_DEPTH_TEST); 
      text ("Fixed Look At", 30, 30);
    }
    
    // =====================================================================
    
    void PaintLamppost () {
    
      pushMatrix();
      int myHeight = 220;
      fill(190, 3, 3);
      translate(lampPost_XValue-0, 
        lampPost_YValue+(myHeight / 2), 
        lampPost_ZValue);
      box(11, myHeight, 11);
      popMatrix();
    }
    
    void CheckCameraSinusLevel () {
    
      // camera rotates in height "Height"
      // position of camera is on a circle calculated with cos and sin
      // look to scene (center/lampPost)
      camera (
        Radius*sin (radians(Angle)) + lampPost_XValue, Height, Radius* cos (radians(Angle)) + lampPost_ZValue, 
        lampPost_XValue, lampPost_YValue, lampPost_ZValue, 
        0.0, 1.0, 0.0);
    }
    //
    
  • Based on this very simple code here comes

    "Rotate a camera around Z axis".

    The lamp post has not changed, it does not rotate.

    Only camera position moves.

    Rotating again but with a fixed Z and rotating in the X/Y plane. I think.

    Since when the camera is directly above and directly under the Lamp post the camera would flip up side down we have to compensate this by saying up = up * -1; or something similar.

    Forget the lamp post just look at function CheckCameraSinusLevel().

    import shapes3d.utils.*;
    import shapes3d.animation.*;
    import shapes3d.*;
    
    
    
    //import shapes3d.utils.*;
    //import shapes3d.org.apache.commons.math.util.*;
    //import shapes3d.org.apache.commons.math.*;
    //import shapes3d.org.apache.commons.math.geometry.*;
    //import shapes3d.*;
    
    // =========================================================================
    // Lamppost
    int ZentraleStange_XValue = 250; // nach rechts
    int ZentraleStange_YValue = 330; // nach unten
    int ZentraleStange_ZValue = -150; // nach hinten
    // Lamppost
    int TiefeQuerbalken = 122;
    // hanging basket
    int HoeheEinesBlumenkastens = 20;
    int TiefeEinesBlumenkastens = 122;
    int BreiteEinesBlumenkastens = 50;
    //  hanging basket
    Cone cone;
    Ellipsoid [] ellipsoid = new Ellipsoid [4];
    String actualTextMessage = "Use 0,1,2,3 for Camera";
    String actualTextMessage2 = "Camera circles at constant height";
    // ==================================================================
    // Kamerasteuerung:
    //final int CheckCamera_Type_SinusLevel = 0;    // rotates
    final int CheckCamera_Type_SinusSpirale = 1;  // Spirale
    //final int CheckCamera_Type_SinusMouse = 2;    // Mouse
    //final int CheckCamera_Type_SinusPath = 3;     // Path
    float Angle = 0.0; // Angle bei Kreis
    float Height = 330;    // Höhe der Kamera über der Szene
    int HeightAdd = 2;   // wird bei Spirale zu der Höhe der Kamera addiert
    float Radius = 340.0;  // Anfangsradius des Kreises
    // Art des Kameraverhaltens
    int CheckCamera_Type = CheckCamera_Type_SinusSpirale;
    // Camera
    int LookAtX;
    int LookAtY;
    int LookAtZ;
    // Camera with Path Behaviour (CheckCamera_Type = 3)
    boolean boolGoesOnAPath = true;
    float PathValue;
    float PathValueMaximal = 95.0;
    
    float up=1; 
    
    // ==========================================================
    void setup() {
      size(600, 600, P3D);
      // Grass
      //  initGrass();
      // LookAt
      LookAtX=ZentraleStange_XValue;
      LookAtY= ZentraleStange_YValue;
      LookAtZ= ZentraleStange_ZValue;
      // hanging baskets
      for (int i = 0; i < ellipsoid.length; i = i+1) {
        ellipsoid[i] = new Ellipsoid(this, 40, 40);
        ellipsoid[i].drawMode(Shape3D.SOLID);
        ellipsoid[i].setRadius(12, 7, 12);
        ellipsoid[i].moveTo(ZentraleStange_XValue, ZentraleStange_YValue+40, ZentraleStange_ZValue-(TiefeQuerbalken/2)+5);
      }
      ellipsoid[0].moveTo(ZentraleStange_XValue, ZentraleStange_YValue+40, ZentraleStange_ZValue-(TiefeQuerbalken/2)-1);
      ellipsoid[1].moveTo(ZentraleStange_XValue, ZentraleStange_YValue+40, ZentraleStange_ZValue+(TiefeQuerbalken/2)-1);
      ellipsoid[2].moveTo(ZentraleStange_XValue-(TiefeQuerbalken/2)+0, ZentraleStange_YValue+40, ZentraleStange_ZValue);  
      ellipsoid[3].moveTo(ZentraleStange_XValue+(TiefeQuerbalken/2)+0, ZentraleStange_YValue+40, ZentraleStange_ZValue);
    } // function
    
    void draw() {
      background(153);
      textMode(SCREEN);
      fill(242);
      //text(actualTextMessage + " (now: " + actualTextMessage2 + ").", 10, 30);
      //CheckCamera();
      CheckCameraSinusLevel();
      //  Grass();
      stroke(22);
      PaintLamppost();
      Angle=Angle+1.0;
      if (boolGoesOnAPath) {
        PathValue=PathValue+1;
      }
      PaintHangingBasket();
      camera();
      hint(DISABLE_DEPTH_TEST); 
      text ("Angle "
        +Angle, 
        30, 30);
    }
    // =====================================================================
    void PaintHangingBasket () {
      // Schleife!
      for (int i = 0; i < ellipsoid.length; i = i+1) {
        stroke(22);
        // Blumenkasten
        switch(i) {
    
        case 0: 
          ellipsoid[i].fill(color(255, 2, 2));
          break; 
    
        case 1: 
          ellipsoid[i].fill(color(0, 255, 2));
          break; 
    
        case 2: 
          ellipsoid[i].fill(color(255, 2, 255));
          break; 
    
        case 3: 
          ellipsoid[i].fill(color(2, 2, 255));
          break;
        }//switch 
    
        ellipsoid[i].draw();
        stroke(22);
        // je zwei Drähte
        line (ellipsoid[i].x()-10, ellipsoid[i].y(), ellipsoid[i].z(), 
          ellipsoid[i].x(), ellipsoid[i].y()-43, ellipsoid[i].z());
        line (ellipsoid[i].x()+10, ellipsoid[i].y(), ellipsoid[i].z(), 
          ellipsoid[i].x(), ellipsoid[i].y()-43, ellipsoid[i].z());
        // je drei Blumen
        pushMatrix();
        translate (ellipsoid[i].x(), ellipsoid[i].y()-17, ellipsoid[i].z());
        scale(.2);
        rotateY(.7);
        //Flower(1, 1, +200, 17, 3);
        popMatrix();
        pushMatrix();
        translate (ellipsoid[i].x()-2, ellipsoid[i].y()-17, ellipsoid[i].z()-2);
        scale(.25);
        rotateY(2*PI-.2);  
        //Flower(1, 1, ellipsoid[i].x()-22, ellipsoid[i].y()+117, ellipsoid[i].z()-21);
        popMatrix();
        pushMatrix();
        translate (ellipsoid[i].x()+2.2, ellipsoid[i].y()-17, ellipsoid[i].z()-2.3);
        rotateY(1.32);  
        scale(.23);
        //Flower(1, 1, +2, 7, 221);
        popMatrix();
      } // for
    } // SR
    // =====================================================================
    void PaintLamppost () {
      // http://en.wikipedia.org/wiki/Street_light
      // central lamppost
      pushMatrix();
      int Hoehe = 220;
      fill(190, 3, 3);
      translate(ZentraleStange_XValue-0, 
        ZentraleStange_YValue+(Hoehe / 2), 
        ZentraleStange_ZValue-0);
      rotateY(0.0);
      box(11, Hoehe, 11);
      popMatrix();
      // Querbalken 1
      pushMatrix();
      fill(9, 3, 233);
      translate(ZentraleStange_XValue, 
        ZentraleStange_YValue, 
        ZentraleStange_ZValue);
      rotateY(0.0);
      box(11, 11, TiefeQuerbalken);
      popMatrix();
      // Querbalken 2
      pushMatrix();
      fill(9, 9, 223);
      translate(ZentraleStange_XValue, 
        ZentraleStange_YValue, 
        ZentraleStange_ZValue);
      rotateY(radians(90.0));
      box(11, 11, TiefeQuerbalken);
      popMatrix();
    }
    void CheckCameraSinusLevel () {
      // look to scene (center)
      if (Angle>=360) {
        Angle=0;
      }
    
      if (Angle % 180 == 0) {
        if (up==1.0) 
          up=-1; 
        else up=1;
      }
      camera (
        Radius*sin (radians(Angle)) + ZentraleStange_XValue, Radius* cos (radians(Angle)) + ZentraleStange_XValue, ZentraleStange_ZValue, 
        ZentraleStange_XValue, ZentraleStange_YValue, ZentraleStange_ZValue, 
        0.0, up, 0.0);
    }//func
    //
    
  • edited April 2018

    to explain the cos and sin formula here is a circle example.

    Where the ellipses are shown on the circle circumference, the camera is placed in the examples above!

    If you don't understand this 2D sketch you will have difficulties to understand the 3D sketches above...

    (maybe I swapped cos and sin above, not sure what this means though)

    float Angle;
    
    void setup() {
      size(600, 600);
    }
    
    void draw() {
      // background(153);
    
      float Radius = 200;
    
      float x =  Radius* cos (radians(Angle)) + width/2;
      float y =  Radius* sin (radians(Angle)) + height/2;
    
      ellipse(x, y, 7, 7);
    
      Angle += 5;
    }
    
  • maybe I swapped cos and sin above, not sure what this means though

    This just means a phase change of a multiple of 90 degrees. Check trig identities here for instance: http://www2.clarku.edu/~djoyce/trig/identities.html. As a demonstration consider:

    sin(a+b)=sin a x cos b + cos a x sin b where x denotes multiplication. If we assigned b=90 deg then the above reduces to:

    sin (a+90)=sin a x cos(90) + cos a * sin(90)

    Since sin(90) is 1 and cos(90) is 0, then we have

    sin(a+90) = cos(a)

    So switching your sin and cos just means you are introducing a phase shift. For a coder, it means that the reference for angles is not the standard observed in a Cartesian plane. For instance, using the code above:

      float x =  Radius* cos (radians(Angle)) + width/2;
      float y =  Radius* sin (radians(Angle)) + height/2;
    

    Angle zero degrees is with respect to (wrt) the positive X axis. Increasing the angle means your angle increases CCW as in a proper Cartiesian coordinate system. However, as you know in Processing, the Y axis is inverted so the angle increases CW instead.

    If you switch the sin and cos terms above, your zero degree reference is wrt the positive Y axis (Positive Y axis points down). The angle increases CCW.

    So, in summary, you want to make sure you are defining your functions based on your provided coordinate system.

    Kf

  • Paganini, are you talking about z axis as into / out of the screen, like roll?

    That would require changing the up vector. The usual is using the y axis (0, 1, 0) but you can replace that with (sin a, cos a, 0) where a is the angle of rotation.

  • edited April 2018

    Thanks for you answers, I think I can formulate what I want to do in a simpler and better way :

    1) I want to simulate a simplified plane flight - Me being inside the plane, pilot view

    2) As shown here I need pan, tilt, roll : http://www.conitec.net/beta/aentity-pan.htm

    3) koogs can you confirm that panning, tilting and rolling the camera is all about applying transformations to the UP vector ?

    3) BUT what confuse me is that I don't want only to pan, tilt and roll the camera but also the trajectory of the plane. I mean if I only tilt I would "see" more sky but the trajectory of the plane will be still at the same altitude - I want to still look straight in front of me but having the plane going up on the slope.

    4) The movements of the plane must combine, it could roll and tilt and turn left or right at the same time.

    5) So, is it a UP vector AND velocity vector update question ?

    6) If yes, what are the easier solutions to apply these transformations ? I know nothing about quaternions and transformation matrices. Any library I could use ? I search the Internet for hours but found no code to borrow, or that I could understand

    7) Should I watch again and again https://youtube.com/watch?v=qMq-zd6hguc ? Is the answer there ? Applying angular motion on every axis ?

    All of this is not for coding a realistic plane simulation but only small sequences of fake flights over landscapes, or in space that will be put together later in a video editing software (there is not graphic representation of the cookpit - the camera is at the nose of the plane)

    My life would be easier if I wasn't so bad at maths ;-)

  • Do you have any code on this? A landscape?

  • koogs can you confirm that panning, tilting and rolling the camera is all about applying transformations to the UP vector

    No, just rolling is, and that's all you mentioned in the original question. (Chrisir, please note)

    Panning a camera would involve moving the camera position and the look at by equal amounts.

    But a plane is more about roll, pitch and yaw... Using gamestudio jargon is probably a bad idea given that you aren't using gamestudio, it'll just confuse people.

  • I don't want only to pan, tilt and roll the camera but also the trajectory of the plane. I mean if I only tilt I would "see" more sky but the trajectory of the plane will be still at the same altitude - I want to still look straight in front of me but having the plane going up on the slope.

    Then this should've been your original question.

  • Answer ✓

    Quick demo

    plane doesn't really fly up and down but only forward left / right

    use wasd plus mouse

    // https : // forum.processing.org/two/discussion/24912/radar-view-of-infinite-3d-space#latest
    // https : // www.openprocessing.org/sketch/25255
    
    // all spheres 
    ArrayList<Sphere3D> Sphere3Ds = new ArrayList();
    
    // camera / where you are 
    float xpos, ypos, zpos, 
      xlookat, ylookat, zlookat, 
      xCamOwnAngle=0, yCamOwnAngle=1.0, zCamOwnAngle=0;
    
    float angle=0.0; // (angle left / right; 0..359)
    
    // "Floor" has y-value (the plane you fly in (y-direction)) 
    final float floorLevel = 500.0;
    
    // ---------------------------------------------------------------
    
    void setup() {
      size(1900, 990, P3D);
    
      for ( int i = 0; i < 112; i++ ) {
        Sphere3Ds.add( new Sphere3D() );
      }
    }//func 
    
    void draw() {
    
      background(0);
    
      // draw Main Scene in 3D
      drawMainSceneIn3D(); 
    
      // read key throughout 
      keyPressedIsCheckedContinuusly();
    
      // HUD https : // en.wikipedia.org/wiki/Head-up_display ------------- 
      camera();
      noLights();
      hint(DISABLE_DEPTH_TEST);  
      fill(255);
      text("wasd to move, mouse to look left/right (or combine mouse and keyboard)", 
        18, 18);
    }//func 
    
    //---------------------------------------------------------------
    
    void drawMainSceneIn3D() {
      // draw Main Scene in 3D (main screen)
    
      hint(ENABLE_DEPTH_TEST);
    
    
      pushMatrix();
      CheckCameraMouse();
      lights();
      noFill();
      stroke(255);
      gridOnFloor();
      for ( Sphere3D Sphere3D : Sphere3Ds ) { 
        Sphere3D.simulate(); 
        Sphere3D.draw3D();
      }
      popMatrix();
    }//func
    
    // --------------------------------------------------------------------------
    // Inputs 
    
    void keyPressedIsCheckedContinuusly() {
    
      float Radius = 13; 
    
      if (keyPressed) {
    
        // ----------------------------    
        // forward & backward
        if (key == 'w' || key == 'W') {
          // forward : should be running towards lookat 
          xpos =   Radius*sin(radians(angle)) + xpos;
          zpos =   Radius*cos(radians(angle)) + zpos;
        }
        if (key == 's' || key == 'S') {
          // backward
          xpos =  xpos- (Radius*sin(radians(angle))) ;
          zpos =  zpos- (Radius*cos(radians(angle))) ;
        }
        // ----------------------------    
        // left & right 
        if (key == 'a' || key == 'A') {
          // left
          xpos =   xpos- Radius*sin(radians(angle-90)) ;
          zpos =   zpos- Radius*cos(radians(angle-90)) ;
        }
        if (key == 'D' || key == 'd') {
          // right 
          xpos =   Radius*sin(radians(angle-90)) + xpos;
          zpos =   Radius*cos(radians(angle-90)) + zpos;
        } 
        // ----------------------------    
        // fly up & down 
        if (key == 'r' || key == 'R') {
          ypos-=4;  // fly up
        }
        if (key == 'f' || key == 'F') {
          ypos+=4;  // down 
          if (ypos > floorLevel-120) {  // check Floor
            ypos = floorLevel-120;
          }
        }     
        // ----------------------------    
        // fly up & down FAST 
        if (key == 'b' || key == 'B') {
          // Bird
          ypos-=400;
        }    
        if (key == 'n' || key == 'N') {
          // un-Bird: go deeper 
          ypos+=400; 
          if (ypos > floorLevel-120) { // check Floor
            ypos = floorLevel-120;
          }
        }        
        // ----------------------------        
        if (key == 'i' || key == 'I') {
          // Saves a TIFF file named "diagonal.tif"
          save("Runner1.tif");
        }
        //
      } // if(keyPressed)
    }
    
    // --------------------------------------------------------------------------------
    // Tools 
    
    void CheckCameraMouse () {
    
      // Mouse  
    
      float Radius = 450.0;  // radius 
    
      float rmx=mouseX; 
      float rmy=mouseY;
    
      // command map: See Help. 
      angle = map(rmx, width, 0, 0, 359); // left right 
    
      // look at 
      xlookat = Radius*sin(radians(angle)) + xpos;
      ylookat = map(rmy, 0, height, -270, 333); // look up / down // OR just 300 as fix value 
      zlookat = Radius*cos(radians(angle)) + zpos; 
    
      if (rmx<width/2) 
        yCamOwnAngle= map(rmx, 0, width, 0, 1); 
      yCamOwnAngle=.3;
      zCamOwnAngle=.3;
    
      camera (xpos, ypos, zpos, 
        xlookat, ylookat, zlookat, 
        xCamOwnAngle, yCamOwnAngle, zCamOwnAngle
        );
    }
    
    void gridOnFloor() {
    
      // pushMatrix();
    
      noFill();
    
      // add
      int d = int ( 24000 / 200);
    
      // diameter
      int d1 = d;
    
      float floorLevel1 = 2* floorLevel; 
    
      // color
      stroke(111);  // gridColor);
      // rectMode(CENTER);
      strokeWeight(1);
    
      for (int x = -12000; x <= 12000; x += d) {
        for (int y = -12000; y <= 12000; y += d) {
          //rect(x, y, d1, d1);
          line(x, floorLevel1, y, 
            x+d1, floorLevel1, y); 
          line(x, floorLevel1, y, 
            x, floorLevel1, y+d1); 
          line(x, floorLevel1, y+d1, 
            x+d1, floorLevel1, y+d1);
        }
      }
    
      // rectMode(CORNER);
      //popMatrix();
    }
    
    
    //=============================================================
    
    class Sphere3D {
    
      // class for a 3D sphere
    
      PVector p; //position 
      color c;   //color
    
      // pulsating effect (OFF)
      float size, 
        primSize, 
        add=0;       // 0 = no pulsating (OFF)
    
      // constr 
      Sphere3D() {
    
        //position 
        p = new PVector( random(-12180, 1180), random(-1180, 1180), random(-12180, 1180) );
        //color
        c = color( random(255), random(255), random(255) );
        size     = random(14, 21);
        primSize = size;
      }// constr 
    
      void simulate() {
        // move it or whatever?
        size+=add;
        if (size>primSize)
          add=-1; 
        else if (size<12)
          add=1;
      }
    
      void draw3D() {  
        // draw main scene in 3D
        pushMatrix();
        translate(p.x, p.y, p.z);
        noStroke();
        fill(c);
        sphere(size);
        popMatrix();
      }
      //
    }//class 
    //
    
  • Then this should've been your original question.

    I'm learning as i go. If I had the capacity to ask the "right" questions, I would find the answers by myself.

  • Chrisir I ran your code that seems a very good base to understand camera movements... Thanks a lot !

Sign In or Register to comment.