Sign in to follow this  

2D Vector Representation

This topic is 4107 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to create my own 2D engine using SDL even though I'm just a beginner, mainly because I learn faster this way rather than re-using already created code. I want to know what is the best form to represent a vector quantity, such as Velocity or Acceleration of an object. Here are the various options I thought of: 1) Vector( r, theta ) - Polar representation of magnitude and angle made with the horizontal X-axis. This method makes it easy to conceptualize how the vector will be, but makes the code slightly larger because you have to resolve the vectors and use the next method for addition of two vectors anyway. 2) Vector( x, y ) - Cartesian representation. This makes it easy to add/subtract multiple vectors but little harder to visualize how a vector will be in reality given values for x and y. Now since the pixel co-ordinate system has the Y-axis positive downwards, I don't know which one is better to use: 2-a) In Vector( x, y ), where y is the magnitude vertically upwards. When applying this to a moving object:
Vector Displacement = Velocity * deltaTime;
Obj->X += Displacement.X; //Add X-value of displacement vector 
Obj->Y -= Displacement.Y; //Sub Y-value of displacement vector 
2-b) In Vector( x, y ), where y is the magnitude vertically downwards. When applying this to a moving object:
Vector Displacement = Velocity * deltaTime;
Obj->X += Displacement.X; //Add X-value of displacement vector 
Obj->Y += Displacement.Y; //Add Y-value of displacement vector 
Which method is usually used, (1), (2a) or (2b)?

Share this post


Link to post
Share on other sites
I'd definately go for 2, though whether A or B is up to you. However, you should make a constructor for the vector that accepts theta and r and converts them to an internal x and y, since that would be handy in some cases.

Share this post


Link to post
Share on other sites
Ok I'm having a bit of problem with vector addition here:

SAGE_Vector.h

#ifndef SAGE_VECTOR_H
#define SAGE_VECTOR_H

#include "SAGE.h"

namespace SAGE {

class Vector {

public:

/** Horizontal Component of the vector **/
float X;

/** Vertical Component of the vector **/
float Y;

/** Constructor for defnite vector **/
Vector( float x, float y );

/** Constructor for null vector **/
Vector();

/** Destructor **/
~Vector();

/** Get the magnitude of the vector **/
float GetMagnitude();

/** Get the angle made my the vector with the X-axis **/
float GetAngle( bool radians = false );

/** Set the vector in polar form **/
void SetPolar( float r, float theta, bool radians = false );

/** Reverses direction of the vector **/
void Reverse();

/** Vector-Scalar Operator overloading **/
Vector operator* ( float scalar );
Vector operator/ ( float scalar );

/** Vector-Vector Operator overloading **/
Vector operator+ ( Vector v2 );
void operator+= ( Vector v2 );
Vector operator- ( Vector v2 );
void operator-= ( Vector v2 );

};

};

#endif



SAGE_Vector.cpp

#include "SAGE.h"

#ifndef PI
#define PI 3.141592654
#endif

namespace SAGE {


Vector::Vector(float x, float y){
X = x;
Y = y;
}

Vector::Vector(){
X = 0.0;
Y = 0.0;
}

Vector::~Vector(){

}

void Vector::Reverse(){
X = -1 * X;
Y = -1 * Y;
}

float Vector::GetMagnitude(){
float msq = X*X + Y*Y;
float magnitude;
if( msq <= 0.0001 ){
//Null vector
magnitude = 0;
} else {
magnitude = std::sqrt( msq );
}
return magnitude;
}

float Vector::GetAngle(bool radians){
float theta;
if( fabs(X) < 0.0001 && fabs(Y) < 0.0001 ){
//Null vector
theta = 0;
} else {
theta = atan2( Y, X );
if( radians == false ){
theta = theta * 180 / PI;
}
}
return theta;
}

void Vector::SetPolar(float r, float theta, bool radians){
if( radians == false ){
theta = theta * PI / 180;
}
X = r * cos(theta);
Y = r * sin(theta);
}

Vector Vector::operator* ( float scalar ){
return Vector( X*scalar, Y*scalar );
}

Vector Vector::operator/ ( float scalar ){
return Vector( X/scalar , Y/scalar );
}

Vector Vector::operator+ ( Vector v2 ){
return Vector( X + v2.X, Y + v2.Y );
}

void Vector::operator+= ( Vector v2 ){
*this = (*this + v2);
}

Vector Vector::operator- ( Vector v2 ){
return Vector( X - v2.X, Y - v2.Y );
}

void Vector::operator-= ( Vector v2 ){
*this = (*this - v2);
}

};



