Posted
Comments 0

Part 4 of C++ tutorial – a 3D vector & transform library

The classic vector operations are the dot & cross products.

Here are the respective helper functions:

double dot_product(const Vector& v) const { return x*v.x+y*v.y+z*v.z; }
Vector cross_product(const Vector& v) const { return Vector(y*v.y-v.y*z,v.x*z-x*v.y,x*v.y-v.x*y); }

The dot product returns a scalar, whereas the cross product returns a vector (more specifically, a normal).

For the dot operation, one might think one could try and overload the dot operator (not legal C++), but it is a member selection operator rather than an arithmetic one, so its semantics don’t map. However, we could overload the multiply operator, but it is not clear whether it should indicate dot or cross product. To maintain clarity, it’s probably best if dot and cross products are named explicitly.

One could provide friend functions taking two arguments, e.g. dot_product(u,v). However, I think it’s going to provide greater clarity upon use if member functions taking single arguments are used, e.g. u.dot(v). That way, more complicated expressions are easier to read, e.g. (<vector expression>).dot(<vector expression>) vs dot_product(<vector expression>,<vector expression>). As they are acting as operators (as opposed to public methods), I’m inclined to name them in lower case.

Therefore the Vector methods for dot and cross products are:

double dot(const Vector& v) const { return dot_product(v); }
Vector cross(const Vector& v) const { return cross_product(v); }	// NB Result is Normal vector

For example:

cout<<"u.v="<<u.dot(v)<<"\n";
cout<<"u x v="<<u.cross(v)<<"\n";

Which outputs:

u=(1,2,3)
v=(-1,3,-6)
u.v=-13
u x v=(-3,-6,5)

Complete code:

#include <iostream>	// For console output functionality

using namespace std;	// Avoids having to use std:: scoping prefix

class Vector	// A vector class with addition and subtraction functionality
{
public:
	double x,y,z;	// Representation

protected:
	// Non-modifying functions
	Vector addition(const Vector& v) const { return Vector(x+v.x,y+v.y,z+v.z); }
	Vector subtraction(const Vector& v) const { return Vector(x-v.x,y-v.y,z-v.z); }
	Vector multiplication(double m) const { return Vector(x*m,y*m,z*m); }
	Vector division(double d) const { if (d) return Vector(x/d,y/d,z/d); else throw std::runtime_error("Divide by Zero"); }
	double dot_product(const Vector& v) const { return x*v.x+y*v.y+z*v.z; }
	Vector cross_product(const Vector& v) const { return Vector(y*v.y-v.y*z,v.x*z-x*v.y,x*v.y-v.x*y); }


	// Modifying methods
	void assign(const Vector& v) { x=v.x; y=v.y; z=v.z; }
	void add(const Vector& v) { assign(addition(v)); }
	void subtract(const Vector& v) { assign(subtraction(v)); }
	void multiply_by(double m) { assign(multiplication(m)); }
	void divide_by(double d) { assign(division(d)); }

public:
	Vector(double a,double b,double c):x(a),y(b),z(c) { }

	double dot(const Vector& v) const { return dot_product(v); }
	Vector cross(const Vector& v) const { return cross_product(v);  }

	Vector& operator+=(const Vector& v) { add(v); return *this; }
	Vector& operator-=(const Vector& v) { subtract(v); return *this; }

	friend Vector operator+(const Vector& u,const Vector& v) { return Vector(u.addition(v)); }
	friend Vector operator-(const Vector& u,const Vector& v) { return Vector(u.subtraction(v)); }

	Vector& operator*=(double m) { multiply_by(m); return *this; }
	Vector& operator/=(double d) { divide_by(d); return *this; }	// Throws div0

	friend Vector operator*(const Vector& v,double m) { return Vector(v.multiplication(m)); }
	friend Vector operator*(double m,const Vector& v) { return Vector(v.multiplication(m)); }
	friend Vector operator/(const Vector& v,double d) { return Vector(v.division(d)); }

	friend ostream& operator<<(ostream& os,const Vector& v)
	{ return os<<'('<<v.x<<','<<v.y<<','<<v.z<<')'; }	// Output, e.g. (1,2,3)
};

int main()	// The program
{	Vector u(1,2,3);	// A test vector
	Vector v(-1,3,-6);	// A test vector

	cout<<"u="<<u<<"\n";
	cout<<"v="<<v<<"\n";

	cout<<"u.v="<<u.dot(v)<<"\n";
	cout<<"u x v="<<u.cross(v)<<"\n";

}

