Sign in to follow this  
Chozo

Camera Class

Recommended Posts

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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
I see what you're doing - I guess you're just moving around but not rotating. This is more like what I had in mind:


void Camera::Move()
{
if (/*rotate left key*/)
{
m_angle -= rotspeed; // Note: you may have to switch left and right
if (m_angle < 0.0f)
m_angle += 360.0f;
}
if (/*rotate right key*/)
{
m_angle += rotspeed;
if (m_angle > 360.0f)
m_angle -= 360.0f;
}
if (/*forward key*/)
Move(m_pos, m_angle, m_speed);
if (/*backward key*/)
Move(m_pos, m_angle, -m_speed);

// You could probably also do something like this:
if (/*move left key*/)
Move(m_pos, m_angle - 90.0f, m_speed);
// And likewise for move right
}



With something like that, you should be able to rotate and have your movements adjust accordingly.

Having said that, it might be about time to start looking into a more 'correct' and robust implementation. As I often do, I recommend the book '3D Math Primer', which outlines matrix and vector math, and how to use matrices and quaternions to represent orientations.

Anyway, let us know if you have any more questions.

Share this post


Link to post
Share on other sites
Cool, I'll take a look more into quaternions tomorrow. I've read elsewhere that quaternions take care of issues like gimbal lock, is that correct? One of the first results on google for quaternion camera is an article on gamedev, so I'll probably start there and see where it takes me.

Thanks again for the help. I think I've got a better understanding of how to approach the problem so hopefully I'll get my head around a quaternion implementation after a bit of messing with it.

Share this post


Link to post
Share on other sites
Quote:
I've read elsewhere that quaternions take care of issues like gimbal lock, is that correct?


Actually you can avoid gimbal lock with matrices as well, but quaternions are just a little nicer to work with. So I recommend them.

Still, for 2.5 D (like Quake) you might find them to be overkill. But if you get into 6DOF movement (like Descent) they are a must have.

Let me know if that new code works.

Share this post


Link to post
Share on other sites
Good deal. I wrote up a Quaternion class using the operations described in the gamedev article. Everything looks good with rotations so far. This is how my move/rotation looks:


void Camera::move(CameraDirection direction, float speed)
{
float angle = 0.0f;
switch(direction)
{
case CameraDirectionUp:
m_position[1] += speed;
return;
case CameraDirectionDown:
m_position[1] -= speed;
return;
case CameraDirectionLeft:
angle = 90.0f;
break;
case CameraDirectionRight:
angle = -90.0f;
break;
case CameraDirectionForward:
angle = 180.0f;
break;
case CameraDirectionBackward:
angle = 0.0f;
break;
}

const float sa = -std::sin(DEG_RAD(angle));
const float ca = std::cos(DEG_RAD(angle));

m_position += EnVector3<GLdouble>(sa * speed, 0.0, ca * speed);
}


void Camera::rotate(GLfloat yaw, GLfloat pitch)
{
std::cout << "Before: " << m_lookat.to_string() << std::endl;

m_lookat = m_lookat.rotate(yaw, m_up);
m_lookat = m_lookat.rotate(pitch, EnVector3<GLdouble>(1.0, 0.0, 0.0));

std::cout << "After: " << m_lookat.to_string() << std::endl;
}





So the problem right now is that the movement isn't relative to where I'm looking. It's still stuck moving down specific axes. I see that it's just a matter of figuring out what the angles should be relative to where I'm looking, but I don't know how to extract the look angle from the Quaternion. Is there a way of doing that? It's not in any of the articles I"ve seen so far.

