HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

The Java programming language and platform are revolutionizing the development and deployment of distributed software. I believe that the huge "twitch" game market (based on game consoles and PCs) will continue to grow, but the largest market growth eventually will be in distributed games and entertainment experiences.

With steady improvements in runtime performance, Java is well positioned to be the language of choice for writing distributed entertainment software.

JavaSoft has recently released the specification for the Java3D API that will provide a cross-platform solution to real-time 3D graphics. Microsoft has also released a beta API of Java bindings for their DirectX libraries. The Microsoft Java API for DirectX is specific to the Windows environment. This article demonstrates the relative ease of generating a simple 3D world using Java and the Direct3D retained-mode library. There is quite a bit of documentation that you must eventually read if you want to take maximum advantage of the DirectX/Direct3D libraries; however, we will introduce real-time 3D programming with a very simple and thoroughly explained example: two texture mapped cubes in orbit around a central point. You can navigate around the orbiting cubes. If you have programmed DirectX applications in C or C++, then you will have a pleasant surprise: it is much easier to use the Java API for DirectX (and programs are much shorter).

The DirectX libraries for Java can be downloaded for free from Microsoft's Web site (they are included with the Java SDK 2.0 but you will also want to download the 5 megabytes of DirectX and Direct3D documentation). The DirectX libraries include:

  • DirectDraw - low level drawing utilities using abstract model for graphics hardware
  • Direct3D - medium level library for displaying 3D objects
  • Direct3DRM - Retained Mode high level library that makes it fairly simple to load 3D models and display them (built using Direct3D)
  • DirectInput - abstraction for all input devices
  • DirectSound - abstraction for sound hardware and a high level API to play sounds
In all, Microsoft supplies hundreds of pages of documentation. We will get you started quickly in this article; read the documentation after you have some fun with the example program and the documentation will be easier to understand. One of the more difficult aspects of generating any real-time 3D environments is generating 3D artwork (or models). the ZIP file for this article uses a simple cube model that you can apply to arbitrary texture files (BMP format files with image dimensions that are a power of two; e.g., 64x64, 128x256, etc.)

We will primarily use Direct3DRM in this simple example. Direct3D uses a left-handed coordinate system.

By default, we look down the positive Z-axis (see Figure 1) away from the origin; here, the positive Z-axis points into the page. (This is a left-handed coordinate system.) For the example, we will place two rotating cubes (one predominately blue and the other brown) in orbit in the X-Z plane. We initially position ourselves at x=0, y=0 and z=-14 (looking by default down the positive Z-axis towards the origin x=y=z=0).

Figure 1
Figure 1:

The structure of the example program is simple:

  • Read the shape and color texture data for the two cubes from the files ncube1.x, ncube2.x, blue.bmp, and brown.bmp. The files ncube1.x and ncube2.x are identical except that they use different texture files.
  • Initialize the Direct3D Component Object Model (COM) interfaces.
  • Enter a loop that draws the current 3D world view from our current viewing position and viewing direction and then polls for keyboard input that is used to change our observation point and direction.

Figure 2 shows the application window. The a' and z' keys move closer and further to the objects (in +Z direction) and the arrow keys rotate the camera' point of view. The u' and d' keys move the viewpoint up and down in the Y direction.

Figure 2
Figure 2:

We will use the following Java classes (which are derived from the Microsoft "castle" and "viewer" sample programs) to implement the orbiting cube example:

  • SimData - container for the Direct3dRM data for storing the cubes, the camera, camera viewing position and direction, graphics device and keyboard input state
  • AnimFrame - encapsulates most of the Direct3dRM setup
  • Animator - animation of 3D frames when the example is running
  • test - test Applet class which creates a SimData object and a AnimFrame. After the ViewFrame is initialized (and we have a Frame or window on the screen) we can create an instance of class Animator.

If you have never used Microsoft's COM model or DirectX before, you need to learn a few new terms:

  • COM - Component Object Model. COM objects are accessed via interfaces (which are largely abstracted away in the Java API).
  • U-V coordinates - a coordinate system used to map textures onto three-dimensional objects (not required in our example since the texture wrapping is specified in the ncube1.x and ncube2.x files).
  • 3D transformations - matrix arithmetic used to move, rotate and size objects and to move the viewing position and orientation.
  • Retained mode - adds scene management and object management functionality to the underlying Direct3D library.
