uhm... follow camera / camera orbiting an object

Started by hasta84, June 15, 2008, 05:44:59

Previous topic - Next topic

hasta84

Hi folks,

I just started 3D programming (think you heard that from others before...), I began with J3D and got good results and I am understanding the projection of a 3D space to the flat screen and so on...
After all that J3D I read about lwjgl an started exploring. So far so good. I really began to like lwjgl an the direct access to openGL.
But what I am trying to do (for DAYS now! ;)) is to rotate the "camera" (view) around an object to simulate a following camera view.
That means in game context, I have a player model in the middle of the screen and I can move around the "player model" with moving the mouse having a button pressed. Phew... didn't think it would take that much...
I fully understand the input system (and worked with java before).
My approach was to solve my "problem" with the gluLookAt. Works pretty fine unless I try to rotate.
So here is what I am trying to do:
I have the "player position" in space as p.x, p.y, p.z and I have the "cam position" as cam.x, cam.y, cam.z

I've tried tenth of different approaches and workarounds to get the correct new camera position coordinates, if I rotate the "player",
so I really don't know wich one to post.

Can anyone help me with this? Would very very much appreciate...



P.S: If my english is not that good, please forgive me... I am a german ;)


hasta84

Ah ja, here is what I got the best results from, but it doesen't really work like it should. If I try to rotate about the y-axis by just adjusting the x and z coords, the "camera" goes nearer the sourrounded object and finally hits it.
This is my Quaternion class:

public class Quaternion {
  public double x, y, z, w;
  
  public Quaternion(){}
  
  public Quaternion(double w, Point3f p){
    this.w = w;
    x = p.x;
    y = p.y;
    z = p.z;
  }
  
  public double length(){
    return Math.sqrt(x * x + y * y +
              z * z + w * w);
  }
  
  public void normalize(){
    double L = this.length();
    x /= L;
    y /= L;
    z /= L;
    w /= L;
  }
  
  public void conjungate(){
    x = -x;
    y = -y;
    z = -z;
    w = -w;
  }
  
  public void mult(Quaternion B)
  {
    Quaternion A = this;
    x = A.w*B.x + A.x*B.w + A.y*B.z - A.z*B.y;
    y = A.w*B.y - A.x*B.z + A.y*B.w + A.z*B.x;
    z = A.w*B.z + A.x*B.y - A.y*B.x + A.z*B.w;
    w = A.w*B.w - A.x*B.x - A.y*B.y - A.z*B.z;
  }
  
}


And here is what I do with it:

Quaternion view = new Quaternion();
    view.x = cam.getX();
    view.y = cam.getY();
    view.z = cam.getZ();

double angle = mouseMovementX;

 Quaternion rotation = new Quaternion();
    rotation.x = pointToRotateAround.x * Math.sin(angle);
    rotation.y = pointToRotateAround.y * Math.sin(angle);
    rotation.z = pointToRotateAround.z * Math.sin(angle);
    rotation.w = Math.cos(angle);
    
view.mult(rotation);

GLU.gluLookAt(view.x, cam.getY() , view.z, pointToRotateAround.x, pointToRotateAround.y, pointToRotateAround.z, 0, 1, 0);


So that rotates the view around the y axis of pointToRotateAround. Why does the Camera go further to the pointToRotateAround?
My brain is just... woaaa...

wolf_m

Let me get this straight: You want a third person camera, similar to Tomb Raider?
- One can move it with the mouse,
- it keeps a constant distance to the player,
- it is always looking at the player.

So if you move the mouse,
- the mouse's x value represents the latitude,
- the mouse's y value represents the longitude
.. of the camera's position on a fixed sphere around the player model and the camera is always oriented towards the sphere's center?

If that's the case, you could do it in the following way:
1. Draw everything except the player model
2. Draw the player model
3. Translate to the point in the player model you want to look at
4a. Call glRotatef() with your -x and -y mouse values
4b. If your camera should follow player model rotations, simply add them provided you first translate and then rotate to reach location and orientation of the player
5. Translate a fixed amount backwards ((0,0,-3) for example)

Note: Don't call gluLookAt() at all. Only call gluLookAt() if you want to look from arbitrary positions in arbitrary directions (stationary camera).

No rotations or translations should follow after that.

