Subject: tips for writing better C++ 3D graphics utility classes Here are some practices which I have found useful in my code. I would be interested in hearing opposing opinions, or about tricks that other people use, or web/printed resources that give similar tips. - Michael McGuffin, August 2003 (Disclaimer (added 2005): I did not invent or discover these tricks on my own. I learned them from the code of some other, talented programmers, who were more experienced than me, and whom I had the good fortune of working with. My appreciation goes out to them.) ------ Tip #1 ------ If you're writing a 3D point or vector class, don't store the components in separate data members like this: class Point3D { ... float x, y, z; // dare we make these public ? ... }; Instead, store them in an array, and provide public methods to access them as if they were separate data members: class Point3D { private: float p[3]; ... public: float x() const { return p[0]; } float y() const { return p[1]; } float z() const { return p[2]; } float& x() { return p[0]; } float& y() { return p[1]; } float& z() { return p[2]; } const float * get() const { return p; } float * get() { return p; } ... }; Having an array is nice for calling various OpenGL routines: Point3D a(...); ... glVertex3fv( a.get() ); // faster than glVertex3f( a.x(), a.y(), a.z() ); ... Vector3D b(...); ... glVertex3fv( (a+b).get() ); // avoids need to store the sum explicitly (warning: see tip #3 below) It's also (more rarely) nice to be able to access the ith component of a vector, where i is a variable equal to 0, 1, or 2. This can sometimes be used to avoid writing similar code 3 times to handle the x, y, and z cases. For example: Vector3D v1(...); for ( int dimension = 0; dimension < 3; ++ dimension ) { Vector3 v2(0,0,0); v2.get()[dim] = v1.get()[dim]; // Now v2 is the projection of v1 onto the (dimension)th axis ... } Also, if you have methods on your point/vector class to return the index (0, 1, or 2) of the smallest and largest components (in absolute terms), then you can easily compute the min and max components as Vector3D v(...); float min = fabs( v.get()[ v.indexOfLeastComponent() ] ); float max = fabs( v.get()[ v.indexOfGreatestComponent() ] ); Here's another trick: Vector3D v1(...); Vector3D v2 = v1; v2.get()[ v1.indexOfGreatestComponent() ] = 0; // Now v2 is the projection of v1 onto the axis-aligned plane // that is most perpendicular to v1 ------ Tip #2 ------ Implement separate classes for 3D points and 3D vectors, and overload operators such that the usual conventions are followed: point + vector = point vector + vector = vector point - point = vector etc. Why ? Becase it creates implicit documentation in your code, making it more evident to readers what each variable means. You must provide some way of converting between points and vectors though, as the need for this invariably crops up. class Vector3D { ... explicit inline Vector3D( const Point3D& ); // converts point to vector ... }; // likewise for Point3D ... Some developers go as far as recommending a 3rd class for unit vectors. Other developers go the opposite way. As I recall, Peter Shirley (in the introductory material of "Realistic Ray Tracing", 2000) just uses one class for both points and vectors. ------ Tip #3 ------ If your point/vector class stores 3 coordinates in an array, as suggested by tip #1 above, be careful not to do glLightfv( GL_LIGHT0, GL_POSITION, point.get() ); as this routine expects a 4 element array, i.e. it expects a 4th ``w'' component. My own point and vector classes always store a 4th component, even though it's usually a waste of RAM, just to avoid problems with glLightfv() (and maybe other OpenGL routines?) and to also simplify math with 4x4 matrices. ------ Tip #4 ------ If you have an axis-aligned box class (e.g. for storing bounding boxes), or an axis-aligned 2D rectangle class, consider adding a method called getDiagonal() const that returns a 3D or 2D vector, respectively, equal to the difference of the opposite corners of the box/rect. This vector provides a compact way of passing around a box or rect's dimensions, and you can easily get at the individual dimensions thus: float width = rect.getDiagonal().x(); float height = rect.getDiagonal().y(); ------ Tip #5 ------ Again, if you have an axis-aligned box class, consider adding a method called getCorner( int i ) const that returns the ith corner, where 0 <= i <= 7, using the following numbering convention: 0: (min_x, min_y, min_z) 1: (max_x, min_y, min_z) 2: (min_x, max_y, min_z) 3: (max_x, max_y, min_z) 4: (min_x, min_y, max_z) 5: (max_x, min_y, max_z) 6: (min_x, max_y, max_z) 7: (max_x, max_y, max_z) This numbering scheme makes it easy to implement getCorner(): Point3D getCorner( int i ) const { return Point3D( i & 1 ? max_x : min_x, i & 2 ? max_y : min_y, i & 4 ? max_z : min_z ); } It's also easy to implement methods for finding a point within the box that is furthest along a given direction: Point3D getExtremalCorner( const Vector3D & v ) const { return Point3D( v.x() > 0 ? max_x : min_x, v.y() > 0 ? max_y : min_y, v.z() > 0 ? max_z : min_z ); } int getIndexOfExtremalCorner( const Vector3D & v ) const { return (v.x() > 0) | ((v.y() > 0)<<1) | ((v.z() > 0)<<2); } Furthermore, if i is the index of any corner, then the index of the diagonally opposite corner is just (i^7).