Published on ONJava.com (http://www.onjava.com/)
See this if you're having trouble printing code examples

O'Reilly Book Excerpts: Killer Game Programming in Java

## Killer Game Programming in Java: A 3D Checkerboard, Part 2

Editor's note: In part one of this two-part excerpt from Killer Game Programming in Java, author Andrew Davison strode through some complex programming issues for developing Java 3D graphics, such as how to add shapes, lighting, and backgrounds to a Checkers3D application. Here in part two, Andrew continues the theme by demonstrating how to create a floating sphere for the Checkers3D app.

### The Floor

The floor is made of tiles created with my `ColouredTiles` class, and axis labels made with the Java 3D `Text2D` utility class. Figure 15-5 shows the floor branch, previously hidden inside a "Floor Branch" box in Figure 15-3.

Figure 15-5. Floor branch of the scene graph

The floor subgraph is constructed with an instance of my `CheckerFloor` class and made available via the `getBG( )` method:

``````

The `CheckerFloor( )` constructor uses nested `for` loops to initialize two `ArrayList`s. The `blueCoords` list contains all the coordinates for the blue tiles, and `greenCoords` holds the coordinates for the green tiles. Once the `ArrayList`s are filled, they are passed to `ColouredTiles` objects, along with the color that should be used to render the tiles. A `ColouredTiles` object is a subclass of `Shape3D`, so can be added directly to the floor's graph:

``````

The red square at the origin (visible in Figure 15-1) is made in a similar way:

``````
Point3f p1 = new Point3f(-0.25f, 0.01f, 0.25f);
Point3f p2 = new Point3f(0.25f, 0.01f, 0.25f);
Point3f p3 = new Point3f(0.25f, 0.01f, -0.25f);
Point3f p4 = new Point3f(-0.25f, 0.01f, -0.25f);

ArrayList oCoords = new ArrayList( );

The square is centered at (0, 0) on the XZ plane and raised a little above the y-axis (0.01 units) so it's visible above the tiles.

Each side of the square is the length of 0.5 units. The four `Point3f` points in the `ArrayList` are stored in a counterclockwise order. This is true for each group of four points in `blueCoords` and `greenCoords`. Figure 15-6 shows the ordering of the square's points.

Figure 15-6. OrigMarker, viewed from above

#### The Colored Tiles

My `ColouredTiles` class extends `Shape3D` and defines the geometry and appearance of tiles with the same color. The geometry uses a Java 3D `QuadArray` to represent the tiles as a series of quadrilaterals (quads). The constructor is

``````

The vertex format is an ORed collection of `static` integers, which specify the different aspects of the quad to be initialized later, such as its coordinates, color, and normals. In `ColouredTiles`, the `QuadArray` plane is created using this line of code:

``````
GeometryArray.COORDINATES | GeometryArray.COLOR_3 );``````

The `size( )` method returns the number of coordinates in the supplied `ArrayList`. The coordinate and color data is supplied in `createGeometry( )`:

``````
int numPoints = coords.size( );
Point3f[] points = new Point3f[numPoints];
coords.toArray( points );   // ArrayList->array
plane.setCoordinates(0, points);

Color3f cols[] = new Color3f[numPoints];
for(int i=0; i < numPoints; i++)
cols[i] = col;
plane.setColors(0, cols);``````

The order in which a quad's coordinates are specified is significant. The front of a polygon is the face where the vertices form a counterclockwise loop. Knowing front from back is important for lighting and hidden face culling, and by default, only the front face of a polygon will be visible in a scene. In this application, the tiles are oriented so their fronts are facing upward along the y-axis.

It's necessary to ensure that the points of each quad from a convex, planar polygon, or rendering may be compromised. However, each quad in the coordinates array doesn't need to be connected or adjacent to the other quads, which is the case for these tiles.

Since a quad's geometry doesn't include normals information, a `Material` node component can't be used to specify the quad's color when lit. I could use a `ColoringAttributes`, but a third alternative is to set the color in the geometry, as done here (`plane.setColors(0, cols);`). This color will be constant, unaffected by the scene lighting.

