GeoGL Maths

General

   This is the introductory paragraph

Vectors

   GeoGL provides a Vector2D<t>, Vector3D<t>, and Vector4D<t> class. All 3 are template classes with <t> being the scalar type representing the vector components. Thus, it is possible to have a vector of ints, floats, doubles... depending on the need. Common typedefs are provided for types most likely to be used in OpenGL programming.

Raw typeTypedef synonym
Vector2D<int>iVector2D
Vector2D<float>fVector2D
Vector2D<double>dVector2D
Vector2D<unsigned char>bVector2D
Vector3D<int>iVector3D
Vector3D<float>fVector3D
Vector3D<double>dVector3D
Vector3D<unsigned char>bVector3D
Vector4D<int>iVector4D
Vector4D<float>fVector4D
Vector4D<double>dVector4D
Vector4D<unsigned char>bVector4D

   Operators exist to make vectors function in a textbook fashion. Operators exist for scalar multiplication and division, vector addition and subtraction, and vector comparison (equals and not equals).

Vector3D Operators
// Nothing useful, just show some operators
void playWithVector(fVector3D &v1)
{
   fVector3D v2;               // Two vectors, yet uninitialized

   // Two ways to assign them values
   v1(5,0,0);
   v2 = fVector3D(0,3,0);

   // Vector math
   fVector3D v3 = v2 - v1;     // Vector subtraction, v3=(5,-3,0)
   v3 = v2 + -v1;              // Same thing: addition, negation

   if ((v3 / 5.0f) == fAxisX)  // Scalar division, vector comparison
      v3.x = 0;                // Individual x,y,z assignment

   // Vectors convert to arrays for OpenGL
   glVertex3fv(v2);

   // Alternate constructors
   fVector4D v4(v3,2);         // Construct a 4D vector from a 3D
   bVector3D Vb('A',255,3);    // A vector of bytes
}

   Notice that in the above example, the v2 vector was not assigned values in the declaration. The vector classes to not initialize their contents to zero when declared! This is a speed optimization to prevent redundant re-assignment. If you want a vector to start as zero, declare it as fVector3D v(0,0,0) to ensure that it contains an initial value.

   Vectors have implicit conversions for OpenGL functions so they can be passed directly to openGL

Vectors with OpenGL
// Vectors with OpenGL
void main()
{
   fVector3D position3D;      // Position in 3D space
   fVector4D position4D;      // Position in 4D space

   // Create a 3D vector, then a matching 4D vector
   position3D(10.0f, 20.0f, 30.0f);
   position4D = position3D.get4D(1.0f); // Vector is (10,20,30,1)

   // OpenGL translation using a vector
   glTranslatefv(position3D);
   // glLightfv expects 4 components, not 3
   glLightfv(GL_LIGHT0,GL_POSITION, position4D);

}

   The vector classes also contain methods to perform basic geometric operations.

Vector3D Methods
// Lets do some geometry
void vectorGeometry()
{
   fVector3D v1,v2;

   // Two vectors define a plane
   v1(5,0,2);
   v2(5,5,2);

   // Compute normal vector to the plane
   fVector3D normal = v1.cross(v2);
   normal.normalize();

   // A directional light source
   fVector3D lightDir(20,25,50);
   lightDir.normalize();

   // Angle between light and plane
   float fAngle = lightDir.dot(normal);

   // Length between two vectors
   float fTrueLength = (v2-v1).length();     // [dx2 + dy2]
   float fGridLength = (v2-v1).manLength();  // dx2 + dy2
}

   The cross() method computes a vector cross product of two vectors and returns a new vector while the dot() method returns the scalar dot product. length() returns the length of a vector, manLength() returns the "manhattan length." Then normalize() method re-normalizes a vector. This method does not create a new vector and return it, instead it normalizes in place. If you need to keep the old value before the normalize, a copy must be made first. Usually, the un-normalized vector is no longer needed. This method is also equivalent to dividing the vector by the length() value.

Sphere3D, Cylinder3D helpers

   The vector classes are intended for rectangular coordinates, but GeoGL's Sphere3D and Cylinder3D classes help to map alternate coordinate systems to vectors. Sphere3D represents a point in spherical coordinates (r,theta,phi) and Cylinder3D uses (r,theta,z). Both classes can be converted to Vector3D classes, and parallel the vector classes in design.

