Sponsored By

3D Graphics Programming in Java, Part II

Last week, Bill Day introduced you to the basic concepts of Java 3D programming. This week, in the second of his three-part installment, he delves deeper into the API, performance optimizations, reuse of 3D content, and interoperability with VRML.

January 22, 1999

21 Min Read

Author: by Bill Day

Java 3D has the potential to allow Java programmers of all sorts to include vibrant 3D content in their applets and applications. This is fact, but not certainty, as Sun faces heavy competition from other technologies, especially OpenGL. Beyond the basics that every 3D API must provide, what does Java 3D have going for it?

Last week we began our exploration of Java 3D with static content and small scenes. This week we'll conclude our introduction by delving into loaders for larger scene graphs and giving our worlds behaviors and interactivity. We'll also discuss the interplay between Java 3D and VRML, review the performance characteristics of Java 3D, and set the stage for next month's look at Java OpenGL bindings.

Java 3D Transforms and Placement

I received several questions about the placement of the 3D objects in last week's examples. One reader in particular asked why, when he modified the transform so the cube's z coordinate was positive, he could no longer see the cube. Let's reexamine the example code from last week's Example 3 (see Resources for links to the code archive) to learn more about how things are situated in three dimensional space using Java 3D.

The critical line of code from this example is:

myTransform3D.setTranslation(new Vector3f(-0.5f,-0.5f,-2.3f));

The arguments in the Vector3D constructor are x, y, and z coordinates. The cube is placed by this transform at x = -0.5; y = -0.5; and z = -2.3 (in floating point values, hence the "f" suffixes).

It's critical to note that your viewing location can change. This is determined by the location of the associated ViewPlatform, which, in turn, is situated in the 3D world using the TransformGroup above it (actually, using a Transform3D node to transform the TransformGroup above it).

Also note that in Example 3, we don't move the ViewPlatform; we construct all of the view branch using the default constructors, which use an identity Transform3D node (more on identity later) in the TransformGroup above ViewPlatform. Thus, the ViewPlatform is located at x = 0.0; y = 0.0; and z = 0.0, the origin of the 3D coordinate system.

And finally, it's important to note that the coordinate system is aligned with your view into it. With the default view branch, the +x axis runs from left to right across the screen and the +y axis runs from bottom to top, which means, using the right-hand rule, that the +z axis runs out of the screen towards you. By default, your view into the world is looking in the -z direction and the origin is located in the middle of your screen.

 

img_1.gif

Figure 1: Default Java 3D coordinate axes, as seen from directly in front of the monitor. We are looking in the -z axis direction, directly into the monitor. The origin is located in the center of the x-y plane.



img_2.gif

Figure 2: Default Java 3D coordinate axes, as seen from directly above the front of the monitor. We are looking in the -y axis direction along the x-y plane. The +z axis is sticking straight out of the monitor.

 

Because you're "sitting" at the origin (the view platform is located at 0.0, 0.0, 0.0) looking in the negative-z direction, anything in the positive-z section of the 3D world is behind you and you can't see it. Don't you just love 3D! (See Resources for more information on Transform3D.)

Exploring the com.sun.j3d class archives
Now, let's return to the issue of the contents of Sun's private jar files. Last week I briefly mentioned that Sun's Java 3D implementation installs both standard Java 3D classes (as specified in the Java 3D API specification) and nonstandard (Sun implementation-specific) classes. These classes are made available in one of several jar archives placed within the new Java 2 platform (aka JDK 1.2 for developers) extensions directory (see last week's article for more information on this). We have discussed the standard classes and jars in sufficient detail, but we still have some ground to cover on the Sun-specific ones.

As a reminder, Sun's archives include:

  • j3daudio.jar, which archives the com.sun.j3d.audio packages, described last week

  • j3dutils.jar, which encapsulates a variety of Sun utility classes in 17 total packages and subpackages underneath com.sun.j3d (more details in a moment)

