Finding the Surface Height


Creating Ground Cover

Ground cover is the 2D scenery that decorates a landscape. An item of ground cover is represented by a transparent GIF pasted onto a quad that stands on the terrain's surface and is always oriented towards the viewer. The quad is implemented as a four-sided QuadArray inside a GroundShape object (a subclass of OrientedShape3D). The pasting uses blended transparency so the quad is invisible and only the opaque parts of the GIF are rendered.

Typical ground cover includes trees, bushes, and road signs. Such elements will appear many times inside a scene, and it would be inefficient to create a separate shape for each one. Instead, the GroundShape object (e.g., a tree) is embedded in a SharedGroup node, which allows the geometry to be shared by multiple transformGroups. Each transfromGroup specifies a location for a particular ground cover element, but the object is a shared node.

The approach is illustrated by Figure 27-19.

Figure 27-19. A subgraph using a shared GroundShape object


Since a GroundShape object is attached to landBG, terrain coordinates can be used to set its position inside the landscape.

The constructor for Landscape( ) adds ground cover by calling:

     GroundCover gc = new GroundCover(fname);     landBG.addChild( gc.getCoverBG( ) );

The GroundCover object, gc, manages the creation of the subgraph holding the ground cover items. The call to getCoverBG( ) returns the coverBG BranchGroup, the top-level of the subgraph.

The Ground Cover

Information about what ground cover should be added to the terrain is given in a <fname>GC.txt text file, where <fname> is the name of the landscape OBJ file. For instance, the information for the test2 scene is stored in test2GC.txt in models/:

     range 0 500     tree1.gif 90 200     tree2.gif 90 200     tree3.gif 90 200     tree4.gif 90 200

Here is the format of a GC file:

     range [ min max ]     <GS file1> scale1 number1     <GS file2> scale2 number2     // more gs image lines

The range line specifies the height range within which ground cover may appear. A range restriction is useful for stopping scenery appearing on the surface of lakes or the tops of mountains.

A GS file contains a transparent GIF image, which is loaded into its own GroundShape object. The scale argument is used to set the size of the screen, and number determines the number of copies of the ground cover placed in the scene. As with 3D models, the best scale value is largely a matter of trial and error, but 90 is a good starting value since it makes the cover a bit taller than the user's viewpoint.

I don't supply terrain coordinates for the scenery, which would further complicate the example. Instead, to keep things simple, the scenery is positioned at random locations. Even this is difficult, since the (x, y, z) data must be obtained from somewhere. The solution used in GroundCover is to read in the OBJ file (e.g., test2.obj) as a text file and record the v data (the (x, y, z) coordinates of its mesh) as Vector3d objects in a coords ArrayList. Positioning becomes a matter of randomly selecting a coordinate from the coords list. This approach is surprisingly fast though memory intensive. The selection of the coordinates is done by the code instead of being forced on the programmer. Arguably, this may be a drawback since there's no programmer control over placement, including the inability to clump ground cover together: each mesh coordinate is 30 terrain units apart.

Figure 27-20 gives class diagrams for GroundCover and GroundShape, showing all the data and methods.

In GroundCover, loadCoverInfo( ) loads and parses the GC text file. It calls loadObj( ) and readObj( ) to parse the coordinates information extracted from the OBJ file, storing them in the coords ArrayList. Each GS file line from the GC file triggers a call to loadGC( ), which attaches a subgraph to coverBG similar to the one in Figure 27-19.

     private void loadGC(String gcFnm, double scale, int gcNo)     {       String gcFile = new String("models/" + gcFnm);

Figure 27-20. GroundCover and GroundShape classes


       SharedGroup gcSG = new SharedGroup( );       gcSG.addChild( new GroundShape((float) scale, gcFile) );       gcSG.setPickable(false);   // so not pickable in scene       Vector3d coordVec;       for(int i=0; i < gcNo; i++) {  // make multiple TGs using same SG         coordVec = selectCoord( );         placeCover(gcSG, coordVec);       }     }

The arguments passed to loadGC( ) are the three values supplied on the GS file line in the GC file. A single GroundShape object is created, and multiple transforms are connected to it by repeatedly calling placeCover( ):

     private void placeCover(SharedGroup gcSG, Vector3d coordVec)     {       Transform3D t3d = new Transform3D( );       t3d.rotX( Math.PI/2.0 );    // to counter rotation of the land       TransformGroup rotTG = new TransformGroup(t3d);       rotTG.addChild( new Link(gcSG) );       Transform3D t3d1 = new Transform3D( );       t3d1.set( coordVec );       TransformGroup posTG = new TransformGroup(t3d1);       posTG.addChild( rotTG );       coverBG.addChild( posTG );     }

placeCover( ) creates a single branch down from coverBG to the GroundShape node (see Figure 27-19). A common coding error when using a SharedGroup node is to attempt to link it directly to the rest of the scene graph. Each link must be through a Link node.

Figure 27-19 shows that each branch from coverBG to a Link node holds two transformGroups. The first (posTG) moves the shape to a location on the terrain's surface; its value comes from a call to selectCoord( ) in loadGC( ):

     private Vector3d selectCoord( )     // randomly select a landscape coordinate     { int index = (int) Math.floor( Math.random( )*coords.size( ));       return (Vector3d) coords.get(index);     }

The second transformGroup (rotTG) in Figure 27-19 plays a similar role to the sTG node for 3D models (see Figure 27-12): it counters the -90-degree rotation around the x-axis applied to the landscape.

The Ground Shape

A GroundShape object displays a transparent GIF drawn on the front face of a four-sided QuadArray. The center of the quad's base is at (0, 0, 0) and rests on the ground. It has sides of screenSize and is always oriented toward the viewer. The orientation is achieved by making GroundShape a subclass of OrientedShape3D and setting its axis of rotation to be the y-axis.

GroundShape's createAppearance( ) sets up the necessary transparency attributes for the shape and loads the GIF as a texture:

     private void createAppearance(String fnm)     {       Appearance app = new Appearance( );       // blended transparency so texture can be irregular       TransparencyAttributes tra = new TransparencyAttributes( );       tra.setTransparencyMode( TransparencyAttributes.BLENDED );       app.setTransparencyAttributes( tra );       // Create a two dimensional texture with min and mag filtering       TextureLoader loader = new TextureLoader(fnm, null);       Texture2D tex = (Texture2D) loader.getTexture( );       if (tex == null)         System.out.println("Image Loading Failed for " + fnm);       else {         tex.setMinFilter(Texture2D.BASE_LEVEL_LINEAR);         tex.setMagFilter(Texture2D.BASE_LEVEL_LINEAR);         app.setTexture(tex);       }       setAppearance(app);     }  // end of createAppearance( )

Min filtering improves the texture's appearance when it's reduced in size, while mag-filtering is used when the texture is enlarged.



Killer Game Programming in Java
Killer Game Programming in Java
ISBN: 0596007302
EAN: 2147483647
Year: 2006
Pages: 340

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net