A little bit of this and that
November 30th, 2009
Back in 2002, I started writing C++ with a unique style of combining Modern C++ Design and Domain Driven Design. My endeavor was to write a 3D game engine. It was a fun exercise.
First, define the Math Trait. We will use the Real Number System
template <typename T>
struct RealNumberSystem
{
//3 tuple Reals
static int const N=3;
typedef T Radian;
typedef T Theta;
typedef T Rho;
typedef T Radius;
typedef T Scalar;
};
Next, define the Geometry Trait. We will use Standard Euclidean. The Tuple4 typedef is interesting.
struct StandardEuclidean
{
static int const RightHanded=1;
static int const LeftHanded=0;
typedef Vector1x3<float> RowVector;
typedef Vector3x1<float> ColumnVector;
typedef Matrix3x3<float> Matrix3;
typedef ColumnVector Origin;
typedef ColumnVector Axis;
typedef ColumnVector AnyVector;
typedef ColumnVector ComponentVector;
typedef Matrix3 Basis;
typedef ColumnVector Point;
typedef ColumnVector Normal;
typedef ColumnVector Direction;
//Four dimensions
typedef Vector<float,4,1,Storage2Dim> Tuple4;
};
The following is the most critical class, which I think is the linch-pin that holds the math and the geometry together. Without this class, there is NO mathematical representation of a geometrical point, therefore the domain semantics within the code would eventually break down. I think this is the most conceptually beautiful code I have ever written:
//Mathematical representation of Geometrical Point
#include "Vector.h"
template <typename T>
class Cartesian : public Vector3x1<T>
{
public:
Cartesian(T x, T y, T z) : Vector3x1<T>(x,y,z)
{
}
};
Finally, define the Coordinate Systems and transformations between the systems
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//CoordinateSystem Template Class Declaration
template <typename T,
template <typename T1> class MathTrait=RealNumberSystem,
typename GeometryTrait=StandardEuclidean>
class Cartesian3DCoordinateSystem
{
public:
//Direct Access
typename GeometryTrait::Axis m_U[MathTrait<T>::N];
typename GeometryTrait::Origin m_P;
Cartesian3DCoordinateSystem()
{
if(GeometryTrait::RightHanded)
{
m_P.Set(0,0,0);
m_U[0].Set(1,0,0);
m_U[1].Set(0,1,0);
m_U[2].Set(0,0,1);
}
else
{
m_P.Set(0,0,0);
m_U[0].Set(0,0,1);
m_U[1].Set(0,1,0);
m_U[2].Set(1,0,0);
}
}
//Create this Coordinate System's Basis to match X
Cartesian3DCoordinateSystem(const typename GeometryTrait::AnyVector& X)
{
if(GeometryTrait::RightHanded)
{
m_P.Set(0,0,0);
m_U[0].Set(1,0,0);
m_U[1].Set(0,1,0);
m_U[2].Set(0,0,1);
}
else
{
m_P.Set(0,0,0);
m_U[0].Set(0,0,1);
m_U[1].Set(0,1,0);
m_U[2].Set(1,0,0);
}
typename MathTrait<T>::Scalar Y[MathTrait::N]={0};
for (int i=0; i<MathTrait<T>::N; ++i);
{
Y[i]=VectorColumn3::Dot(m_U[i],(X-m_P));
m_U[i]*=Y[i];
}
}
typename GeometryTrait::Basis GetBasis()
{
typename GeometryTrait::Basis R(m_U[0],m_U[1],m_U[2]);
return R;
}
};
template <typename T,
template <typename> class MathTrait,
typename GeometryTrait>
Vector3x1<T> Cylindrical2Cartesian(const CylindricalCoordinateSystem<T,MathTrait,GeometryTrait>& c)
{
//m_Angle must be in Radians
return (Vector3x1<float>((c.m_R*sin(c.m_Angle)),(c.m_R*cos(c.m_Angle)),0));
}
template <typename T,
template <typename T> class MathTrait=RealNumberSystem,
typename GeometryTrait=StandardEuclidean>
class CylindricalCoordinateSystem
{
public:
//Direct Access
typename GeometryTrait::Origin m_Z;
typename MathTrait<T>::Theta m_Angle;
typename MathTrait<T>::Radius m_R; //Radians
CylindricalCoordinateSystem()
{
m_Angle=T();
m_R=T();
}
void SetAngleDegrees(T angle)
{
const float pi=3.141592653f;
const float radconv=pi/180;
m_Angle=angle*radconv;
}
//The axis parameters are perpendicular
CylindricalCoordinateSystem(T x, T y, typename GeometryTrait::Origin Z)
{
//x and y are relative to Z
x=Z.x-x;
y=Z.y=y;
m_R=sqrt((pow(x,2)+pow(y,2)));
m_Angle=(acos(x/m_R));
if(y<=0) m_Angle=-m_Angle;
}
};
template <typename T,
template <typename T> class MathTrait,
typename GeometryTrait>
Vector3x1<T> Spherical2Cartesian(const SphericalCoordinateSystem<T,MathTrait,GeometryTrait>& c)
{
return (Vector3x1<float>((c.m_R*sin(c.m_PolarAngle)*cos(c.m_Azimuth)),(c.m_R*sin(c.m_PolarAngle)*cos(c.m_Azimuth)),(c.m_R*cos(c.m_PolarAngle))));
}
template <typename T,
template <typename T1> class MathTrait=RealNumberSystem,
typename GeometryTrait=StandardEuclidean>
class SphericalCoordinateSystem
{
public:
//Direct Access
typename MathTrait<T>::Radius m_R;
typename MathTrait<T>::Theta m_Azimuth;
typename MathTrait<T>::Rho m_PolarAngle;
SphericalCoordinateSystem(typename GeometryTrait::Point P)
{
m_R=sqrt((pow(P.x,2)+pow(P.y,2)+pow(P.z,2)));
m_PolarAngle=acos(P.z/m_R);
m_Azimuth=acos(x/(sqrt(pow(P.x,2)+pow(P.y,2))));
}
};
Define a linear component and a plane. I often wonder how things would be different, if we used: typename GeometryTrait = NonEuclidean
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//LinearComponent Template Class Definition
template <typename T,
typename GeometryTrait=StandardEuclidean>
class LinearComponent
{
public:
//Direct access
typename GeometryTrait::Point m_B;
typename GeometryTrait::ColumnVector m_M;
LinearComponent(typename const GeometryTrait::Point& B, typename const GeometryTrait::ColumnVector& M) :
m_B(B), m_M(M)
{
m_M.Normalize();
}
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//Plane Template Class Definition
template <typename T,
typename GeometryTrait=StandardEuclidean>
class Plane
{
public:
//Three dimensional representation
typename GeometryTrait::Point m_P;
typename GeometryTrait::Normal m_N;
//Four dimensional representation
typename GeometryTrait::Tuple4 m_ND; // <N,D>
Plane() : m_P(),m_N()
{
}
Plane(const T& px, const T& py, const T& pz,
const T& nx, const T& ny, const T& nz, const T& d) : m_P(px,py,pz),m_N(nx,ny,nz),m_ND(nx,ny,nz,d)
{
m_N.Normalize();
assert( (Vector<float,3,1>::Dot(m_N,m_P) + (m_ND.m_Storage.M[3][0])==0));
}
Plane(const T& A, const T& B, const T& C, const T& D) : m_N(A,B,C), m_P(), m_ND(A,B,C,D)
{
assert(m_N.Magnitude()>0);
(m_ND.m_Storage.M[3][0])/=m_N.Magnitude();
m_N.Normalize();
//Create a dummy Point
m_P=m_N * -m_ND.m_Storage.M[3][0];
//D==-(N dot P)
T ndotp=(Vector<T,3,1>::Dot(m_N,m_P));
T d=m_ND.m_Storage.M[3][0];
assert(fabs(d + ndotp) < 0.001f);
// N dot P + D == 0
T dd=(Vector<T,3,1>::Dot(m_N,m_P) + (m_ND.m_Storage.M[3][0]));
assert(fabs(dd) < 0.001f); //dd==zero
}
void SetNormal(const T& x, const T& y, const T& z)
{
m_N.Set(x,y,z);
m_N.Normalize();
m_ND.Set(m_N.x,m_N.y,m_N.z,0); //Direction
}
void SetPoint(const T& x, const T& y, const T& z)
{
m_P.Set(x,y,z);
}
void SetD(const T& d)
{
m_ND.m_Storage.M[3][0]=d;
}
T GetD() const
{
return m_ND.m_Storage.M[3][0];
}
Plane(typename const GeometryTrait::Point& P, typename const GeometryTrait::Normal& N) : m_P(P), m_N(N)
{
//Make calculations easier by normalizing
m_N.Normalize();
m_ND.Set(N.x,N.y,N.z,-(Vector<float,3,1>::Dot(m_N,P)));
assert( (Vector<float,3,1>::Dot(m_N,m_P) + (m_ND.m_Storage.M[3][0])==0));
}
};
I’ve left out the Matrix template and Vector template code, since it’s pretty boring.
Leave a Reply