When you explode the j3dutils.jar archive, you see the following subpackages of com.sun.j3d:

  • audioengines

  • audioengines.javasound

  • loaders

  • loaders.lw3d

  • loaders.objectfile

  • utils

  • utils.applet

  • utils.audio

  • utils.behaviors

  • utils.behaviors.interpolators

  • utils.behaviors.keyboard

  • utils.behaviors.mouse

  • utils.behaviors.picking

  • utils.geometry

  • utils.image

  • utils.internal

  • utils.universe

Now that you know the basic layout of Sun's utility class archives, you can delve into them more deeply if you need any of the functionality they provide. For instance, if you're looking to capture mouse events to control interpolated behaviors and you need to load in LightWave 3D content, you should review com.sun.j3d.utils.behaviors.mouse, com.sun.j3d.utils.behaviors.interpolators, and com.sun.j3d.loaders.lw3d.

Please note that while some documentation is provided for these packages in the general Java 3D documentation from Sun, the quality of documentation varies from package to package and from class to class. I recommend that you monitor the java3d-interest mailing list for information on the particular utility classes you need to use.

Behaviors and Interpolators

Interactivity results from being able to program an object to react to certain stimuli. In Java 3D, these behaviors are scheduled when the view platform crosses the stimulus bounds, a region of space defined by the programmer. Bounds assign a spatial scope to objects. There are bounds for both sounds and behaviors in Java 3D.

The advantage of requiring bounds for objects is that the runtime can safely disregard (not render) things that are out of bounds. If you build behaviors into your Java 3D applications, you will be required to set bounds in order to have the Java 3D runtime scheduler prepare your behaviors for execution. The scene graph includes a set of nodes for setting up and scheduling behaviors and bounds. In addition, several of the utilities described above help you to more easily deal with interactivity, including the aptly named "behaviors" subpackages.

