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.

Figure 6. Color variation by elevation

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
    // Select shading
   ColoringAttributes ca = new ColoringAttributes(matColor,ColoringAttributes.SHADE_GOURAUD);
    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.

Figure 7. ViewPlatform object interactions

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();
    add("Center", canvas);
//
// 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);
      world.addChild(bg);
   //
   // 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);
      lights.addChild(ambLight);
      // 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);
      lights.addChild(dirLight);
       world.addChild(lights);
//
// Create a universe and attach the branch group
//
   universe = new SimpleUniverse(canvas);
  universe.addBranchGraph(world);
   }
  /**
  *  Loads elevation data file, creates and initializes the view
  * and viewing platform to conform to the terrain model
  */
  public void load(String fileName,StatusWindow stat)
  {
    model = new ElevationModel(fileName,stat);
    world.addChild(model);
 //
//  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
  view.setFieldOfView(Math.toRadians(FIELD_OF_VIEW));
//
//  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.

Figure 8. Navigation control menu
Figure 9. Navigation control panel

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:
| 1 2 3 4 5 Page 3