I'm also a little unsure of how to do looking up/down. I'm doing an FPS style camera so I don't want to move relative to the yaw, I just want to rotate the camera. I figure this means rotating the quarternion around the relative x-axis (once I can extract the angles from the quaternion) and then rotating the up vector to match where I'm looking (which means I"d have to change the horizontal rotation to not use the up vector, but rather the y-axis vector).

It's a lot of new stuff for me here, so please forgive me if I'm missing some detail somewhere. I'm trying to take it all in while applying it to what I'm doing. Thanks again for all of the help with this.

EDIT - m_lookat is a quaternion, m_up is a vector, and m_position is a vector. Just in case that's not clear.

EDIT - Added the pitch rotation (which obviously doesn'twwork exactly because it's not rotating the vector to rotate around).

[Edited by - Chozo on October 19, 2004 11:58:32 PM]

Share this post


Link to post
Share on other sites
Ok, a few thoughts...

It's good that you're getting started with quaternions, but as I may have mentioned earlier they may actually be overkill for a 2 1/2 D camera like you're trying to do. So first let's think about an easier implementation for a second.

All you really need is:

float yaw;
float pitch;
float speed;

Unless I screwed up somewhere, the code I posted earlier should give you everything you need to move and strafe using the above variables (you'd use the yaw angle for the move function).

You want the pitch to change view direction only, not movement. So you want to create a lookat vector from an angle (yaw) and an azimuth (pitch). To be honest I've never had to do this and don't know the math off the top of my head, but it should be easy to find - try googling 'vector angle azimuth'.

In your current code you're not really doing anything with the quaternion - you're just using it to store an angle, which you then want to extract right back out.

I would recommend trying to get the basics working without the quats - just get yourself moving and looking in the right directions, using an angle/azimuth lookat vector and the code I posted earlier for movement (assuming I got it right).

Once you start working on 6dof movement, it'll be time for quaternions and matrices.

Anyway, I hope I'm helping and not making things worse. Let us know if you have more questions.

Share this post


Link to post
Share on other sites
Is it still 6dof if you ignore roll? In other words, if I wanted movement in the exact direction I'm looking, including accounting for the pitch. Right now I'm using the camera as a way to look at what shapes I'm building from different angles so I'm not sure if I want the strict FPS move inside the xz-plane idea. Does that make sense? I guess I'd just rather have the most general approach so that I can make into whatever I need depending on what I end up doing with the camera class.

Share this post


Link to post
Share on other sites
In the long run you'll certainly be better off with the general approach. And if you just exclude changes in roll from your input routine, it will behave as you are describing.

So you may have a little work ahead of you. I'd recommend getting a book like '3D Math Primer' and really going through all the matrix and quaternion stuff.

It can be kind of tricky, because different books and math libraries use different conventions - right or left handed, row or column vectors, etc. Getting any one of these things wrong can throw everything off.

I've got my math library set up for OpenGL (right-handed). I'd be glad to send it to you if it would help you get started.

Let me know if you need any other help.

Share this post


Link to post
Share on other sites
I think you can extract the look vector by converting your quaternion to a matrix. It should be one of the row/column vectors of the matrix. I don't know offhand which one.

I believe the idea is supposed to be that you work with quaternions all the way through your engine, and then just convert to matrix form at the end, when you want to render your objects.

Share this post


Link to post
Share on other sites
I can't afford any more books right now, however it turns out I picked up Physics for Game Developers a while back and it has an appendix about quaternions and a chapter on using them for rotation. I'll take a look at that and see what I can come up with from it.

Thanks again for all of the help. I really appreciate it. I'll probably post again when I work out some of this to make sure I'm doing it correctly.

Share this post


Link to post
Share on other sites
I feel like I'm somewhat close right now. This is what I've got:

Camera stuff:

void Camera::move(CameraDirection direction, float speed)
{
std::cout << "Rotate before: " << m_position.to_string() << std::endl;

EnVector3<GLdouble> velocity;
switch(direction)
{
case CameraDirectionUp:
m_position[1] += speed;
return;
case CameraDirectionDown:
m_position[1] -= speed;
return;
case CameraDirectionLeft:
velocity[0] = 1.0;
velocity = m_lookat.unit().rotate(velocity);
break;
case CameraDirectionRight:
velocity[0] = -1.0;
velocity = m_lookat.unit().rotate(velocity);
break;
case CameraDirectionForward:
velocity[2] = -1.0;
velocity = m_lookat.unit().rotate(velocity);
break;
case CameraDirectionBackward:
velocity[2] = 1.0;
velocity = m_lookat.unit().rotate(velocity);
break;
}

m_position += velocity * speed;

std::cout << "Rotate after: " << m_position.to_string() << std::endl;
}


void Camera::rotate(GLdouble yaw, GLdouble pitch)
{
// 60 degrees in radians
static const GLdouble dr60 = 1.04719755;

std::cout << "Rotate before: " << m_lookat.to_string() << std::endl;

m_pitch += pitch;

// constrain the pitch
float diff=0.0;
if(m_pitch < -dr60)
diff = m_pitch + dr60;
else if(m_pitch > dr60)
diff = m_pitch - dr60;

pitch -= diff;
m_pitch -= diff;

m_lookat = m_lookat.rotate(yaw, EnVector3<GLdouble>(0.0, 1.0, 0.0));
m_lookat = m_lookat.rotate(pitch, EnVector3<GLdouble>(1.0, 0.0, 0.0));

std::cout << "Rotate after: " << m_lookat.to_string() << std::endl;
}



Usage stuff:

void keyboard_function(unsigned char key, int x, int y)
{
static const float speed = 0.25f;

switch(key)
{
case 'q': case 'Q':
g_camera.move(CameraDirectionUp, speed);
break;
case 'e': case 'E':
g_camera.move(CameraDirectionDown, speed);
break;
case 'w': case 'W':
g_camera.move(CameraDirectionForward, speed);
break;
case 's': case 'S':
g_camera.move(CameraDirectionBackward, speed);
break;
case 'a': case 'A':
g_camera.move(CameraDirectionLeft, speed);
break;
case 'd': case 'D':
g_camera.move(CameraDirectionRight, speed);
break;
case 27: // Escape
exit(0);
break;
}
}


void special_function(int key, int x, int y)
{
// 2 degrees in radians
static const GLdouble radians = 0.035;

switch(key)
{
case GLUT_KEY_UP:
g_camera.rotate(0.0, radians);
break;
case GLUT_KEY_DOWN:
g_camera.rotate(0.0, -radians);
break;
case GLUT_KEY_LEFT:
g_camera.rotate(radians, 0.0);
break;
case GLUT_KEY_RIGHT:
g_camera.rotate(-radians, 0.0);
break;
}
}



It works almost (very almost) perfectly except for strafing. It looks like movement in the look direction works great, but moving perpendicular to that seems to be just a touch off from correct. Does it look like I'm doing something wrong again? I'm hoping it's just a small correction I need to make, but I've got mystelf prepared for rewriting large chunks again. ;)

Share this post


Link to post
Share on other sites
I'm not quite sure what all the things in your code do. Is m_lookat a quaternion?

One thing is that that seems like kind of a roundabout way of clamping your pitch. It seems like this:

if (pitch < -dr60)
pitch = -dr60;

Would do the same thing.

Also, you're not adjusting yaw at this point, is that correct?

My main question would be about how you're handling your quaternion. It looks like you intend to construct it from scratch from your yaw and pitch values every frame. But to do that, you would want to set it to identity first, which I don't see in your code. Are you doing that somewhere?

Share this post


Link to post
Share on other sites
Sorry about that. m_lookat is a quaternion, m_up and m_position are 3D vectors, and m_pitch is a float. The rotation is incremental, which is why I'm only clamping the difference regarding the pitch and why I'm not resetting the quaternion. So basically one tap of the left key rotates 2 degrees relative to where I'm currently looking. I just keep the total pitch in a variable so that I can do the clamping (didn't see a way to do it using just the quaternion).

