Other Vector operations

Posted
Comments 0

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

What remains to implement for the Vector class is rather ancillary, i.e. constructors and conversions, element access, and informational functions.

The Vector is represented by three doubles: x,y,z. However, it is also an array of three doubles, i.e. double [3]. Although the Microsoft C++ compiler permits anonymous structs which would allow the use of a union to enable the superimposition of double x,y,z with double[3], for portability, I will use casts and conversion operators to implement this. The array is simply the address of the first element, i.e. &x. Accessing elements can be done by overloading the array element operator, which permits bounds checking. The helper methods are thus:

double* array() { return &x; }

double& element(int i)
{	switch (i)
	{	case 0: return x; case 1: return y; case 2: return z;
		default: throw std::out_of_range("Element index");
	}
}

These are also implemented in const form, but note that the const element method returns a const reference rather than a value – being an accessor rather than a function returning a value.

These become public operators of the Vector class as follows:

explicit operator const double* () const { return array(); }
explicit operator double* () { return array(); }

const double& operator[](int i) const { return element(i); }
double& operator[](int i) { return element(i); }

Array conversion operators must be made explicit, otherwise invalid vector arithmetic expressions are liable to be converted into pointer arithemtic expressions. Otherwise, a Vector is just as much an array as it is x,y,z.

Another point on arrays to note is that C++ does not encode the size of the array when matching arguments, i.e. an array of 4 doubles will match an array of 3. Thus the following constructor (from an array of 3 doubles) will take any sized array or pointer-to-double.

explicit Vector(const double p[3]):x(p[0]),y(p[1]),z(p[2]) { }

Thus, it should be explicit to improve readability and safety, e.g. an array of three doubles (or object with an implicit conversion to such) is not necessarily a vector.

There is a special vector, which is the null vector. There are thus a few members concerned with this:

bool is_null() const { return !x&&!y&&!z; }
explicit operator bool() const { return !is_null(); }	// Don't want Vector implicitly converted to int
bool operator!() const { return is_null(); }
static const Vector null;

The ‘null’ member is initialised as const Vector Vector::null=Vector(0,0,0); and may sometimes be useful (referenced via Vector::null).

I have implemented an explicit cast of a vector to a double, by way of providing a means to obtaining the vector’s magnitude. I would argue that this is a valid, and unsurprising explicit conversion of a vector to a scalar.

For other methods such as equality comparison, I leave you to locate these in the code 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
{
protected:
	// Accessor helpers
	const double* array() const { return &x; }
	double* array() { return &x; }

	const double& element(int i) const	// Const array element accessor
	{	switch (i)
		{	case 0: return x; case 1: return y; case 2: return z;
			default: throw std::out_of_range("Element index");
		}
	}

	double& element(int i) // Non-const array element accessor - for assignment
	{	switch (i)
		{	case 0: return x; case 1: return y;	case 2: return z;
			default: throw std::out_of_range("Element index");
		}
	}

	double* read(double v[3]) const { v[0]=x; v[1]=y; v[2]=z; return v; }	// Copies this vector to supplied array

	// Non-modifying helpers
	bool is_null() const { return !x&&!y&&!z; }
	bool is_equal_to(const Vector& v) const { return x==v.x&&y==v.y&&z==v.z; }
	double magnitude_squared() const { return x*x+y*y+z*z; }
	double magnitude() const { return sqrt(magnitude_squared()); }
	Vector negation() const { return Vector(-x,-y,-z); }
	Vector normalisation() const { return division(magnitude()); }
	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 v.x*x+v.y*y+v.z*z; }
	Vector cross_product(const Vector& v) const { return Vector(v.y*y-v.y*z,v.x*z-v.y*x,v.y*x-v.x*y); }

	// Modifying helpers
	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:
	// Elements and accessors
	double x,y,z;	// Representation

	explicit operator const double* () const { return array(); }	// Cast to const array
	explicit operator double* () { return array(); }	// Cast to non-const array

	const double& operator[](int i) const { return element(i); }	// Const array element accessor
	double& operator[](int i) { return element(i); }	// Non-const array element accessor - for assignment

	double* Read(double p[3]) const { return read(p); }

	// Constructors
	Vector(double a,double b,double c):x(a),y(b),z(c) { }
	explicit Vector(const double p[3]):x(p[0]),y(p[1]),z(p[2]) { }

	static const Vector null;
	// Assignment
	Vector& operator=(const Vector& v) { assign(v); return *this; }

	// Informational
	explicit operator bool() const { return !is_null(); }	// Don't want Vector implicitly converted to int or bool
	bool operator!() const { return is_null(); }

	explicit operator double() const { return magnitude(); }

	bool operator==(const Vector& v) const { return is_equal_to(v); }
	bool operator!=(const Vector& v) const { return !is_equal_to(v); }

	// Scalar operations

	Vector operator-() const { return Vector(negation()); }

	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)); }

	// Vector operations

	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)); }

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

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

const Vector Vector::null=Vector(0,0,0);	// A standard vector

int main()	// The program
{	const Vector u(1,2,3);	// A test vector
	double d[4]={6,4,3,1};
	Vector v(d);	// A test vector

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

	double t0=v[0],t1=*(v+1);

	cout<<"t0,t1="<<t0<<","<<t1<<"\n";

	Vector v0=Vector::null;

	cout<<"v0="<<v0<<"\n";

	if (v0)
		cout<<"v0 is true\n";
	else
		if (!v0)
			cout<<"v0 is false\n";
		else
			cout<<"v0 is neither true nor false\n";

	return 0;
}

Program output is:

u=(1,2,3)
v=(6,4,3)
t0,t1=6,4
v0=(0,0,0)
v0 is false

Author

Comments

There are currently no comments on this article.

Comment

Enter your comment below. Fields marked * are required. You must preview your comment before submitting it.