We access the native Windows libraries for the Direct3D retained mode through the interfaces for a few objects:
  • Direct3dRMDevice - Visual display destination for rendering a scene
  • Direct3dRMFrame - used for positioning one or more objects within a scene. We attach visual objects (or a viewpoint) to a frame; move or rotate the frame and all attached objects move or rotate.
  • Direct3dRMViewport - defines how a 3D scene is rendered (or mapped) to a 2D Java Frame
There are other objects used in Direct3D (eventually, you should read the excellent Direct3D documentation that is included with the DirectX developers kit) that we do not use in this simple example (e.g., lights). The simple example shown in Listings 1 through 4 is derived from the more detailed Microsoft example programs "castle" and "viewer".

Listing 1 shows the implementation of the SimData class. This class is a simple container for public data for: a vector, dir, for storing the viewing direction; the Direct3dRM object, d3drm, used to access basic retained mode functions; retained mode frames scene, camera, cube_frame_1, and cube_frame_2 for defining the scene, camera and rotating cube frames of reference; the device, dev, for handling the display destination; rx, ry, rz and rtheta for calculating changes in viewing angle/orientation; and keydown for keeping track of which keyboard keys are currently in a down state.

Listing 2 shows the implementation of class AnimFrame. This class requires a finalize method for releasing reference counts on COM objects used in the example program. It is important to do this because unlike Java variables, COM reference counts affect objects outside of the Java virtual machine. The test class (Listing 4) creates an instance of AnimFrame after the application (or applet) frame is created (this order is required). The method AnimFrame.startDirect creates and returns an instance of class Animator (Listing 3) after initializing the retained mode COM objects and loading the cube objects from data files. The method startDirect creates a DirectDrawClipper object and sets the clip size (i.e., the 2D area inside which drawing operations are allowed) based on the size of the application window. The switch statement is used to determine the hardware color depth and to then set the correct texture map parameters. The class variable info (of type SimData) is used to store data that will be used by the Animator class (Listing 3); the second argument to the Animator class constructor is a reference to the instance info of class SimData, that is stored inside of the Animator class. All non-temporary data that AnimFrame.startDirect initializes is stored in SimData info. The frames info.cube_frame_1 and info.cube_frame_2 are created as children of the global scene frame that is stored in info.scene. Temporary objects Direct3dMeshBuilder builder is used to initialize the cube frames using data files contained in the ZIP file for this article. The viewport info.view is created from the display device, camera (viewing) frame and the size of the display device (a tiny 240 by 180 pixels in this example). Usually, frames are created with a fixed position, orientation and zero angular velocity; the position and orientation can be modified every time a new animation frame is rendered. The cube frames are unusual because they are initialized with non-zero angular velocity components so they appear to rotate like planets in orbit with a slight "wobble" about the vertical (in this case Y) axis.

Efficiency note: all Java objects used in the example program are created before we start rendering frames in real time. Once the program starts (and you see the real-time graphics display), hopefully no new data is created in the Java heap (although method calls will allocate and deallocate stack space) so garbage collection should not slow down the example application once it is running.

Listing 3 shows the implementation of the Animator class. Remember that the constructor for the Animator class is passed an instance of class SimData, which has all the Direct3D, retained mode objects required for the animation of the example scene. The class Animator implements the Runnable interface so it must create and initialize a work thread and define the method run which is automatically called when the work thread is started. The class constructor stores the SimData object reference in the variable win for later access by the method run. The constructor initializes the arrays x1, y1, z1, x2, y2 and z2 that are used to pre-compute the positions of the cubes as they rotate around the origin. The vectors dir, up and right are used to compute changes to the current viewing angles. The vector pos is used to calculate changes to the current viewing position.

The method Animator.run calls the method Animator.Render to update moving objects (remember that the retained mode Direct3D libraries perform scene and moving object management for us automatically) and render a new frame in the current viewport. The method Animator.run also explicitly sets the positions of the two cube frames to move the cubes in orbits around the origin (x=y=z=0) using the pre-calculated values in the arrays x1, y1, z1, x2, y2 and z2. The method Animator.run then updates the viewport's viewing orientation and position (by changing the state of win.camera) based on any keyboard key-down events set in class test (Listing 4). Remember that the variable win.camera is a frame object that defines the current viewport's position and orientation in space.