I am rotating around the z-axis (yaw), I'm just not clamping it. The quaternion rotation should take care of it wrapping at 360/-360 degrees, correct? Or will I have a problem when the floating point numbers wrap around to the opposite sign?

So basically, I'm not constructing the quaternion from scratch, just incrementing it each time the key is pressed. For the movement, I'm constructing a unit vector in the direction of movement, then rotating it into the direction of the quaternion unit vector, and adding that (times the speed) to my current position vector. It's just not giving me quite the correct values when I move around.

Share this post


Link to post
Share on other sites
The quat constructor uses sinf() and cosf() internally, presumably, so I don't think it'll care if the angle is < 0 or > 360.

So I see what you're doing with rotating the velocity vector by the quat. As to why it's not working, it's hard to say. Are you doing the quat-vec multiplication correctly? In the right order?

You might do a simple test, such as making a quat that represents a 90 degree yaw and then rotating a unit vector by it and seeing if you get the right result.

If it's still not working, how so? Are you sort of moving in the right direction, but not quite? Or is it totally off?

Share this post


Link to post
Share on other sites
For reference, here's my quat class:


/*
====================
File: EnQuaternion.h
Author: Shane Lillie
Description: Quaternion template header.

(c) 2004 Energon Software

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
====================
*/


#if !defined __ENQUATERNION_H__
#define __ENQUATERNION_H__

#if _MSC_VER >= 1000
#pragma once
#endif


#include "EnVector.h"


/*
class EnQuaternion
*/

