A Simple Geometry library for OpenGL

by Steve Baker

Introduction

Simple Geometry (SG) is simple set of matrix, quaternion and vector math functions that were originally written to support the SSG (Simple Scene Graph) API - but which are rather useful for all sorts of OpenGL applications,

SG is now a part of PLIB.

Since SG is designed to work with OpenGL, it uses similar naming conventions - all SG functions and other tokens that work with 'float' precision math begin with 'sg' or 'SG'. There are a complete duplicate set that work with double-precision math that begin with 'sgd' or 'SGD'.

Most SG functions are implemented as 'inline' C++ functions - for speed. The underlying data types are mostly just arrays of 'float' - which is generally the most efficient type for OpenGL programs.

SGD functions are absolutely identical to their SG eqivelents - except for working at double precision. This document doesn't describe the SGD functions, types and constants. That would just be a waste of space - they are just identical to the SG versions.

Usage.

You need to #include "sg.h" and link to the SG binary.

Basic SG Types.

Most SG functions operate on SGfloat data elements - which are normally '#define'd as 'float'. Common data types are:

  SGfloat    -- A floating point number.
  sgVec2     -- A two   element array of SGfloat
  sgVec3     -- A three element array of SGfloat
  sgVec4     -- A four  element array of SGfloat

  sgMat4     -- A 4x4 element array of SGfloat

  sgCoord    -- A simple structure used to hold
                euler rotation angles and a 3D
                translation:

       struct sgCoord
       {
         sgVec3 xyz ;
         sgVec3 hpr ;
       } ;

  sgQuat     -- A simple structure used to hold
                a Quaternion:

       struct sgQuat
       {
         SGfloat w, x, y, z ;
       } ;

  class sgSphere   -- A sphere
  class sgBox      -- An axially aligned box.
  class sgFrustum  -- An OpenGL-style view frustum

  ...and the same again but in 'double' precision:

    SGDfloat, sgdVec2, sgdVec3, sgdVec4,
    sgdMat4, sgdCoord, sgdQuat, sgdSphere,
    sgdBox, sgdFrustum

Note 1: All angles used by or produced by SG are in degrees.

Note 2: Some functions refer to 'angle+axis' notation for rotations, this is the same format that glRotate uses.

sgVec*

The sgVec* types are just simple arrays - designed especially to hold values for OpenGL vertices, normals, texture coordinates and colours with the minimum of fuss. These arrays come in 2, 3 and four element versions - and almost all SG functions that can operate on an sgVec are provided in three appropriately named versions, one for each length varient. By convention, the elements are in the order:

sgMat4

sgMat4 arrays are SM_float[4][4] arrays for convenience - but notice that the order of the elements is such that you can take the address of an sgMat4, cast that into a 'float *' ('double *' for SGD) and pass the result to any OpenGL matrix function. eg:

   sgMat4 m ;
   sgSetIdentityMat4 ( m ) ;
   glLoadMatrixf ( (GMfloat *) m ) ;

...or...

   sgdMat4 m ;
   sgdSetIdentityMat4 ( m ) ;
   glLoadMatrixd ( (GMdouble *) m ) ;

Another way to look at this is to treat an sgMat4 as an array of four sgVec4's. This is convenient for quite a few reasons. For example, you can extract the sgVec3 that represents the translation part of an sgMat4 by just using mat[3] anywhere where an sgMat3 would be appropriate.

sgCoord

This simple structure contains two members - each of type 'sgVec3' - the first is 'xyz', the second 'hpr' - and together, they represent a rotation followed by a translation.

There is a rich set of functions to interconvert sgCoord's and sgMat4's.

sgQuat

This simple structure contains four members - each of type 'SGfloat'. A quaternion can be used to encode a rotation.

There is a rich set of functions to interconvert sgQuat's and other rotation structures.

sgSphere

An sgSphere contains a sgVec3 for the center of the sphere and an SGfloat for the radius.

class sgSphere
{
  SGfloat *getCenter (void) ;
  SGfloat  getRadius (void) ;

  void setCenter ( SGfloat x, SGfloat y, SGfloat z ) ;
  void setRadius ( SGfloat r ) ;

  int isEmpty (void) ;
  void empty  (void) ;

  void orthoXform ( sgMat4 m ) ;

  void extend ( sgSphere *s ) ;
  void extend ( sgBox    *b ) ;
  void extend ( sgVec3    v ) ;