Whenever I add two vectors with postive X and Y values it works fine, but when I try to add Vector( -10, 0 ) + Vector( 0, -10 ) the program crashes. At first I thought this could be a problem in the GetAngle() or GetMagnitude() function, but I'm certain its a problem with the + operator now. I don't currently have a working debugger on me so any suggestions before I can download one?

Share this post


Link to post
Share on other sites
This can and probably should be all done in the header file.


#ifndef SAGE_VECTOR_H
#define SAGE_VECTOR_H

#include "SAGE.h"

namespace SAGE {

class Vector {

// tip 1: use the single line comments for single lines
public:

// it is standard to use lowercase variables in vector classes,
// and it's always good to follow de-facto standards
float x, x;

// we use the initialiser list for our variables, and default
// arguments - they can be the same name, it's an allowed part
// of the C++ spec, and will work as expected
Vector( float x = 0, float y = 0) : x(x), y(y)
{
}

// A destructor is not required, because they are primitives

// I would make all these functions friend functions, so you
// use them like this: float x = Magnitude(my_vector);, rather
// than float x = my_vector.Magnitude();
//
// There are several reasons for this, but it's mostly a question
// of style.

// Magnitude
friend float Magnitude(const Vector& v)
{
return sqrt(v.x * v.x + v.y * v.y);
}

// we'll leave the others as an excercise for the reader

// these actually HAVE to be friend functions, because you can't
// do float * vector, you can only do vector * float (as the operators
// take the parameters as the RHS)
friend Vector operator * ( const Vector& v, float s)
{
return Vector(v.x * s, v.y * s);
}

friend Vector operator * ( float s, const Vector& v)
{
return Vector(v.x * s, v.y * s);
}

// same for the other operators

// okay: you should be using references, and returning references,
// and consting everything
Vector operator + ( const Vector& v )
{
return Vector(x + v.x, y + v.y);
}

Vector& operator += ( const Vector& v )
{
x += v.x; y += v.y;
return *this;
}


};

};



Okay, hopefully that clears some things up. I wasn't going to write the whole thing out again, but hopefully you get the gist.

Edit: Changed Magnitude() to actually work, instead of assuming.

[Edited by - _goat on September 16, 2006 12:09:40 PM]

Share this post


Link to post
Share on other sites
Thanks a lot _goat, but I would also like to know why some of the things you mentioned are to be implemented. I usually like to understand the theory and reasoning behind every technique implemented, so please can you answer these questions:

1.) Why should Magnitude( V ) be used instead of V.Magnitude() ? It gives the same result and it would contain the function to be a method of the Vector class rather than being a global function who's name might clash with another function.

2.) Funtions that are declared with the friend keyword inline inside the class... will they be inside the SAGE namespace?

3.) Does the use of const enhance performance / save memory resources or something? Or is it just to prevent the change of a variable passed by referance?

4.) Why is there a return value to the += operator? Doesn't that make this legal:
v3 = ( v1 += v2 );
Or is this the reason the return type is set to Vector&, thereby returning v1 itself by referance? I think this is the case but not sure.

5.) In your Magnitude() function, this is being used:
sqrt(v.x * v.x + v.y * v.y)
Shouldn't we check that at least one of x and y is non-zero? Because in my (limited) experience, floating point numbers with a value of zero often give -0.0, which will then cause the sqrt() function to cause problems?

