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.