template<typename T>
class EnQuaternion
{
public:
explicit EnQuaternion(const T& w=T(), const T& x=T(), const T& y=T(), const T& z=T())
: m_scalar(w), m_vector(x, y, z)
{
}

EnQuaternion(const T& scalar, const EnVector3<T>& vector)
{
m_scalar = scalar;
m_vector = vector;
}

// array constructor
EnQuaternion(const T& scalar, const T* const vector)
{
assert(vector);
m_scalar = scalar;
std::memmove(m_vector, vector, dim * sizeof(T));
}

template <class V>
EnQuaternion(const EnQuaternion<V>& quaternion)
{
m_scalar = static_cast<T>(quaternion.m_scalar);
m_vector = quaternion.m_vector;
}

virtual ~EnQuaternion()
{
}

public:
EnQuaternion<T>& clear()
{
m_scalar = T();
m_vector.clear();
return *this;
}

// constructs a quaternion
// from euler angles
void create(float yaw, float pitch, float roll)
{
EnQuaternion<T> qyaw(std::cos(yaw / 2.0f), EnVector3<T>(0, 0, std::sin(yaw / 2.0f)));
EnQuaternion<T> qpitch(std::cos(pitch / 2.0f), EnVector3<T>(0, std::sin(pitch/ 2.0f), 0));
EnQuaternion<T> qroll(std::cos(roll / 2.0f), EnVector3<T>(std::sin(roll / 2.0f), 0, 0));

*this = qyaw * qpitch * qroll;
}

// constructs a quaternion
// from an angle and an axis
template <class V>
void create(float angle, const EnVector3<V>& axis)
{
m_scalar = std::cos(angle / 2.0f);
m_vector = std::sin(angle / 2.0f) * axis;
}

public:
// returns the quaternion
// as a unit quaternion
EnQuaternion<T> unit() const
{
return (*this) / length();
}

// returns the quaternion
// as a unit quaternion safely
// returns a cleared quaternion
// if the length is 0
EnQuaternion<T> safe_unit() const
{
const T len = length();
return len ? (*this) / length() : EnQuaternion<T>();
}

// makes the quaternion
// a unit quaternion
EnQuaternion<T>& normalize()
{
*this /= length();
return *this;
}

// makes the quaternion
// a unit quaternion safely
// clears the quaternion
// if the length is 0
EnQuaternion<T>& safe_normalize()
{
const T len = length();
len ? *this /= len : clear();
return *this;
}

// returns the euclidean norm
// (length) of this quaternion
T length() const
{
float len = std::sqrt(length_squared());
return static_cast<T>(len);
}

// returns the length of
// the quaternion squared
T length_squared() const
{
return (*this) ^ (*this);
}

// returns this quaternion with
// integer precision components
EnQuaternion<T> snap() const
{
EnQuaternion<int> t = static_cast<EnQuaternion<int> >(*this);
return static_cast<EnQuaternion<T> >(t);
}

// rotates a quaternion by this one
template <class V>
EnQuaternion<T> rotate(const EnQuaternion<V>& quaternion) const
{
return (*this) * quaternion * ~(*this);
}

// rotates a vector
// by this quaternion
template <class V>
EnVector3<T> rotate(const EnVector3<V>& vector) const
{
EnQuaternion<T> ret(*this);
ret *= vector;
ret *= ~(*this);
return ret.vector();
}

// rotates the quaternion
// around a vector by an angle (radians)
template <class V>
EnQuaternion<T> rotate(float angle, const EnVector3<V>& vector) const
{
EnQuaternion<T> rot;
rot.create(angle, vector);
return rot.rotate(*this);
}

// returns the angle of rotation
// about the vector axis
float angle() const
{
return 2.0f * std::acos(m_scalar);
}

// returns the unit vector
// representing the axis of rotation
EnVector3<T> axis() const
{
return m_vector.unit();
}

public:
const T& w() const
{
return m_scalar;
}

void w(const T& w)
{
m_scalar = w;
}

const T& x() const
{
return m_vector.x();
}

void x(const T& x)
{
m_vector.x(x);
}

const T& y() const
{
return m_vector.y();
}

void y(const T& y)
{
m_vector.y(y);
}

const T& z() const
{
return m_vector.z();
}

void z(const T& z)
{
m_vector.z(z);
}

const T& scalar() const
{
return m_scalar;
}

void scalar(const T& scalar)
{
m_scalar = scalar;
}

const EnVector3<T>& vector() const
{
return m_vector;
}

void vector(const EnVector3<T>& vector)
{
m_vector = vector;
}

public:
// returns the value at index idx
T& operator[](size_t idx)
{
assert(idx < 4);
if(!idx) return m_scalar;
return m_vector[idx];
}

// returns the value at index idx as a constant
const T& operator[](size_t idx) const
{
assert(idx < 4);
if(!idx) return m_scalar;
return m_vector[idx];
}

// tests for quaternion equality
template <class V>
bool operator==(const EnQuaternion<V>& rhs) const
{
return m_scalar == rhs.m_scalar && m_vector == rhs.m_vector;
}

// tests for quaternion inequality
template <class V>
bool operator!=(const EnQuaternion<V>& rhs) const
{
return !((*this) == rhs);
}

// tests for scalar-length equality
bool operator==(const T& rhs) const
{
return length() == rhs;
}

// tests for scalar-length inequality
bool operator!=(const T& rhs) const
{
return !(*this == rhs);
}

// returns a quaternion that is
// the negation of this one
EnQuaternion<T> operator-() const
{
return EnQuaternion<T>(-m_scalar, -m_vector);
}

// adds two quaternions
template <class V>
EnQuaternion<T> operator+(const EnQuaternion<V>& rhs) const
{
return EnQuaternion<T>(m_scalar + rhs.m_scalar, m_vector + rhs.m_vector);
}

// adds two quaternions and assigns
// the value to this quaternion
template <class V>
const EnQuaternion<T>& operator+=(const EnQuaternion<V>& rhs)
{
*this = *this + rhs;
return *this;
}

// subtracts two quaternions
template <class V>
EnQuaternion<T> operator-(const EnQuaternion<V>& rhs) const
{
return (*this) + (-rhs);
}

// subtracts two quaternions and assigns
// the value to this quaternion
template <class V>
const EnQuaternion<T>& operator-=(const EnQuaternion<V>& rhs)
{
return (*this) += (-rhs);
}

// multiplies two quaternions
template <class V>
EnQuaternion<T> operator*(const EnQuaternion<V>& rhs) const
{
const EnVector3<V> vector = rhs.m_vector;
return EnQuaternion<T>(m_scalar * rhs.m_scalar - m_vector * vector, m_scalar * vector + rhs.m_scalar * m_vector + (m_vector ^ vector));
}

// multiplies two quaternions
// and assigns
template <class V>
const EnQuaternion<T>& operator*=(const EnQuaternion<V>& rhs)
{
*this = *this * rhs;
return *this;
}

// scales a quaternion
EnQuaternion<T> operator*(const T& rhs) const
{
return EnQuaternion<T>(m_scalar * rhs, m_vector * rhs);
}

// scales a quaternion and assigns
// the value to this quaternion
const EnQuaternion<T>& operator*=(const T& rhs)
{
*this = *this * rhs;
return *this;
}

// vector multiplication
template <class V>
EnQuaternion<T> operator*(const EnVector3<V>& rhs)
{
return *this * EnQuaternion<T>(0, rhs);
}

// vector multiplication
// and assignment
template <class V>
const EnQuaternion<T>& operator*=(const EnVector3<V>& rhs)
{
*this = *this * EnQuaternion<T>(0, rhs);
return *this;
}

// dot-product
template <class V>
T operator^(const EnQuaternion<V>& rhs) const
{
return (m_scalar * rhs.m_scalar) + (m_vector * rhs.m_vector);
}

// scales a quaternion by 1 / rhs
EnQuaternion<T> operator/(const T& rhs) const
{
return EnQuaternion<T>(m_scalar / rhs, m_vector / rhs);
}

// scales a quaternion by 1 / rhs and
// assigns the value to this quaternion
const EnQuaternion<T>& operator/=(const T& rhs)
{
*this = *this / rhs;
return *this;
}

// assignment operator
template <class V>
const EnQuaternion<T>& operator=(const EnQuaternion<V>& rhs)
{
m_scalar = rhs.m_scalar;
m_vector = rhs.m_vector;
return *this;
}

// conjugate
EnQuaternion<T> operator~() const
{
return EnQuaternion<T>(m_scalar, -m_vector);
}

public:
// tests for scalar-length equality
friend bool operator==(const T& lhs, const EnQuaternion<T>& rhs)
{
return lhs == rhs.length();
}

// tests for scalar-length inequality
friend bool operator!=(const T& lhs, const EnQuaternion<T>& rhs)
{
return !(lhs == rhs);
}

// vector multiplication
template <class V>
friend EnQuaternion<T> operator*(const EnVector3<V>& lhs, const EnQuaternion<T>& rhs)
{
return EnQuaternion<T>(0, lhs) * rhs;
}

// scales a quaternion
friend EnQuaternion<T> operator*(const T& lhs, const EnQuaternion<T>& rhs)
{
return EnQuaternion<T>(lhs * rhs.m_scalar, lhs * rhs.m_vector);
}

public:
std::string to_string() const
{
std::ostringstream ostr;
ostr << "(" << m_scalar << ", " << m_vector.to_string() << ")";
return ostr.str();
}

protected:
T m_scalar;
EnVector3<T> m_vector;
};