You don't need quaternions for this. Quaternions are still nice though :)

I hope I got it right.. But it looks like it.

hasta84

Okay, I tried that... Here's what I have:

//Get the Mouse Deltas
mMovementX = Mouse.getDX();
mMovementY = Mouse.getDY();

GL11.glCallList(1); //Draw a simple "terrain"
GL11.glCallList(2); //Draw the Player Model

//Then the rotating...
GL11.glTranslatef(player().getX(), player().getY(), player().getZ());
GL11.glRotatef( mMovementY*0.01f, 1.0f, 0.0f, 0.0f);
GL11.glRotatef( mMovementX*0.01f, 0.0f, 1.0f, 0.0f);
GL11.glTranslatef(0, 0, -2);
       


But nothing happens...


hasta84

I cannot really get it to work properly, but with gluLookAt I was pretty successfull:

import java.util.logging.Level;
import java.util.logging.Logger;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.*;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Vector3f;

public class test {
  
  static Vector3f camPos = new Vector3f();
  static float distance = 4.0f;
  static float angleY = 0.0f;
  static float angleX = 0.0f;
  
  public static void main(String[] args){
    try {
      DisplayMode displayMode = null;

      Display.setFullscreen(false);
      DisplayMode[] d = Display.getAvailableDisplayModes();
      for (int i = 0; i < d.length; i++) {
        if (d[i].getWidth() == 640 && d[i].getHeight() == 480 && d[i].getBitsPerPixel() == 32) {
          displayMode = d[i];
          break;
        }
      }
      Display.setDisplayMode(displayMode);
      Display.setTitle("test");
      Display.create();
      
      GL11.glShadeModel(GL11.GL_SMOOTH);
      GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 
      GL11.glClearDepth(1.0f); 
      GL11.glEnable(GL11.GL_DEPTH_TEST); 
      GL11.glDepthFunc(GL11.GL_LEQUAL); 

      GL11.glMatrixMode(GL11.GL_PROJECTION); 
      GL11.glLoadIdentity(); 

      GLU.gluPerspective(45.0f, (float) displayMode.getWidth() / (float) displayMode.getWidth(),0.1f,100.0f);
      GL11.glMatrixMode(GL11.GL_MODELVIEW);

      GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);
      
      boolean running = true;
      
      Mouse.create();
      
      while(running){
        
        if(Mouse.isButtonDown(0)){
          angleY += Mouse.getDX() * 0.5f;
          angleX += Mouse.getDY() * 0.5f;
        }
        if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {       // Exit if Escape is pressed
          running = false;
        }
        if(Display.isCloseRequested()) {                     // Exit if window is closed
          running = false;
        }
        
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);          
        GL11.glMatrixMode(GL11.GL_MODELVIEW); 

        GL11.glLoadIdentity();
        
        float camX = (float) (distance * Math.sin(angleY * Math.PI / 180.0));
        float camZ = (float) (distance * Math.cos(angleY * Math.PI / 180.0));
        Vector3f rotY = new Vector3f(camX, camPos.y, camZ);
        
        float camY = (float) (distance * Math.sin(angleX * Math.PI / 180.0));
        camZ = (float) (distance * Math.cos(angleX * Math.PI / 180.0));
        Vector3f rotX = new Vector3f(camPos.x, camY, camZ);
        
        //Take the Y rotation
        camPos.x = rotY.x;
        camPos.z = rotY.z; 
        
        //Take the X rotation
        //camPos.y = rotX.y;
        //camPos.z = rotX.z; 
        
        GLU.gluLookAt(camPos.x, camPos.y, camPos.z, 0, 0, 0, 0, 1, 0);
        
        GL11.glBegin(GL11.GL_TRIANGLES);                    
            GL11.glColor3f(1.0f,0.0f,0.0f);             
            GL11.glVertex3f( 0.0f, 1.0f, 0.0f);         
            GL11.glColor3f(0.0f,1.0f,0.0f);             
            GL11.glVertex3f(-1.0f,-1.0f, 1.0f);         
            GL11.glColor3f(0.0f,0.0f,1.0f);            
            GL11.glVertex3f( 1.0f,-1.0f, 1.0f);        
            GL11.glColor3f(1.0f,0.0f,0.0f);            
            GL11.glVertex3f( 0.0f, 1.0f, 0.0f);        
            GL11.glColor3f(0.0f,0.0f,1.0f);             
            GL11.glVertex3f( 1.0f,-1.0f, 1.0f);         
            GL11.glColor3f(0.0f,1.0f,0.0f);             
            GL11.glVertex3f( 1.0f,-1.0f, -1.0f);            
            GL11.glColor3f(1.0f,0.0f,0.0f);             
            GL11.glVertex3f( 0.0f, 1.0f, 0.0f);         
            GL11.glColor3f(0.0f,1.0f,0.0f);             
            GL11.glVertex3f( 1.0f,-1.0f, -1.0f);            
            GL11.glColor3f(0.0f,0.0f,1.0f);             
            GL11.glVertex3f(-1.0f,-1.0f, -1.0f);            
            GL11.glColor3f(1.0f,0.0f,0.0f);             
            GL11.glVertex3f( 0.0f, 1.0f, 0.0f);         
            GL11.glColor3f(0.0f,0.0f,1.0f);             
            GL11.glVertex3f(-1.0f,-1.0f,-1.0f);         
            GL11.glColor3f(0.0f,1.0f,0.0f);             
            GL11.glVertex3f(-1.0f,-1.0f, 1.0f);         
        GL11.glEnd(); 
	
        Display.update();
      }
      
