Version 2.2
Copyright © 2001–2007 Iowa State University
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with the Invariant Sections being Appendix A, GNU Free Documentation License, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in Appendix A, GNU Free Documentation License.
$Date: 2007-12-13 22:21:26 -0600 (Thu, 13 Dec 2007) $
Table of Contents
gmtl::Vec<S, T> Helper
Classgmtl::Vec3f and
gmtl::Vec4fgmtl::Matrix44f Helper
Classgmtl::Matrix44fList of Figures
vrj::App hierarchyvrj::GlApp interface
extensions to vrj::Appvrj::PfApp interface
extensions to vrj::Appvrj::GlApp application
classvrj::PfApp application
classvrj::OpenSGApp application
classvrj::OsgApp application
classList of Tables
List of Examples
gadget::DigitalInterface in an
Application Objectgadget::KeyboardMouseInterface in
an Application ObjectpfExit() with a
Heap-Allocated Application ObjectpfExit() with a
Stack-Allocated Application Objectvpr::SerializableObjectMixin<T>cluster::UserData<T>latePreFrame()draw()vpr::Intervalconfig_app.jdefConfigApp.jconfMyDelegate.mm: Basic Delegate
ImplementationTable of Contents
gmtl::Vec<S, T> Helper
Classgmtl::Vec3f and
gmtl::Vec4fgmtl::Matrix44f Helper
Classgmtl::Matrix44fIn this book, we present a “how-to” for writing VR Juggler applications. We will explain concepts used in VR Juggler and present carefully annotated example code whenever appropriate. There are two groups of people who should read this book:
Those who are required to read it in order to do a project for work or school. To those in this category, fear not—VR Juggler is very simple to use after getting through the initial learning stages. It is a powerful tool that will allow the creation of interesting and powerful applications very quickly.
Those who are just interested in creating compelling, interesting VR applications. VR Juggler facilitates the construction of extremely powerful applications that will run on nearly any combination of hardware architecture and software platform.
To help readers get the most from this book, recommendations follow to provide an idea of what previous experience is necessary. Various programming skills are needed, of course, but programming for VR requires more than just knowledge of a given programming language. VR Juggler takes advantage of many programming design patterns and advanced concepts to make it more powerful, more flexible, and more extensible. A good background in mathematics is helpful for performing the myriad transformations that must be applied to three-dimensional (3D) geometry.
To get the most from this chapter, there are a few prerequisites:
C++ programming experience
Some graphics programming background (e.g., OpenGL, OpenGL Performer, etc.)
Reasonable mathematical background (linear algebra knowledge is very useful)
For some of the advanced sections of this book, it is recommended that readers review the VR Juggler architecture book. This is optional, though it may be helpful in gaining a quicker understanding of some topics and concepts.
Readers who already have experience with other VR software development environments can easily skim through this book and find the relevant new information. The book is designed for easy skimming. Simply look at the headings to get a good determination of what should be read and what may be skipped.
This book is organized into three main parts:
Introduction: The introduction to the key VR Juggler application development concept, application objects. We also describe common helper classes that simplify the process of writing applications.
Writing applications: The presentation of application development including how to get input from devices and how to write applications for each of the supported graphics application programmer interfaces (APIs).
Advanced topics: An extension of the previous chapters showing how to incorporate run-time reconfiguration into applications and how to write multi-threaded applications.
Table of Contents
In VR Juggler, all applications are written as objects that are handled by the kernel. The objects are known as application objects, and we will use that term frequently throughout this text. Application objects are introduced and explained in this chapter.
VR Juggler uses the application object to create the VR environment with which the users interact. The application object implements interfaces needed by the VR Juggler virtual platform.
Since VR Juggler applications are objects, developers do
not write the traditional main() function.
Instead, developers create an application object that
implements a set of pre-defined interfaces. The VR Juggler
kernel controls the application's processing time by calling
the object's interface implementation methods.
In traditional programs, the main()
function defines the point where the thread of control
enters the application. After the main()
function is called, the application starts performing any
necessary processing. When the operating system (OS) starts the
program, it gives the main() function some
unit of processing time. After the time unit (quantum) for the
process expires, the OS performs what is called a
“context switch” to change control to another
process. VR Juggler achieves similar functionality but in a
slightly different manner.
The application objects correspond to processes in a normal OS. The kernel is the scheduler, and it allocates time to an application by invoking the methods of the application object. Because the kernel has additional information about the resources needed by the applications, it maintains a very strict schedule to define when the application is granted processing time. This is the basis to maintain coherence across the system.
The first step in defining an application
object is to implement the basic interfaces defined by
the kernel and the Draw Managers. There is a base class for the interface that
the kernel expects (vrj::App) and a base class handled by each Draw Manager
interface (vrj::PfApp, vrj::GlApp, etc.). See Figure 2.1, “vrj::App hierarchy” for a visual
representation of the complete application interface hierarchy.
The interface defined in
vrj::App specifies methods for initialization, shutdown,
and execution of the application. This is the abstract type
that is seen by the VR Juggler kernel. The Draw Manager
interfaces specified in the vrj::*App
classes define the API-specific functions necessary to render
the virtual environment. For example, an interface used by a
Draw Manager could have functions for drawing the scene and for
initializing context-specific information.
To implement an application in VR Juggler, developers simply need to “fill in the blanks” of the appropriate interfaces. To simplify this process, there are default implementations of most methods in the interfaces. Hence, the user must only provide implementations for the aspects they want to customize. If an implementation is not provided in the user application object, the default is used, but it is important to know that in most cases, the default implementation does nothing.
When overriding a virtual method defined by a VR
Juggler application class, it is best to call the parent
class method implementation before performing any
application-specific processing. For example, if a
user-defined application object overrides
vrj::App::init() in the class
userApp, the method
userApp::init() should invoke
vrj::App::init() before performing
its own initialization steps.
As stated earlier, the most common approach for VR
application development is one where the application defines the
main() function. That
main() function in turn calls library
functions when needed. (This is the model followed by software
packages such as the CAVElibs™ and the
Diverse Toolkit.) The library in this model only executes code
when directed to do so by the application. As a result, the
application developer is responsible for coordinating the
execution of the different VR system components. This can lead to
complex applications.
As a virtual platform, VR Juggler does not use the model described above because VR Juggler needs to maintain control of the system components. This control is necessary to make changes to the virtual platform at run time. As the controller of the execution, the kernel always knows the current state of the applications, and therefore, it can manage the run-time reconfigurations of the virtual environment safely. With run-time reconfiguration, it is possible to switch applications, start new devices, reconfigure running devices, and send reconfiguration information to the application object.
Application objects lead to a robust architecture as a result of low coupling and well-defined inter-object dependencies. The application interface defines the only communication path between the application and the virtual platform, and this allows restriction of inter-object dependencies. This decreased coupling allows changes in the system to be localized, and thus, changes to one object will not affect another unless the interface itself is changed. The result is code that is more robust and more extensible.
Because the application is simply an object, it is possibly to load and unload applications dynamically. When the virtual platform initializes, it waits for an application to be passed to it. When the application is given to the VR Juggler kernel at run time, the kernel performs a few initialization steps and then executes the application.
Since applications use a distinct interface to communicate with the virtual platform, changes to the implementation of the virtual platform do not affect the application. Changes could include bug fixes, performance tuning, or new device support.
By treating applications as objects, we can mix
programming languages in the VR Juggler kernel. For example, an
application object could be written in Python, C#, or even
VB.NET, but the VR Juggler kernel (written in standard C++)
will still see it as an instance of the abstract interface
vrj::App. The use of application objects has allowed
such extensions to VR Juggler to be written without requiring
any changes to VR Juggler.
In this section, we describe one way to start VR Juggler. We
will use the traditional main() function in
C++, but this is not the only way to do it. We have written Python
applications that start the VR Juggler kernel, and it is possible
to write a VR Juggler daemon that loads applications
on demand at runtime. In other words, the VR Juggler startup
procedure is quite flexible, and we choose to focus on the
simplest method here.
Previously, we explained how VR Juggler applications do
not have a main() function, but further
explanation is required. While it is true that user
applications do not have a
main() function because they are objects,
there must still be a main() somewhere
that starts the system. This is because the operating system
uses main() as the starting point for all
applications. In typical VR Juggler applications, there is a
main(), but it only starts the VR Juggler
kernel and gives the kernel the application to run. It then
waits for the kernel to shut down before exiting.
The following is a typical example of a
main() function that will start the VR
Juggler kernel and hand it an instance of a user application
object. The specifics of what is happening in this code are
described below.
1 #include <vrj/Kernel/Kernel.h>
#include <simpleApp.h>
int main(int argc, char* argv[])
5 {
vrj::Kernel* kernel = vrj::Kernel::instance(); // Get the kernel
simpleApp* app = new simpleApp(); // Create the app object
kernel->loadConfigFile(...); // Configure the kernel
10 kernel->start(); // Start the kernel thread
kernel->setApplication(app); // Give application to kernel
kernel->waitForKernelStop(); // Block until kernel stops
return 0;
15 }
| This line finds (and may create) the VR Juggler kernel. The kernel reference is stored in the handle so that we can use it later. |
| We instantiate a copy of the user application
object ( |
| This statement represents the code that will be
in the |
| As a result of this statement, the VR Juggler kernel begins running. It creates a new thread of execution for the kernel, and the kernel begins its internal processing. From this point on, any changes made reconfigure the kernel. These changes can come in the form of more configuration files or in the form of an application object to execute. At this point, it is important to notice that the kernel knows nothing about the application. Moreover, there is no need for it to know about configuration files yet. This demonstrates how the VR Juggler kernel executes independently from the user application. The kernel will simply work on its own controlling and configuring the system even without an application to run. |
| This statement finally tells the kernel what application it should run. The method call reconfigures the kernel so that it will now start invoking the application object's member functions. It is at this time that the application is now running in the VR system. |
VR Juggler 2.2 introduces support for Cocoa on Mac OS X.
Cocoa itself is quite different than the X11 or Win32 APIs as
far as restrictions on threading and the implementation of the
main() function. The fundamental issue,
however, is that both the NSApplication
singleton object and the vrj::Kernel
singleton object want to be in charge of application execution.
A balance has been struck that generally allows VR Juggler
applications to look and act the same on Mac OS X as on any
other platform while still taking advantage of unique features
that Cocoa and Mac OS X have to offer.
First and foremost, when using a Cocoa-aware version of VR Juggler, applications have to be constructed as bundles. The details of OS X application bundles are far beyond the scope of this document. However, VR Juggler provides just about everything that is required to make an application bundle. This makes sense because VR Juggler is an application framework that dictates how applications are written and executed. Thus, it also provides the core information required for proper application construction and execution on Mac OS X.
To get started, it is helpful to understand what the
application bundle will look like when we have everything in
place. An application bundle is a directory structure with a
special name. For example, MPApp.app
will show up in the Finder as an
application named “MPApp” that can be
double-clicked. The name can contain spaces if so
desired.
Under the base directory is the
Contents subdirectory. It will contain
the files Info.plist and
PkgInfo.
Next, there are two subdirectories
MacOS and
Resources. Compiled binaries should
normally go into the MacOS
subdirectory. If nothing else, the bundle executable (the
compiled application binary) must go in
the MacOS directory. Shared libraries
can go into the Resources directory if
desired. Generally, though, the
Resources directory will contain
platform-independent data files.
In the resources directory, there will be another
subdirectory en.lproj[1] which in turn contains
MainMenu.nib from
$VJ_BASE_DIR/share/vrjuggler/data/bundle.
This is a critical part of the application bundle, and it is
very important that this NIB behave correctly. The NIB
defines the user interface for the VR Juggler application,
and what is provided with VR Juggler is set up and ready to
go for the most common cases. It is possible to use a
different MainMenu.nib, but customizing
it will require care.
The files and directories needed for application
bundle creation can be found in the directory
$VJ_BASE_DIR/share/vrjuggler/data/bundle.
The first of these is
Contents/Info.plist, the basic
property list for an application bundle. Open it with the
Property List Editor application and change the
@APP_NAME@ strings accordingly for the
name of the application being constructed. For the
CFBundleExecutable property, be sure
to change @APP_NAME@ to be the name of
the executable that will be in the Contents/MacOS
directory. Other properties to change are those that
include copyright and version information.
The contents of
Contents/PkgInfo define the
application bundle as an application bundle. The contents
of the file will often be APPL????,
though other characters are allowed in place of the
???? part.
The Contents/Resources
directory contains data files related to application
execution. The file vrjuggler.plist,
the use of which is highly recommended, must be put in
this directory. The application bundle icon file (a file
with the extension .icns) is also
stored in this directory. A default icon file,
vrjuggler.icns, can be used, or a
custom one can be made. The file to use must be named in
Contents/Info.plist.
For VR Juggler application bundles, a useful file
is vrjuggler.plist. The default
version of this file from
$VJ_BASE_DIR/share/vrjuggler/data/bundle
disables VR Juggler configuration file loading through
NSApplication channels and
identifies the NSApplication
delegate class type that will be used (see the section called “Making a Custom NSApplication
Delegate on Mac OS X”). These are set
using the properties VRJConfigHandling
and VRJDelegateClass
respectively.
The “configuration file loading through
NSApplication channels” bit
has to do with associating VR Juggler configuration files
with application bundles. There are different means by
which data can be delivered to applications on Mac OS X.
For example, double-clicking on a file in the
Finder (or selecting
in the context menu
for the file) causes a registered handler application to
be opened. The file is given to the application once it
has opened through the
NSApplication event system. By
setting the VRJConfigHandling property
to false in
Contents/Resources/vrjuggler.plist,
this capability is disabled for the application bundle in
question. That is, the application bundle will ignore the
NSApplication events pertaining to
configuration files to be loaded as a result of
double-clicking on the said files. Configuration files
can still be opened on the fly using the
menu defined in the default NIB
(see below).
The en.lproj subdirectory of
Contents/Resources contains
information translated in the English language. The most
important item in this directory is MainMenu.nib
(discussed next), but the optional file
$VJ_BASE_DIR/share/vrjuggler/data/bundle/InfoPlist.strings,
or some other version of same, can be copied into this
directory. Other language-specific directories can be
created as subdirectories of
Contents/Resources as necessary.
They, too, must contain a
MainMenu.nib.
This is the NIB for the VR juggler application
bundle. It has been designed using Interface Builder with
a simple user interface that knows how to interact with a
VR Juggler application. The interface includes the
menu with the item for opening VR
Juggler configuration files on the fly. Other menus and
menu items can be added by making a custom MainMenu.nib.
Using the version from
$VJ_BASE_DIR/share/vrjuggler/data/bundle
as a starting point is a good idea. Note that
MainMenu.nib is a directory, and it
should be copied recursively to
Contents/Resources/en.lproj.
The main() function
implementation show in the section called “Structure of a main()
Function” will work without
any problems on Mac OS X. However, many of the VR Juggler
sample and test applications have a slightly more
complicated main() function. In
particular, these applications usually determine whether the
user has passed in any arguments through the command line
and exit with an error message explaining how to run the
application if none were given. The example below shows how
this is commonly done with an if
statement before anything else:
1 @include <iostream>
#include <cstdlib>
#include <vrj/Kernel/Kernel.h>
#include <simpleApp.h>
5
int main(int argc, char* argv[])
{
if ( argc <= 1 )
{
10 std::cout << "Usage: " << argv[0]
<< "cfgfile[0] cfgfile[1] ... cfgfile[n]"
<< std::endl;
std::exit(EXIT_FAILURE);
}
15
vrj::Kernel* kernel = vrj::Kernel::instance(); // Get the kernel
simpleApp* app = new simpleApp(); // Create the app object
kernel->loadConfigFile(...); // Configure the kernel
20 kernel->start(); // Start the kernel thread
kernel->setApplication(app); // Give application to kernel
kernel->waitForKernelStop(); // Block until kernel stops
return 0;
25 }This approach will not necessarily work on Mac OS X
because users normally expect to be able to launch an
application by double-clicking on its icon in the
Finder and then loading data into
it through a GUI. If a user launched the above application
that way, the application would exit immediately, and the
user would have to open the
Console application to find out
what went wrong. Launching from the command line, while
perfectly valid on Mac OS X, is simply not what users
expect. At the same time, remote launching of VR juggler
applications in a cluster will almost certainly require
launching without the use of the
Finder and passing in arguments
through argv. This is definitely the case
when using Maestro.
If a VR Juggler application will be run on Mac OS X, the programmer has to decide whether command line processing is critical to the execution of the application. There are three options available to VR Juggler programmers on Mac OS X: rely exclusively on command line arguments to launch, handle no command line arguments, or allow the use of command line arguments but do not require them. Given that VR Juggler applications written on all other platforms are highly likely to rely exclusively on command line arguments to launch, the first choice is expected to be the most commonly chosen approach. Nevertheless, we will explain all three.
If a VR Juggler application used on Mac OS X will
rely solely on command line arguments to launch, it is
operating in exactly the same manner as on all other
platforms. The main() function can
be written as is shown in this document to exit if no
command line arguments are supplied. There is still one
more thing to do, though. In the
Resources directory of the
application bundle, there must be a property list file in
the bundle called
Contents/Resources/vrjuggler.plist.
This property list must have the property
VRJConfigHandling set to
false. This is the default setting for
this property if the vrjuggler.plist
file that comes with VR Juggler was used in constructing
the application bundle.
To execute the application, run the program in
<appname>.app/Contents/MacOS
from a command prompt and pass in the command line
arguments. The appropriate event handling will be set up
so that the application will behave just as any other Mac
OS X application.
If no command line arguments are to be handled
(i.e., the application is to behave the same as typical
Mac OS X applications), then the
main() function shown above needs to
change. Specifically, it cannot exit with an error
message if no command line arguments are passed in. To
keep the application portable, a preprocessor conditional
can be used, as shown in Example 2.1, “Ignoring Command Line Arguments on Mac OS
X”. Then, in
Contents/Resources/vrjuggler.plist,
set the VRJConfigHandling property to
true. To execute the application,
double-click the application bundle icon in the
Finder or use the
open command from a command
prompt.
Example 2.1. Ignoring Command Line Arguments on Mac OS X
1 @include <iostream>
#include <cstdlib>
#include <vrj/Kernel/Kernel.h>
#include <simpleApp.h>
5
int main(int argc, char* argv[])
{
#if ! defined(VRJ_USE_COCOA)
if ( argc <= 1 )
10 {
std::cout << "Usage: " << argv[0]
<< "cfgfile[0] cfgfile[1] ... cfgfile[n]"
<< std::endl;
std::exit(EXIT_FAILURE);
15 }
#endif
vrj::Kernel* kernel = vrj::Kernel::instance(); // Get the kernel
simpleApp* app = new simpleApp(); // Create the app object
20
#if ! defined(VRJ_USE_COCOA)
kernel->loadConfigFile(...); // Configure the kernel
#endif
kernel->start(); // Start the kernel thread
25 kernel->setApplication(app); // Give application to kernel
kernel->waitForKernelStop(); // Block until kernel stops
return 0;
}A compromise can be struck between the previous two
options by allowing optional use of command line
arguments. The compromise is simple: do not require
command line arguments but still handle them if they are
given. The form of the main() function that allows this
is shown in Example 2.2, “Handling Optional Command Line Arguments on Mac
OS X”. Note that
the #if around the call to
vrj::Kernel::loadConfigFile()
has been removed.
Example 2.2. Handling Optional Command Line Arguments on Mac OS X
1 @include <iostream>
#include <cstdlib>
#include <vrj/Kernel/Kernel.h>
#include <simpleApp.h>
5
int main(int argc, char* argv[])
{
#if ! defined(VRJ_USE_COCOA)
if ( argc <= 1 )
10 {
std::cout << "Usage: " << argv[0]
<< "cfgfile[0] cfgfile[1] ... cfgfile[n]"
<< std::endl;
std::exit(EXIT_FAILURE);
15 }
#endif
vrj::Kernel* kernel = vrj::Kernel::instance(); // Get the kernel
simpleApp* app = new simpleApp(); // Create the app object
20
kernel->loadConfigFile(...); // Configure the kernel
kernel->start(); // Start the kernel thread
kernel->setApplication(app); // Give application to kernel
kernel->waitForKernelStop(); // Block until kernel stops
25
return 0;
}The only decision left to make is what value to use
for the VRJConfigHandling property in
Contents/Resources/vrjuggler.plist.
If we set it to false (recall that
that is the default in the
$VJ_BASE_DIR/share/vrjuggler/data/bundle/vrjuggler.plist
version), the application cannot be used as a handler for
VR Juggler configuration files. If we set it to true, we
have to be prepared for configuration files to come in
other than through the command line or through the use of
the menu. The default
NSApplication delegate
(VRJBasicDelegate) can deal with
either case. The default behavior for VR Juggler sample
applications is to allow configuration files to be
specified on the command line through the use of a
main() function similar to that
shown in Example 2.2, “Handling Optional Command Line Arguments on Mac
OS X” and to
leave the property VRJConfigHandling
in
Contents/Resources/vrjuggler.plist
set to false.
Before proceeding into application object details, we must understand how VR Juggler calls the application, and we must know what a frame is. In the code above, the statement on line 9 tells the kernel thread to start running. When the kernel begins its execution, it follows the sequence shown in Figure 2.2, “Kernel loop sequence”. The specific methods called are described in more detail in the following section. This diagram will be useful in understanding the order in which the application object methods are invoked.
The VR Juggler kernel calls each of the methods in the application object based on a strictly scheduled frame of execution. The frame of execution is shown in Figure 2.2, “Kernel loop sequence”; it makes up all the lines within the “while(!quit)” clause.
During the frame of execution, the kernel calls the
application methods and performs internal updates (the
updateAllData() method call). Because
the kernel has complete control over the frame, it can make
changes at pre-defined “safe” times when the
application is not doing any processing. At these times, the
kernel can change the virtual platform configuration as long as
the interface remains the same.
The frame of execution also serves as a framework for the
application. That is, the application can expect that when
preFrame() is called, the devices have
just been updated. Applications can rely upon the system being
in well-defined stages of the frame when the kernel invokes the
application object's methods.
Within this section, we provide a brief overview of the
member functions from the base VR Juggler application interface.
This interface is defined by
vrj::App, and the member functions are shown in Figure 2.3, “Application object interface”. Refer to Figure 2.2, “Kernel loop sequence” for a visual presentation
of the order in which the methods are invoked.
The base interface of the application object defines the following functions:
init()
apiInit()
preFrame()
intraFrame()
postFrame()
As previously described, the VR Juggler kernel calls these functions from its control loop to allocate processing time to them. These functions handle initialization and computation. Other member functions that can be used for reconfiguration, focus control, resetting, and exiting will be covered later in this book.
The following is a description of the application objects related to the initialization of a VR Juggler application. The order of presentation is the same as the order of execution when the application is executed by the kernel.
The init() method is called
by the kernel to initialize any application data. When the
kernel prepares to start a new application, it first calls
init() to signal the application
that it is about to be executed.
This member function is called immediately after the kernel is told to start running the application and before any graphics API handling has been started by VR Juggler.
This member function is for any graphics API-specific initialization required by the application. Data members that cannot be initialized until after the graphics API is started should be initialized here.
In OpenGL, there is no concept of initializing the API, so this method is normally empty in such applications.
This member function is called after the graphics API has been started but before the kernel frame is started.
Once the application object has been initialized by the VR Juggler kernel, the kernel frame loop begins. Each frame, there are specific application object methods that are invoked, and understanding the timing and potential uses of these methods can improve the functionality of the immersive application. In some cases, it is possible to use these member functions to optimize the application to improve the frame rate and the level of interactivity.
As of VR Juggler 2.0 Alpha 1, applications can specify
the units of measure that are the basis for the graphics
they render. The default unit of measure is feet (identified
by the constant scale factor
gadget::PositionUnitConversion::ConvertToFeet)
to maintain backwards compatibility with the previous VR
Juggler semantics. By overriding this method, applications
can identify the unit of measure they expect. The default
implementation is the following:
float vrj::App::getDrawScaleFactor()
{
return gadget::PositionUnitConversion::ConvertToFeet;
}Overriding this method means changing the rendering
scale factor used by the VR Juggler Draw Managers. The
current list of constants (defined in
gadget/Position/PositionUnitConversion.h)
is as follows:
gadget::PositionUnitConversion::ConvertToFeet
gadget::PositionUnitConversion::ConvertToInches
gadget::PositionUnitConversion::ConvertToMeters
gadget::PositionUnitConversion::ConvertToCentimeters
Because the value returned is simply a scaling factor, user applications can define whatever units they want. Note that internally, VR Juggler is treating all units as meters, so the scaling factor converts from meters to the desired units.
The preFrame() method is
called when the system is about to trigger drawing. This is
the time that the application object should do any
last-minute updates of data based on input device status. It
is best to avoid doing any time-consuming computation in
this method. The time used in this method contributes to the
overall device latency in the system. The devices will not
be re-sampled before rendering begins.
The latePreFrame() method is
called after preFrame() and after
shared application-specific data is synchronized among the
cluster nodes (see the section called “Cluster Application Programming”
for more details) but before the scene is rendered. Scene
graph-based application objects making use of
application-specific data in a cluster configuration should
perform scene graph updates based on the most recently
received copy of the shared application data. Application
objects not using a scene graph can make state updates in
this method or in the rendering method
(draw() in the case of
vrj::GlApp). The writer node must
have written to the shared application data in
preFrame() to minimize the latency
of the data.
This method is called after application-specific data is sychronized among the cluster nodes but before triggering rendering of the current frame.
When using shared application-specific data with a
scene-graph based application object in a cluster
configuration, the nodes that read from the shared data
(those where
cluster::UserData<T>::isLocal()
returns false) should perform state updates based on the
freshly received update to the shared data.
The code in this method executes in parallel with the rendering method. That is, it executes while the current frame is being drawn. This is the place to put any processing that can be done in advance for the next frame. By doing parallel processing in this method, the application can increase its frame rate because drawing and computation can be parallelized. Special care must be taken to ensure that any data being used for rendering does not change while rendering is happening. One method for doing this is buffering. Use of synchronization primitives is not recommended because that technique could lower the frame rate.
This method is invoked after rendering has been triggered but before the rendering has finished.
Finally, the postFrame()
method is available for final processing at the end of the
kernel frame loop. This is a good place to do any data
updates that are not dependent upon input data and cannot be
overlapped with the rendering process (see the discussion on
vrj::App::intraFrame()
above).
This method is invoked after rendering has completed but before VR Juggler updates devices and other internal data.
Beyond the basic methods common to all applications, there
are methods that are specific to a given Draw Manager. The
application classes are extended for each of the specific Draw
Managers. The graphics API-specific application classes derive
from vrj::App and extend this interface further. They add extra
“hooks” that support the abilities of the specific
API.
The OpenGL application base class adds several methods to
the application interface that allow rendering of OpenGL
graphics. The extensions to the base
vrj::App class are shown in Figure 2.4, “vrj::GlApp interface
extensions to vrj::App”. In the following, we describe
the method vrj::GlApp::draw(), the
most important element of the interface. More details about the
vrj::GlApp class are provided in the section called “OpenGL Applications”, found in Chapter 4, Application Authoring Basics.
The OpenGL Performer application base class adds
interface functions that deal with the OpenGL Performer scene
graph. Some of the interface extensions are shown in Figure 2.5, “vrj::PfApp interface
extensions to vrj::App”. The following is a description
of only two methods in the
vrj::PfApp interface. More detailed discussion on this
class is provided in the section called “OpenGL Performer Applications”, found
in Chapter 4, Application Authoring Basics.
The initScene() member
function is called when the application should create the
scene graph it will use.
[1] This is the English language project data.
Translations of MainMenu.nib
would go into the appropriate language-specific
subdirectory.
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.