  int intersects ( sgSphere *s ) ;
  int intersects ( sgVec4 plane ) ;
  int intersects ( sgBox *b ) ;
} ;

Since sgSphere's are frequently used as simple bounding shapes for field-of-view culling, it's quite useful to be able to represent an 'empty' sphere. This is represented using a sphere of Negative radius.

The 'isEmpty()' function tests to see if a sphere is empty and the 'empty()' function forces the sphere to be empty.

The 'orthoXform(m)' function transforms the sphere using the matrix 'm' - which must be orthographic (since transforming a sphere by a non-ortho matrix yields something that's no longer spherical).

Three 'extend' functions allow you to expand the sphere to encompass other shapes. These functions attempt to minimise the radius of the resulting sphere:


  void extend ( sgSphere *s ) ;
  void extend ( sgBox    *b ) ;
  void extend ( sgVec3    v ) ;

There are also three intersection tests that return TRUE if the volume contained by the sphere intersects the volume contained by the other object:

  int intersects ( sgSphere *s ) ;
  int intersects ( sgBox *b ) ;
  int intersects ( sgVec4 plane ) ;

('plane' in this context means the semi-infinite volume bounded by that plane).

sgBox

An sgBox contains an sgVec3 for the vertex of the cube at the minimum (X,Y,Z) and another at the maximum (X,Y,Z).

class sgBox
{
public:
  SGfloat *getMin (void) ;
  SGfloat *getMax (void) ;

  void setMin ( SGfloat x, SGfloat y, SGfloat z ) ;
  void setMax ( SGfloat x, SGfloat y, SGfloat z ) ;

  int isEmpty(void) ;
  void empty (void) ;

  void extend  ( sgSphere *s ) ;
  void extend  ( sgBox    *b ) ;
  void extend  ( sgVec3    v ) ;

  int intersects ( sgSphere *s ) ;
  int intersects ( sgBox    *b ) ;
  int intersects ( sgVec4   plane ) ;
} ;


Since sgBox'es are frequently used as simple bounding shapes for field-of-view culling, it's quite useful to be able to represent an 'empty' box. This is represented using a box whose minumum vertex has larger X, Y or Z than its maximum vertex.

The 'isEmpty()' function tests to see if a box is empty and the 'empty()' function forces the box to be empty.

Three 'extend' functions allow you to expand the box to encompass other shapes. These functions attempt to minimise the size of the resulting box:


  void extend ( sgSphere *s ) ;
  void extend ( sgBox    *b ) ;
  void extend ( sgVec3    v ) ;

There are also three intersection tests that return TRUE if the volume contained by the box intersects the volume contained by the other object:

  int intersects ( sgSphere *s ) ;
  int intersects ( sgBox *b ) ;
  int intersects ( sgVec4 plane ) ;

('plane' in this context means the semi-infinite volume bounded by that plane).

sgFrustum

An sgFrustum corresponds to the set of parameters that is used to describe an OpenGL view frustum.

class sgFrustum
{
public:

  sgFrustum (void) ;

  void setFrustum ( SGfloat left  , SGfloat right,
                    SGfloat bottom, SGfloat top,
                    SGfloat near  , SGfloat far ) ;
  void setFOV     ( SGfloat h, SGfloat v ) ;
  void setNearFar ( SGfloat n, SGfloat f ) ;
 
  SGfloat getHFOV (void) ;
  SGfloat getVFOV (void) ;
  SGfloat getNear (void) ;
  SGfloat getFar  (void) ;
  SGfloat getLeft (void) ;
  SGfloat getRight(void) ;
  SGfloat getTop  (void) ;
  SGfloat getBot  (void) ;

  void getFOV     ( SGfloat *h, SGfloat *v ) ;
  void getNearFar ( SGfloat *n, SGfloat *f ) ;

  int  contains ( sgVec3    p ) ;
  int  contains ( sgSphere *s ) ;
} ;

There are two ways to use an sgFrustum: The constructor for an sgFrustum produces a simple 45 degree by 45 degree frustum with the near clip at one unit and the far clip at one million units.

  int  contains ( sgVec3    p ) ;
  int  contains ( sgSphere *s ) ;

...returns SG_OUTSIDE if the point or sphere lies entirely outside the volume contained by the frustum, SG_INSIDE if it lies entirely inside the frustum - or SG_STRADDLE if they only partially overlap.

Constants.

Since finding a portable definition for PI is impossible (thanks Mr Gates), SG supplies a suitable definition - and some convenience multipliers to convert degrees to radians and back again.

#define SG_PI
#define SG_DEGREES_TO_RADIANS
#define SG_RADIANS_TO_DEGREES

Vector functions:

Pretty much all the low level vector functions are available for sgVec2, sgVec3 and sgVec4. The order of parameters is always destination first, operands next - in the order that you'd write them in a C-style assignment statement. Hence (for example):

  sgAddVec3 ( dst, a, b ) ;   /* dst = a + b ; */
  sgAddVec3 ( dst, a ) ;      /* dst += a ; */

The following functions exist: Here are all the function prototypes - as you can see, the names are 100% consistent and the paramter order is easy to remember.
  void sgZeroVec2 ( sgVec2 dst ) ;
  void sgZeroVec3 ( sgVec3 dst ) ;
  void sgZeroVec4 ( sgVec4 dst ) ;

  void sgSetVec2  ( sgVec2 dst, SGfloat x, SGfloat y ) ;
  void sgSetVec3  ( sgVec3 dst, SGfloat x, SGfloat y, SGfloat z ) ;
  void sgSetVec4  ( sgVec4 dst, SGfloat x, SGfloat y, SGfloat z, SGfloat w ) ;

  void sgCopyVec2 ( sgVec2 dst, sgVec2 src )
  void sgCopyVec3 ( sgVec3 dst, sgVec3 src )
  void sgCopyVec4 ( sgVec4 dst, sgVec4 src )

  void sgAddVec2  ( sgVec2 dst, sgVec2 src )
  void sgAddVec3  ( sgVec3 dst, sgVec3 src )
  void sgAddVec4  ( sgVec4 dst, sgVec4 src )

  void sgAddVec2  ( sgVec2 dst, sgVec2 src1, sgVec2 src2 )
  void sgAddVec3  ( sgVec3 dst, sgVec3 src1, sgVec3 src2 )
  void sgAddVec4  ( sgVec4 dst, sgVec4 src1, sgVec4 src2 )

  void sgSubVec2  ( sgVec2 dst, sgVec2 src )
  void sgSubVec3  ( sgVec3 dst, sgVec3 src )
  void sgSubVec4  ( sgVec4 dst, sgVec4 src )

  void sgSubVec2  ( sgVec2 dst, sgVec2 src1, sgVec2 src2 )
  void sgSubVec3  ( sgVec3 dst, sgVec3 src1, sgVec3 src2 )
  void sgSubVec4  ( sgVec4 dst, sgVec4 src1, sgVec4 src2 )

  void sgNegateVec2 ( sgVec2 dst )
  void sgNegateVec3 ( sgVec3 dst )
  void sgNegateVec4 ( sgVec4 dst )

  void sgNegateVec2 ( sgVec2 dst, sgVec2 src )
  void sgNegateVec3 ( sgVec3 dst, sgVec3 src )
  void sgNegateVec4 ( sgVec4 dst, sgVec4 src )

  void sgScaleVec2  ( sgVec2 dst, SGfloat s )
  void sgScaleVec3  ( sgVec3 dst, SGfloat s )
  void sgScaleVec4  ( sgVec4 dst, SGfloat s )

  void sgScaleVec2  ( sgVec2 dst, sgVec2 src, SGfloat s )
  void sgScaleVec3  ( sgVec3 dst, sgVec3 src, SGfloat s )
  void sgScaleVec4  ( sgVec4 dst, sgVec4 src, SGfloat s )

  int sgCompareVec2 ( sgVec2 a, sgVec2 b, SGfloat tol )
  int sgCompareVec3 ( sgVec3 a, sgVec3 b, SGfloat tol )
  int sgCompareVec4 ( sgVec4 a, sgVec4 b, SGfloat tol )

  int sgEqualVec2   ( sgVec2 a, sgVec2 b )
  int sgEqualVec3   ( sgVec3 a, sgVec3 b )
  int sgEqualVec4   ( sgVec4 a, sgVec4 b )

  SGfloat sgScalarProductVec2 ( sgVec2 a, sgVec2 b )
  SGfloat sgScalarProductVec3 ( sgVec3 a, sgVec3 b )
  SGfloat sgScalarProductVec4 ( sgVec4 a, sgVec4 b )

  void sgVectorProductVec3 ( sgVec3 dst, sgVec3 a, sgVec3 b ) ;

  SGfloat sgDistanceVec2 ( sgVec2 a, sgVec2 b )
  SGfloat sgDistanceVec3 ( sgVec3 a, sgVec3 b )
  SGfloat sgDistanceVec4 ( sgVec4 a, sgVec4 b )

  SGfloat sgLengthVec2 ( sgVec2 src )
  SGfloat sgLengthVec3 ( sgVec3 src )
  SGfloat sgLengthVec4 ( sgVec4 src )

  /* Anglo-US spelling issues.   */

  #define sgNormalizeVec2 sgNormaliseVec2
  #define sgNormalizeVec3 sgNormaliseVec3
  #define sgNormalizeVec4 sgNormaliseVec4

  void sgNormaliseVec2 ( sgVec2 dst )
  void sgNormaliseVec3 ( sgVec3 dst )
  void sgNormaliseVec4 ( sgVec4 dst )

  void sgNormaliseVec2 ( sgVec2 dst, sgVec2 src )
  void sgNormaliseVec3 ( sgVec3 dst, sgVec3 src )
  void sgNormaliseVec4 ( sgVec4 dst, sgVec4 src )

Matrix Functions.

The following functions let you construct an sgMat4 from a variety of input data formats:

  void sgCopyMat4      ( sgMat4 dst, sgMat4 src ) ;

  void sgMakeIdentMat4 ( sgMat4 dst ) ;

  void sgMakeCoordMat4 ( sgMat4 dst, SGfloat x, SGfloat y, SGfloat z,
                                     SGfloat h, SGfloat p, SGfloat r ) ;
  void sgMakeCoordMat4 ( sgMat4 dst, sgVec3 xyz, sgVec3 hpr )
  void sgMakeCoordMat4 ( sgMat4 dst, sgCoord *src )

  void sgMakeRotMat4   ( sgMat4 dst, sgVec3 hpr )
  void sgMakeRotMat4   ( sgMat4 dst, SGfloat h, SGfloat p, SGfloat r )
  void sgMakeRotMat4   ( sgMat4 dst, sgQuat *q ) ;
  void sgMakeRotMat4   ( sgMat4 dst, SGfloat angle, sgVec3 axis ) ;

  void sgMakeTransMat4 ( sgMat4 dst, sgVec3 xyz ) ;
  void sgMakeTransMat4 ( sgMat4 dst, SGfloat x, SGfloat y, SGfloat z ) ;


You can multiply matrices:

  void sgMultMat4      ( sgMat4 dst, sgMat4 a, sgMat4 b ) ;
  void sgPostMultMat4  ( sgMat4 dst, sgMat4 a ) ;
  void sgPreMultMat4   ( sgMat4 dst, sgMat4 a ) ;

You can do a cheap matrix inversion (for simple rotate/translate matrices):

  void sgTransposeNegateMat4 ( sgMat4 dst ) ;
  void sgTransposeNegateMat4 ( sgMat4 dst, sgMat4 src ) ;

You can transform vertices and points using a matrix. The 'Vec*' forms assume that the vector merely need to be rotated (like you would want for a surface normal for example) - whilst the 'Pnt*' forms perform a rotate and translate. If your matrix is more complex and contains scaling, shearing, perspective and such like, then use the 'FullXform' versions - which are quite a bit slower than the other forms:

  void sgXformVec3     ( sgVec3 dst, sgMat4 mat ) ;
  void sgXformVec3     ( sgVec3 dst, sgVec3 src, sgMat4 mat ) ;

  void sgXformPnt3     ( sgVec3 dst, sgMat4 mat ) ;
  void sgXformPnt3     ( sgVec3 dst, sgVec3 src, sgMat4 mat ) ;

  void sgFullXformPnt3 ( sgVec3 dst, sgMat4 mat ) ;
  void sgFullXformPnt3 ( sgVec3 dst, sgVec3 src, sgMat4 mat ) ;

  void sgXformVec4     ( sgVec4 dst, sgMat4 mat ) ;
  void sgXformVec4     ( sgVec4 dst, sgVec4 src, sgMat4 mat ) ;

  void sgXformPnt4     ( sgVec4 dst, sgMat4 mat ) ;
  void sgXformPnt4     ( sgVec4 dst, sgVec4 src, sgMat4 mat ) ;

  void sgFullXformPnt4 ( sgVec4 dst, sgMat4 mat ) ;
  void sgFullXformPnt4 ( sgVec4 dst, sgVec4 src, sgMat4 mat ) ;

Coord routines:

These routines operate on 'sgCoord' structures:

  void sgZeroCoord ( sgCoord *dst ) ;
  void sgSetCoord  ( sgCoord *dst, SGfloat x, SGfloat y, SGfloat z,
                                   SGfloat h, SGfloat p, SGfloat r ) ;
  void sgSetCoord  ( sgCoord *dst, sgVec3 xyz, sgVec3 hpr ) ;
  void sgSetCoord  ( sgCoord *coord, sgMat4 src ) ;
  void sgCopyCoord ( sgCoord *dst, sgCoord *src ) ;

The varient of sgSetCoord that takes a matrix is somewhat tricky since there is no way to convert matrices that are not simple rotate/translates into sgCoord's - and even when the matrix is a simple rotate/translate, there are a number of possible sets of Euler rotations that could be appropriate to reproduce the behavior of the input matrix. sgSetCoord picks a valid rotation - but just beware of this behaviour.

Quaternion routines:

Quaternions are convenient alternatives to Matrices for storing rotations.
  void sgMakeIdentQuat ( sgQuat *dst ) ;
Creates the identity quaternion. (0,0,0,1)
  void sgSetQuat ( sgQuat *dst, SGfloat w,
                                SGfloat x,
                                SGfloat y,
                                SGfloat z ) ;
Sets the four components of the quaternion.
  void sgCopyQuat ( sgQuat *dst, sgQuat *src ) ;
Copies one quaternion into another.
  void sgNormalizeQuat ( sgQuat *dst ) ;
  void sgNormalizeQuat ( sgQuat *dst, sgQuat *src ) ;
For most operations, quaternions need to be normalized, these functions ensure that they are.
  void sgInvertQuat ( sgQuat *dst ) ;
  void sgInvertQuat ( sgQuat *dst, sgQuat *src ) ;
Computes the inverse of a quaternion.
  void sgQuatToAngleAxis ( SGfloat *angle, sgVec3 axis, sgQuat *src ) ;
  void sgQuatToAngleAxis ( SGfloat *angle,
                           SGfloat *x, SGfloat *y, SGfloat *z,
                                                        sgQuat *src ) ;
Converts a quaternion into an angle+axis format - just the thing to pass to glRotate for example.
  void sgMakeQuat ( sgQuat *dst, SGfloat angle, sgVec3 axis ) ;
  void sgMakeQuat ( sgQuat *dst, SGfloat angle,
                                 SGfloat x, SGfloat y, SGfloat z ) ;
Converts an angle+axis rotation into a quaternion.
  void sgMultQuat     ( sgQuat *dst, sgQuat *a, sgQuat *b ) ;
  void sgPostMultQuat ( sgQuat *dst, sgQuat *q )
  void sgPreMultQuat  ( sgQuat *dst, sgQuat *q )
Concatenates quaternion rotations.
  void sgMakeRotMat4 ( sgMat4 dst, sgQuat *q ) ;
Converts a quaternion into a rotation matrix.
  void sgRotQuat ( sgQuat *dst, SGfloat angle, sgVec3 axis )
  void sgRotQuat ( sgQuat *dst, SGfloat angle,
                                SGfloat x, SGfloat y, SGfloat z )
Rotates a quaternion by an angle/axis rotation.

Miscellaneous Functions.

  void sgHPRfromVec3 ( sgVec3 hpr, sgVec3 src ) ;
This function takes a vector representing a direction and computes a suitable heading and pitch angle to look along that vector. Since the roll angle is indeterminate, it is set to zero.
  void sgMakeNormal ( sgVec3 dst, sgVec3 a, sgVec3 b, sgVec3 c ) ;
This finds the surface normal of a triangle given three vertices.

Plane Equation Functions:

  SGfloat sgDistToPlaneVec3 ( sgVec4 plane, sgVec3 pnt ) ;
This returns the distance from the point to the plane.
  SGfloat sgHeightAbovePlaneVec3 ( sgVec4 plane, sgVec3 pnt ) ;
This returns the vertical (Z) distance from the point to the plane (my applications tend to use Z-is-up conventions).
  void sgMakePlane ( sgVec4 dst, sgVec3 a, sgVec3 b, sgVec3 c ) ;
This finds the plane equation of a triangle given three vertices.
  void sgMakePlane ( sgVec4 dst, sgVec3 normal, sgVec3 pnt ) ;
This finds the plane equation of a plane given its normal and a point on the plane.

Credits.

Thanks to Kevin Thompson who kindly donated the original Quaternion code.
Steve J. Baker. <sjbaker1@airmail.net>