Author

Posted
Comments 0

Part 3b of C++ tutorial – a 3D vector & transform library

Before continuing, one should pause to check the objectives and principles to be adopted in developing this library.

The library should be easy to use, and ideally more attractive to use than the option of writing one’s own library from scratch (for most users).

It should be unsurprising and predictable, with as few ‘gotchas’ as possible.

It should be useful, and cover the essential areas in terms of functionality.

It should focus on 3D, i.e. not cater for 2D or more than 3 dimensions.

It should not sacrifice usability/convenience in pursuit of flexibility or efficiency.

Author

Posted
Comments 0

Part 3 of C++ tutorial – a 3D vector & transform library

Multiplying/dividing vectors by a scalar is probably the next overloaded operator to implement.

Thus we have the respective helper functions:

Vector multiplication(double m) const { return Vector(x*m,y*m,z*m); }
Vector division(double d) const { if (d) return Vector(x/d,y/d,z/d);
else throw runtime_error("Divide by Zero"); }

Note that it is here, in the division function, that we can throw an exception in the event that it is attempted to divide a vector by zero. If you know of a superior way of handling this, post it in a comment.

The corresponding modifying methods are:

void multiply_by(double m) { assign(multiplication(m)); }
void divide_by(double d) { assign(division(d)); }

The respective overloaded operators can be seen in the complete code here:

#include <iostream>	// For console output functionality

using namespace std;	// Avoids having to use std:: scoping prefix

class Vector	// A vector class with addition and subtraction functionality
{
public:
	double x,y,z;	// Representation

protected:
	// Non-modifying functions
	Vector addition(const Vector& v) const { return Vector(x+v.x,y+v.y,z+v.z); }
	Vector subtraction(const Vector& v) const { return Vector(x-v.x,y-v.y,z-v.z); }
	Vector multiplication(double m) const { return Vector(x*m,y*m,z*m); }
	Vector division(double d) const { if (d) return Vector(x/d,y/d,z/d); else throw std::runtime_error("Divide by Zero"); }

	// Modifying methods
	void assign(const Vector& v) { x=v.x; y=v.y; z=v.z; }
	void add(const Vector& v) { assign(addition(v)); }
	void subtract(const Vector& v) { assign(subtraction(v)); }
	void multiply_by(double m) { assign(multiplication(m)); }
	void divide_by(double d) { assign(division(d)); }

public:
	Vector(double a,double b,double c):x(a),y(b),z(c) { }

	Vector& operator+=(const Vector& v) { add(v); return *this; }
	Vector& operator-=(const Vector& v) { subtract(v); return *this; }

	friend Vector operator+(const Vector& u,const Vector& v) { return Vector(u.addition(v)); }
	friend Vector operator-(const Vector& u,const Vector& v) { return Vector(u.subtraction(v)); }

	Vector& operator*=(double m) { multiply_by(m); return *this; }
	Vector& operator/=(double d) { divide_by(d); return *this; }	// Throws div0

	friend Vector operator*(const Vector& v,double m) { return Vector(v.multiplication(m)); }
	friend Vector operator*(double m,const Vector& v) { return Vector(v.multiplication(m)); }
	friend Vector operator/(const Vector& v,double d) { return Vector(v.division(d)); }

	friend ostream& operator<<(ostream& os,const Vector& v)
	{ return os<<'('<<v.x<<','<<v.y<<','<<v.z<<')'; }	// Output, e.g. (1,2,3)
};

int main()	// The program
{	Vector v(1,2,3);	// A test vector

	cout<<"v="<<v<<"\n";
	cout<<"3*v="<<3*v<<"\n";
	cout<<"v*3="<<v*3<<"\n";
	cout<<"v/2="<<v/2<<"\n";
}

Note that because multiplication of a vector by a scalar should be commutative, both operator overloads are provided. However, division of a scalar by a vector is a different kettle of fish to a vector divided by a scalar – the latter being equivalent to multiplication by the reciprocal of the scalar divisor, thus: v/s == v*(1/s).

This program outputs the following:

v=(1,2,3)
3*v=(3,6,9)
v*3=(3,6,9)
v/2=(0.5,1,1.5)

Author

Posted
Comments 0

Part 2 of C++ tutorial – a 3D vector & transform library