Thanks a lot for your help [smile]

Share this post


Link to post
Share on other sites
Hi Verminox.
You've asked some good questions there. With such an inquisitive nature, you'll go far [wink].

1. There is no real reason, but it makes the code slightly more readable and can help avoid associativity errors when dealing with many nested brackets. Though, like _goat said, it's mostly a matter of style.

2. Yes, no matter who befriends the function, it will belong to the namespace it is defined in. I pulled this concise snippet from the web somewhere:
"Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function, the friend class or function is a member of the innermost enclosing namespace."

3. Depending on the type and size of the variable in question, specifying 'const' can give performance gains or losses, but in most cases the difference is negligible. Performance purposes aside, 'const' should be used about as liberally as possible. The main reason being, of course, that it enforces good practice and helps avoid bugs. If you are really concerned about the speed difference, consider the effects of the compiler using an inline constant (as it usually will with constant primitives): If the variable is small and simple, inline compiling of consts will tend to free up registers and avoid unnecessary duplications and dereferences.

4. You're right, and that code snippet is perfectly valid, if a touch confusing.

5. A very keen observation [smile]. Taking the sqrt() of -0 will cause trouble. However, testing for equality against zero isn't a great idea as floats don't tend to equate too well. I'd guess that the best way to tackle this would be to fit a call to fabs() in before the sqrt().

Regards
Admiral

Share this post


Link to post
Share on other sites
Cool... Thanks [wink]

Edit: Still having the same problem.