      Display.destroy();
      
    } catch (LWJGLException ex) {
      Logger.getLogger(test.class.getName()).log(Level.SEVERE, null, ex);
      System.exit(0);
    }
    
  }
}


The only problem wich remains with this, is how to combine the X and Y rotations properly?
I tried matrices and quaternions, but couldn't get the results I expected.

wolf_m

Quote from: hasta84 on June 24, 2008, 17:18:02
I cannot really get it to work properly
That's unfortunate.
Quote
but with gluLookAt I was pretty successfull:

[...]
        float camX = (float) (distance * Math.sin(angleY * Math.PI / 180.0));
        float camZ = (float) (distance * Math.cos(angleY * Math.PI / 180.0));
[...]

You're using angleY for both camX and camZ. That's probably not what you want to do - it has no effect for camZ; maybe you want to use angleX for camZ to zoom in and out by dragging the mouse vertically? In that case, you can replace angleY with angleX in the camZ definition line. Sorry, I was wrong there. Let me read the code again.
gluLookAt is of course pretty nice, but not always necessary. If you're happy with it, that's cool anyway :)
Quote
The only problem wich remains with this, is how to combine the X and Y rotations properly?
I tried matrices and quaternions, but couldn't get the results I expected.
Please say exactly what the camera has to do upon which input. I can't really tell from what you've posted so far.

Edit: Also, your classname is supposed to start with an uppercase character. And 'Test' is not a good name for a class - maybe you want to call it 'Game'?
I also would suggest that you split input from display code and put the gameloop into a separate class if your ambitions go beyond simple playthings. That makes it easier once it gets more complex.
Don't make a class for every tidbit of information, but at least keep different topics in separate classes, preferably those topics that represent atomar game elements. Like camera, screen, mouse, keyboard, game startup and keep-alive, graphical elements etc.

wolf_m

Maybe this is what you want?
[...]

if(Mouse.isButtonDown(0)){
          angleY += Mouse.getDX() * 0.5f;
          
          float oldAngleX = angleX;
          angleX += Mouse.getDY() * 0.1f;
          if( angleX > 7 || angleX < -1 ) angleX=oldAngleX;
        }
        if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {       // Exit if Escape is pressed
          running = false;
        }
        if(Display.isCloseRequested()) {                     // Exit if window is closed
          running = false;
        }
        
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);          
        GL11.glMatrixMode(GL11.GL_MODELVIEW); 

        GL11.glLoadIdentity();
        
        double angleYProcessed = angleY * Math.PI / 180.0;
        
        GLU.gluLookAt(	(float) ( distance * Math.sin( angleYProcessed )),
       			angleX,
			(float) ( distance * Math.cos( angleYProcessed )),
			0, 0, 0,
			0, 1, 0);

[...]


The mouse Y value changes the eye's height here as long as it's between 7 and -1. The orbiting around the Y axis stays.
Note that I removed redundant operations / unneccessary vectors.

hasta84

