Billboarding/Look at camera

edited May 2016 in How To...

Hi All,

Looking for a way to make a sphere (or anything really) rotate in 3d to look at the camera or another object. It seems so simple, but it's driving me crazy. I've got a pretty close (I think) solution, but I feel like I'm just inputting the wrong values or using the wrong math method.

here's where I'm at:

PVector eye, pos;

void setup() {
  size(800, 800, P3D);
  smooth(8);

  eye = new PVector(0.0f, 0.0f, -1000.0f);
  pos = new PVector(0, 0, 0);
}

void draw() {
  background(0);
  lights();

//  eye.x=0;
//  eye.y=0;
//  eye.z=-1000;
  camera(eye.x, eye.y, eye.z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  translate(pos.x, pos.y, pos.z);
  //PVector rMatrix = faceCamera(eye.get(), pos.get());
  PVector rMatrix = faceCamera(new PVector(mouseX+width/2, mouseY+height/2, -1000), pos.get());//simulate camera movement

  rotateX(rMatrix.x);
  rotateY(rMatrix.y);
  rotateZ(rMatrix.z);

  sphere(200);
}

void keyPressed() {
  if(keyCode == 38) {
    eye.y -=5;
  }
  if(keyCode == 40) {
    eye.y +=5;
  }
  if(keyCode == 37) {
    eye.x -=5;
  }
  if(keyCode == 39) {
    eye.x +=5;
  }
  println(eye);
}

PVector faceCamera(PVector c, PVector p) {
  c.normalize();
  p.normalize();
  float dx = p.x - c.x;
  float dy = p.y - c.y;
  float dz = p.z - c.z;

  PVector ang = new PVector();
  ang.x = atan2(dz, dy);
  ang.y = atan2(dx, dz);
  ang.z = atan2(dy, dx);
  return ang;
}

I'm using the mouse to represent the camera to speed up testing, but in the end, I'll have it always pointed to the camera. I'd like to avoid using a camera library like Proscene just to keep things simple, but I see that there is an example that works for what I'm trying to do.

Any suggestions would be awesome!

thanks! ak

Answers

  • edited October 2014

    I did a similar thing once.

    I rotated a cam around a scene.

    When I wanted to display a text looking towards it I just said

     rotateY (-radians(camCurrentAngle+270));
    

    So I used the camAngle and just rotated the letter back to it.

  • I can't apply this to your code, but just to get you going

  • here

    // States dictate the behaviour of this program 
    final int Help = 0;
    final int Game = 1;
    int state = Help;
    
    // different views for the grid (keys 0 to 6) 
    final int viewsTotal      = 0;  // show all 
    final int viewsNeighbours = 1;  // 26 Neighbours 
    final int viewsPlaneXY    = 2;  // show a plane 
    final int viewsNeighboursInThePlaneXZ = 3;  // 8 Neighbours 
    final int viewsNeighboursInThePlaneXY = 4;
    final int viewsNeighboursInThePlaneYZ = 5;
    final int viewsNeighboursDiagonally   = 6;  // 8 Neighbours 
    
    int view = viewsTotal;
    
    // boxes / grid / letters 
    BoxClass[][][] boxes = new BoxClass[6][6][6];
    
    // cam 
    PVector camPos;     // its vectors 
    PVector camLookAt;
    PVector camUp;
    
    // cam rotation 
    float camCurrentAngle;         // for cam rot around center
    float camRadius = 859;         // same situation 
    
    PFont font1;
    
    PVectorInt selected;  // the selected cell 
    
    String currEntry = "";   
    
    boolean showGrid = true;  // the boxes and letters or only the letters   
    
    // --------------------------------------------------------
    // main funcs 
    
    void setup() {
      size(800, 800, OPENGL);
    
      // set cams vectors 
      camPos    = new PVector(width/2.0, height/2.0, 600);
      camLookAt = new PVector(width/2.0, height/2.0, -300);
      camUp     = new PVector( 0, 1, 0 );
    
      defineBoxes();
      background(111);
      font1 = createFont("Arial", 32);
      textFont(font1); 
      selected = new PVectorInt (3, 3, 0);
    
      camCurrentAngle=90;
      lookAtAngle();
    
      println ("X to reset. F1 Help.");
      //
    } // func 
    
    void draw() {
      background(111);
      switch (state) {
    
      case Help:
        showTheHelp();
        break; 
    
      case Game:
        playTheGame();   
        break;
      } // switch 
      //
    } // func draw()
    
    // ----------------------------------------------------
    
    void showTheHelp () {
      // the help 
      textSize(22);
      int i=80;
      fill(255, 0, 0);
      text ( "The game Boggle", 20, i +=0 );
      fill(255);
      i+=20;
      text ( "F1 - This Help", 20, i +=20 );
      i+=20;
      fill(255, 0, 0);
      text ( "How the game works ", 20, i +=20 );
      fill(255);
      text ( "You can select a cell", 20, i +=20 );
      text ( "With space you add the letter to your word", 20, i +=20 );
      text ( "With Backspace you delete the last letter", 20, i +=20 );
      text ( "With Return you submit your word", 20, i +=20 );
      text ( "The longer the word the more points you get", 20, i +=20 );
      text ( "(not working)", 20, i +=20 );
      text ( "X - new letters ", 20, i +=20 );
      i+=20;
      fill(255, 0, 0);
      text ( "How to select a cell", 20, i +=20 );
      fill(255);
      text ( "use mouse button Left/Right and wheel", 20, i +=20 );
      text ( "use cursor and wasd (w and s for depth).", 20, i +=20 );
      text ( "Four red spheres mark the selected cell,", 20, i +=20 );
      text ( "its outline is thicker.", 20, i +=20 );
      i+=20;
      fill(255, 0, 0);
      text ( "How to change view", 20, i +=20 );
      fill(255);
      text ( "use keyboard L and R (or + and - )", 20, i +=20 );
      text ( "use 0..6", 20, i +=20 );
      text ( "use PAGE UP / DOWN", 20, i +=20 );
      i+=20;
      fill(255, 0, 0);
      text ( "Press any key", 220, i +=50 );
      fill(255);
    }
    
    void playTheGame() {
      // reset some stuff that was set differently by the HUD (see end of draw())
      hint(ENABLE_DEPTH_TEST);
      textSize(32);
    
      camera (camPos.x, camPos.y, camPos.z, 
      camLookAt.x, camLookAt.y, camLookAt.z, 
      camUp.x, camUp.y, camUp.z);
    
      lights();
      showTheGridDependingOnCurrentView();
      strokeWeight(5);          
      boxes[ selected.x][ selected.y][ selected.z].show(true);
      strokeWeight(1);
      showRedSpheres();
    
      // camera
      if (keyPressed&&key=='r')
        camCurrentAngle++;
      if (keyPressed&&key=='l')
        camCurrentAngle--;
      if (keyPressed&&key=='+')
        camCurrentAngle++;
      if (keyPressed&&key=='-')
        camCurrentAngle--;
      lookAtAngle();
    
      // 2D part / HUD  ---------------------------------------------
      camera();
      //  hint(DISABLE_DEPTH_MASK);
      hint(DISABLE_DEPTH_TEST);
      noLights();
      textMode(MODEL);
      //  textMode(SHAPE);
      textSize(22);
      text("selected letter: " + boxes[ selected.x][ selected.y][ selected.z].letter+
        ". Hit space to add. Return to submit. " + "Current: "+ currEntry+".", 
      10, 20 );
      // text(mouseX + "," + mouseY, mouseX+7, mouseY-7);
    }
    
    // ------------------------------------------------------------------
    
    void showTheGridDependingOnCurrentView() {
    
      switch (view) {
    
      case viewsTotal:
        // show boxes : all
        for (int i = 0; i < boxes.length; i++) {
          for (int j = 0; j < boxes[i].length; j++) {
            for (int k = 0; k < boxes[i][j].length; k++) {
              if (selected.x==i&&selected.y==j&&selected.z==k)
                strokeWeight(5); 
              else
                strokeWeight(1); 
              boxes[i][ j][k].show(false);
            }
          }
        }// for
        break;
    
      case viewsNeighbours:
        // show boxes : only the neighbours of the selected cell 
        for (int i = 0; i < boxes.length; i++) {
          for (int j = 0; j < boxes[i].length; j++) {
            for (int k = 0; k < boxes[i][j].length; k++) {
              if (abs (selected.x-i) <= 1 && 
                abs (selected.y-j) <= 1 && 
                abs(selected.z-k) <= 1) {
                if (selected.x==i&&selected.y==j&&selected.z==k)
                  strokeWeight(5); 
                else
                  strokeWeight(1); 
                boxes[i][ j][k].show(false);
              } // if
            }
          }
        }// for
        break;
    
      case viewsPlaneXY:
        // show boxes in the plane of the selected cell (all)
        // for (int i = 0; i < boxes.length; i++) {
        for (int j = 0; j < boxes[1].length; j++) {
          for (int k = 0; k < boxes[0][j].length; k++) {
            if (selected.y==j && selected.z==k)
              strokeWeight(5); 
            else
              strokeWeight(1); 
            boxes[int(selected.x)][ j][k].show(false);
          }
        }
        // }// for
        break;
    
      case viewsNeighboursInThePlaneXZ:
        // show boxes in the plane of the selected cell (neighbours)
        for (int i = 0; i < boxes.length; i++) {
          for (int j = 0; j < boxes[i].length; j++) {
            for (int k = 0; k < boxes[i][j].length; k++) {
              if (abs (selected.x-i) <= 1 && 
                selected.y==j && 
                abs(selected.z-k) <= 1) {
                if (selected.x==i&&selected.y==j&&selected.z==k)
                  strokeWeight(5); 
                else
                  strokeWeight(1); 
                boxes[i][ j][k].show(false);
              }
            }
          }
        }// for
        break;
    
    
      case viewsNeighboursDiagonally:
        // show boxes in the diagonal of the selected cell (neighbours)
        for (int i = 0; i < boxes.length; i++) {
          for (int j = 0; j < boxes[i].length; j++) {
            for (int k = 0; k < boxes[i][j].length; k++) {
              if (abs (selected.x-i) == 1 && 
                abs (selected.y-j) == 1  && 
                abs(selected.z-k) == 1) {
                if (selected.x==i&&selected.y==j&&selected.z==k)
                  strokeWeight(5); 
                else
                  strokeWeight(1); 
                boxes[i][ j][k].show(false);
              }
            }
          }
        }// for
        strokeWeight(5);          
        boxes[ selected.x][ selected.y][ selected.z].show(true);
        strokeWeight(1); 
        break;
    
      case viewsNeighboursInThePlaneXY :
        //
        // show boxes in the plane of the selected cell (neighbours)
        for (int i = 0; i < boxes.length; i++) {
          for (int j = 0; j < boxes[i].length; j++) {
            for (int k = 0; k < boxes[i][j].length; k++) {
              if ( (selected.x==i) && 
                abs(selected.y-j) <= 1 && 
                abs(selected.z-k) <= 1) {
                // 
                if (selected.x==i&&selected.y==j&&selected.z==k)
                  strokeWeight(5); 
                else
                  strokeWeight(1); 
                boxes[i][ j][k].show(false);
              }
            }
          }
        }// for
        break;
    
      case viewsNeighboursInThePlaneYZ :
        // 
        // show boxes in the plane of the selected cell (neighbours)
        for (int i = 0; i < boxes.length; i++) {
          for (int j = 0; j < boxes[i].length; j++) {
            for (int k = 0; k < boxes[i][j].length; k++) {
              if (abs (selected.x-i) <= 1 && 
                abs(selected.y-j) <= 1    && 
                selected.z==k ) {
                //
                if (selected.x==i&&selected.y==j&&selected.z==k)
                  strokeWeight(5); 
                else
                  strokeWeight(1); 
                boxes[i][ j][k].show(false);
              }
            }
          }
        }// for
        break;
    
      default:
        // error 
        break;
      } // switch 
    
      //
    } // func 
    
    void showRedSpheres() {
    
      float x1=  boxes[ 0][ selected.y][ selected.z].x-41;
      float y1=  boxes[ selected.x][ selected.y][ selected.z].y-0;
      float z1=  boxes[ selected.x][ selected.y][ selected.z].z-0;
      sphereParam(x1, y1, z1);
    
      x1=  boxes[ boxes.length-1][ selected.y][ selected.z].x+41;
      y1=  boxes[ selected.x][ selected.y][ selected.z].y-0;
      z1=  boxes[ selected.x][ selected.y][ selected.z].z-0;
      sphereParam(x1, y1, z1);
    
      x1=  boxes[ selected.x][ selected.y][ selected.z].x+0;
      y1=  boxes[ selected.x][ 0][ selected.z].y-44;
      z1=  boxes[ selected.x][ selected.y][ selected.z].z-0;
      sphereParam(x1, y1, z1);
    
      x1=  boxes[ selected.x][ selected.y][ selected.z].x+0;
      y1=  boxes[ selected.x][ boxes.length-1][ selected.z].y+44;
      z1=  boxes[ selected.x][ selected.y][ selected.z].z-0;
      sphereParam(x1, y1, z1);
    } 
    
    // ----------------------------------------------------
    // input funcs
    
    void keyPressed () {
    
      switch (state) {
    
      case Help:
        state = Game; 
        break;
    
      case Game:
        if (!(key==CODED)) {
          // not CODED -----------------------------------
          if (key=='X') {
            // reset  
            defineBoxes();
          }
          else if (key>='0' && key <= '9') {
            view = key-48;
            println ("view: "+view);
          }
          else if (key == ' ') {
            // space key 
            currEntry+=boxes[ selected.x][ selected.y][ selected.z].letter;
            println (currEntry);
          }
          else if (key == RETURN || key == ENTER) {
            // submit
            println ("You submitted "+currEntry);
            currEntry="";
          } 
          else if (key==BACKSPACE) {
            if (currEntry.length()>0) {
              currEntry=currEntry.substring(0, currEntry.length()-1);
            }
          }
          // wasd 
          else if (key=='w') {
            selectedCellGoesAway();
          }
          else if (key=='s') {
            selectedCellGoesTowards();
          }
          else if (key=='a') {
            selected.x--;
            if (selected.x<0)
              selected.x = 0;
          }
          else if (key=='d') {
            selected.x++;
            if (selected.x>=boxes.length)
              selected.x = boxes.length-1;
          }
          else {
            // do nothing
          }
        }
        else {
          // if (key==CODED) { --------------------------------------------
          //
          switch (keyCode) {
    
          case java.awt.event.KeyEvent.VK_F1:
            // help
            state=Help;
            break;
    
          case java.awt.event.KeyEvent.VK_PAGE_UP:
            camRadius--;
            break;
    
          case java.awt.event.KeyEvent.VK_PAGE_DOWN:
            camRadius++;
            break;
    
          case UP:
            selected.y--;
            if (selected.y<0)
              selected.y = 0;
            break;
    
          case DOWN:
            selected.y++;
            if (selected.y>=boxes.length)
              selected.y = boxes.length-1;
            break;
    
          case LEFT:
            selected.x--;
            if (selected.x<0)
              selected.x = 0;
            break;
    
          case RIGHT:
            selected.x++;
            if (selected.x>=boxes.length)
              selected.x = boxes.length-1;
            break;
    
          default:
            // do nothing 
            break;
          } // switch
        } // else
    
        //
        break;
      } // switch 
      //
    } // func 
    
    // ----------------------------------------------------
    // misc funcs
    
    void defineBoxes() {
      // define boxes
      for (int i = 0; i < boxes.length; i++) {
        for (int j = 0; j < boxes[i].length; j++) {
          for (int k = 0; k < boxes[i][j].length; k++) {
            // prepare values 
            color currentCol = color (random(255), random(255), random(255));
            boolean exist = true;
            //        // the percentage 
            //        if (random(100) > 60) 
            //          exist = true;
            //        else
            //          exist = false;  
            // create a box   
            boxes[i][ j][k] = new BoxClass( 158 + i*(height/10), 
            158 + j*(height/10), 
            - k*(height/10), 
            currentCol, 
            exist);
          }
        }
      }
    } // func 
    
    void lookAtAngle() {
      // rotate in the plane : cam 
      // camRadius = camLookAt.dist (camPos); 
      // camRadius = 100;
      camPos.x = camRadius * cos (radians(camCurrentAngle)) + camLookAt.x;
      camPos.z = camRadius * sin (radians(camCurrentAngle)) + camLookAt.z;
    } // func 
    
    // ----------------------------------------
    
    void mousePressed() {
    
      if (mouseButton==LEFT) {
        selected.x--;
        if (selected.x<0)
          selected.x = 0;
      }
      else {
        // RIGHT 
        selected.x++;
        if (selected.x>=boxes.length)
          selected.x = boxes.length-1;
      }
    }
    
    void mouseWheel(MouseEvent event) {
      // mousewheel 
    
      // scroll 
    
      float e = event.getAmount();
      // eval 
      if (e<0) {
        // away 
        selectedCellGoesAway();
      } // if 
      else if (e>0) {
        // towards 
        selectedCellGoesTowards();
      } // else if
      //
    } // func
    
    // ------------------------------------ 
    // Misc 
    
    void selectedCellGoesAway() {
      // away 
      selected.z++;
      if (selected.z>=boxes.length)
        selected.z = boxes.length-1;
    }  // func 
    
    void selectedCellGoesTowards() {
      // towards  
      selected.z--;
      if (selected.z<0)
        selected.z = 0;
    } // func 
    
    void sphereParam(float x1, float y1, float z1) {
      // the red spheres 
      pushMatrix();
      translate(x1, y1, z1);
      noStroke();
      fill(255, 2, 0);
      sphere(16);
      popMatrix();
    }
    
    // =====================================================
    // classes
    
    class BoxClass {
    
      // this class represents one Box / Cell
    
      float x;
      float y;
      float z;
    
      char letter = char ( int ( random (65, 65+24) )) ;  
    
      color col;
      boolean exist = true; 
    
      // constr
      BoxClass(float x_, float y_, float z_, 
      color col_, 
      boolean exist_ ) {
        x = x_;
        y = y_;
        z = z_;
        col = col_;
        exist = exist_;
      } // constr
    
      void show(boolean showBox) {
        // if (exist) {
        if (true) {
    
          pushMatrix();
          translate(x-0, y+0, z);
    
          pushMatrix();
          rotateY (-radians(camCurrentAngle+270));
          noStroke();
          fill(col);
          fill(0);
          text (letter, -12, 12);
          popMatrix();
    
          noFill();
          stroke(col);
          // stroke(255);
          if (showBox||showGrid)      
            box(55);
          popMatrix();
          //
        } // if
      } // method
    } // class 
    
    // ==============================================================
    
    class PVectorInt {
      int x, y, z;
      PVectorInt(int x_, int y_, int z_) {
        x=x_;
        y=y_;
        z=z_;
      }
    }
    // ============================================================
    
  • to try it, use l and r

    ( or + and - )

  • Awesome Chrisir, This looks very promising!

    Ill let you know how it works out!

    Thanks!

  • Hmm, it's close, but it looks like it's more of an orthographic camera solution. It always looks forward, which is good, but it isn't always looking straight at the camera. Here's how I implemented it:

    // cam 
    PVector camPos;     // its vectors 
    PVector camLookAt;
    PVector camUp;
    
    // cam rotation 
    float camCurrentAngle;         // for cam rot around center
    float camRadius = 409;         // same situation 
    
    void setup() {
      size(800, 800, P3D);
      smooth(8);
    
      // set cams vectors 
      camPos    = new PVector(width/2.0, height/2.0, 600);
      camLookAt = new PVector(width/2.0, height/2.0, -300);
      camUp     = new PVector( 0, 1, 0 );
    
      camCurrentAngle=90;
      lookAtAngle();
    }
    
    void draw() {
      background(0);
      lights();
    
      camera (camPos.x, camPos.y, camPos.z, 
      camLookAt.x, camLookAt.y, camLookAt.z, 
      camUp.x, camUp.y, camUp.z);
    
    
    //  rotateY ( -radians(90 + 270) );
    //  rotateX(radians(-90));
    translate(width/2, height/2, -400);
          rotateY (-radians(camCurrentAngle+270));
    
      sphere(200);
    
      // camera
      if (keyPressed&&key=='r')
        camCurrentAngle++;
      if (keyPressed&&key=='l')
        camCurrentAngle--;
      if (keyPressed&&key=='+')
        camCurrentAngle++;
      if (keyPressed&&key=='-')
        camCurrentAngle--;
      lookAtAngle();
    
    }
    
    void lookAtAngle() {
      // rotate in the plane : cam 
      // camRadius = camLookAt.dist (camPos); 
      // camRadius = 100;
      camPos.x = camRadius * cos (radians(camCurrentAngle)) + camLookAt.x;
      camPos.z = camRadius * sin (radians(camCurrentAngle)) + camLookAt.z;
    } // func 
    

    I think I can adapt a formula I used for a different project to solve from this though. Ill post my results.

    Thanks! ak

Sign In or Register to comment.