This is an improved Vector2 class I'm working on with suggestions from Leander Hasting, though I haven't touched upon all of them yet. Beyond that, some of the critiques seem subjective to a point, and other professionals he pointed me to, make the same argument; and that is the argument of the use 0f non-member functions. With intellisense working, I find using member functions to be the easiest way of doing things, as I think it is easier for other people to work with; as someone who's trying to write an engine, I understand the importance of including extra functions in a separate header file, though I think that can get messy. So, with what is proposed you could do VectorUtil::len(vector), though having the VectorUtilthere is ugly, though without it there, how do you know that function even exists or where it's coming from? So for starters, it's more useful to split the vector up like this, because everything doesn't need access to these functions, and it helps build times. Though, in the end, when this engine is done, all of the information is most likely going to be included anyway; lets say I want to add scripting support with my own version of intellisense, I want to make it as easy as possible for someone not used to my engine to be able to interactively see what they can do with a Vector by simply using "vector." and everything they have available to them pop up. So, because the functions are accessing public members, they have no need to be member functions; so while some may claim using member functions breaks encapsulation, I think it makes it easier to open up a class to the user and what it can do. Though it's interesting that intellisense in Visual Studio also shows private and protected functions you don't have access to...
Anyway, what I would like to do with my code is make it as easy as possible for anyone to use, I'm not worried that much about technicalities and hardcore programmers squirming over things I don't care about. Though that makes me wonder, should I care about that, and perhaps just take it with a grain of salt?
Anyway, here's my Vector2 class in it's improved glory.
Vector2.h:
/* __ __ ___ _____ ____
* \ \ / / / _ \ | __ \ | |
* \ \/\/ / / / \ \ | | / / | __|
* \_/\_/ /_/ \_\ |_| \_\ |_|
* Take it to the next Level
*
* Copyright (c) 2009 Brian Ernst.
*/
#ifndef w_Vector2
#define w_Vector2
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
namespace _Warp
{
typedef float Scalar;
typedef int Bool;
class Vector2
{
public:
Scalar X;
Scalar Y;
Vector2();
Vector2(Scalar x, Scalar y);
Vector2& operator+=(const Vector2& pVector);
Vector2& operator-=(const Vector2& pVector);
Vector2& operator*=(Scalar num);
Vector2& operator/=(Scalar num);
Bool operator!=(const Vector2& vect) const;
Bool operator==(const Vector2&; vect) const;
friend Vector2 operator-(Vector2& vect);
friend Vector2 operator+(const Vector2& vect1,const Vector2& vect2);
friend Vector2 operator-(const Vector2& vect1,const Vector2& vect2);
friend Vector2 operator*(const Vector2& vect1, Scalar value);
friend Vector2 operator/(const Vector2& vect1, Scalar value);
friend Vector2 operator*(const Vector2& vect1,const Vector2& vect2);
friend Vector2 operator/(const Vector2& vect1,const Vector2& vect2);
};
inline Vector2::Vector2()
{
}
inline Vector2::Vector2(Scalar x,Scalar y)
: X( x )
, Y( y )
{
}
inline Bool Vector2::operator!=(const Vector2& vector) const
{
return X != vector.X || Y != vector.Y;
}
inline Bool Vector2::operator==(const Vector2& vect) const
{
return (*this != vect) == FALSE;
}
inline Vector2& Vector2::operator+=(const Vector2& pVector)
{
X += pVector.X;
Y += pVector.Y;
return *this;
}
inline Vector2& Vector2::operator-=(const Vector2& pVector)
{
X -= pVector.X;
Y -= pVector.Y;
return *this;
}
inline Vector2& Vector2::operator*=(Scalar num)
{
X *= num;
Y *= num;
return *this;
}
inline Vector2& Vector2::operator/=(Scalar num)
{
X /= num;
Y /= num;
return *this;
}
inline Vector2 operator+(const Vector2& vect1,const Vector2& vect2)
{
return Vector2(vect1.X + vect2.X,vect1.Y + vect2.Y);
}
inline Vector2 operator-(const Vector2& vect1,const Vector2& vect2)
{
return Vector2(vect1.X - vect2.X,vect1.Y - vect2.Y);
}
inline Vector2 operator-(Vector2& vect)
{
return vect * -1;
}
inline Vector2 operator*(const Vector2& vect1, Scalar value)
{
return Vector2(vect1.X * value,vect1.Y * value);
}
inline Vector2 operator/(const Vector2& vect1, Scalar value)
{
return Vector2(vect1.X / value,vect1.Y / value);
}
inline Vector2 operator*(const Vector2& vect1,const Vector2& vect2)
{
return Vector2(vect1.X * vect2.X,vect1.Y * vect2.Y);
}
inline Vector2 operator/(const Vector2& vect1,const Vector2& vect2)
{
return Vector2(vect1.X / vect2.X,vect1.Y / vect2.Y);
}
}
#endif
VectorUtil.h
/* __ __ ___ _____ ____
* \ \ / / / _ \ | __ \ | |
* \ \/\/ / / / \ \ | | / / | __|
* \_/\_/ /_/ \_\ |_| \_\ |_|
* Take it to the next Level
*
* Copyright (c) 2009 Brian Ernst.
*/
#ifndef w_VectorUtil
#define w_VectorUtil
#include <cmath>
using std::sin;
using std::cos;
using std::sqrt;
using std::atan2;
#include "Vector2.h"
namespace _Warp
{
class Vector2Util
{
static Scalar len(const Vector2& vect);
static Scalar len2(const Vector2& vect);
static Vector2& invert(Vector2& vect);
static Vector2& normalize(Vector2& vect);
static Vector2& clamp(Vector2& vect, Scalar value);
static Vector2& setLength(Vector2& vect, Scalar value);
static Vector2 polar (Scalar x, Scalar y);
static Vector2 cartesian (Scalar radius, Scalar angle);
static Scalar dot (const Vector2& pVec1, const Vector2& pVec2);
static Vector2 rotate (const Vector2& pVec, Scalar angle);
};
inline Scalar len(const Vector2& vect)
{
return sqrt(vect.X * vect.X + vect.Y * vect.Y);
}
inline Scalar len2(const Vector2& vect)
{
return vect.X * vect.X + vect.Y * vect.Y;
}
inline Vector2& invert(Vector2& vect)
{
return vect *= -1;
}
inline Vector2& normalize(Vector2& vect)
{
if(vect.X == 0 && vect.Y == 0)
{
return vect;
}
Scalar length = len(vect);
vect.X /= length;
vect.Y /= length;
return vect;
}
inline Vector2& clamp(Vector2& vect,Scalar value)
{
Scalar length = len2(vect);
if(length <= value * value)
{
return vect;
}
length = sqrt(length);
vect.X *= value / length;
vect.Y *= value / length;
return vect;
}
inline Vector2& setLength(Vector2& vect, Scalar value)
{
if(vect.X == 0 && vect.Y == 0)
{
return vect;
}
Scalar length = len(vect);
vect.X *= value / length;
vect.Y *= value / length;
return vect;
}
inline Vector2 cartesian(Scalar radius,Scalar angle)
{
return Vector2(radius * cos(angle),radius * sin(angle));
}
inline Vector2 polar(Scalar x,Scalar y)
{
return Vector2(atan2(y,x),sqrt(x * x + y * y));
}
inline Scalar dot(const Vector2& pVec1,const Vector2& pVec2)
{
return pVec1.X * pVec2.X + pVec1.Y * pVec2.Y;
}
inline Vector2 rotate(const Vector2& pVec,Scalar angle)
{
Scalar cosResult = cos(angle);
Scalar sinResult = sin(angle);
//Essentially, apply a 2x2 rotation matrix to the vector
Scalar newX = pVec.X * cosResult - pVec.Y * sinResult;
Scalar newY = pVec.X * sinResult + pVec.Y * cosResult;
return Vector2(newX,newY);
}
}
#endif
Notes:
With this class, you can't do this:
Vector2 vect = 2;
This was possible with my old Vector2 class, and frankly should be an error. If you are going to initialize the vars of a Vector2 at a later time, you can do this:
Vector2 vect = Vector2(Vector2::Uninitialized());
The reason for this is, doing this:
Vector2 vect = Vector2();
...creates a Vector2(0,0) by default. I could setup a default constructor and not use default values, however that could result in bugs from coders not properly initializing Vector2 objects. So, it is this reason the Uninitialized class is being used.
Feel free to use this improved Vector2 class, and if the c-style functions in VectorUtil bug you, feel free to make them member functions again in Vector2.
Download the Vector2 improved here: Vector2 class files.
[Update: As of 5/11/10 I noticed a typo, which I fixed, however I need to update the source files. I've also realized there are also a couple other utility functions I could "add", so will do so sometime soon; I got the idea from looking at another blog, however I realized he just named the functions to something easily understandable. For example, the dot and cross product; their names don't say anything about what the function really does, what the data represents. So, it seems useful to package up those functions to make it easier for programmers using this utility, and of course leaving the original functions in-tact (perhaps?) so other programmers looking for them won't be confused when they think they can't find them. So, more cool things to come! :) ]
Comments