#endif



And my vector class:

/*
====================
File: EnVector.h
Author: Shane Lillie
Description: Vector template header.

(c) 2002-2004 Energon Software

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
====================
*/


#if !defined __ENVECTOR_H__
#define __ENVECTOR_H__

#if _MSC_VER >= 1000
#pragma once
#endif


#if defined WIN32
#define _USE_MATH_DEFINES
#endif

#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <sstream>


/*
class EnVector

Base class representing
an n-dimensional vector.
*/

template <class T, size_t dim>
class EnVector
{
public:
EnVector()
{
clear();
}

// array constructor
EnVector(const T* const vector)
{
assert(vector);
std::memmove(m_vector, vector, dim * sizeof(T));
}

template <class V>
EnVector(const EnVector<V, dim>& vector)
{
// have to loop because vector
// may be of a different type
for(size_t i=0; i<dim; ++i)
m_vector[i] = static_cast<T>(vector[i]);
}

virtual ~EnVector()
{
}

public:
EnVector<T, dim>& clear()
{
for(size_t i=0; i<dim; ++i)
m_vector[i] = T();
return *this;
}

public:
// returns the vector as a unit vector
EnVector<T, dim> unit() const
{
return (*this) / length();
}

// returns the vector as a unit vector safely
// returns a cleared vector if the length is 0
EnVector<T, dim> safe_unit() const
{
const T len = length();
return len ? (*this) / length() : EnVector<T, dim>();
}

// makes the vector a unit vector
EnVector<T, dim>& normalize()
{
*this /= length();
return *this;
}

// makes the vector a unit vector safely
// clears the vector if the length is 0
EnVector<T, dim>& safe_normalize()
{
const T len = length();
len ? *this /= len : clear();
return *this;
}

// returns the euclidean norm (length) of this vector
T length() const
{
float len = sqrtf(length_squared());
return static_cast<T>(len);
}

// returns the manhattan norm of this vector
T manhattan_norm() const
{
T ret = 0;
for(size_t i=0; i<dim; ++i)
ret += std::fabs(m_vector[i]);
return ret;
}

// returns the infinite norm of this vector
T infinite_norm() const
{
T ret = 0;
for(size_t i=0; i<dim; ++i)
ret = std::max(ret, std::fabs(m_vector[i]));
return ret;
}

// returns the length of the vector squared
T length_squared() const
{
return (*this) * (*this);
}

// returns the angle between this vector and another (slow)
template <class V>
float angle(const EnVector<V, dim>& vector) const
{
return std::acos((*this * vector) / (length() * vector.length()));
}

// returns the distance between this vector and another
template <class V>
T distance(const EnVector<V, dim>& vector) const
{
return (vector - *this).length();
}

// returns this vector with integer precision components
EnVector<T, dim> snap() const
{
EnVector<int, dim> t = static_cast<EnVector<int, dim> >(*this);
return static_cast<EnVector<T, dim> >(t);
}

// returns the vector as an array
const T* const array() const
{
return m_vector;
}

// returns true if the vector is the 0 vector
bool zero() const
{
for(size_t i=0; i<dim; ++i) {
if(m_vector[i])
return false;
}
return true;
}

public:
// returns the value at index idx
T& operator[](size_t idx)
{
assert(idx < dim);
return m_vector[idx];
}

// returns the value at index idx as a constant
const T& operator[](size_t idx) const
{
assert(idx < dim);
return m_vector[idx];
}

// tests for vector equality
template <class V>
bool operator==(const EnVector<V, dim>& rhs) const
{
// have to loop because rhs
// may be of a different type
for(size_t i=0; i<dim; ++i)
if(m_vector[i] != rhs[i])
return false;
return true;
}

// tests for vector inequality
template <class V>
bool operator!=(const EnVector<V, dim>& rhs) const
{
return !((*this) == rhs);
}

// tests for scalar-length equality
bool operator==(const T& rhs) const
{
return length() == rhs;
}

// tests for scalar-length inequality
bool operator!=(const T& rhs) const
{
return !(*this == rhs);
}

// returns a vector that is the conjugate of this one
// this is the same as reversing the vector
EnVector<T, dim> operator-() const
{
EnVector<T, dim> ret;
for(size_t i=0; i<dim; ++i)
ret[i] = -(*this)[i];
return ret;
}

// adds two vectors
template <class V>
EnVector<T, dim> operator+(const EnVector<V, dim>& rhs) const
{
EnVector<T, dim> ret;
for(size_t i=0; i<dim; ++i)
ret[i] = m_vector[i] + rhs[i];
return ret;
}

// adds two vectors and assigns
// the value to this vector
template <class V>
const EnVector<T, dim>& operator+=(const EnVector<V, dim>& rhs)
{
*this = *this + rhs;
return *this;
}

// subtracts two vectors
template <class V>
EnVector<T, dim> operator-(const EnVector<V, dim>& rhs) const
{
return (*this) + (-rhs);
}

// subtracts two vectors and assigns
// the value to this matrix
template <class V>
const EnVector<T, dim>& operator-=(const EnVector<V, dim>& rhs)
{
return (*this) += (-rhs);
}

// dot-product
template <class V>
T operator*(const EnVector<V, dim>& rhs) const
{
T ret(0);
for(size_t i=0; i<dim; ++i)
ret += static_cast<T>(m_vector[i] * rhs[i]);
return ret;
}

// scales a vector
EnVector<T, dim> operator*(const T& rhs) const
{
EnVector<T, dim> ret;
for(size_t i=0; i<dim; ++i)
ret[i] = m_vector[i] * rhs;
return ret;
}

// scales a vector and assigns the value to this vector
const EnVector<T, dim>& operator*=(const T& rhs)
{
*this = *this * rhs;
return *this;
}

// scales a vector by 1 / rhs
EnVector<T, dim> operator/(const T& rhs) const
{
EnVector<T, dim> ret;
for(size_t i=0; i<dim; ++i)
ret[i] = m_vector[i] / rhs;
return ret;
}

// scales a vector by 1 / rhs and assigns the value to this vector
const EnVector<T, dim>& operator/=(const T& rhs)
{
*this = *this / rhs;
return *this;
}

// assignment operator
template <class V>
const EnVector<T, dim>& operator=(const EnVector<V, dim>& rhs)
{
// have to loop because rhs
// may be of a different type
for(size_t i=0; i<dim; ++i)
m_vector[i] = static_cast<T>(rhs[i]);
return *this;
}

public:
// tests for scalar-length equality
template <class V>
friend bool operator==(const V& lhs, const EnVector<T, dim>& rhs)
{
return static_cast<T>(lhs) == rhs.length();
}

// tests for scalar-length inequality
template <class V>
friend bool operator!=(const V& lhs, const EnVector<T, dim>& rhs)
{
return !(lhs == rhs);
}

// scales a vector lhs
friend EnVector<T, dim> operator*(const T& lhs, const EnVector<T, dim>& rhs)
{
EnVector<T, dim> ret;
for(size_t i=0; i<dim; ++i)
ret[i] = lhs * rhs[i];
return ret;
}

public:
std::string to_string() const
{
std::ostringstream ostr;
ostr << "(" << m_vector[0];
for(size_t i=1; i<dim; ++i)
ostr << ", " << m_vector[i];
ostr << ")";
return ostr.str();
}

public:
size_t dimensions() const
{
return dim;
}

protected:
T m_vector[dim];
};


