Table of Contents
gmtl::Vec<S, T> Helper
Classgmtl::Vec3f and
gmtl::Vec4fgmtl::Matrix44f Helper
Classgmtl::Matrix44fWithin this chapter, we present information on some helper classes that are provided for use with VR Juggler. These classes are intended to make it easier for application programmers to write their code. Ultimately, we want application programmers to focus more on compelling immersive content and less on the many details that are involved with 3D graphics programming. The classes presented in this chapter focus on mathematical computations and on input from hardware devices. VR Juggler uses the Graphics Math Template Library or GMTL (part of the Generic Graphics Toolkit software) for mathematical computation. An overview of the most commonly used GMTL data types and operations is presented here. In addition to the GMTL operations, special attention is paid to Gadgeteer, the input system used by VR Juggler, and its device interfaces and device proxies.
This section is intended to provide an introduction to how
the helper class gmtl::Vec<S,
T> works and how it can be used in VR Juggler
applications. It begins with a high-level description of the
classes which forms the necessary basis for understanding them in
detail. Then, examples of how to use all the available operations
in the interfaces for these classes are provided. It concludes
with a description of the internal details of the classes.
The class gmtl::Vec<S,
T> is designed to work the same way as a
mathematical vector, typically of 3 or 4 dimensions. There are
predefined vector types that would normally be used in a VR
application that are provided for convenience. That is, a
gmtl::Vec3f object can be thought of as
a vector of the form <x, y, z>. Similarly, a
gmtl::Vec4f can be thought of as a
vector of the form <x, y, z, w>. An existing
understanding of mathematical vectors is sufficient to know how
these classes can be used. The question then becomes, how are
they used? We will get to that later, and readers who have
experience with vectors can skip ahead. If vectors are an
unfamiliar topic, it may be convenient to think of these
classes as three- and four-element C++ arrays of
floats respectively. Most benefits of the vector
concept are lost with that simpler idea, however. Therefore, if
the reader needs to think of them as arrays, then arrays should
probably be used until vectors feel more comfortable. Once the
use of vectors seems familiar and straightforward, readers are
encouraged to come back and read further.
Vectors are typically used to contain spatial data or something similar. For convenience, however, they can be visualized as a more general-purpose container for numerical data upon which well-defined operations can be performed. There is no need to constrain thinking of them as only holding the coordinates for some point in space or some other limited-scope use. The GMTL vectors use by VR Juggler retain this generality and can be used wherever vectors come in handy.
gmtl::Vec3f and
gmtl::Vec4f, as specific implementations
of mathematical vectors, hide vector operations on
single-precision floating-point numbers (float)
behind a simple-to-use interface. For a single vector, the
following standard vector operations are available:
Inversion (changing the sign of all elements)
Normalization
Calculation of length
Multiplication by a scalar
Division by a scalar
Conversion to a Performer vector
For two vectors, the following operations can be performed:
Assignment
Equality/inequality comparison
Dot product
Cross product
Addition
Subtraction
Using GMTL vectors should be straightforward if readers
understand these operations and keep in mind that
gmtl::Vec3f and
gmtl::Vec4f can be thought of at this
high level.
With an understanding of these classes as standard
mathematical vectors, it is time to learn how to deal with them
at the C++ level. In some cases, the mathematical operators are
overloaded to simplify user code; in other cases, a named
method must be invoked on an object. Before any of that,
however, make sure that the source file includes the
gmtl/Vec.h header file. From here on, the
available operations are presented in the order they were
listed in the previous section. We begin with creating the
objects and setting their values.
Before doing anything with vectors, some must be created.
The examples here use gmtl::Vec3f, but
the example is equally applicable to
gmtl::Vec4f. To create a
gmtl::Vec3f, use the default constructor
which initializes the vector to <0.0, 0.0, 0.0>:
gmtl::Vec3f vec1;
After creating the vector vec1, its
elements can be assigned values all at once as follows:
vec1.set(1.0, 1.5, -1.0);
or individually:
vec1[0] = 1.0; vec1[1] = 1.5; vec1[2] = -1.0;
Note that in the last example, the individual elements of the vector can be accessed exactly as with a normal array. To do the above steps all at once when the vector is created, give the element values when declaring the vector:
gmtl::Vec3f vec1(1.0, 1.5, -1.0);
All of the above code has exactly the same results but accomplishes them in different ways. This flexibility is just one of the ways that GMTL vectors are more powerful than C++ arrays (of the same size, of course).
Once a vector is created, the simplest operation that can be performed on it is finding its inverse. The following code demonstrates just that:
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2; vec2 = -vec1;
The vector vec2 now has the value
<-1.0, -1.5, 1.0>. That is all there is to it. (Readers
interested in details should note that the above does a copy
operation to return the negative values.)
Normalizing a vector is another simple operation (at the interface level anyway). The following code normalizes a vector:
gmtl::Vec3f vec1(1.0, 1.5, -1.0); gmtl::normalize( vec1 );
The vector vec1 is now normalized.
Clean and simple.
Besides normalizing a given vector, a vector can be
tested to determine if it has already been normalized. This is
done as follows (assuming the vector vec has
already been declared before this point):
if ( gmtl::isNormalized( vec1 ) )
{
// Go here if vec is normalized
}Part of normalizing a vector requires finding its length first. To get a vector's length, do the following:
gmtl::Vec3f vec1(1.0, 1.5, -1.0); float length; length = gmtl::length( vec1 );
In this case, length is assigned the value 2.061553 (or
more accurately, the square root of 4.25). Finding the length
of a vector appears simple from the programmer's perspective,
but it has some hidden costs. Namely, it requires a square root
calculation. For optimization purposes, GMTL provides a
function called gmtl::lengthSquared() that
returns the length of the vector without calculating the square
root.
The GMTL vector classes provide an easy way to multiply a vector by a scalar. There are several ways to do it depending on what is required. Examples of each method follow.
To multiply a vector by a scalar and store the result in another vector, do the following:
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2; vec2 = 3 * vec1;
(The order of the factors in the multiplication can be
swapped depending on preference or need.) Here,
vec2 gets the value <3.0, 4.5,
-3.0>.
To multiply a vector by a scalar and store the result in the same vector, do the following:
gmtl::Vec3f vec1(1.0, 1.5, -1.0); vec1 *= 3;
After this, vec1 has the value
<3.0, 4.5, -3.0>.
Very similar to multiplying by a scalar, division by scalars is also possible. While the examples are almost identical, they are provided here for clarity.
To divide a vector by a scalar and store the result in another vector, do the following:
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2; vec2 = vec1 / 3;
Here, vec2 gets the value
<0.333333, 0.5, -0.333333>. Note that the scalar must
come after the vector because the operation would not make
sense otherwise.
To divide a vector by a scalar and store the result in the same vector, do the following:
gmtl::Vec3f vec1(1.0, 1.5, -1.0); vec1 /= 3;
After this, vec1 has the value
<0.333333, 0.5, -0.333333>.
SGI's OpenGL Performer likes to work with its own
pfVec3 class, and to facilitate the use
of it with gmtl::Vec3f, two conversion
functions are provided for converting a
gmtl::Vec3f to a
pfVec3 and vice versa. The first works
as follows:
gmtl::Vec3f vj_vec; pfVec3 pf_vec; // Do stuff to vj_vec... pf_vec = vrj::GetPfVec(vj_vec);
where vj_vec is passed by reference
for efficiency. (pf_vec gets a copy of a
pfVec3.) To convert a
pfVec3 to a
gmtl::Vec3f, do the following:
pfVec3 pf_vec; gmtl::Vec3f vj_vec; // Do stuff to pf_vec... vj_vec = vrj::GetVjVec(pf_vec);
Here again, pf_vec is passed by
reference for efficiency, and vj_vec gets a
copy of a gmtl::Vec3f. Both of these
functions are found in the header
vrj/Draw/Pf/PfUtil.h.
We have already demonstrated vector assignment, though it was not pointed out explicitly. It works just as vector assignment in mathematics. The C++ code that does assignment is as follows:
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2; vec2 = vec1;
After the assignment, vec2 has the
value <-1.0, -1.5, 1.0>. Ta da! Note that this is a copy
operation which is the case for all the types of assignments of
GMTL vectors.
To compare the equality of two vectors, there are three available methods (one is just the complement of the other, though):
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0);
if ( gmtl::isEqual(vec1, vec2) )
{
// Go here if vec1 and vec2 are equal.
}or
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0);
if ( vec1 == vec2 )
{
// Go here if vec1 and vec2 are equal.
}or
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0);
if ( vec1 != vec2 )
{
// Go here if vec1 and vec2 are not equal.
}Choose whichever method is most convenient.
Given two vectors, finding the dot product is often needed. GMTL vectors provide a way to do this quickly so that programmers can save themselves the time of typing in the formula over and over. It works as follows:
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0); float dot_product; dot_product = gmtl::dot(vec1, vec2);
Now, dot_product has the value
4.0.
Besides the dot product of two vectors, the cross product is another commonly needed result. It is calculated thusly:
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0), vec3; vec3 = gmtl::cross(vec1, vec2);
The result is that vec3 gets a copy of
vec1 cross vec2.
Adding two vectors can be done one of two ways. The first method returns a resulting vector, and the second method performs the addition and stores the result in the first vector.
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0), vec3; vec3 = vec1 + vec2;
Now, vec3 has the value <2.5, 2.5,
-2.0>.
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0); vec1 += vec2;
This time, vec1 has the value <2.5,
2.5, -2.0>.
Subtracting two vectors gives the same options as addition, and while the code is nearly identical, it is provided for the sake of clarity.
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0), vec3; vec3 = vec1 - vec2;
Now, vec3 has the value <-0.5, 0.5,
0.0>.
gmtl::Vec3f vec1(1.0, 1.5, -1.0), vec2(1.5, 1.0, -1.0); vec1 -= vec2;
In this case, vec1 has the value
<-0.5, 0.5, 0.0>.
It is often helpful to apply a transformation to a
vector. Transformations are represented by a matrix, so it is
necessary to multiply a matrix and a vector. The function
gmtl::xform() does this job. For the
following example, assume that there is a
gmtl::Matrix44f transformation matrix
xform_mat:
gmtl::Vec3f vec(1.0, 1.0, 1.0), result_vec; gmtl::xform(result_vec, xform_mat, vec1);
Depending on the transformations contained within
xform_mat, result_vec
will be transformed fully. The operation as a mathematical
equation would be:

where V and V' are vectors and M is a 4×4 transformation matrix.
The details behind gmtl::Vec3f and
gmtl::Vec4f really are not all that
gory. Internally, they are represented as three- and
four-element arrays of floats respectively. Access
to these arrays is provided through the member function
getData(). For example, this access
can be used in the following way:
gmtl::Vec3f pos(4.0, 1.0982, 10.1241); glVertex3fv(pos.getData());
Granted, this particular example is rather silly and much
slower than just listing the values as the individual arguments
to glVertex3f(), but it should get the
point across.
In general, the getData() member
function should be treated very carefully. Access to it is
provided mainly so that operations similar to this example can
be performed quickly. An example of abusing access to
getData() follows:
gmtl::Vec4f my_vec; my_vec.getData()[0] = 4.0; my_vec.getData()[1] = 1.0982; my_vec.getData()[2] = 10.1241; my_vec.getData()[3] = 1.0;
Do not do this. It can be confusing to readers of the code who do not necessarily need to know the details of the internal representation. Instead, use one of the methods described above for creating vectors and assigning the elements values.