To avoid laboriously adding a vectors via addition of their elements, one can improve the facility of the vector class by adding arithmetic operator overloads for addition and subtraction.

My preferred approach is to implement additional functionality in protected const helper functions, that can be used directly by all other methods, whether const or non-const, public or friend.

Thus the first thing to do is to create a helper function that calculates the addition of a supplied vector to this object and returns the resulting vector.

Thus: Vector addition(const Vector& v) const { return Vector(x+v.x,y+v.y,z+v.z); }

A modifying helper method that adds the supplied vector to this object, setting this object with the result, can be implemented using a simple assignment function, thus:

void assign(const Vector& v) { x=v.x; y=v.y; z=v.z; }
void add(const Vector& v) { assign(addition(v)); }

The overloaded operators can then be written succinctly in terms of helpers, thus:

Vector& operator+=(const Vector& v) { add(v); return *this; }
friend Vector operator+(const Vector& u,const Vector& v) { return Vector(u.addition(v)); }

Subtraction is simply a matter of changing the sign.

The complete code is below:

#include <iostream>	// For console output functionality

using namespace std;	// Avoids having to use std:: scoping prefix

class Vector	// A vector class with addition and subtraction functionality
{
public:
	double x,y,z;	// Representation

protected:
	// Non-modifying functions
	Vector addition(const Vector& v) const { return Vector(x+v.x,y+v.y,z+v.z); }
	Vector subtraction(const Vector& v) const { return Vector(x-v.x,y-v.y,z-v.z); }

	// Modifying methods
	void assign(const Vector& v) { x=v.x; y=v.y; z=v.z; }
	void add(const Vector& v) { assign(addition(v)); }
	void subtract(const Vector& v) { assign(subtraction(v)); }

public:
	Vector(double a,double b,double c):x(a),y(b),z(c) { }

	Vector& operator+=(const Vector& v) { add(v); return *this; }
	Vector& operator-=(const Vector& v) { subtract(v); return *this; }

	friend Vector operator+(const Vector& u,const Vector& v) { return Vector(u.addition(v)); }
	friend Vector operator-(const Vector& u,const Vector& v) { return Vector(u.subtraction(v)); }

	friend ostream& operator<<(ostream& os,const Vector& v)
	{ return os<<'('<<v.x<<','<<v.y<<','<<v.z<<')'; }	// Output, e.g. (1,2,3)
};

int main()	// The program
{	Vector v(1,2,3);	// A test vector

    cout << "v="<<v<<"\n";	// Output it

	Vector u(-1,-3,-5);

	cout<<"u="<<u<<"\n";

	Vector s=Vector(u.x+v.x,u.y+v.y,u.z+v.z);	// Sum of two vectors

	cout<<"u+v="<<s<<"\n";

	cout<<"u+v="<<u+v<<"\n";
	cout<<"u-v="<<u-v<<"\n";

}

Program output is:

v=(1,2,3)
u=(-1,-3,-5)
u+v=(0,-1,-2)
u+v=(0,-1,-2)
u-v=(-2,-5,-8)

Author

Posted
Comments 0

If the first program that C++ coders write is “Hello World”, then a definition of a 3D vector class is probably the second.

Let’s have a go:

#include <iostream>	// For console output functionality

using namespace std;	// Avoids having to use std:: scoping prefix

class Vector
{
public:
	double x,y,z;	// Representation

	Vector(double a,double b,double c):x(a),y(b),z(c) { }	// Constructor

	friend ostream& operator<<(ostream& os,const Vector& v)	// Non-member, thus must be friend, but defined inline
	{ return os<<'('<<v.x<<','<<v.y<<','<<v.z<<')'; }	// Output, e.g. (1,2,3)
};

int main()	// The program
{	Vector v(1,2,3);	// A test vector

    cout << "v="<<v<<"\n";	// Output it
}

When this is run it produces the following output:

v=(1,2,3)

There’s not much we can do with this vector. We can read and write to the individual elements (x,y,z), but addition is labourious, e.g.

Vector u(-1,-3,-5);

cout<<"u="<<u<<"\n";

Vector s=Vector(u.x+v.x,u.y+v.y,u.z+v.z);	// Sum of two vectors

cout<<"u+v="<<s<<"\n";

When this is run it adds the following output:

u=(-1,-3,-5)
u+v=(0,-1,-2)

Author