Camera Class

Started by
52 comments, last by Chozo 19 years, 5 months ago
I've been trying to come up with a decent camera class for myself (I know there's a new article up on this, but I'm trying to make sure I really understand what's going on). This is what I have so far: Camera.h

/*
====================
File: Camer.h
Author: Shane Lillie
Description: Camera module header.
====================
*/

#if !defined __CAMERA_H__
#define __CAMERA_H__

#if _MSC_VER >= 1000
#pragma once
#endif

#include "EnVector.h"


/*
class Camera
*/
class Camera
{
public:
    Camera();
    ~Camera();

public:
    // calls gluLookAt()
    // does contraints on the camera
    void look();

    // moves the camera
    // in the looking direction
    void move(const EnVector3<GLdouble>& vector);

    // rotates the camera
    // (these are angles in degrees)
    void rotate(GLfloat yaw, GLfloat pitch);

private:
    EnVector3<GLdouble> m_position;
    EnVector3<GLdouble> m_lookat;
    EnVector3<GLdouble> m_up;
    GLfloat m_yaw, m_pitch;

private:
    Camera(const Camera& camera);
    Camera& operator=(const Camera& rhs);
};

#endif

Camera.cpp

/*
====================
File: Camer.cc
Author: Shane Lillie
Description: Camera module source.
====================
*/

#include <cassert>
#include <cmath>
#include <iostream>

#include <GL/glut.h>

#include "Camera.h"


#define DEG_RAD(d) ((d) * 0.0174532925) // M_PI / 180.0 is about 0.0174532925


/*
 *  Camera methods
 *
 */


Camera::Camera()
    : m_position(0.0, 0.0, 10.0),
        m_lookat(0.0, 0.0, -90.0),
        m_up(0.0, 1.0, 0.0),
        m_yaw(90.0), m_pitch(0.0f)
{
}


Camera::~Camera()
{
}


void Camera::look()
{
    gluLookAt(m_position.x(), m_position.y(), m_position.z(),
              m_position.x() + m_lookat.x(), m_position.y() + m_lookat.y(), m_position.z() + m_lookat.z(),
              m_up.x(), m_up.y(), m_up.z());

#if 1
    glBegin(GL_LINES);
        glVertex3f(m_position.x(), m_position.y(), m_position.z());
        glVertex3f(m_position.x() + m_lookat.x(), m_position.y() + m_lookat.y(), m_position.z() + m_lookat.z());
    glEnd();
#endif
}


void Camera::move(const EnVector3<GLdouble>& vector)
{
    // rotate the vector into
    // the direction we're looking
    // only on the xz-plane
    EnVector3<GLdouble> nv(vector);
    nv[0] += std::cos(DEG_RAD(m_yaw));

    // move the camera in the
    // direction we're looking
    m_position += nv;
}


void Camera::rotate(GLfloat yaw, GLfloat pitch)
{
    m_yaw += yaw;
    m_pitch -= pitch;

    // constrain the yaw
    if(m_yaw <= -360.0 || m_yaw >= 360.0)
        m_yaw = 0.0;

    // constrain the pitch
    if(m_pitch > 60.0)
        m_pitch = 60.0;
    else if(m_pitch < -60.0)
        m_pitch = -60.0;

std::cout << "yaw: " << m_yaw << ", pitch: " << m_pitch << std::endl;

    // set the new lookat vector
    // radius = 100
    m_lookat = EnVector3<GLdouble>(std::cos(DEG_RAD(m_yaw)),
                                   std::sin(DEG_RAD(m_pitch)),
                                   -std::sin(DEG_RAD(m_yaw)));

std::cout << m_lookat.to_string() << std::endl;

/*    // set the new up vector
    // (position - lookat) x vector
    EnVector3<GLdouble> vector = m_position - m_lookat;
    if(!m_yaw) m_up = vector.cross(EnVector3<GLdouble>(-1.0, 0.0, 0.0));            // looking down the z-axis
    else if(m_yaw > 0.0f) m_up = vector.cross(EnVector3<GLdouble>(0.0, 0.0, -1.0)); // positive rotation
    else m_up = vector.cross(EnVector3<GLdouble>(0.0, 0.0, 1.0));                   // negative rotation

std::cout << m_up.to_string() << std::endl;*/
}

