Howdy, Stranger!

We are about to switch to a new forum software. Until then we have removed the registration on this forum.

Billboarding/Look at camera

edited May 2016

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

Tagged:

• 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

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_TEST);
noLights();
textMode(MODEL);
//  textMode(SHAPE);
textSize(22);
text("selected letter: " + boxes[ selected.x][ selected.y][ selected.z].letter+
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:
break;

case java.awt.event.KeyEvent.VK_PAGE_DOWN:
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
} // 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();
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) );
translate(width/2, height/2, -400);

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