Listing 4 shows the implementation of the class test. This class simply defines an applet that holds instances of classes AnimFrame and Animator that are created in the method test.init. The old event model (pre JDK 1.1) is used (I followed the example programís "castle" and "viewer") by implementing the methods keyUp and keyDown (these methods simply set the variable int anim.win.keydown to encode the keys that are currently pressed on the keyboard while the example applet is running).

Running the example applet: The example can be run by unzipping the ZIP file for this article in any directory and running the applet with Microsoft's Java Virtual machine:

jview test

This example is simple, but if you take a few minutes to run the example and to carefully read the code, then the Microsoft DirectX/Direct3D documentation will be easier to understand. When you install the Java SDK 2.0, you will also see other interesting examples for using DirectSound and DirectInput. Java is a great language for writing distributed entertainment systems, so using Microsoft's Direct3D, Sun/JavaSoft's Java3D or a combination of VRML (Virtual Reality Modeling Language) and Java, is a winning combination. Have fun!

About the Author
Mark Watson is a Java consultant and the author of nine books. The ZIP file of this article can be found at http://www.javadevelopersjournal.com. Mark can be reached at http://www. markwatson.com

	

Listing 1.  SimData.java 

import com.ms.com.*; 
import com.ms.awt.*; 
import com.ms.awt.peer.*; 
import com.ms.directX.*; 

public class SimData 
{ 
  D3dVector dir = new D3dVector(); 
  public Direct3dRM d3drm; 
  public Direct3dRMFrame scene; 
  public Direct3dRMFrame camera; 
  public Direct3dRMDevice dev; 
  public Direct3dRMViewport view; 
  public Direct3dRMFrame cube_frame_1; 
  public Direct3dRMFrame cube_frame_2; 
  public float rx,ry,rz,rtheta; 
  public int keydown; 
} 

Listing 2.  AnimFrame.java
 
import java.awt.*; 
import java.util.Vector; 
import com.ms.com.*; 
import com.ms.directX.*; 

public class AnimFrame implements DirectXConstants, ILoadTextureCallback { 
  Direct3d d; 
  Direct3dRM d3drm; 
  DirectDrawClipper ddclipper; 
  Direct3dRMMeshBuilder builder; 
  Direct3dRMTexture[] textures = new Direct3dRMTexture[2]; 
  int textureCount; 
  SimData info; 
  Animator f = null; 

  protected void finalize() { 
    for ( int i = 0; i < 2; i++ ) 
      if ( textures[i] != null ) 
        ComLib.release(textures[i]); 

    ComLib.release(info.cube_frame_1); 
    ComLib.release(info.cube_frame_2); 
    ComLib.release(builder); 
    ComLib.release(info.camera); 
    ComLib.release(info.scene); 
    ComLib.release(info.view); 
    ComLib.release(ddclipper); 
    ComLib.release(info.dev); 
    ComLib.release(d); 
    ComLib.release(d3drm); 
  } 