Quote from: wolf_m on June 24, 2008, 21:02:23

Edit: Also, your classname is supposed to start with an uppercase character. And 'Test' is not a good name for a class - maybe you want to call it 'Game'?
I also would suggest that you split input from display code and put the gameloop into a separate class if your ambitions go beyond simple playthings. That makes it easier once it gets more complex.
Don't make a class for every tidbit of information, but at least keep different topics in separate classes, preferably those topics that represent atomar game elements. Like camera, screen, mouse, keyboard, game startup and keep-alive, graphical elements etc.


What I've posted is just a little showcase, so one can just copy, paste, compile & run.

What I want the view to do on Mouse Movement is:
Orbit "an object" (in this case the origin) in given "distance" by horizontal an vertical mouse movement. So if I move the Mouse horizontally, the "camera" should orbit the origin horizontally (rotate around the y axis) and moving the mouse vertically should orbit the origin vertically (rotate around the x axis). All at once... So, I think, the third Person view would work. The Rotations on their own work perfectly, but my problem is combining them into one. That means, if in one loop the Mouse is moved left AND up, the "camera" position which I give the gluLookAt, should move left AND up, always looking at the origin.

Thank you very much for your effort!

wolf_m

Note that the code in my previous post contains orbiting around the Y axis for horizontal and translating along the Y axis for vertical mouse movement. Maybe that's good enough for you?

Orbiting around the object would be a good job for quaternions since they basically describe vectors on the surface of a sphere.

Unfortunately, the code I've got is heavily embedded in my framework, I can't easily share that with you. And you simply won't be able to put it to good use since it's rather math-heavy stuff.

What you want to achieve isn't exactly trivial with gluLookAt since the z value of the eye position results from the rotation around the X and Y axis.

But it's trivial if you introduce glPushMatrix and glPopMatrix and ignore gluLookAt, as I said before.

Here's the corresponding code:
[ ... ]
        if(Mouse.isButtonDown(0)){
          angleY += Mouse.getDX() * 0.5f;
          
          angleX += Mouse.getDY() * 0.5f;
        }
        if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {       // Exit if Escape is pressed
          running = false;
        }
        if(Display.isCloseRequested()) {                     // Exit if window is closed
          running = false;
        }
        
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);          
        GL11.glMatrixMode(GL11.GL_MODELVIEW); 

        GL11.glLoadIdentity();
        
        GL11.glTranslatef(0, 0, -distance);
        GL11.glRotatef(angleX, 1, 0, 0);
        GL11.glRotatef(angleY, 0, 1, 0);
        // First object
        GL11.glPushMatrix(); //Remember position and rotation
        GL11.glTranslatef(OBJECT_POSITION); //Substitute with appropriate values
        GL11.glRotatef(OBJECT_ROTATION); //Substitute with appropriate values
        GL11.glBegin(GL11.GL_TRIANGLES);                    
            GL11.glColor3f(1.0f,0.0f,0.0f);             
            GL11.glVertex3f( 0.0f, 1.0f, 0.0f);         
            // Et cetera
        GL11.glEnd();
        GL11.glPopMatrix(); // Jump back to remembered position

        //Next object, again with glPushMatrix and glPopMatrix
        //Move to the object's position with glTranslate and rotate with glRotate INBETWEEN glPushMatrix and glPopMatrix
        //And so on

[ ... ]

So what you do is
1. Go to cam position
2. Direct the cam towards a point by rotating it
3. Push the matrix to remember the current Modelview matrix
4. Go to object position
5. Rotate the object's local matrix
6. Paint the object in the local matrix
7. Pop the matrix to go back to the cam's location and reapply the cam's rotation
8. Repeat 3-7 for all objects

In the code I posted here, the player model is ALWAYS at (0,0,0), so you'd have to move the world around it.
I leave it to you as an exercise to figure out how to let the world be static, move the player model instead and still keep the camera orbiting around it.
This is exactly what I posted above, and it's just a rehash of what the Redbook chapter I linked to describes.

Hope that helps.

Edit: Note that this method of rotation introduces gimbal lock, meaning you will lose an axis if you rotate in certain ways.
But you said rotation around the z axis isn't your concern; as long as that is the case, you don't have to worry. As soon as you want to rotate around the z axis though, you really should switch to quaternions.