# Navigate through virtual worlds using Java 3D

### Use level-of-detail and fly-through behaviors

Page 3 of 5

To get interesting color variations, I used a simple ratio based on the vertex's elevation divided by the `(maxElevation-minElevation)`. For red and green values, I multiplied the material colors by this ratio, resulting in darker hues at lower elevations, as shown in Figure 6. I set the blue value to 1 ratio, thus more blue is present at lower elevations, such as lakes, rivers, and oceans. An alternate strategy would be to create a table of colors to be used based on the elevation. For example, elevations near the minimum could be shades of blue, those above the tree line could be gray, those above the snow line could be white. The least complex method would be to just use the material color and allow the lighting calculation to account for all color variations.

The source code for the `ElevationSegment` object is shown below:

```public ElevationSegment( int elevations[][],   int startRow, int startColumn, int stopRow, int stopColumn,
int minEl, int maxEl, GroundCoordinates gc,    float exageration,
float lowX, float highX, float lowZ, float highZ, int resolution)
{
//
// Save the ground coordinates
//
groundCoordinates = gc;
//
// Set up material properties
//
setupAppearance();
//
// Process the 2D elevation array
//
dRows = (int)Math.ceil((stopRow-startRow+1)/(double)resolution);
dColumns = (int)Math.ceil((stopColumn-startColumn+1)/(double)resolution);
xStart = lowX;
zStart = lowZ;
deltaX = (highX-lowX)/(stopColumn-startColumn);
deltaZ = (highZ-lowZ)/(stopRow-startRow);
//
// First, create an interleaved array of colors, normals, and points
//
vertexData = new float[FLOATSPERVERTEX*(dRows)*2*(dColumns-1)];
if(vertexData == null)
{
System.out.println("Elevation segment: memory allocation failure");
return;
}
//
// Populate vertexData a strip at a time
//
int row, col; // Used as indexes into the elevations array
int i; // Used as an index into vertexData
for( col = startColumn, i = 0; col <= stopColumn-resolution; col += resolution)
{
for(row = startRow; row <= stopRow; row += resolution)
{
if(row+resolution > stopRow) // Always use last data line to prevent seams
row = stopRow;
setColor(i+COLOR_OFFSET,elevations[row][col],minEl,maxEl);
setCoordinate(elevations, i+COORD_OFFSET,row,col,startRow,startColumn,exageration);
i += FLOATSPERVERTEX;
int c = col;
if(c+resolution > stopColumn-resolution) // Always use last data line to prevent seams
c = stopColumn-resolution;
setColor(i+COLOR_OFFSET,elevations[row][c+resolution],minEl,maxEl);
setCoordinate(elevations, i+COORD_OFFSET,row,c+resolution,startRow,startColumn,exageration);
i += FLOATSPERVERTEX;
}
}
//
// Create a stripCount array showing the number of vertices in each
// strip
//
int[] stripCounts = new int[dColumns-1];
for(int strip = 0; strip < dColumns-1; strip++)
stripCounts[strip] = (dRows)*2;
//
// Create and set the geometry
//
tStrip = new InterleavedTriangleStripArray(vertexData.length/FLOATSPERVERTEX,
GeometryArray.COORDINATES|GeometryArray.COLOR_3|GeometryArray.NORMALS
|GeometryArray.BY_REFERENCE|GeometryArray.INTERLEAVED, stripCounts);
tStrip.setInterleavedVertices(vertexData);
tStrip.generateNormals(true);
setGeometry(tStrip);
}
/**
*  Set up the material properties and coloring attributes
*
*/
private void setupAppearance()
{
Appearance app = new Appearance(); // Create an appearance
Material mat = new Material(); // Create a material
app.setColoringAttributes(ca); // Add coloring attributes to the appearance
mat.setLightingEnable(true); // Allow lighting
mat.setDiffuseColor(matColor); // Set diffuse color (used by directional lights)
mat.setAmbientColor(matColor); // Set ambient color (used by ambient lights)
mat.setSpecularColor(0f,0f,0f); // No specular color
mat.setShininess(1.0f); // No shininess
mat.setLightingEnable(true); // Allow lighting
app.setMaterial(mat); // Add the material to the appearance setAppearance(app); // Add the appearance to the object // Allows calls to setgeometry setCapability(Shape3D.ALLOW_GEOMETRY_WRITE|Shape3D.ALLOW_GEOMETRY_READ);
}
/**
* Store coordinate data into vertex data array
*
*  @param elevations array of elevations
*  @param i index into vertexData to store coordinate
*  @param row elevation row
* @param col elevation column
* @param startRow first row used in elevations
*  @param stopColumn first column used in elevations
*  @param exageration elevation exageration factor
*/
public void setCoordinate(int[][] elevations, int i, int row, int col, int
startRow, int startColumn, float exageration)
{
vertexData[i] = (float)(((col-startColumn)*deltaX)+xStart);
vertexData[i+1] = elevations[row][col]*exageration;
vertexData[i+2] = (float)(zStart+((row-startRow)*deltaZ));
}
/**
* Store color data into vertex data array, compute color based
* on the elevation's distance between min and max elevations
*
*  @param i index into vertexData to store coordinate
*  @param elevation  vertex elevation (no exageration)
*  @param minElevation minimum elevation in model
*  @param maxElevation maximum elevation in model
*/
public void setColor(int i, int elevation,int minElevation, int maxElevation)
{
float ratio = ((float) elevation)/(float) (maxElevation-minElevation);
vertexData[i] = matColor.x*ratio; // Set red
vertexData[i+1] = matColor.y*ratio; // Set green
vertexData[i+2] = (float)(1-ratio); // Trick to bring blue for the lowest elevations
}
```