  public Animator startDirect(Component c) { 
    info = new SimData(); 
    d3drm = new Direct3dRM(); 
    info.d3drm = d3drm; 

    ddclipper = new DirectDrawClipper(); 
    ddclipper.setComponent(c); 

    d = new Direct3d(); 
    _Guid g = d.findDeviceForColorModel(D3DCOLOR_MONO, 0); 
    info.dev = d3drm.createDeviceFromClipper(ddclipper, g, 240, 180); 

    switch (d.systemBpp()) { // from Microsoft examples: 
    case 1: 
      info.dev.setShades(4); 
      d3drm.setDefaultTextureShades(4); 
      break; 
    case 16: 
      info.dev.setShades(32); 
      d3drm.setDefaultTextureColors(64); 
      d3drm.setDefaultTextureShades(32); 
      info.dev.setDither(0); 
      break; 
    case 24: 
    case 32: 
      info.dev.setShades(256); 
      d3drm.setDefaultTextureColors(64); 
      d3drm.setDefaultTextureShades(256); 
      info.dev.setDither(0); 
      break; 
    default: 
      info.dev.setShades(1); 
      info.dev.setDither(0); 
      d3drm.setDefaultTextureShades(1); 
      break; 
    } 

    info.dev.setQuality(D3DRMRENDER_UNLITFLAT); 
    info.dev.setTextureQuality( D3DRMTEXTURE_NEAREST ); 

    info.scene = d3drm.createFrame(null); // create scene: 

    // cube # 1 
    info.cube_frame_1 = d3drm.createFrame(info.scene); 
    info.cube_frame_1.setRotation(info.scene, 0.1F, 1.0F, 0.3F, 0.05F); 
    info.cube_frame_1.setPosition(info.scene, 0.0F, 0.0F, 10.0F); 
    builder = d3drm.createMeshBuilder(); 
    builder.loadFromFileByPos("ncube1.x", 0, 0, (ILoadTextureCallback)this, null); 
    builder.setColorRGB(0.8F, 0.8F, 0.8F); 
    info.cube_frame_1.addVisualMeshBuilder(builder); 

    // cube # 2 
    info.cube_frame_2 = d3drm.createFrame(info.scene); 
    info.cube_frame_2.setRotation(info.scene, 0.14F, 1.0F, 0.2F, 0.07F); 
    info.cube_frame_2.setPosition(info.scene, 7.0F, 0.0F, 0.0F); 
    builder = d3drm.createMeshBuilder(); 
    builder.loadFromFileByPos("ncube2.x", 0, 0, (ILoadTextureCallback)this, null); 
    builder.setColorRGB(0.8F, 0.8F, 0.8F); 
    info.cube_frame_2.addVisualMeshBuilder(builder); 

    info.camera = d3drm.createFrame(info.scene); 
    info.camera.setPosition(info.scene, 0.0F, 0.0F, -14.0F); 

    info.view = d3drm.createViewport(info.dev, info.camera, 0, 0, 
                                     info.dev.getWidth(), 
                                     info.dev.getHeight()); 
    info.view.setBack(5000.0F); 

    f = new Animator(Thread.NORM_PRIORITY, info); 
    return f; 
  } 

  public Direct3dRMTexture callbackLoadTexture(String name, IUnknown a) { 
    return textures[textureCount++] = d3drm.loadTexture(name); 
  } 
} 

Listing 3.  Animator.java
 
import java.awt.*; 
import java.applet.*; 
import com.ms.com.*; 
import com.ms.awt.*; 
import com.ms.awt.peer.*; 
import com.ms.directX.*; 

class Animator implements Runnable, DirectXConstants 
{ 
  private Thread work; // work thread 
  private boolean running = true; 

  public SimData win; 

  private D3dVector pos; 
  private D3dVector dir; 
  private D3dVector up; 
  private D3dVector right; 
  private D3dVector zaxis; 

  // for cube positions: 
  private float x1[] = new float[3000]; 
  private float z1[] = new float[3000]; 
  private float x2[] = new float[3000]; 
  private float z2[] = new float[3000]; 
  private int index = 0; 

  public Animator(int p, SimData info) { 
    for (int i=0; i<3000; i++) { 
      float theta = ((float)i)*6.28f / 3000.0f; 
      x1[i] = (float)(10.0f*Math.sin(theta+0.25)); 
      z1[i] = (float)(10.0f*Math.cos(theta+0.25)); 
      x2[i] = (float)(4.0f*Math.sin(2.0f*theta)); 
      z2[i] = (float)(4.0f*Math.cos(2.0f*theta)); 
    } 

    win = info; 
                 
    work = new Thread(this); 
    work.setPriority(p); 

    pos   = new D3dVector(); 
    dir   = new D3dVector(); 
    up    = new D3dVector(); 
    right = new D3dVector(); 
  } 

  public void run() { 
    while(running) { 
      Render(); 
      // update the positions of the two cubes: 
      win.cube_frame_1.setPosition(win.scene, x1[index], 0.0F, z1[index]); 
      win.cube_frame_2.setPosition(win.scene, x2[index], 0.0F, z2[index]); 
      index++; 
      if (index>=3000)  index=0; 
      // adjust camera viewing rotation and position: 
      win.camera.getOrientation(win.scene, dir, up); 
      win.camera.getPosition(win.scene, pos); 
      win.d3drm.vectorCrossProduct(right, up, dir); 
      if (win.keydown == 0 ) { // stop rotation 
        win.rtheta = 0.0F; 
        win.ry     = 0.0F; 
        win.rx     = 0.0F; 
        win.rz     = 0.0F; 
      } 
      win.camera.setRotation(win.scene, win.rx, win.ry, win.rz, win.rtheta); 
      if ((win.keydown  & 0xC) > 0) { 
        pos.x += win.dir.x; 
        pos.z += win.dir.z; 
      } 
      if ((win.keydown & 0x40) > 0) { // up 
        pos.y += 0.1f; 
      } 
      if ((win.keydown & 0x80) > 0) { // down 
        pos.y -= 0.1f; 
      } 
      win.camera.setPosition(win.scene, pos.x,pos.y,pos.z); 
    } 
  } 
         