A vector with both x and y negative does not work :(

Vector v;
v = Vector(0,0); //works
v = Vector(10,10); //works
v = Vector(-10,0); //works
v = Vector(0,-10); //works
v = Vector(-10,-10); //crashes



Edit 2: It's not a problem with the vector class, it was a problem when I was using sprintf() to get the x, y, etc. in a char buffer and then display it on the screen... I think it's called buffer overflow... when both X and Y had a negative sign to show it requird one more character than the size of the buffer.

I feel so stupid now :(

Thanks for the help guys!

[Edited by - Verminox on September 17, 2006 5:48:03 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Verminox
Edit 2: It's not a problem with the vector class, it was a problem when I was using sprintf() to get the x, y, etc. in a char buffer and then display it on the screen... I think it's called buffer overflow... when both X and Y had a negative sign to show it requird one more character than the size of the buffer.
Ah, the perils of C :-) Fortunately, you're using C++ so you have a bevy of superior tools at your disposal for text/string output and manipulation. I'd start by looking into streaming (e.g. std::cout).

Share this post


Link to post
Share on other sites
Yes but I'm not making a console app. I needed the string to be stored in a buffer to be used later with SDL_ttf to display on the screen, and specifically the std::sprintf() to format the floating numbers to the precision required and return them in string form... unfortnuately I don't know a way to make use of std::sprintf() for std::string... so I had to use a char buffer :\

Share this post


Link to post
Share on other sites
Quote:
Original post by Verminox
Yes but I'm not making a console app. I needed the string to be stored in a buffer to be used later with SDL_ttf to display on the screen, and specifically the std::sprintf() to format the floating numbers to the precision required and return them in string form... unfortnuately I don't know a way to make use of std::sprintf() for std::string... so I had to use a char buffer :\
This can all be done using streaming and stream manipulators. There may be a small learning curve involved, but (IMO) if you're going to program in C++ you'll be much better off using C++ idioms and constructs and the C++ standard library.

Don't worry though, it's a transition many people (especially self-taught) have to make. You learn from enough outdated tutorials or dig through enough Quake engine source code, and you think everything has to be '#define this' and 'char* that'. Then you gain a little more experience and start to discover that there are better solutions available in C++. At least that's how it went for me (ah, the days of tracking down endless char*-related bugs :).

Share this post


Link to post
Share on other sites
Even though you solved the problem, take my vectors, there are more overloaded operators. Ignore the vector multiplication, I added that without thinking. :)

Also why does your variable class contain two floats with the exact same name?


//Vector.h
class vec2f
{
public:
vec2f();
vec2f(float x, float y);
float GetX() const;
float GetY() const;
void SetX(float x);
void SetY(float y);
vec2f operator-=(const vec2f &b);
vec2f operator+=(const vec2f &b);
vec2f operator*=(const vec2f &b);
vec2f operator/=(const vec2f &b);
vec2f operator*=(const float &b);
vec2f operator/=(const float &b);
vec2f operator*=(const double &b);
vec2f operator/=(const double &b);
private:
float x, y;
};

vec2f operator-(const vec2f &a, const vec2f &b);
vec2f operator+(const vec2f &a, const vec2f &b);
vec2f operator*(const vec2f &a, const vec2f &b);
vec2f operator/(const vec2f &a, const vec2f &b);
vec2f operator*(const vec2f &a, const float &b);
vec2f operator/(const vec2f &a, const float &b);
vec2f operator*(const vec2f &a, const double &b);
vec2f operator/(const vec2f &a, const double &b);
vec2f operator*(const vec2f &a, const int &b);
vec2f operator/(const vec2f &a, const int &b);

//Vector.cpp
vec2f::vec2f(){
x = y = 0;
}

vec2f::vec2f(float x, float y){
this->x = x; this->y = y;
}

float vec2f::GetX() const{
return x;
}

float vec2f::GetY() const{
return y;
}

void vec2f::SetX(float x){ this->x = x; }

void vec2f::SetY(float y){ this->y = y; }

vec2f operator-(const vec2f &a, const vec2f &b){
return vec2f(a.GetX() - b.GetX(), a.GetY() - b.GetY());
}

vec2f operator+(const vec2f &a, const vec2f &b){
return vec2f(a.GetX() + b.GetX(), a.GetY() + b.GetY());
}

vec2f operator*(const vec2f &a, const vec2f &b){
return vec2f(a.GetX() * b.GetX(), a.GetY() * b.GetY());
}

vec2f operator/(const vec2f &a, const vec2f &b){
return vec2f(a.GetX() / b.GetX(), a.GetY() / b.GetY());
}

vec2f vec2f::operator-=(const vec2f &b){
this->SetX(this->GetX() - b.GetX()); this->SetY(this->GetY() - b.GetY());
return *this;
}

vec2f vec2f::operator+=(const vec2f &b){
this->SetX(this->GetX() + b.GetX()); this->SetY(this->GetY() + b.GetY());
return *this;
}

vec2f vec2f::operator*=(const vec2f &b){
this->SetX(this->GetX() * b.GetX()); this->SetY(this->GetY() * b.GetY());
return *this;
}

vec2f vec2f::operator/=(const vec2f &b){
this->SetX(this->GetX() / b.GetX()); this->SetY(this->GetY() / b.GetY());
return *this;
}

vec2f operator*(const vec2f &a, const float &b){
return vec2f(a.GetX() * b, a.GetY() * b);
}

vec2f operator/(const vec2f &a, const float &b){
return vec2f(a.GetX() / b, a.GetY() / b);
}

vec2f operator*(const vec2f &a, const double &b){
return vec2f(a.GetX() * b, a.GetY() * b);
}

vec2f operator/(const vec2f &a, const double &b){
return vec2f(a.GetX() / b, a.GetY() / b);
}

vec2f vec2f::operator*=(const float &b){
this->SetX(this->GetX() * b); this->SetY(this->GetY() * b);
return *this;
}

vec2f vec2f::operator/=(const float &b){
this->SetX(this->GetX() / b); this->SetY(this->GetY() / b);
return *this;
}

vec2f vec2f::operator*=(const double &b){
this->SetX(this->GetX() * b); this->SetY(this->GetY() * b);
return *this;
}

vec2f vec2f::operator/=(const double &b){
this->SetX(this->GetX() / b); this->SetY(this->GetY() / b);
return *this;
}


Share this post


Link to post
Share on other sites

This topic is 4107 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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