PDA

View Full Version : Implementing camera using gluLookAt(...)



Pr0gMa
11-02-2014, 01:56 AM
Hi there.
I'm completely new to the whole openGL thing, so please forgive the dumbness of my question; on top of that, I'm no English native speaker, so please pardon my writing skills too.

What I'm trying to do is implementing a sort of FPS camera you can move using the keyboard, and these are the lines of codes concerned I've written so far.



const double PI = 3.14159265;
const double RAD = PI / 180;

const double roaming_step = .2;
const double angle_step = 1.5;

double angleXZ = .0;
double angleYZ = .0;

const double look_at_offset = 9.0;

double x_pos = .0;
double y_pos = 3.0;
double z_pos = 9.0;
double x_look_at = .0;
double y_look_at = 3.0;
double z_look_at = z_pos - look_at_offset;

bool keyStates[256] = { false }; // used to keep track of pressed buttons

void keyboardOperations()
{
if(keyStates['a'])
{
x_pos -= roaming_step * cos(angleXZ * RAD);
z_pos -= roaming_step * sin(angleXZ * RAD);

x_look_at -= roaming_step * cos(angleXZ * RAD);
z_look_at -= roaming_step * sin(angleXZ * RAD);
}

if(keyStates['d'])
{
x_pos += roaming_step * cos(angleXZ * RAD);
z_pos += roaming_step * sin(angleXZ * RAD);

x_look_at += roaming_step * cos(angleXZ * RAD);
z_look_at += roaming_step * sin(angleXZ * RAD);
}

if(keyStates['w'])
{
x_pos += roaming_step * sin(angleXZ * RAD);
z_pos -= roaming_step * cos(angleXZ * RAD);

x_look_at += roaming_step * sin(angleXZ * RAD);
z_look_at -= roaming_step * cos(angleXZ * RAD);
}

if(keyStates['s'])
{
x_pos -= roaming_step * sin(angleXZ * RAD);
z_pos += roaming_step * cos(angleXZ * RAD);

x_look_at -= roaming_step * sin(angleXZ * RAD);
z_look_at += roaming_step * cos(angleXZ * RAD);
}

if(keyStates['o'])
{
angleXZ += angle_step;

if(angleXZ >= 360.0)
angleXZ -= 360.0;

x_look_at = x_pos + look_at_offset * sin(angleXZ * RAD);
z_look_at = z_pos - look_at_offset * cos(angleXZ * RAD);
}

if(keyStates['i'])
{
angleXZ -= angle_step;

if(angleXZ < .0)
angleXZ += 360.0;

x_look_at = x_pos + look_at_offset * sin(angleXZ * RAD);
z_look_at = z_pos - look_at_offset * cos(angleXZ * RAD);
}
}

void display(void)
{
keyboardOperations();

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glLoadIdentity();
gluLookAt(x_pos, y_pos, z_pos,
x_look_at, y_look_at, z_look_at,
.0, 1.0, .0);

// ...stuff...

glutSwapBuffers();
}


For some reason, rotating my camera around the X and Y axis leads to strange behaviors: everything seems to work fine at the beginning, but it starts to act oddly very soon. Instead, it works perfectly if I limit the rotation around only one of the two axis.
I think the problem is somehow related to 'z_look_at', but I have no idea how to solve it. Any suggestion?

Thank you for your attention.

GClements
11-02-2014, 06:58 PM
The code you posted doesn't use angleYZ at all, it only covers movement in the X-Z plane and rotation about the Y axis. If you're only experiencing problems when angleYZ is non-zero, it would help to post code where angleYZ is actually used.
gluLookAt() is the wrong tool for this control model. Just use glTranslate and glRotate instead.
Making the ground plane X-Z rather than X-Y is silly (but unfortunately common; for some reason I've never managed to figure out, people just seem to assume that the default view direction "must" be North).

Pr0gMa
11-03-2014, 12:29 PM
Hi GClements, thank you for your reply.

You're right... I've just figured out I missed all the YZ-related code when copy-pasting. It seems that everyone agrees on gluLookAt being the worst way to carry out the task, so I decided to switch to the glRotate/glTranslate solution. Although, the camera I'm trying to implement is a sort of fly-by camera, "locked" on an object that moves inside the space but keeps always the same (x,y) viewport coordinates (something like GTA's camera, where the character is always at the center of the screen - a part from several specific cases - and the camera "follows" him). Is it possible to easily write code for this type of camera using glTranslate/glRotate? I have a couple ideas in mind about how to do that but I'm wondering if there's something I need to know before I trash all the code I have already written.

For what concerns the XZ plane being more "newbie-friendly" than the XY one, I think it's due to that fact that people learn planar geometry first and then move on and add the missing dimension. What you're looking for when first dealing with 3D is a plane, "a floor", and it is easy to pick the XZ as your new default plane since it is always drawn as the horizontal plane in examples.
Maybe. :P

Anyway, thanks for your suggestions!

GClements
11-03-2014, 04:03 PM
Although, the camera I'm trying to implement is a sort of fly-by camera, "locked" on an object that moves inside the space but keeps always the same (x,y) viewport coordinates (something like GTA's camera, where the character is always at the center of the screen - a part from several specific cases - and the camera "follows" him). Is it possible to easily write code for this type of camera using glTranslate/glRotate?
No. For that case, you should use gluLookAt(). But then you'd be using the character position as the look-at target, and the camera position would be made to follow it. Changing the player's direction would leave their position (the look-at target) unchanged.