  public void stop() { running = false; } 
         
  public void start() { running = true; work.start(); } 

  private void Render() { 
    win.scene.move(1.0F); 
    win.view.clear(); 
    win.view.render(win.scene); 
    win.dev.update(); 
  } 
} 

Listing 4.  test.java
 
import java.awt.*; 
import java.applet.*; 
import com.ms.com.*; 
import com.ms.directX.*; 

public class test extends Applet 
{ 
  Animator anim = null; 
  AnimFrame vFrame = null; 
  D3dVector dir = new D3dVector(); 
  D3dVector up = new D3dVector(); 
  D3dVector right = new D3dVector(); 
         
  public static void main(String args[]) { 
    test applet = new test(); 

    Frame frame = new Frame("Java/Direct3D Demo"); 

    frame.setSize(240,180); 
    frame.add("Center", applet); 

    frame.show(); 
    applet.init(); 
    applet.start(); 
  } 

  public void init() { 
    vFrame = new AnimFrame(); 
    anim = vFrame.startDirect(this); 
    requestFocus(); 
  } 

  public void start() { anim.start(); } 

  public void stop() { anim.stop(); } 

  public boolean keyUp(Event e, int key) { 
    switch(key) { 
      case Event.UP: 
        anim.win.keydown &= ~0x1; 
        break; 
      case Event.DOWN: 
        anim.win.keydown &= ~0x2; 
        break; 

      case 'a': 
      case 'A': 
        anim.win.keydown &= ~0x4; 
        anim.win.dir.x = 0; 
        anim.win.dir.z = 0; 
        break; 
      case 'z': 
      case 'Z': 
        anim.win.keydown &= ~0x8; 
        anim.win.dir.x = 0; 
        anim.win.dir.z = 0; 
        break; 
      case 'q': 
      case 'Q': 
      case 27: // escape 
 System.exit(0); 
 break; 

      case Event.LEFT: 
        anim.win.keydown &= ~0x10; 
        break; 
      case Event.RIGHT: 
        anim.win.keydown &= ~0x20; 
        break; 
    case 'u': 
        anim.win.keydown &= ~0x40; 
        break; 
    case 'd': 
        anim.win.keydown &= ~0x80; 
        break; 

      } 

    return true; 
  } 

  public boolean keyDown(Event e, int key) { 
    anim.win.camera.getOrientation(anim.win.scene, dir, up); 
    anim.win.d3drm.vectorCrossProduct(right, up, dir); 

    switch(key) { 
      case Event.UP: 
        anim.win.keydown |= 0x1; 
        anim.win.rtheta = 0.01F;  // speed to rotate 
        anim.win.rx     = right.x;  // axis to rotate about 
        anim.win.rz     = right.z; 
        break; 

      case Event.DOWN: 
        anim.win.keydown |= 0x2; 
        anim.win.rtheta = 0.01F; 
        anim.win.rx     = -right.x; 
        anim.win.rz     = -right.z; 
        break; 
                  
      case 'a': 
      case 'A': 
        anim.win.keydown |= 0x4; 
        anim.win.dir.x = dir.x * 0.2F; 
        anim.win.dir.z = dir.z * 0.2F; 
        break; 

      case 'z': 
      case 'Z': 
        anim.win.keydown |= 0x8; 
        anim.win.dir.x = dir.x * -0.2F; 
        anim.win.dir.z = dir.z * -0.2F; 
        break; 

      case 'u': 
      case 'U': 
        anim.win.keydown |= 0x40; 
        anim.win.dir.y = dir.y * -0.2F; // move up 
        break; 

      case 'd': 
      case 'D': 
        anim.win.keydown |= 0x80; 
        anim.win.dir.y = dir.y * 0.2F; // move down 
        break; 

      case Event.LEFT: 
        anim.win.keydown |= 0x10; 
        anim.win.rtheta = 0.01F; 
        anim.win.ry     = -up.y; 
        break; 

      case Event.RIGHT: 
        anim.win.keydown |= 0x20; 
        anim.win.rtheta = 0.01F; 
        anim.win.ry     = up.y; 
        break; 
      } 
    return true; 
  } 
}

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.