Navigate through virtual worlds using Java 3D

Use level-of-detail and fly-through behaviors

Java 3D is a full-featured 3D graphics API that has been evolving over the past five years. Resources provides a link to Sun Microsystems' Website, where download and installation notes can be found. Java 3D supports a range of applications, including computer-aided design, Web advertising, motion picture special effects, and, of course, computer games. Java 3D uses conventions resembling OpenGL, but is actually a layer that sits atop low-level graphics APIs such as OpenGL and DirectX. Java 3D, like Java, is platform independent.

Geographic information system (GIS) developers wishing to expand into the 3D world and game developers looking for an alternative to virtual world representation appreciate the difficulty in creating virtual worlds that accurately portray the real world and can be navigated by interactive programs. Java 3D provides powerful and efficient mechanisms to display virtual worlds and convenient user interfaces to manipulate the worlds' views. In this article, I describe how to load and display data available from the US Geological Survey (USGS) in DEM (digital elevation model) format and how to create a user interface that allows keyboard and mouse controls to navigate through the virtual world. Figure 1 presents an aerial view of the Grand Canyon created by the application. The application's complete source code and Javadoc HTML files are available in Resources, as is a link to the USGS Website where the data resides.

Figure 1. Aerial view generated by demonstration application

To display a virtual world, a data file containing the coordinates of the hills, valleys, rivers, and oceans is necessary. You can either create this yourself or use an existing one. The USGS has such files available for download in DEM format. A digital elevation model consists of a sampled array of ground elevations (in meters) for positions at regularly spaced intervals. The basic elevation model is produced for the Defense Mapping Agency (DMA) and is distributed by the USGS and EROS (Earth Resources Observation System) Data Center in the DEM data record format. The 1-Degree DEM (3-by-3 arc-second data spacing) provides coverage in 1-by-1 degree blocks (about 68 miles by 68 miles) for the entire continental United States, Hawaii, and parts of Alaska. The 3-by-3 arc-second spacing results in a 1,201-by-1,201 array of elevations for each 1-degree block, or roughly one elevation measurement every 92 meters or 100 yards.

A newer STDS (Spatial Data Transfer Standard) format is also available. These elevation readings can be used to create 3D representations of the landscape, referred to as terrain modeling. When combined with other data types, such as stream locations and weather data, these readings can also be used to assist in forest fire control, determine the volume of proposed reservoirs, calculate the amount of cut-and-fill materials, and assist in determining landslide probability. Along with these useful applications, DEM data can make for realistic virtual worlds for gaming.

Application architecture

Computer games and 3D graphical information systems typically allow the user to change what is displayed on the screen by either manipulating the location of objects in a scene or by moving through the scene, often refereed to as flying through the scene.

To support fly-through and other real-time screen update sequences, developers must implement the most efficient data structures and scene-rendering techniques. One scene-rendering technique used to display virtual worlds is called level-of-detail (LOD) optimizations. This technique draws objects close to the viewer in fine detail, and objects farther away in less detail. For example, when viewing a person's face up close, all the details of nose, eyes, mouth, teeth, and so on, would display on the screen. A simple oval may represent the same person viewed from afar. This can save much processor time, especially if you are depicting a stadium full of people!

Some graphics programmers have shunned Java for its perceived inefficiency and overhead in allocating objects. Java 3D gives the programmer numerous ways to represent a scene's geometry. For example, a coordinate with x, y, z values can be stored as 3 ints, 3 floats, 3 doubles, a Vector3f object, a Point3f object, or a Tuple3f object, to name just a few. A TriangleFanArray, TriangleArray, or TriangleStripArray can represent a triangle whose attributes are passed by value or by reference, indexed or nonindexed, and interleaved (where coordinate, color, and texture data are stored in the same array) or noninterleaved (where coordinate, color, and texture data are stored in separate arrays).

Knowing I would be programming many data points, I tested the Java 3D geometric primitives to see which were the most efficient. I tested several different configurations comparing TriangleArrays versus TriangleStripArrays, interleaved versus noninterleaved attributes, and indexed versus nonindexed attributes. I found that the combination of using TriangleStripArrays (explained more in the section below) with interleaved arrays of floating point data was by far the fastest configuration for rendering the screen and consumed the least space. This means not using low-level objects such as Color3f, Point3f, and Vector3f to store data. Instead, I use arrays of floats. I describe these arrays' formats in more detail later. Since I scaled the scene such that 1 unit equals 1 meter, floats gives me more than enough precision; also, using doubles serves no point, as the documentation indicates that Java 3D converts everything to floats anyway.