Behaviors can be keyed to any number of Java events or user interactions. When a user hovers near a certain object, for instance, you can cause the object to move, change color, or otherwise exhibit behavior. (In this case, the stimuli would be the user's presence inside of a specified bounds, or region, of space.)

Many behaviors can be controlled using interpolators, objects that accept a range of values to provide a time-varying behavior. You can build your own interpolators if you like, but Java 3D's built-in interpolators and Sun's utilities should suffice for most of your needs. In Example 4 (see Resources), we modify last week's Example 3 to make the Gamasutra rotate.

001   /**
002    * constructContentBranch() is where we specify the 3D graphics
003    * content to be rendered.  We return the content branch group
004    * for use with our SimpleUniverse.  We have added a RotationInterpolator
005    * to Example03 so that in this case, our "Gamasutra" text rotates
006    * about the origin.  We have also removed the scaling and static
007    * rotation from the text, and the scaling from our ColorCube.
008   **/
009   private BranchGroup constructContentBranch() {
010     Font myFont = new Font("TimesRoman",Font.PLAIN,10);
011     Font3D myFont3D = new Font3D(myFont,new FontExtrusion());
012     Text3D myText3D = new Text3D(myFont3D, "Gamasutra");
013     Shape3D myShape3D = new Shape3D(myText3D, new Appearance());   
014     Shape3D myCube = new ColorCube();
015 
016     BranchGroup contentBranchGroup = new BranchGroup();
017     Transform3D myTransform3D = new Transform3D();
018     TransformGroup contentTransformGroup = new TransformGroup(myTransform3D);
019     contentTransformGroup.addChild(myShape3D);
020 
021     Alpha myAlpha = new Alpha();
022     myAlpha.setIncreasingAlphaDuration(10000);
023     myAlpha.setLoopCount(-1);
024     RotationInterpolator myRotater = 
025                       new RotationInterpolator(myAlpha,contentTransformGroup);
026     myRotater.setAxisOfRotation(myTransform3D);
027     myRotater.setMinimumAngle(0.0f);
028     myRotater.setMaximumAngle((float)(Math.PI*2.0));
029     BoundingSphere myBounds = new BoundingSphere();
030     myRotater.setSchedulingBounds(myBounds);
031     contentTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
032     contentTransformGroup.addChild(myRotater);
033 
034     contentBranchGroup.addChild(contentTransformGroup);
035 
036     myTransform3D.setTranslation(new Vector3f(-0.5f,-0.5f,-2.3f));
037     TransformGroup cubeTransformGroup = new TransformGroup(myTransform3D);
038     cubeTransformGroup.addChild(myCube);
039     contentBranchGroup.addChild(cubeTransformGroup);
040 
041     return(contentBranchGroup);
042   }



img_3.gif

Figure 3: Rotating 'Gamasutra' in the 3D world using RotationInterpolator -- Part 1



img_4.gif

Figure 4: 'Gamasutra' continues to rotate around in the 3D world -- Part 2



By default, behaviors have no scheduling bounds and are never scheduled. You must set scheduling bounds yourself to execute behaviors in Java 3D. It's also important to remember that even with nondefault bounds specified, your behaviors may not be executed if the view platform isn't within the bounded region. This is, of course, by design, but it can make for frustrating debugging experiences if you're not careful.

Reusing the World's 3D Content

If you were limited to using only content you could manually code into your Java 3D applications, you would either limit yourself to small scene graphs with a few nodes, or relinquish any hope of sleeping ever again. Writing Java 3D code to create complex 3D worlds from scratch is certainly nontrivial. Realizing that such limits aren't good for the success of Java 3D (not to mention your sanity), Sun has made it easy to import from standard 3D file formats into Java 3D using loaders.

Put simply, a loader knows how to read content from a standard 3D file format -- say, for instance, Wavefront's Object file format (OBJ) -- then construct a Java 3D scene from it. There are a variety of loaders available on the Web, including several provided by Sun within the Java 3D release itself. All of them are documented at the Java 3D Loaders archive (see Resources). As of this writing, 19 publicly known Java 3D file loaders are currently available, supporting such common formats as AutoCAD's Drawing Interchange File (DXF), LightWave's Scene Format (LWS) and Object Format (LWO), 3D-Studio's 3DS, and application-specific formats like the Protein Data Bank's PDB.

The world is full of free and commercial archives of 3D content. So, given that loaders and content exist, how do you use them?

Our first loader: Wavefront OBJ Content Using ObjectFile

Sun has included support for Wavefront's OBJ and LightWave's LightWave 3D file formats built into its implementation of Java 3D. Both formats are fairly prevalent in the modeling and animation community, and both are well specified and understood (again, please refer to the Loaders Archive URL for more information on these formats). I picked the OBJ format to provide a simple example of how to use a loader.

Wavefront OBJ is supported by Sun's com.sun.j3d.utils.loaders.objectfile package. The main class we will use to load and parse the content is ObjectFile, which encapsulates an OBJ file and provides methods to load its content into Java 3D. In the simplest case, we can load in OBJ content with a few lines of code in a modified constructContentBranch() method, as illustrated in Example 5 (see Resources):

001   /**
002    * constructContentBranch() is where we specify the 3D graphics
003    * content to be rendered.  Here we read in a cube using
004    * Sun's OBJ loader, then return this to be rendered.  This
005    * cube could be replaced with more complicated content exported
006    * from 3D modeling programs supporting the OBJ format.
007   **/
008   private BranchGroup constructContentBranch() {
009     ObjectFile myOBJ = new ObjectFile();
010     Scene myOBJScene = null;
011 
012     //Attempt to load in the OBJ content using ObjectFile.
013     try {
014       myOBJScene = myOBJ.load("cube.obj");
015     } catch (FileNotFoundException e) {
016       System.out.println("Could not open OBJ file...exiting");
017       System.exit(1);
018     }
019 
020     //Construct and return branch group containing our OBJ scene.
021     BranchGroup contentBranchGroup = new BranchGroup();
022     contentBranchGroup.addChild(myOBJScene.getSceneGroup());
023     return(contentBranchGroup);
024   }

 

This method will load in the cube data, build a com.sun.j3d.utils.loaders.Scene from it, and use the scene to return a scene group that can be added to the content branch for rendering.

This is the simplest possible case, however, and won't handle some of the details we would normally want to deal with (things like crease angles, which help the runtime determine how to render a scene). For a more robust OBJ loader application, we can turn to Sun's Java 3D demo app, ObjLoad. ObjLoad deals with crease angles and all the other details, sets a default background color, and provides a more mature interface to load and examine OBJ content. This demo is a good example of how Java 3D applications can take advantage of Sun's support for loaders.

 

img_5.gif

Figure 5: cube.obj loaded in Sun's ObjLoad application



Loading more complicated scenes than cube.obj is then a simple matter of specifying the particular filename in your code or command-line parameters. Because of their ease of use coupled with their flexibility in design and implementation, loaders greatly increase the usefulness of Java 3D by increasing interoperability with other applications and runtimes.

VRML97 Meets Java 3D

For Web-based 3D graphics, the standard to interoperate with is VRML. There has been a lot of discussion (which will likely continue) of how and where Java 3D competes with VRML. How do the two compare?

Java 3D will not replace VRML. In fact, the two are largely complementary. VRML is predominantly a file format for 3D data for the Web, while Java 3D is predominantly a 3D graphics runtime system. In fact, just like OBJ, DXF, and other loaders mentioned above, VRML loaders already exist to load a VRML scene from a file into a Java 3D runtime. Java 3D is a runtime programming API, first and foremost. For a realistic and complicated 3D world, however, you probably want to build your geometry and models (or have someone else build them) using modeling programs. For this purpose, VRML is a decent choice.

Authoring programs from companies like Platinum Technologies help you create VRML content with thousands of nodes, then you can load that content into the Java 3D runtime using one of the various loaders available for free on the Web (listed in the Loaders Archive).

As noted previously, VRML is certainly not the only choice for content. But it is a good one: it is now an ISO standard (see the VRML97 standard listing in Resources) and it was designed with the Web in mind, so it's fairly compact and browser-friendly. If you deploy Java 3D apps that load VRML content, you can readily repurpose your content for Web sites.

In addition, the Java 3D and VRML working group (see Resources for a link) is building a VRML97-compliant browser in Java using the Java 3D API. The browser is a free, open-source browser, meant, in part, to illustrate how to build large and interesting applications using Java 3D. Sun's Java 3D team is heavily involved in the project: One of the cochairs of the working group is Henry Sowizral, Sun's lead for Java 3D API and implementation engineering.

If you would like take the Java 3D VRML97 browser out for a spin, you'll need the following (in addition to setting up your system for Java 3D as we discussed last week):

  • Java 3D VRML97 browser, available from the VRML Consortium (see Resources). The latest snapshot, 1.1 Beta 2 Rev C, is approximately 2.8 MB as packaged for Win32 developers in vj3ddv11b2rc.exe. This includes source, vrml97.jar, and examples.

Assuming you download and install the developer-packaged (not JRE runtime only) VRML97 browser, the class file archive (vrml97.jar) will be placed in the /jre/lib/ext directory discussed in last week's article.

You can test out your installation of the VRML97 browser by loading the default world, a cube similar to our previous OBJ content. Do this by changing into the VRML97 examples directory and executing the Vrml97Viewer application.

 

img_6.gif

Figure 6: Vrml97Viewer displays the default simple.wrl cube. The browser provides basic menu commands and allows the user to rotate and examine the loaded object.



Performance, Performance, Performance!

A slow 3D graphics runtime is useless. Every ounce of speed that can be squeezed out should be squeezed out, except of course for concessions pertaining to usability and understandability (though some graphics programmers might even disagree with that).

With OpenGL being the longstanding champion of cross-platform, high-performance 3D graphics, Java 3D has a certain heritage to live up to if it is to be accepted by graphics aficionados. Sun has made every effort to see that it has a chance to meet those expectations. Because implementation-specific optimizations are subject to rapid change, I've decided not to go into great detail here. Sun does a good job of documenting its performance decisions anyway; please refer to the company's Performance Guide (see Resources) for details. I have instead decided to illustrate the obvious and important parts of the API that help to boost performance.

In Sun's own words, some of the major performance-related API features are:

  • Capability bits

  • Compilation of BranchGroups and SharedGroups

  • Bounds

  • Unordered rendering

We briefly covered capability bits last time. In a nutshell, they allow the Java 3D runtime to optimize the scene graph for faster rendering performance. They work in concert with compilation, which I'll discuss in greater detail in a moment.

You will no doubt encounter capability bits at some point when you attempt to modify something and receive a compile-time exception from Java 3D. In order to work around the exception, you must set one or more capability bits. These tell the runtime that you will want to modify some portion of the scene graph at a later time, and thus, that it cannot optimize away your ability to do so.

Finally, unordered rendering allows the Java 3D runtime to pick the optimum rendering path through the scene graph. A great deal of effort has been expended in determining how to optimize rendering speeds when the order of objects to be rendered can be controlled. Sun's Java 3D takes advantage of this previous work to speed things up.

For more information on all of these API performance-oriented features, please refer to Resources.

Compiling Java 3D Branch Groups

When a developer calls BranchGroup.compile(), Java 3D checks which capability bits are set and compiles those portions of the branch group. That is, it rearranges the applicable portion(s) of the scene graph for optimum rendering performance. The runtime may, in fact, do all sorts of weird things to the geometry and other nodes of the scene graph unbeknownst to you. The net result is that things speed up without your needing to understand all of the "magic" behind the process.

But without some statistics to back it up, this is just cheap talk. I added compile() methods to our previous Java 3D examples to see what effect, if any, they would have. For instance, I added the line

contentBranchGroup.compile();

to Example 4 to generate Example 6 (see Resources).

I did not, however, see any noticeable performance differences in any of the previous examples. All loaded and executed in roughly the same time with and without the compile statement.

I ascribe this to the fact that I do not have very complicated content branches that would allow Java 3D to optimize their performance. If you have more complicated applications of Java 3D, I suggest trying compilation to speed things up. As always, your mileage may vary.

Loose Ends

Java 3D is an interesting technology with a broad scope and an equally broad range of tips and tricks. I have endeavored to provide you with good examples with which to get started exploring the API, but there just isn't enough room or time to go into all the nuances in great enough detail. I have included more information on tips and tricks in the Resources section to help you find your away around this exciting API.

You'll find pointers to:

  • Sample code for visually debugging normal problems in Java 3D;

  • Information on getting the frame rate from the Java 3D rendering engine; and

  • A how-to for running Java 3D applets in Web browsers.

One more thing to note: I've received several questions on the applicability of Java 3D to collaborative world building and interaction. I believe Java 3D to be more than capable of handling 3D rendering tasks for building collaborative virtual worlds. There is no fundamental restriction to building collaborative virtual worlds in Java 3D. In fact, some of Sun's customers are building, demoing, and deploying large scale simulations and collaborative apps using the technology today (see Sun's Java 3D product site for examples of this).

The networking support inherent to the Java platform makes the communication aspects of collaboration easier than many other languages and APIs. You might have difficulties with client-side availability, however, because Java 3D is not installed in browsers or on desktops by default, and it is not currently available beyond Win32 and Solaris. But if your problem domain and customer base allows you to install software onto participating users' systems and to deal with a Win32- and Solaris-only solution, Java 3D is worthy of further investigation.

Next Month: Java bindings for OpenGL In this article and the last, we've seen how to create and manipulate Java 3D scene graphs, how to load in content from popular file formats and external programs, and how to use VRML97 and Java 3D together.

Next month, we'll shift our attention from Java 3D and VRML to Java bindings for OpenGL, the current frontrunner in cross-platform graphics APIs. I'll give a quick review of the publicly available Java-OpenGL bindings, then settle in for a discussion of one of the most popular bindings -- and the likely starting point for the OpenGL ARB's Java binding standard -- Magician. As always, if you have questions or comments, please let me know.

Resources

 

Bill Day is a software engineer at Silicon Graphics Computer Systems and an ACM Distinguished Lecturer. Bill writes the monthly technical column Media Programming for JavaWorld magazine and is currently authoring a book entitled Java Media Players for O'Reilly & Associates.

Read more about:

Features
Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like