Sphere3D, Cylinder3D
// Lets do some more geometry
void neatoGeometry()
{
   fVector3D rect1;      // Store rectangular coordinate later

   // Spherical coordinate, radius 10, 45 degrees around theta
   // Sphere3D constructor assumes radians, so use the DtoR macro to convert
   fSphere3D sphere1(10.0f, DtoR(45.0f), 0);  
   sphere1.setDegrees(10.0f, 45.0f, 0);             // Alternative

   // Convert this point to rectangular coordinates
   rect1 = sphere1.getVector3D();    // Explicit method
   rect1 = sphere1;                  // Implicit conversion operator

   // Cylindrical coordinate, radius 10, 45 degrees rotation, 5 along Z
   // Cylinder3D constructor assumes radians, so use the DtoR macro to convert
   fCylinder3D cylinder1(10.0f, DtoR(45.0f), 5.0f); 
   cylinder1.setDegrees(10.0f, 45.0f, 5.0f);        // Alternative

   rect1 = cylinder1.getVector3D();  // Explicit method
   rect1 = cylinder1;                // Implicit conversion operator
}

   The above code constructs a spherical coordinate, then converts it to a rectangular coordinate, and stores it in a vector. There is an explicit method to retrieve the vector, or it can be done automatically using the C++ conversion operator build into Sphere3D. The constructor for Sphere3D assumes the values are specified in radians. The DtoR() and RtoD() macros convert radians to degrees, and vice-versa. The methods can be very handy when performing geometries. An alternative way to set the spherical coordinates is with the setDegrees method. This sets the r, theta, and phi values but assumes degrees. The same methods are applied to the Cylinder3D class.

Putting it all together
//////////////////////////////////////////////////////////////////////////////
// -  Generate a series of points for a cylinder
//////////////////////////////////////////////////////////////////////////////
void drawCylinder(float fCylinderRot)
{
   float fSpacingDeg = 360.0f / fSlices;           // Degrees between each slice
   float fSpacingZ   = fCylinderHeight / fStacks;  // Units between each stack

   // Reset model view
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();                   

   // Camera setup
   glTranslatef(0,0,-45);

   // Draw the cylinder using points
   glPushMatrix();
      // Position, orientation of cylinder
      glTranslatef(10,0,0);
      glRotatef(fCylinderRot,0,1,0);

      glColor3f(0.0f, 1.0f, 1.0f);
      glBegin(GL_POINTS);

      // Along Z axis of cylinder
      for (float fPosZ = 0; fPosZ < fCylinderHeight; fPosZ+= fSpacingZ)
      {
         // Around cylinder
         for (float fAngle = 0; fAngle < 360; fAngle += fSpacingDeg)
         {
            fCylinder3D pointCylinder;
            fVector3D   pointRectangular;

            // Generate (radius,theta,Z) point on the cylinder and convert to Vector3D
            pointCylinder.setDegrees(fCylinderRadius, fAngle, fPosZ - fCylinderHeight/2);
            pointRectangular = pointCylinder.getVector3D();

            // Plot the point
            glVertex3fv(pointRectangular);
         }
      }

      glEnd();
   glPopMatrix();
}

Quaternions and Matrices

   The Matrix4x4 and Vector4D classes exist to manpilate matrix and quaternion operations. These can be used for geometric transforms in 3D applications. The Vector4D class is an extension to the Vector3D class with a few nifty tricks added on. The Matrix4x4 class provides basic matrix math and some conveniences for 3D applications such as 3D rotations.

   Below is an example of using a rotation matrix to perform a simple rotation.

Simple rotations
// Rotate a point
fVector3D rotatePoint(fVector3D point, fVector3D rotation)
{
   // Create a rotation matrix from the rotation angles
   fMatrix4x4 rotMatrix = fMatrix4x4::fromEulerDeg(rotation);

   // Multiply the point by the rotation matrix, and return the new point
   return rotMatrix * point;
}