Assuming all of my vector operations are done correctly, I'm wondering why my rotations don't work quite right. For example, if I rotate towads the +x-axis and then move down the rotated z-axis, then back on the z-axis, then forward and so on, it moves me down the x-axis. I'm sure it's something being inconsistant between my rotation and movement calculations, it's just not clear to me what it is. I appreciate any advice or suggestions. I'm not so good with the math here so it's been a very slow process getting what I have. I feel close to the correct solution, it's just enough out of my reach that I don't see what I'm doing wrong. EDIT - Also, the line I'm trying to draw from my position to where I'm looking (so I can see where I'm looking) doesn't show up at all. I have a polygon I'm rendering in front of me that I'm using a reference, but I'd really like to see that line if I can get it. [Edited by - Chozo on October 18, 2004 4:58:13 PM]
Advertisement
First of all, the line drawn in front of you may be only a dot, if that, because it's heading straight into the screen. That may be why you're not seeing it.

I'm not sure I understand your move function. It looks like you're only making an assignment to the x component of the delta vector. I'd think you'd want to assign the sin of the yaw angle to the z component as well. Then you might need to mess with the signs to get the motion going in the right direction.

I may be misunderstanding though.
That's what I was doing at first, but I ran into a problem with it. I start out with a yaw of 90 degrees (looking down the -z axis), which makes the sin 1. If I try to move forward (down the -z axis) I end up canceling out the movement in that direction (it's basically limiting me to one direction). I'm guessing I'll have the same problem if I face down the x-axis and try to move there (since the cos becomes 1). Is there a fix for that problem that I haven't come across?

Thanks again for the help. I appreciate whatever suggestions I can get on this.

EDIT - It's probably not clear, but I'm calling move like this:

g_camera.move(EnVector3<GLdouble>(0.0, 0.0, -0.25));

And if I add this:

nv[2] += std::sin(DEG_RAD(m_yaw));

to the move() method, I can only move backwards, regardless of whether I use my forward key or my backward key.
I don't have much time, but a couple quick thoughts.

Why are you passing an initial z value in to your move routine? It seems that would throw things off and bias your movement in the world z direction, which I can't imagine is what you want.

Anyway, take a look at the following generic function to move an object given a position, an angle, and a speed:

Vector3 Move(const Vector3& pos, float angle, float speed){    float s = sinf(DegToRad(angle));    float c = cosf(DegToRad(angle));    Vector3 delta(s * speed, 0.0f, c * speed);    return pos + delta;}


Although you would probably have to mess with the signs of the delta x and z components, that should work for the general case.

If that doesn't work, you may have some other problem. Perhaps you're not doing the camera transform correctly, and although you're actually moving correctly it's coming out wrong visually. Remember that you need to translate by the negative of your camera position.

Anyway, let us know if any of that helps.
The initial z value of the position was just so that I didn't have to move around to see the polygon sitting at the origin. I'll move things around so the camera starts at (0,0,0) and gets re-oriented to view the polygon. See if that helps things.

