Skip to main content
GameDev.net gamedev.net
🔒 Locked

Vector class?

Started by Elfs1der Nov 13, 2005 at 11:33 PM 16 replies 1.9k views
Original Post
Elfs1der
Elfs1der
Hello I havent posted on here for a long time now... Anyways I'm going to school for Computer Programmer Analyst and I'm in the 3rd semester and we are doing win32 GDI right now. I was wondering how I could make a 2d vector class. I have looked around and the most trouble I'm having is understanding how to implement the vector physics on a win32 coordinate system. I know how to do stuff with vectors on paper but now on a screen. Any help? P.S. My goal is to make a billiards game....
Rainault
Rainault
It's been about a year since I've done physics, but let's see what I can remember.

You'll probably have two member variables, one for each vector coordinate. You should also have methods for calculating the magnitude and angle of the vector. Of course, you'll certainly need the Big 3 (copy constructor, destructor, assignment operator). It would also make sense to overload the addition, subtraction, equality, and multiplication (dot product) operators. As I recall, the cross product doesn't apply to a 2-dimensional plane, since it determines a vector orthogonal to the two vectors. I could be mistaken, though.

Anyway, you can use this as a rough start on building your vector class:

class Vector2D{public:   Vector2D( float initX, float initY );                // Constructor with initial coordinate values   Vector2D( const Vector2D & from );                   // Copy constructor   ~Vector2D();                                         // Destructor   Vector2D & operator=( const Vector2D & rhs );        // Overloaded assignment operator   bool operator==( const Vector2D & rhs ) const;       // Overloaded equality operator   Vector2D & operator+( const Vector2D & rhs ) const;  // Overloaded addition operator   Vector2D & operator-( const Vector2D & rhs ) const;  // Overloaded subtraction operator   float operator*( const Vector2D & rhs ) const;       // Overloaded multiplication operator (dot product)   float magnitude( void ) const;                       // Calculates the magnitude of the vector   float angle( void ) const;                           // Calculates the angle of the vector with respect to a particular axis (probably the x-axis)private:   float _xcoord;   float _ycoord;};
Elfs1der
Elfs1der
Thx Rainault. I get how to design my class and all, but more specifically how would i implement this class using the normal MM_TEXT mode coordinate system. I understand vectors on the normal cartesien plane, but not a display coordinate system.
Rainault
Rainault
Ech. Then this is probably something out of my league; I'm a rookie when it comes to anything beyond the bare basics in Windows programming. Sorry.
Fruny
Fruny
Quote:
Original post by Rainault
Of course, you'll certainly need the Big 3 (copy constructor, destructor, assignment operator).


Not for a simple numeric vector, no. The compiler-generated defaults are good enough.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
stylin
stylin
Quote:
Original post by Rainault
Anyway, you can use this as a rough start on building your vector class:

*** Source Snippet Removed ***

You shouldn't be returning references from +, -, * operators - what do you have to reference? Return a vector by value from these functions - additionally, I'd return a const vector by value, just so we can emulate the built-in types as much as possible.

You might want to give initial values for x and y, probably 0, which would necessitate an explicit constructor. Unless you took your +, -, * operators out of the class and made them free functions, because you want to support things like,
   vector2D v( 4,20 );   point2D p1( 6,9 );   point2D p2( v+p1 );     // works as expected   point2D p3( p2+v1 );    // <- this would fail with class member operators

then your constructor should be non-explicit to accept these implicit conversions.
:stylin: "Make games, not war." "...if you're doing this to learn then just study a modern C++ compiler's implementation." -snk_kid
Ravyne
Ravyne
The coordinate system is simply a convention, it makes no difference what coordinate system your vector class works in, you just use it and let something else (the drawing routine, for instance) worry about any conversion that may need to be done to give you meaningfull output. This is the same principle that both 2D and 3D games have used since their inception. 3D games use 3D vectors, which are converted to 2D equivilants for display via a process called projection.

While you're free to use what you would like, most vector classes use the standard coordinate system unless there's a compelling reason not to (for instance, using the polar coordinate system.)

Whatever case you choose, it has nothing to do with the coordinate space of the output medium, and if it does, you either have a fundamental misunderstanding or you're letting portions which should be seperate bleed into each other.
throw table_exception("(? ???)? ? ???");
_goat
_goat
Feel free to use mine as a template - on the proviso (and I'll take it on good faith), that you'll use it knowing full-well how it works and why. I believe it's watertight - if it's not someone will point it out - and I hope they do.

//#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#//#//#		Name:	SVector2//#//#		Desc:	A 2d vector//#//#		Gods:	Jonathan "goat" Runting//#//#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#struct SVector2{	//=====================================================================	// constructors	//=====================================================================	SVector2 () : x(0), y(0)	{	}	SVector2 (float _x, float _y) : x(_x), y(_y)	{	}	//=====================================================================	// addition	//=====================================================================	inline friend const SVector2 operator + (const SVector2& lhs, const SVector2& rhs)	{		return SVector2(lhs.x + rhs.x, lhs.y + rhs.y);	}		//=====================================================================	// subtraction	//=====================================================================	inline friend const SVector2 operator - (const SVector2& lhs, const SVector2& rhs)	{		return SVector2(lhs.x - rhs.x, lhs.y - rhs.y);	}		//=====================================================================	// multiplication	//=====================================================================	inline friend const SVector2 operator * (const SVector2& lhs, float rhs)	{		return SVector2(lhs.x * rhs, lhs.y * rhs);	}		inline friend const SVector2 operator * (float lhs, const SVector2& rhs)	{		return SVector2(rhs.x * lhs, rhs.y * lhs);	}		//=====================================================================	// division	//=====================================================================	inline friend const SVector2 operator / (const SVector2& lhs, float rhs)	{		return SVector2(lhs.x * rhs, lhs.y * rhs);	}		inline friend const SVector2 operator / (float lhs, const SVector2& rhs)	{		return SVector2(rhs.x / lhs, rhs.y / lhs);	}			//=====================================================================	// self-addition	//=====================================================================	inline SVector2& operator += (const SVector2& rhs)	{		x += rhs.x; y += rhs.y;		return *this;	}	//=====================================================================	// self-subtraction	//=====================================================================	inline SVector2& operator -= (const SVector2& rhs)	{		x -= rhs.x; y -= rhs.y;		return *this;	}		//=====================================================================	// self-mutliplication	//=====================================================================	inline SVector2& operator *= (const SVector2& rhs)	{		x *= rhs.x; y *= rhs.y;		return *this;	}		//=====================================================================	// self-division	//=====================================================================	inline SVector2& operator /= (const SVector2& rhs)	{		x /= rhs.x; y /= rhs.y;		return *this;	}			//=====================================================================	// assignment	//=====================================================================	inline SVector2& operator = (const SVector2& rhs)	{		x = rhs.x; y = rhs.y;		return *this;	}	//=====================================================================	// equality	//=====================================================================	inline friend bool operator == (const SVector2& lhs, const SVector2& rhs)	{		return (lhs.x == rhs.x && lhs.y == rhs.y);	}		//=====================================================================	// inequality	//=====================================================================	inline friend bool operator != (const SVector2& lhs, const SVector2& rhs)	{		return (lhs.x != rhs.x || lhs.y != rhs.y);	}		//=====================================================================	// Calculate the dot-product	//=====================================================================	inline friend float DotProduct(const SVector2& v1, const SVector2& v2)	{		return (v1.x * v2.x) + (v1.y * v2.y);	}		//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	// data	//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	float x, y;};
Enigma
Enigma
Quote:
Original post by _goat
Feel free to use mine as a template - on the proviso (and I'll take it on good faith), that you'll use it knowing full-well how it works and why. I believe it's watertight - if it's not someone will point it out - and I hope they do.

*** Source Snippet Removed ***

It looks pretty sound. There are a few points I would question though.

The most important is the lack of consistency in your division operators:
SVector2 a, b;SVector2 c = a / 3; // c = {a.x * 3, a.y * 3}SVector2 d = 3 / a; // d = {a.x / 3, a.y / 3}SVector2 e = a / b; // illegalSVector2 f(a);f /= 3; // illegalSVector2 g(a);g /= b; // g = {a.x / b.x, a.y / b.y}

My expectation would be that c and f would mean what d means and d, e and g would be illegal.
Next, I would advise implementing operatorX in terms of operatorX=. So, for example, operator+ would be implemented as:
SVector2 const operator+(SVector2 lhs, SVector2 const & rhs){	return lhs += rhs;}

(Note that lhs is passed by value, not reference). This has the advantage that you do not have multiple implementations doing (almost) exactly the same thing and any changes to the class result in fewer source changes being required. It also helps avoid inconsistency like above.

Finally it is an interesting choice to make your free functions inline friends defined within the class. This has several consequences, although none of them are bad in your case. Functions defined in this way can only ever have external linkage. inline is redundant for a friend functions defined within the class definition, such functions are implicitly inline (although there's no guaranteeing that your compiler will get this right, so it can't hurt to include the inline keyword). Functions defined in this way are in the lexical scope of the class. I'm not entirely sure what that actually means in practice (and neither, it appears, are my compilers) but it can cause problems if any of the functions do not take a parameter of the enclosing class type (Under MSVC++ 7.1 it appears friend functions defined within a class can only be found via Koenig lookup). Of course, there's no need to make the functions friends either since SVector2 has no non-public members. The only use of the friend keyword here appears to be that it allows you to define free functions inside the class. It works in this case, but could bite you in other cases for the reasons explained above.

Enigma
stylin
stylin
Quote:
Original post by Enigma
The only use of the friend keyword here appears to be that it allows you to define free functions inside the class. It works in this case, but could bite you in other cases for the reasons explained above.

Enigma

Or it could be your lifeline. Free-functions (as opposed to member functions) are the only way to convert type on all arguments. If SVector2 was a class template, to get the correct function instantiated it needs to be declared inside the class, and how do you do that? With friendship.
:stylin: "Make games, not war." "...if you're doing this to learn then just study a modern C++ compiler's implementation." -snk_kid
Enigma
Enigma
My point was that in this case a non-member non-friend would be just as valid since SVector2 only has public members.

EDIT: Also the "biting" was in reference to friends defined within a class, not friend declarations in general. Maybe I didn't make that as clear as I had intended.

Enigma
_goat
_goat
Haha, the multiplication was a (horrendeous) typo made from copy-pasting over the multiplication code and not changing all the symbols. I have actually yet to use SVector2 at all, so I hadn't had to catch the error yet. My SVector3 class is free of stupid things like this. [grin]

Thanks for the feedback - the use of friend functions was simply because it seemed like a good spot to put them, and I figured it didn't hurt. I won't be changing it though, haha.
stylin
stylin
Quote:
Original post by Enigma
My point was that in this case a non-member non-friend would be just as valid since SVector2 only has public members.

Certainly, and considering that, it probably should be - since being a friend adds nothing but possible confusion as to why it's a friend.

@ goat : if your 3d vector is sufficiently stable, have you thought about parameterizing your dimensions and/or data type? I've been noticing alot of vector threads lately, I'm sure it would benefit more than a few. [smile]
:stylin: "Make games, not war." "...if you're doing this to learn then just study a modern C++ compiler's implementation." -snk_kid
_goat
_goat
I threw the following together:

Note that CrossProduct has been taken out because the cross product only exists for vectors of dimensions 3 and 7. Also, one could templaterize Magnitude if they really needed to (although sqrt only takes float and double I believe) - I didn't see the point, really. I've also left DotProduct and Normalise using (and returning) floats. I guess this part is up to other people to change if necessary.

//#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#//#//#		Name:	SVector//#//#		Desc:	An N dimensional vector//#//#		Gods:	Jonathan "goat" Runting//#//#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=##include <vector>//#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#template <unsigned long N, typename T = float> struct SVector{	//=====================================================================	// constructors	//=====================================================================	SVector()	{		for (unsigned long i = 0; i < N; i++) elements.push_back(0);	}	//=====================================================================	// addition	//=====================================================================	inline friend const SVector<N, T> operator + (const SVector<N, T>& lhs, const SVector<N, T>& rhs)	{		SVector<N, T> v;		for (unsigned long i = 0; i < N; i++) v.elements = lhs.elements + rhs.elements;		return v;	}		//=====================================================================	// subtraction	//=====================================================================	inline friend const SVector<N, T> operator - (const SVector<N, T>& lhs, const SVector<N, T>& rhs)	{		SVector<N, T> v;		for (unsigned long i = 0; i < N; i++) v.elements = lhs.elements - rhs.elements;		return v;	}		//=====================================================================	// multiplication	//=====================================================================	template <typename Y> friend const SVector<N, T> operator * (const SVector<N, T>& lhs, const Y& rhs)	{		SVector<N, T> v;		for (unsigned long i = 0; i < N; i++) v.elements = lhs.elements * rhs;		return v;	}		template <typename Y> friend const SVector<N, T> operator * (const Y& lhs, const SVector<N, T>& rhs)	{		SVector<N, T> v;		for (unsigned long i = 0; i < N; i++) v.elements = rhs.elements * lhs;		return v;	}		//=====================================================================	// division	//=====================================================================	template <typename Y> friend const SVector<N, T> operator / (const SVector<N, T>& lhs, const Y& rhs)	{		SVector<N, T> v;		for (unsigned long i = 0; i < N; i++) v.elements = lhs.elements / rhs;		return v;	}		template <typename Y> friend const SVector<N, T> operator / (const Y& lhs, const SVector<N, T>& rhs)	{		SVector<N, T> v;		for (unsigned long i = 0; i < N; i++) v.elements = rhs.elements / lhs;		return v;	}	//=====================================================================	// self-addition	//=====================================================================	inline SVector<N, T>& operator += (const SVector<N, T>& rhs)	{		for (unsigned long i = 0; i < N; i++) elements += rhs.elements;		return *this;	}		//=====================================================================	// self-subtraction	//=====================================================================	inline SVector<N, T>& operator -= (const SVector<N, T>& k)	{		for (unsigned long i = 0; i < N; i++) elements -= rhs.elements;		return *this;	}		//=====================================================================	// self-multiplication	//=====================================================================	template <typename Y> SVector<N, T>& operator *= (const Y& rhs)	{		for (unsigned long i = 0; i < N; i++) elements *= rhs;		return *this;	}		//=====================================================================	// self-division	//=====================================================================	template <typename Y> SVector<N, T>& operator /= (const Y& rhs)	{		for (unsigned long i = 0; i < N; i++) elements *= rhs;		return *this;	}	//=====================================================================	// assignment	//=====================================================================	inline SVector<N, T>& operator = (const SVector<N, T>& rhs)	{		for (unsigned long i = 0; i < N; i++) elements = rhs.elements;		return *this;	}		//=====================================================================	// equality	//=====================================================================	inline bool operator == (const SVector<N, T>& rhs) const	{		for (unsigned long i = 0; i < N; i++) if (elements != rhs.elements) return false;		return true;	}		//=====================================================================	// inequality	//=====================================================================	inline bool operator != (const SVector<N, T>& k) const	{		for (unsigned long i = 0; i < N; i++) if (elements != rhs.elements) return true;		return false;	}	//=====================================================================	// Dot-product	//=====================================================================	friend float DotProduct(const SVector<N, T>& v1, const SVector<N, T>& v2)	{		float result = 0;		for (unsigned long i = 0; i < N; i++) result += float(v1.elements * v2.elements);		return result;	}		//=====================================================================	// Normalisation	//=====================================================================	inline friend SVector<N, T> Normalize(const SVector<N, T>& v)	{		float m = Magnitude(v);		if (m == 1) return v;		return SVector<N, T>(v) / m;	}		//=====================================================================	// Magnitude	//=====================================================================	friend float Magnitude(const SVector<N, T>& v)	{		float result = 0;		for (unsigned long i = 0; i < N; i++) result += float(v.elements);		return sqrt(result);	}		//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	// data	//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	std::vector<T> elements;};
Elfs1der
Elfs1der
Wow thanks for the replys, but some of that stuff is insanely advanced for me. I know classes really well but im a bit of a noob when it comes to class templating. How would I convert the coordinate systems.
_goat
_goat
Well, the idea behind the templatized SVector is that it's the only class you'll ever need for vectors.

SVector<3> a_3d_point;SVector<2> a_2d_point;SVector<56> this_probably_is_not_useful___but_possible; // this is for ease of useinline SVector<3> SVector_3(float x, float y, float z){	SVector<3> v;	v.elements[0] = x;	v.elements[1] = y;	v.elements[2] = z;	return v;}
Hey, does anyone know an easy way to insert a Tab in here, short of copy-pasting from an open project?
Rob Loach
Rob Loach
SDL.NET's vector.cs is available with a LGPL license and targeted for 2D development. It's written in C# so you might have to do some translating. Good luck!
Rob Loach [Website] [Projects] [
Ravyne
Ravyne
Quote:
Original post by _goat
I threw the following together:

Note that CrossProduct has been taken out because the cross product only exists for vectors of dimensions 3 and 7.


Even though its not technically a cross product, an equivilant method which returns the z component of what would be a 3D vector (equivilantly, the determinant of the matrix formed by the 2D vectors) is usefull. Depending on its sign, it can tell you the relative orientation between the vectors.
throw table_exception("(? ???)? ? ???");

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.