This is the introductory paragraph
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 type | Typedef 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(); //
|
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.
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();
}
|
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.