HPoint - Homogenous Coordinates

Posted
Comments 0

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

“Homogeneous coordinates are ubiquitous in computer graphics because they allow common vector operations such as translation, rotation, scaling and perspective projection to be represented as a matrix by which the vector is multiplied.” wikipedia.org/Homogeneous_coordinates

Thus we introduce the HPoint class as a 4-ordinate vector representation, i.e. x,y,z with a w divisor.

class XYZW_Const
{
public:
	const double x,y,z,w;

	XYZW_Const(double a,double b,double c,double d):x(a),y(b),z(c),w(d) { }

	void Set(const XYZW_Const& xyzw)
	{	const_cast<double&>(x)=xyzw.x;
		const_cast<double&>(y)=xyzw.y;
		const_cast<double&>(z)=xyzw.z;
		const_cast<double&>(w)=xyzw.w;
	}
	void SetW(double ww)
	{	const_cast<double&>(w)=ww;
	};
};

Very similar to the XYZ triples, this representational class is very similar to XYZ_Const except it has a fourth double, and a dedicated method to set the fourth element, w.

The introduction of a fourth element means the VectorBase class needs tweaking to cater for it (whilst remaining compatible with triple element representations). We can do that by adding a static w member in the XYZ and XYZ_Const classes. Thus XYZ becomes:

class XYZ	// For Vector & Point
{
public:
	double x,y,z;
	static const double w;	// Dummy w

	XYZ(double a,double b,double c,double d):x(a),y(b),z(c) { }
	void Set(const XYZ& xyz) { *this=xyz; }
};

const double XYZ::w=0.0;

So, for triples, the 4 element constructor ignores the 4th element, and the w member exists only as a static constant. It can still be referenced as if a member variable, but without increasing the size of the object. This is one of the few examples where it is indispensable to be able to use the dot operator to access a static member (instead of the :: class scoping operator).

The HPoint class is similar to the Point class, except that while they can still be scaled by a scalar, it makes less sense to permit them to be directly translated by vectors. Translation is best left to homegenous transforms.

There are some query functions appropriate to HPoint, i.e. to query whether it can be converted to a Point (w!=0), whether it is a direction (point at infinity), whether it is the origin, and whether it is valid (invalid being null – origin at infinity).

The implicit conversion of an HPoint to a Point simply involves dividing x,y,z,w by w, i.e. such that w'=1.

Here is the code for the HPoint class:

class HPoint: protected VectorBase<XYZW_Const>
{
public:
protected:
	explicit HPoint(const VectorBase<XYZW_Const>& xyzw):VectorBase<XYZW_Const>(xyzw) {  }
public:
	using VectorBase<XYZW_Const>::x;	// READ ONLY
	using VectorBase<XYZW_Const>::y;	// For write access, use a Point.
	using VectorBase<XYZW_Const>::z;	//	
	using VectorBase<XYZW_Const>::w;	//

	static const HPoint origin;	// [0,0,0,1]	NB Origin is xyz=0 for any non-zero value of w

	using VectorBase<XYZW_Const>::SetW;

	// Constructors
	HPoint(double x,double y,double z,double w=1):VectorBase<XYZW_Const>(x,y,z,w) { }
	HPoint(const Point& p):VectorBase<XYZW_Const>(p.x,p.y,p.z,1) { }

	// Copy & Assign
	HPoint(const HPoint& p):VectorBase<XYZW_Const>(p) { }
	HPoint& operator=(const HPoint& p) { assign(p); return *this; }

	operator Point() const
	{	if (IsPoint()) return Point(x/w,y/w,z/w);
		throw std::runtime_error("Cannot convert HPoint at infinity to Point");
	}

	bool IsPoint() const { return w; }	// Point not at infinity simply requires non=zero w
	bool IsDirection() const { return !w&&!is_null(); }	// Direction (point at infinity) requires w=0, with non-zero xyz
	bool IsOrigin() const { return w&&is_null(); }	// Origin point if non-zero w, and xyz=0
	bool IsValid() const { return w||!is_null(); }	// Valid if non-zero w, or w=0 && xyz!=0
	explicit operator bool() const { return w||!is_null(); }	// True if any element non-zero, i.e. valid
	bool operator!() const { return !w&&is_null(); }	// True if all elements zero, i.e. invalid

	// HPoints may be transformed by a scalar
	HPoint& operator*=(double m) { multiply_by(m); return *this; }

	HPoint& operator/=(double d) { SetW(w*d); return *this; }

	friend HPoint operator*(const HPoint& p,double m) { return HPoint(p.multiplication(m)); }
	friend HPoint operator*(double m,const HPoint& p) { return HPoint(p.multiplication(m)); }
	friend HPoint operator/(const HPoint& p,double d) { return HPoint(p)/=d; }

	friend std::ostream& operator<<(std::ostream& os,const HPoint& p) { return os<<'['<<p.x<<','<<p.y<<','<<p.z<<','<<p.w<<']'; }	// E.g. [1,2,3,1]
};

const HPoint HPoint::origin=HPoint(0,0,0);

And here is a little test program:

int main()	// The program
{
	const Point x(1,2,3);
	const HPoint p(7,3,5,2),q(x);

	cout<<"p="<<p<<"\n";
	cout<<"q="<<q<<"\n";

	HPoint r=p/4;

	cout<<"r=p/4 -> r="<<r<<"\n";

	if (r.IsPoint())
		cout<<"r is point\n";
	else
		cout<<"r is not point\n";

	r/=0;

	cout<<"r/=0  -> r="<<r<<"\n";

	if (r.IsDirection())
		cout<<"r is direction\n";
	else
		cout<<"r is not direction\n";

	r=p*0;

	cout<<"r=p*0  -> r="<<r<<"\n";

	if (r.IsOrigin())
		cout<<"r is origin\n";
	else
		cout<<"r is not origin\n";


	r/=0;

	cout<<"r/=0  -> r="<<r<<"\n";

	if (r.IsValid())
		cout<<"r is valid\n";
	else
		cout<<"r is invalid\n";

	Point a=static_cast<Point>(p)+Vector(1,2,3);

	cout<<"p+[1,2,3]="<<a<<"\n";

	return 0;
}

Which produces the following output:

p=[7,3,5,2]
q=[1,2,3,1]
r=p/4 -> r=[7,3,5,8]
r is point
r/=0  -> r=[7,3,5,0]
r is direction
r=p*0  -> r=[0,0,0,2]
r is origin
r/=0  -> r=[0,0,0,0]
r is invalid
p+[1,2,3]=[4.5,3.5,5.5]

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.