You could either rotate the camera around the players position (i.e. the opposite of what you have now) for a rigid over-the-shoulder view, or have the player drag the camera (when the player changes direction, don't change the camera position; when the player moves, move the camera toward or away from the player to maintain a given distance), or something else.


For what concerns the XZ plane being more "newbie-friendly" than the XY one, I think it's due to that fact that people learn planar geometry first and then move on and add the missing dimension.
That's why you choose XY as the ground plane. Reality doesn't have symmetry between axes; it has two horizontal axes forming a horizontal plane, and one vertical axis. There is no distinguished vertical plane (there are infinitely many, one for each compass heading). The 2D plane is the horizontal plane (i.e. the plan view), the "missing" dimension is the vertical.

Apart from the conceptual issues, there are some practical ones. Even when dealing with 3D, there are often things which require 2D plan-view coordinates (e.g. a map as an aid to navigation). If you use XY for horizontal and Z for vertical, you can pass a pointer to a 3D point to something expecting a pointer to a (plan-view) 2D point and it will work (it will just ignore the Z coordinate). If you have an array of 3D coordinates, you can treat it as an array of 2D coordinates with e.g. glVertexAttribPointer simply by specifying the stride. This won't work with Y-vertical unless you happen to need a north-facing view (or an east-facing view rotated through 90 degrees, which is even less likely).


What you're looking for when first dealing with 3D is a plane, "a floor", and it is easy to pick the XZ as your new default plane since it is always drawn as the horizontal plane in examples.
You should find better examples.

Pr0gMa
11-04-2014, 12:28 AM
Ok, so before reading your post I gave it a try anyway and this new solution seemed to work nice and easy, but soon I discovered the challenging bit of it. This is my code at the moment (obviously only the important chunks).



using namespace std;

const double roaming_step = .13;
double z_offset = .0;
double y_offset = .0;
double x_offset = .0;

const double angle_step = 1.5;
double angle_xz = .0;
double angle_yz = .0;

bool keyStates[256] = { false };

void keyPressed(unsigned char key, int x, int y)
{
keyStates[key] = true;
}

void keyReleased(unsigned char key, int x, int y)
{
keyStates[key] = false;
}

void keyboardOperations()
{
if(keyStates['w'])
z_offset += roaming_step;

if(keyStates['s'])
z_offset -= roaming_step;

if(keyStates['a'])
x_offset += roaming_step;

if(keyStates['d'])
x_offset -= roaming_step;

if(keyStates['i'])
{
angle_xz -= angle_step;

if(angle_xz < .0)
angle_xz += 360.0;
}

if(keyStates['o'])
{
angle_xz += angle_step;

if(angle_xz >= 360.0)
angle_xz -= 360.0;
}

if(keyStates['u'])
{
angle_yz -= angle_step;

if(angle_yz < .0)
angle_yz += 360.0;
}

if(keyStates['j'])
{
angle_yz += angle_step;

if(angle_yz >= 360.0)
angle_yz -= 360.0;
}
}

void camera()
{
glLoadIdentity();

// Forward / Backward
glTranslated(.0, y_offset + roaming_step * sin(angle_yz), z_offset - roaming_step * cos(angle_yz));

// Left / Right
glTranslated(x_offset - roaming_step * sin(angle_xz), .0, z_offset - roaming_step * cos(angle_xz));

// XZ Rotation
glRotated(angle_xz, .0, 1.0, .0);

// YZ Rotation
glRotated(angle_yz, 1.0, .0, .0);
}

void display(void)
{
keyboardOperations();

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

camera();

// [...]

glutSwapBuffers();
}


Well, it doesn't work and I'm having a tough time trying to figure out what's wrong.
What if I don't want the vector specified in glRotate to "originate" in (0,0,0) but somewhere else? That would do the trick and I could avoid all those sin/cos calculations. Is there a way to do that?

GClements
11-04-2014, 04:17 PM
What if I don't want the vector specified in glRotate to "originate" in (0,0,0) but somewhere else?
glRotate() rotates the current coordinate system about its origin. You can rotate it around some other point (cx,cy,cz) with


glTranslatef(cx,cy,cz);
glRotatef(...);
glTranslatef(-cx,-cy,-cz);



That would do the trick and I could avoid all those sin/cos calculations. Is there a way to do that?
If you need to do anything other than graphics (e.g. collision detection), you are going to need to maintain the transformations within the program. This is a large part of the reason why all of the matrix operations were deprecated in OpenGL 3: they're of no use for anything but the most trivial cases. Programs which need to do anything with the geometry besides rendering it just construct the matrices locally and transfer the matrices with glLoadMatrix (legacy OpenGL) or glUniformMatrix() etc (modern OpenGL).

Also, implementing a control model using OpenGL matrix operations means that the matrix has to persist; you can't construct it from scratch each frame starting with glLoadIdentity(). This means that you'll only ever be able to maintain one such transformation. You can use other transformations temporarily by saving the player transformation on the matrix stack. But you'll have to discard any such transformations to restore the player transformation, so you can't use the same technique for anything else, e.g. AI-controlled entities.

So, I'd suggest that you forget about the OpenGL matrix operations and just use GLM instead. Or write your own; the matrices generated by the OpenGL functions are documented in their respective manual pages (https://www.opengl.org/sdk/docs/man2/).