/*
* Specialized EnVector classes
*
*/



// 1-D vector
template <class T>
class EnVector1 : public EnVector<T, 1>
{
public:
explicit EnVector1(const T& x=T())
{
m_vector[0] = x;
}

template <class V>
EnVector1(const EnVector<V, 1>& v)
{
m_vector[0] = static_cast<T>(v[0]);
}


virtual ~EnVector1()
{
}

public:
const T& x() const
{
return m_vector[0];
}

void x(const T& x)
{
m_vector[0] = x;
}

public:
template <class V>
const EnVector<T, 1>& operator=(const EnVector<V, 1>& rhs)
{
m_vector[0] = static_cast<T>(rhs[0]);
return *this;
}
};


template <class T>
class EnVector3;


// 2-D vector
template <class T>
class EnVector2 : public EnVector<T, 2>
{
public:
explicit EnVector2(const T& x=T(), const T& y=T())
{
m_vector[0] = x;
m_vector[1] = y;
}

template <class V>
EnVector2(const EnVector<V, 2>& v)
{
m_vector[0] = static_cast<T>(v[0]);
m_vector[1] = static_cast<T>(v[1]);
}

virtual ~EnVector2()
{
}

public:
// constructs the vector from a length and angle
void construct(float length, float angle)
{
m_vector[0] = length * std::cos(angle);
m_vector[1] = length * std::sin(angle);
}

// returns the angle of the vector w.r.t. the xy-plane
float angle() const
{
return std::atan(y() / x());
}

const T& x() const
{
return m_vector[0];
}

void x(const T& x)
{
m_vector[0] = x;
}

const T& y() const
{
return m_vector[1];
}

void y(const T& y)
{
m_vector[1] = y;
}

// returns the vector as a 3d vector
// with a 0 z component
EnVector3<T> vec3() const
{
return EnVector3<T>(x(), y(), 0);
}

public:
template <class V>
const EnVector<T, 2>& operator=(const EnVector<V, 2>& rhs)
{
m_vector[0] = static_cast<T>(rhs[0]);
m_vector[1] = static_cast<T>(rhs[1]);
return *this;
}
};