To demonstrate the fly-through, terrain-modeling, and level-of-detail optimizations, I created an application capable of reading in a DEM file and allowing the user to navigate through the world. The examples shown in this article use the Grand Canyon East dataset. Figure 2 presents the application's structure. The shaded ovals represent objects I created.

Figure 2. Application architecture

Object responsibilities

This section briefly overviews the functions performed by each of the high-level objects that make up my demonstration application.

  • Main: based on JFrame, creates the application's framework
  • InstructionPanel: based on JPanel, displays mouse and keyboard commands available to the user
  • View3DPanel: based on JPanel, responsible for creating the Java 3D environment (lights and view platforms) and terrain data
  • DemFile: based on ElevationFile, responsible for parsing and loading DEM data into memory
  • ElevationModel: based on BranchGroup, divides the terrain data into segments and creates a LODSegment for each
  • LODSegment: based on BranchGroup, responsible for creating ElevationSegments at various resolutions and setting up the level-of-detail optimization
  • ElevationSegment: based on Shape3D, creates the geometry for a given segment of the terrain data at a given resolution
  • InterleavedTriangleStripArray: based on TriangleStripArray, provides in-place generation of normal vectors

Implementation notes

The following sections describe how critical aspects of these objects were implemented and factors related to their design. In particular, I detail DEM data parsing, creation of Java 3D geometry objects, and user viewpoint manipulation.

Decoding the DEM files

DEM files are available from the USGS as compressed text files. Resources provides links for downloading these files and file format specifications. Each file is composed of 1,024 byte records with fixed-length fields. The first record is referred to as a type "A" record and contains header information, including the quadrangle name, maximum and minimum elevations, latitude and longitude coordinates, and the number of data rows and data columns. One "B" record also exists for each column of data. B records hold the actual elevations (in meters). As a rule, the DEM files contain 1,201 rows by 1,201 columns of data. The specification also mentions an optional type "C" record that has information about data accuracy. Double precision numbers are stored in the old FORTRAN format using a "D" record instead of an "E" record, e.g., 0.1404000000000D+06. Therefore, as the code reads and parses the data, the D must be changed to an E.

I created two classes to handle terrain data. ElevationFile is an abstract class used as a basis for creating format-specific files. ElevationFile defines the minimum common fields needed for constructing 3D images from terrain data, including the minimum and maximum elevations, latitude and longitude of the ground coordinates being mapped, number of rows and columns of data, and a 2D array of elevations (row-column order). By generalizing the file's common aspects, I can more easily modify my application in the future to support other file formats. The source for ElevationFile is shown below:

 * ElevationFile is an abstract base class used to define the interface
 * between files holding terrain data and the Java 3D classes that
 * convert the terrain data into geometric primitives.
 * @author  Mark Pendergast
 * @version 1.0 February 2003
 * Known subclasses @see DemFile
public abstract class ElevationFile{
/** Data file name. */
public String fileName;
/** Geographic name or title of the data. */
public String quadrangleName = "";
/** Minimum elevation in meters. */
public int minElevation = 0;
/** Maximum elevation in meters. */
public int maxElevation = 0;
/** Two-dimensional array of elevation data in meters, array represents equally 
    spaced data points across the groundCoordinates. The first dimension is 
    the row, the second, the column.*/
public int elevations[][] = null; // array of raw elevation data
/** Number of data rows. */
public int nRows;
/** Number of data columns. */
public int nColumns;
/** Holds ground coordinates of the four corners in arc seconds. */
public GroundCoordinates groundCoordinates = new GroundCoordinates();
 public ElevationFile()
 { }

The second class I created, DemFile, inherits ElevationFile and provides code to handle DEM file format specifics. The constructor is passed a filename, which it opens as a BufferedReader. The entire A record is then read into a character array, and the desired fields parsed out and stored in appropriate class variables. I wrote the parseDemDouble() method to parse the old FORTRAN double precision format. Once the A record is read in and processed, the code can read in and parse each B record. To ease this job, I converted BufferedReader into a StreamTokenizer. Since the A record is read in its entirety, the stream is already positioned at the beginning of the first B record.

Now it is just a matter of using a set of nested for loops to read in the B records one at a time (each representing one column of data), and the row data itself. When the process is complete, the streams close. All elevation data is now stored in the 2D elevations array. The elevations array is an integer array since each elevation is to the nearest meter. The source for DemFile is shown below:

1 2 3 4 5 Page 1
Page 1 of 5