#### Control the view

In Java 3D, the image painted on the `Canvas3D` object is created based on where the user is located in the virtual world, what direction she is looking, and her vision characteristics. A `ViewPlatform` object represents the user's location and orientation. Think of this object as an airplane's cockpit. The pilot (user) looks straight ahead, but the plane itself can rotate along any of the three axes and move anywhere in space. The `ViewPlatform` object has a built-in transformation object that controls this movement.

A second object that affects what displays on the screen is the `View` object. This object can be thought of as defining the pilot's vision characteristics. The `View`'s important aspects are the field of view, front clipping distance, and back clipping distance. The field of view specifies how wide an angle the pilot sees; a small angle resembles a horse wearing blinders, a wide angle resembles a camera's fish-eye lens. The front clipping distance determines how close something can be and still be seen; likewise, the back clipping distance determines how far something can be and still be seen.

`FlyingPlatform`, an object based on the abstract `ViewPlatformAWTBehavior`, controls the interaction between input devices, mouse/keyboard, and the `ViewPlatform`. This object is attached to both the `ViewPlatform` and `Canvas3D` objects, as depicted in Figure 7.

#### View3DPanel

`View3Dpanel` is responsible for creating the Java 3D environment. This includes the canvas, universe, lights, view platforms, and loading the terrain data. Both the `View` and the `ViewPlatform` objects are created when the `SimpleUniverse` object is created.