I think I see what I'm doing differently now. I'm thinking of movement as a set of units to travel (ie, 0.25 units in the direction I'm looking) rather than as a speed in a given direction. So I'd end up adding the sin of the angle (1 at 90 degrees) to the number of units I wanted to move. In the approach you present, the speed is multiplied by the sin, giving the number of units to move. I must have just had my thinking backwards. Does that sound like I'm on the right track now?

I'll give that fix a try when I get home tonight and see how it goes. Thanks again for the help.
Okay, I got the changes implemented, but now the problem is places where the cos is 0 (ie, at an angle of 90 degrees). Doing the multiplication forces the value to 0, regardless of the speed. This is my new move() method:

void Camera::move(const EnVector3<GLdouble>& velocity){std::cout << "velocity: " << velocity.to_string() << std::endl;    // rotate the vector into    // the direction we're looking    // only on the xz-plane    EnVector3<GLdouble> delta(velocity);    delta[0] *= std::cos(DEG_RAD(m_yaw));    delta[2] *= std::sin(DEG_RAD(m_yaw));std::cout << "delta: " << delta.to_string() << std::endl;    // move the camera in the    // direction we're looking    m_position += delta;std::cout << "position: " << m_position.to_string() << std::endl;}


Right now I've moved the camera to face down the -z-axis (m_yaw = 90). Since the cos here is 0, I can't "strafe" along the x-axis. Am I still missing the core concept somewhere?

[Edited by - Chozo on October 18, 2004 4:12:43 PM]
The 90 degree issue should indicate a need to google for 'Gimbal Lock'.

Once you understand that, then it will help you to finish off your camera class.

Unless I've missed you accounting for that in the code above! I admit to only skimming it...

Jim.
Quote:Am I still missing the core concept somewhere?


Perhaps. If you are so inclined, humor me and just try the function I gave you as is. Don't maintain a velocity vector - the Move() function will construct it from scratch internally based on your speed and rotation. Just pass the function your position, your angle in degrees, and your speed, and use the position it gives back.

If you move but not in the right direction, try changing the signs of the cos and sin components (4 permutations) until it's right.

The function assumes you have a DegToRad() function of some sort. Also, for now movement is not time based, but you can make it so by making speed units per second and including a dt (frametime) argument in the move function to multiply speed by.

Anyway, I recommend trying it that way and seeing if it works. If it does, then we can probably figure out where your misunderstanding is and why your version doesn't seem to work.
Oh, and Re: Jim's post, once you start trying to consider more than one angle (i.e. yaw) in your movement, you'll run into the problem he mentioned. At that point you'll want to take a different approach using matrices and/or quaternions.

But let's try to get your current version working first.
Alright, I'll hold off on the gimbal lock research for now. I had a feeling it might be a problem later, so it's good to know it's worth looking into.

Here's my implementation:

void Camera::move(float angle, float speed){    Move(m_position, angle, speed);}void Camera::Move(EnVector3<GLdouble>& pos, float angle, float speed){    float s = -sinf(DEG_RAD(angle));    float c = cosf(DEG_RAD(angle));    EnVector3<GLdouble> delta(s * speed, 0.0, c * speed);    pos += delta;}


And I call it using:

    switch(key)    {    case 'q': case 'Q':        //g_camera.move(EnVector3<GLdouble>(0.0, -0.25, 0.0));        break;    case 'e': case 'E':        //g_camera.move(EnVector3<GLdouble>(0.0, 0.25, 0.0));        break;    case 'w': case 'W':        //g_camera.move(EnVector3<GLdouble>(0.0, 0.0, -0.25));        g_camera.move(0.0f, -0.25f);        break;    case 's': case 'S':        //g_camera.move(EnVector3<GLdouble>(0.0, 0.0, 0.25));        g_camera.move(0.0f, 0.25f);        break;    case 'a': case 'A':        //g_camera.move(EnVector3<GLdouble>(-0.25, 0.0, 0.0));        g_camera.move(90.0f, 0.25f);        break;    case 'd': case 'D':        //g_camera.move(EnVector3<GLdouble>(0.25, 0.0, 0.0));        g_camera.move(-90.0f, 0.25f);        break;    }


This works perfectly (given that I'm not doing any actual rotation of the lookat vector yet). It looks like what we're doing differently here is specifying the angle relative to where we're looking and the speed in that direction. Before I was taking a 'velocity' vector and trying to rotate it into the direction I'm looking before applying it. I kind of see why this approach is better, but I'm not sure I fully understand why.

This topic is closed to new replies.

Advertisement