// 3-D vector
template <class T>
class EnVector3 : public EnVector<T, 3>
{
public:
explicit EnVector3(const T& x=T(), const T& y=T(), const T& z=T())
{
m_vector[0] = x;
m_vector[1] = y;
m_vector[2] = z;
}

template <class V>
EnVector3(const EnVector<V, 3>& v)
{
m_vector[0] = static_cast<T>(v[0]);
m_vector[1] = static_cast<T>(v[1]);
m_vector[2] = static_cast<T>(v[2]);
}

virtual ~EnVector3()
{
}

public:
const T& x() const
{
return m_vector[0];
}

void x(const T& x)
{
m_vector[0] = x;
}

const T& y() const
{
return m_vector[1];
}

void y(const T& y)
{
m_vector[1] = y;
}

const T& z() const
{
return m_vector[2];
}

void z(const T& z)
{
m_vector[2] = z;
}

// returns the vector as a 2d vector
// by dropping the z component
EnVector2<T> vec2() const
{
return EnVector2<T>(x(), y());
}

public:
// cross-product
template <class V>
EnVector3<T> operator^(const EnVector<V, 3>& rhs) const
{
return EnVector3<T>(y()*rhs[2] - z()*rhs[1], z()*rhs[0] - x()*rhs[2], x()*rhs[1] - y()*rhs[0]);
}

// cross-product and assign to this
template <class V>
const EnVector3<T>& operator^=(const EnVector<V, 3>& rhs) const
{
*this = *this ^ rhs;
return *this;
}

template <class V>
const EnVector3<T>& operator=(const EnVector<V, 3>& rhs)
{
m_vector[0] = rhs[0];
m_vector[1] = rhs[1];
m_vector[2] = rhs[2];
return *this;
}
};


