# HPoint - Homogenous Coordinates

Posted

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>::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