Once finalized, the `Shape3D`'s geometry is set with:

``````
setGeometry(plane);``````

The shape's appearance is handled by `createAppearance( )`, which uses a Java 3D `PolygonAttribute` component to switch off the culling of the back face. `PolygonAttribute` can be employed to render polygons in point or line form (i.e., as wire frames), and to flip the normals of back facing shapes:

``````
Appearance app = new Appearance( );
PolygonAttributes pa = new PolygonAttributes( );
pa.setCullFace(PolygonAttributes.CULL_NONE);
app.setPolygonAttributes(pa);``````

Once the appearance has been fully specified, it's fixed in the shape with

``````
setAppearance(app);``````

#### The Floor's Axis Labels

The floor's axis labels are generated with the `labelAxes( )` and `makeText( )` methods in `CheckerFloor( )`. `labelAxes( )` uses two loops to create labels along the x and z. Each label is constructed by `makeText( )` and then added to the floor's `BranchGroup` (see Figure 15-5):

``````

`makeText( )` uses the `Text2D` utility class to create a 2D string of a specified color, font, point size, and font style:

``````
Text2D message = new Text2D(text, white, "SansSerif", 36, Font.BOLD);
// 36 point bold Sans Serif``````

A `Text2D` object is a `Shape3D` object with a quad geometry (a rectangle), and appearance given by a texture map (image) of the string, placed on the front face. By default, the back face is culled; if the user moves behind an axis label, the object becomes invisible.

The point size is converted to virtual-world units by dividing by 256. Generally, it's a bad idea to use too large a point size in the `Text2D( )` constructor since the text may be rendered incorrectly. Instead, a `TransformGroup` should be placed above the shape and used to scale it to the necessary size.

The positioning of each label is done by a `TransformGroup` above the shape:

``````
TransformGroup tg = new TransformGroup( );
Transform3D t3d = new Transform3D( );
t3d.setTranslation(vertex);   // the position for the label
tg.setTransform(t3d);

`setTranslation( )` only affects the position of the shape. The `tg TransformGroup` is added to the floor scene graph.

### Viewer Positioning

The scene graph in Figure 15-3 doesn't include the view branch graph; that branch is shown in Figure 15-7.

Figure 15-7. The view branch graph

The branch is created by a call to the `SimpleUniverse` constructor in the `WrapCheckers3D( )` constructor:

``````
su = new SimpleUniverse(canvas3D);``````

`SimpleUniverse` offers simplified access to the view branch graph via the `ViewingPlatform` and `Viewer` classes, which are mapped to the graph (shown as dotted rectangles in Figure 15-7).

`ViewingPlatform` is used in `initUserPosition( )` to access the `TransformGroup` above the `ViewPlatform` node:

``````
ViewingPlatform vp = su.getViewingPlatform( );
TransformGroup steerTG = vp.getViewPlatformTransform( );``````

`steerTG` corresponds to the TG node in Figure 15-7. Its `Transform3D` component is extracted and changed with the `lookAt( )` and `invert( )` methods:

``````
Transform3D t3d = new Transform3D( );
steerTG.getTransform(t3d);

t3d.lookAt( USERPOSN, new Point3d(0,0,0), new Vector3d(0,1,0));
t3d.invert( );

steerTG.setTransform(t3d);``````

`lookAt( )` is a convenient way to set the viewer's position in the virtual world. The method requires the viewer's intended position, the point that she is looking at, and a vector specifying the upward direction. In this application, the viewer's position is `USERPOSN` (the (0, 5, 20) coordinate); she is looking toward the origin (0, 0, 0), and "up" is along the positive y-axis. This is illustrated by Figure 15-8.

Figure 15-8. lookAt( ) depicted graphically

`invert( )` is required since the position is relative to the viewer rather than an object in the scene.

### Viewer Movement

The user is able to move through the scene by utilizing the Java 3D `OrbitBehavior` utility class in the view graph. A combination of control keys and mouse button presses move and rotate (or orbits) the viewer's position.

The behavior is set up in `orbitControls( )` in `WrapCheckers3D`:

``````
OrbitBehavior orbit = new OrbitBehavior(c, OrbitBehavior.REVERSE_ALL);
orbit.setSchedulingBounds(bounds);
ViewingPlatform vp = su.getViewingPlatform( );
vp.setViewPlatformBehavior(orbit);``````

The `REVERSE_ALL` flag ensures that the viewpoint moves in the same direction as the mouse.

TIP: Numerous other flags and methods affect the rotation, translation, and zooming characteristics, explained in the `OrbitBehavior` class documentation.

`MouseRotate`, `MouseTranslate`, and `MouseZoom` are similar behavior classes that appear in many Java 3D examples; their principal difference from `OrbitBehavior` is that they affect the objects in the scene rather than the viewer.

TIP: Most games, such as first-person shooters (FPS), require greater control over the viewer's movements than these utility behaviors can offer, so I'll be implementing my own behaviors in later chapters.

### Viewing the Scene Graph

This chapter has used scene graphs to illustrate the discussed coding techniques, and scene graphs are a useful way of understanding (and checking) code.

I received help with my drawings by using Daniel Selman's `Java3dTree` package. It creates a `JFrame` holding a textual tree representation of the scene graph (Figure 15-9).

Figure 15-9. Java3dTree representation of the Checkers3D scene graph

The tree (a `JTree` object) is initially minimized, and branches can be examined by clicking on the subfolder icons. Information about the currently selected node appears in the bottom window. The package is available in j3dtree.jar as part of the source code downloadable from http://www.manning.com/selman/ for Selman's Java 3D Programming text.

Augmenting code to generate the `JTree` is simple. `WrapCheckers3D` must import the `j3dtree` package and declare a global variable for the `JFrame` tree display:

``````

import com.sun.j3d.utils.behaviors.vp.*;

private Java3dTree j3dTree;``````

The `WrapCheckers3D( )` constructor creates the `j3dTree` object:

``````
public WrapCheckers3D( )
{
// other code
su = new SimpleUniverse(canvas3D);

j3dTree = new Java3dTree( );   // create a display tree for the SG

createSceneGraph( );
initUserPosition( );
orbitControls(canvas3D);

j3dTree.updateNodes( su );    // build the tree display window
}``````

After the scene graph has been completed, (i.e., at the end of the constructor), the tree display is built with a single line:

``````
j3dTree.updateNodes( su );``````

However, prior to this, the capabilities of the scene graph nodes must be adjusted with:

``````
j3dTree.recursiveApplyCapability( sceneBG );``````

This operation should be carried out after the content branch group (`sceneBG`) has been completed, but before it is compiled or made live. In my code, this means adding the line to `createSceneGraph( )`:

``````
private void createSceneGraph( )
{
sceneBG = new BranchGroup( );
// other code to create the scene

j3dTree.recursiveApplyCapability( sceneBG );

sceneBG.compile( );
}``````

Unfortunately, you can't just call:

``````
j3dTree.recursiveApplyCapability( su );``````

without generating errors because the `SimpleUniverse( )` constructor has made the `ViewingPlatform` live, which prevents further changes to its capabilities.

Since only the capabilities in the content branch have been adjusted, the call to `updateNodes( )` will generate some warning messages when the view branch below the `Locale` node is encountered.

Compilation and execution must include j3dtree.jar in the classpath. My preferred approach is to do this via command line arguments:

``````

javac -classpath "%CLASSPATH%;j3dtree.jar" *.java

java -cp "%CLASSPATH%;j3dtree.jar" Checkers3D``````

TIP: If typing the classpath repeatedly isn't to your taste, command lines like these can be hidden inside batch files or shell scripts.

The `Java3dTree` object is a textual representation of the scene, which means that I had to draw the scene graph myself. But the plus side is that tree generation has negligible impact on the rest of the program.

Another solution is to use the Java 3D Scene Graph Editor (http://java3d.netbeans.org/j3deditor_intro.html). This displays a graphical version of the scene graph but has the downside that its installation and usage are complicated and the memory requirements may be severe on some machines.