// 4-D vector
template <class T>
class EnVector4 : public EnVector<T, 4>
{
public:
explicit EnVector4(const T& w=T(), const T& x=T(), const T& y=T(), const T& z=T())
{
m_vector[0] = w;
m_vector[1] = x;
m_vector[2] = y;
m_vector[3] = z;
}

template <class V>
EnVector4(const EnVector<V, 4>& v)
{
m_vector[0] = static_cast<T>(v[0]);
m_vector[1] = static_cast<T>(v[1]);
m_vector[2] = static_cast<T>(v[2]);
m_vector[3] = static_cast<T>(v[3]);
}

virtual ~EnVector4()
{
}

public:
const T& w() const
{
return m_vector[0];
}

void w(const T& x)
{
m_vector[0] = x;
}

const T& x() const
{
return m_vector[1];
}

void x(const T& x)
{
m_vector[1] = x;
}

const T& y() const
{
return m_vector[2];
}

void y(const T& y)
{
m_vector[2] = u;
}

const T& z() const
{
return m_vector[3];
}

void z(const T& z)
{
m_vector[3] = z;
}

public:
template <class V>
const EnVector4<T>& operator=(const EnVector<V, 4>& rhs)
{
m_vector[0] = static_cast<T>(rhs[0]);
m_vector[1] = static_cast<T>(rhs[1]);
m_vector[2] = static_cast<T>(rhs[2]);
m_vector[3] = static_cast<T>(rhs[3]);
return *this;
}
};


#endif



When I run this code:

EnQuaternion<float> q(std::cos(M_PI_2 / 2.0f), 1.0f, 0.0f, 0.0f);
EnVector3<float> v(1.0f, 0.0f, 0.0f);

m_log.vlogln(LogOutputNormal, "Before rotation: %s", v.to_string().c_str());
v = q.rotate(v);
m_log.vlogln(LogOutputNormal, "After rotation: %s", v.to_string().c_str());



I get this output:

Before rotation: (1, 0, 0)
After rotation: (1.5, 0, 0)


The output doesn't look right to me, so I think you're right about my quat implementation being wrong.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this