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 1

Editor's note: Our book excerpt today is for all you Java gamers, especially the 3D junkies--we know you're out there. In part one of a two-part series taken from Chapter 15 of Killer Game Programming in Java author Andrew Davison describes how to create a scene in a Checkers3D application, using Java 3D. And check back next week, Andrew shows how to create a floating sphere for the Checkers3D app.

This chapter describes Checkers3D with a Java 3D example that creates a scene consisting of a dark green and blue tiled surface with labels along the x- and z-axes, a blue background, and a floating sphere lit from two different directions. The user (viewer) can travel through the scene by moving the mouse.

The lefthand screenshot in Figure 15-1 shows the initial view; the picture on the right shows the scene after the user has moved around a bit.

Figure 15-1
Figure 15-1. Initial view, and later

Checkers3D illustrates many of the common, and sometimes tricky, aspects of programming with Java 3D. For example, the 3D scene is displayed using the Java 3D Canvas3D class, which must be integrated with Java's Swing components. All Java 3D applications require a scene graph, and Checkers3D shows how to add basic shapes, lighting (ambient and directional), and a background. The scene graph diagram acts as a visual form of documentation, and a textual version of its information can be generated easily, with the help of Daniel Selman's Java3dTree package. (I'll supply details at the end of this chapter).

The floor and sphere utilize Java 3D's QuadArray, Text2D, and Sphere geometry classes: the floor is a series of quadrilaterals in a QuadArray, and labels are placed along the main axes of the floor using Text2D objects. The sphere shows how a 3D shape is colored, lit, and positioned in space. The user looks into the 3D world from a viewpoint. You'll see how it can be initially positioned, and how it can be moved during execution by using Java 3D's OrbitBehavior class.

Class Diagrams for Checkers3D

The class diagrams in Figure 15-2 show all the public and private data and methods for the Checkers3D application.

Figure 15-2
Figure 15-2. Class diagrams for Checkers3D

Checkers3D is the top-level JFrame for the application. WrapCheckers3D is a JPanel holding the scene graph, which is viewable via a Canvas3D object. CheckerFloor creates the subgraph for the floor (e.g., tiles, axes, etc.), with all the same colored tiles represented by a single ColoredTiles object.

TIP: The source code for this example is in the Checkers3D/ directory.

Killer Game Programming in Java

Related Reading

Killer Game Programming in Java
By Andrew Davison

Integrating Java 3D and Swing

Checkers3D is a JFrame where GUI controls, such as Swing text fields and buttons, would be placed if necessary. In this example, it creates an instance of WrapCheckers3D (a JPanel) and places it in the center of a BorderLayout:

    c.setLayout( new BorderLayout( ) );
    WrapCheckers3D w3d = new WrapCheckers3D( );   // panel for 3D canvas
    c.add(w3d, BorderLayout.CENTER);

The Canvas3D view onto the scene is created inside WrapCheckers3D:

    public WrapCheckers3D( )
      setLayout( new BorderLayout( ) );
      // other initialization code

      GraphicsConfiguration config =
                 SimpleUniverse.getPreferredConfiguration( );
      Canvas3D canvas3D = new Canvas3D(config);
      add("Center", canvas3D);

      // other initialization code}

Some care must be taken when using Canvas3D since it's a heavyweight GUI element (a thin layer over an OS-generated window). Heavyweight components aren't easily combined with Swing controls, which are lightweight; the controls are mostly generated by Java. Problems are avoided if the Canvas3D object is embedded in a JPanel; then the panel can be safely integrated with the rest of the Swing-built application.

TIP: There's a detailed discussion of the issues related to combining Canvas3D and Swing at j3d.org (http://www.j3d.org/tutorials/quick_fix/swing.html).

Compared to applications in earlier chapters, there's no update/draw animation loop. This is unnecessary because Java 3D contains its own mechanism for monitoring changes in the scene and initiating rendering. Here is the algorithm in pseudocode form:

    while(true) {
      process user input;
      if (exit request) break;
      perform behaviors;
      if (scene graph has changed)
        traverse scene graph and render;

Behaviors are scene graph nodes containing code that can influence other parts of the graph, such as moving shapes or changing the lighting. They may be used for monitoring the graph, passing details to the non-3D parts of the application.

The details are more complicated than this pseudocode suggests for example, Java 3D uses multithreading to carry out parallel traversal and rendering. However, having a general idea of the process will help you work through the code in the rest of this chapter.

Scene Graph Creation

The scene graph is created by the constructor for WrapCheckers3D:

    public WrapCheckers3D( )
      // initialization code

      GraphicsConfiguration config =
                 SimpleUniverse.getPreferredConfiguration( );
      Canvas3D canvas3D = new Canvas3D(config);
      add("Center", canvas3D);
      canvas3D.setFocusable(true);     // give focus to the canvas
      canvas3D.requestFocus( );

      su = new SimpleUniverse(canvas3D);

      createSceneGraph( );
      initUserPosition( );        // set user's viewpoint
      orbitControls(canvas3D);   // controls for moving the viewpoint

      su.addBranchGraph( sceneBG );

The Canvas3D object is initialized with a configuration obtained from getPreferredConfiguration( ); this method queries the hardware for rendering information. Some older Java 3D programs don't bother initializing a GraphicsConfiguration object, using null as the argument to the Canvas3D constructor instead. This is bad programming practice.

canvas3D is given focus so keyboard events will be sent to behaviors in the scene graph. Behaviors are often triggered by key presses and releases, but they may be triggered by timers, frame changes, and events generated by Java 3D internally. There aren't any behaviors in Checkers3D, so it's not necessary to set the focus. I've left these lines in since they're needed in almost every other program we'll consider.

The su SimpleUniverse object creates a standard view branch graph and the VirtualUniverse and Locale nodes of the scene graph. createSceneGraph( ) sets up the lighting, the sky background, the floor, and floating sphere; initUserPosition( ) and orbitControls( ) handle viewer issues. The resulting BranchGroup is added to the scene graph at the end of the method:

    private void createSceneGraph( )
      sceneBG = new BranchGroup( );
      bounds = new BoundingSphere(new Point3d(0,0,0), BOUNDSIZE);

      lightScene( );         // add the lights
      addBackground( );      // add the sky
      sceneBG.addChild( new CheckerFloor( ).getBG( ) );  // add floor

      floatingSphere( );     // add the floating sphere

      sceneBG.compile( );   // fix the scene
    } // end of createSceneGraph( )

Various methods add subgraphs to sceneBG to build the content branch graph. sceneBG is compiled once the graph has been finalized to allow Java 3D to optimize it. The optimizations may involve reordering the graph and regrouping and combining nodes. For example, a chain of TransformGroup nodes containing different translations may be combined into a single node. Another possibility is to group all the shapes with the same appearance properties together, so they can be rendered more quickly.

bounds is a global BoundingSphere used to specify the influence of environment nodes for lighting, background, and the OrbitBehavior object. The bounding sphere is placed at the center of the scene and affects everything within a BOUNDSIZE units radius. Bounding boxes and polytopes are available in Java 3D.

The scene graph by the end of WrapCheckers3D( ) is shown in Figure 15-3.

The "Floor Branch" node is my invention to hide some details until later. Missing from Figure 15-3 is the view branch part of the scene graph.

Lighting the Scene

One ambient and two directional lights are added to the scene by lightScene( ). An ambient light reaches every corner of the world, illuminating everything equally.

    Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
    // Set up the ambient light
    AmbientLight ambientLightNode = new AmbientLight(white);

The color of the light is set, the ambient source is created along with bounds and added to the scene. The Color3f( ) constructor takes Red/Green/Blue values between 0.0f and 1.0f (1.0f being "full-on").

A directional light mimics a light from a distant source, hitting the surfaces of objects from a specified direction. The main difference from an ambient light is the requirement for a direction vector.

    Vector3f light1Direction  = new Vector3f(-1.0f, -1.0f, -1.0f);
       // left, down, backwards
    DirectionalLight light1 =  new DirectionalLight(white, light1Direction);

Figure 15-3
Figure 15-3. Partial scene graph for Checkers3D

The direction is the vector between (0, 0, 0) and (-1, -1, -1); the light can be imagined to be multiple parallel lines with that direction, originating at infinity.

Point and spot lights are the other forms of Java 3D lighting. Point lights position the light in space, emitting in all directions. Spot lights are focused point lights, aimed in a particular direction.

The Scene's Background

A background for a scene can be specified as a constant color (as shown here), a static image, or a texture-mapped geometry such as a sphere:

    Background back = new Background( );
    back.setApplicationBounds( bounds );
    back.setColor(0.17f, 0.65f, 0.92f);    // sky color
    sceneBG.addChild( back );

Floating Spheres

Sphere is a utility class from Java 3D's com.sun.j3d.utils.geometry package, a subclass of the Primitive class, which is a Group node with a Shape3D child (see Figure 15-3). Its geometry is stored in a Java 3D TriangleStripArray, which specifies the sphere as an array of connected triangles. I don't have to adjust this geometry, but the sphere's appearance and position do require changes.

The Appearance node is a container for references of to much information, including coloring, line, point, polygon, rendering, transparency, and texture attributes.

ColouringAttributes fixes the color of a shape and is unaffected by scene lighting. For a shape requiring interaction between color and light, the Material component is employed. For light to affect a shape's color, three conditions must be met:

The utility Sphere class can automatically creates normals, so the first condition is easily satisfied.

Coloring the Spheres

The Java 3D Material component controls what color a shape exhibits when lit by different kinds of lights:

    Material mat = new Material(ambientColor, emissiveColor,
                                diffuseColor, specularColor, shininess);

The ambient color argument specifies the shape's color when lit by ambient light: this gives the object a uniform color. The emissive color contributes the color that the shape produces (as for a light bulb); frequently, this argument is set to black (equivalent to off). The diffuse color is the color of the object when lit, with its intensity depending on the angle the light beams make with the shape's surface.

TIP: The diffuse and ambient colors are often set to be the same, which matches the way real-world objects are colored when lit.

The intensity of the specular color parameter is related to how much the shape reflects from its shiny areas. This is combined with the shininess argument, which controls the size of the reflective highlights.

TIP: The specular color is often set to white, matching the specular color produced by most objects in the real world.

In Checkers3D, there are two directional lights, which create two shiny patches on the top of the floating sphere (see Figure 15-1). The floor tiles are unlit since their color is set in the shape's geometry (more on this later in the chapter).

The code in floatingSphere( ) that handles the sphere's appearance is shown here:

    Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
    Color3f blue = new Color3f(0.3f, 0.3f, 0.8f);
    Color3f specular = new Color3f(0.9f, 0.9f, 0.9f); // near white

    Material blueMat= new Material(blue, black, blue, specular, 25.0f);

    Appearance blueApp = new Appearance( );

Positioning the Spheres

Positioning a shape is almost always done by placing its scene graph node below a TransformGroup (see the sphere Group in Figure 15-3). A TransformGroup can be used to position, rotate, and scale the nodes which lie beneath it, with the transformations defined with Java 3D Transform3D objects:

    Transform3D t3d = new Transform3D( );
    t3d.set( new Vector3f(0,4,0));     // place at (0,4,0)
    TransformGroup tg = new TransformGroup(t3d);
    tg.addChild(new Sphere(2.0f, blueApp));
           // set the sphere's radius and appearance
           // and its normals by default

The set( ) method positions the sphere's center at (0, 4, 0) and resets any previous rotations or scalings. set( ) can be used to scale and rotate while resetting the other transformations. The methods setTranslation( ), setScale( ), and setRotation( ) only affect the given transformation.

Unlike some 3D drawing packages, the y-axis in Java 3D is in the vertical direction, while the ground is being defined by the XZ plane, as shown in Figure 15-4.

The position of the sphere is Checkers3D is set to be (0, 4, 0), which places its center four units above the XZ plane.

Figure 15-4
Figure 15-4. Axes in Java 3D

View catalog information for Killer Game Programming in Java

Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.