The code segment below shows the portions of code that create the `Canvas3D`, lights, `SimpleUniverse`, and the `FlyingPlatform` objects, as well as the lines that initialize the `View` object and link them together. In this case, I use a front clipping distance of 1 (meter) to ensure that the user can go right up beside a cliff and not have it disappear. I use a back clipping distance equal to twice the overall length (the model's east-to-west distance) so the user can look down on the screen from above and see the entire DEM data segment (approximately 100,000 meters wide). The textbooks and tutorials caution against having a ratio of front-to-back clipping distance greater than 3,000 as some of the low-level OpenGL drivers and graphics hardware may have a hard time dealing with it. I have not had a problem thus far with my system.

The field-of-view constant I use in the program is 45 degrees, as that provides a natural-looking translation onto the screen. I could have used different values to create special effects (e.g., looking through a periscope or telescope).

Following `View`'s initialization, a `FlyingPlatform` is created. Its constructor is passed a reference to the `Canvas3D` object as well as a reference to the `ElevationModel` object holding the terrain data. The `FlyingPlatform` object needs the reference to the `Canvas3D` object to establish communications for mouse and keyboard events. A reference to the `ElevationModel` object is necessary so the `FlyingPlatform` can query it for elevations at specific points when the terrain-following function is enabled.

The next line of code sets an infinite scheduling bounds for the `FlyingPlatform` so it is always active; following that, the `FlyingPlatform` is set as the behavior for the `ViewPlatform` object. The `FlyingPlatform.goHome()` method is then called to set the `ViewPlatform`'s initial position to a predictable location. The code segment shown below is from `View3DPanel` constructor:

```//
// Add a Canvas to the center of the panel
//
setLayout(new BorderLayout());
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
canvas = new Canvas3D(config);
canvas.stopRenderer();
//
// Create the branch group to hold the world
//
world  =    new BranchGroup();
world.setCapability(Group.ALLOW_CHILDREN_EXTEND);
//
// Create the background
//
Background bg = new Background(backgroundColor);
bg.setApplicationBounds(infiniteBounds);
//
// Create lights
//
BranchGroup lights = new BranchGroup();
// Create the ambient light
AmbientLight ambLight = new AmbientLight(true,ambientColor);
ambLight.setInfluencingBounds(infiniteBounds);
ambLight.setCapability(Light.ALLOW_STATE_WRITE);
ambLight.setEnable(true);
// Create the directional lights
Vector3f dir = new Vector3f(1,-1,1);
DirectionalLight dirLight = new DirectionalLight(true,directionalColor, dir);
dirLight.setCapability(Light.ALLOW_INFLUENCING_BOUNDS_WRITE);
dirLight.setCapability(Light.ALLOW_STATE_WRITE);
dirLight.setInfluencingBounds(infiniteBounds);
dirLight.setEnable(true);
//
// Create a universe and attach the branch group
//
universe = new SimpleUniverse(canvas);
}
/**
*  Loads elevation data file, creates and initializes the view
* and viewing platform to conform to the terrain model
*/
{
model = new ElevationModel(fileName,stat);
//
//  Adjust the view based on the size of the model
//
View view = universe.getViewer().getView();
view.setFrontClipDistance(1);  // Allow user to get close to objects
view.setBackClipDistance(model.length*2);  // Allow user to see far off objects
//
//  Set up the viewing platform
//
platform = new FlyingPlatform(canvas, model);
platform.setSchedulingBounds(infiniteBounds);
universe.getViewingPlatform().setViewPlatformBehavior(platform);
platform.goHome();
```

#### FlyingPlatform

`FlyingPlatform` is based on the `ViewPlatformAWTBehavior` abstract object. As previously described, the `FlyingPlatform` object processes mouse and keyboard events generated in the `Canvas3D` object and converts them into changes to the transform function that controls the `ViewPlatform` object. In addition to processing keyboard and mouse input, `FlyingPlatform` also provides functionality for a pop-up menu, as shown in Figure 8, and a navigation control panel, illustrated in Figure 9.

`FlyingPlatform`'s constructor first invokes the constructor of its base class, passing the reference to the `Canvas3D` object and flags indicating that it will process mouse events, mouse motion events, and keyboard events. These events are processed by the creation of routines that override the ones defined in `ViewPlatformAWTBehavior`. These routines include: `mouseDragged`, `mouseMoved`, `mouseClicked`, and `keyPressed`, among others. `FlyingPlatform` then sets the input focus to the `Canvas3D` object, computes an initial elevation for home base and initial movement increments, and creates a `Vector3f` object to store the initial `ViewPlatform` position.

The navigation control dialog `SettingsDialog` is then constructed but not displayed. It only displays in response to a pop-up menu request. Note the `while` loop in the code segment below that searches up the chain of windows to find the parent frame. This allows the dialog box to be tied to the application, making it easier to position and control.

After the `SettingsDialog` is created, `FlyingPlatform'`s constructor creates the pop-up menu components and attaches them to the `Canvas3D` object. Special note: I had to use AWT (Abstract Window Toolkit) `PopupMenu` objects because, try as I might, I could not get the Swing `JPopupMenu` object to display (I'd be interested in seeing a solution). `ItemListeners` and `ActionListeners` were added to the menu items. `FlyingPlatform'`s source code is shown below:

Related:
| Page 3
``` ```