void main()
{
   fVector3D p(5,0,0);      // A point in 3D space

   // Rotate the point around 360 degrees, in increments of 45
   for (int i = 0;  i < 360; i += 45)
   {
      fVector3D pRot = rotatePoint( p, fVector3D(i, 360-i, 2*i) );
      // geoGL provides stream operators
      cout << "Point at i: " << pRot << endl;
   }
}

   The for loop in the main() will rotate the point (5,0,0) around all three axis, at 45 degree increments. Inside the loop, the rotatePoint function is called to obtain a new rotated point. The resulting point, as an fVector3D is sent to cout using the stream operator provided by GeoGL.

   The first parameter to the rotatePoint() function is the point to rotate, while the second parameter is a vector containing the rotation. The expected form of the rotation vector is 3 Euler rotation values (x,y,z), in degrees, where each value represents a rotation around an axis. GeoGL matrices are used to perform the rotation. The Matrix4x4 class has 2 static methods fromEulerDeg and fromEulerRad each of which return a matrix from the rotation vector. There is no constructor to create a Matrix4x4 from a Vector3D, since it is ambiguous whether the vector contains radians or degrees. The new matrix is stored in the variable rotMatrix. Once the rotation Matrix is created, it is multiplied by the point p that is to be rotated. The result of this multiplication is the transformed point.

   One problem with Euler rotations is the problem of Gimbal lock. This is where the axis "roll-up" into each other, and cause subsequent rotations to be oriented incorrectly. A detailed discussion of Gimbal lock can be found in the references section. Since order of rotations is important, GeoGL assumes x, y, then z when generating rotation matrices. This can result in the z axis rotations being handled incorrectly. This problem can be solved by storing rotations in a quaternion. By using a 4th dimension, the quaternion can express rotations without the gimbal lock problem. The example below uses a quaternion to create a rotation matrix.

Quaternion rotations
// Rotate a point
fVector3D rotatePoint(fVector3D point, fVector3D rotation)
{
   // Create a quaternion from the rotation angles
   fVector4D rotVector = fVector4D::fromEulerDeg(rotation);

   // Create a rotation matrix from the quaternion
   fMatrix4x4 rotMatrix(rotVector);

   // Multiply the point by the rotation matrix, and return the new point
   return rotMatrix * point;
}

void main()
{
   fVector3D p(5,0,0);      // A point in 3D space

   // Rotate the point around 360 degrees, in increments of 45
   for (int i = 0;  i < 360; i += 45)
   {
      fVector3D pRot = rotatePoint( p, fVector3D(i, 360-i, 2*i) );
      // geoGL provides stream operators
      cout << "Point at i: " << pRot << endl;
   }
}

   The only difference in the above code is the addition of an intermediate step where a quaternion is generated. Similar to Matrix4x4, the Vector4D class has static methods for generating quaternions from Vector3D. (fromEulerXDeg, fromEulerYDeg, fromEulerXRad, fromEulerRad,... see the headers for a detailed list). The quaternion can store a rotation matrix in only 4 values. This quaternion is used to create a rotation matrix just as in the first example. This use of quaternions doesn't buy us a whole lot, but it can be built-upon to create some rather powerful camera movement. (see the Camera3D class)

   Like vectors, quaternions and matrices can be multiplied together to combine transforms:

Combining transformations
void main()
{
   fVector4D q1,q2,q3;

   q1 = Vector4D::fromEulerDeg(fVector3D(0,0,30)); // 30 degees along Z axis
   q2 = Vector4D::fromEulerXDeg(-60);              // -60 degrees along X axis
   // Combine rotations in a specific order
   q3 = q1 * q2;

   Matrix4x4 m1,m2,m3;

   m1 = Matrix4x4::fromEulerDeg(fVector3D(0,0,30));// 30 degrees along Z axis
   m2.mat[0][3] = 5;                               // Translation along X
   m2.mat[1][3] = 10;                              // Translation along Y
   m2.mat[2][3] = -15;                             // Translation along Z

   // Rotate this matrix 30 degrees around Z, then translate it by (5,10,-15)
   m3 = m1*m2;

   // Now use openGL to do something with this Matrix
   // Because of the memory layout, matrices work with openGL nicely
   glMultMatrixf(m3);
}

   The above example shows the importance of order when multiplying quaternions and matrices. Two quaternion rotations are combined in the above example, performing the rotation around Z first, and X next. The result is store in the quaterion q3, which can be used to create a rotation matrix as in the preceeding examples.

   A similar process is demonstrated using matrices. A matrix can represent rotations and translations, so first a rotation matrix m1 is created, then a translation matrix in m2. Unlike vectors, matrices are initialized to the identity when matrix during construction. This makes it simple to create a translation matrix by accessing the members of the Matrix4x4 class directly, and inserting the values needed to create a translation matrix. The two matrices are combined, with the rotation first followed by the translation. This matrix can be used to rotate a point then move it.

   Matrices and quaternions are used extensively in the Camera3D class provided in geoGL. This class demonstrates using quaternions to produce rotations that are free of Gimbal lock and offer convenient features for rotating and positioning a camera in 3D space.