Adding Landscape Walls


Building the Landscape

The Landscape object is created by WrapTerra3D, and is passed a reference to the world's scene (sceneBG) and the filename supplied on the command line (e.g., test1).

Landscape's primary purpose is to display a terrain composed from a mesh and a texture. Landscape looks in the models/ subdirectory for a OBJ file containing the mesh (e.g., test1.obj) and for a JPG (e.g., test1.jpg) to act as the texture. The OBJ file is loaded, becoming the landBG BranchGroup linked to sceneBG. The texture is laid over the geometry stored within landBG.

Landscape can add two kinds of scenery to the terrain:


3D shapes

Loaded with PropManager. This type of scenery includes irregular objects that the user can move around and perhaps enter (e.g., the castle shown in Figure 27-1).


Ground cover

Represented by 2D images that rotate to face the user. This kind of scenery is for simple, symmetrical objects that decorate the ground, such as trees and bushes (see Figures 27-1 and 27-2). Ground cover shapes are managed by a GroundCover object.

The terrain is surrounded by walls covered in a mountain range image.

Loading the Mesh

The Landscape( ) constructor loads the mesh, checks that the resulting Java 3D subgraph has the right characteristics, and extracts various mesh dimensions. At the end of the constructor, the land is added to the world and the texture laid over it:

     // globals     private BranchGroup sceneBG;     private BranchGroup landBG = null;     private Shape3D landShape3D = null;     // various mesh dimensions, set in getLandDimensions( )     private double landLength, minHeight, maxHeight;     private double scaleLen;     public Landscape(BranchGroup sceneBG, String fname)     {       loadMesh(fname);        // initialize landBG       getLandShape(landBG);   // initialize landShape3D       // set the picking capabilities so that intersection       // coords can be extracted after the shape is picked       PickTool.setCapabilities(landShape3D, PickTool.INTERSECT_COORD);       getLandDimensions(landShape3D);   // extracts sizes from landShape3D       makeScenery(landBG, fname);       // add any scenery       addWalls( );                       // walls around the landscape       GroundCover gc = new GroundCover(fname);       landBG.addChild( gc.getCoverBG( ) );   // add any ground cover       addLandtoScene(landBG);       addLandTexture(landShape3D, fname);     }

loadMesh( ) uses Java 3D's utility class, ObjectFile, to load the OBJ file. If the load is successful, the geometry will be stored in a TRiangleStripArray below a Shape3D node and BranchGroup. loadMesh( ) assigns this BranchGroup to the global landBG:

     private void loadMesh(String fname)     {       FileWriter ofw = null;       String fn = new String("models/" + fname + ".obj");       System.out.println( "Loading terrain mesh from: " + fn +" ..." );       try {         ObjectFile f = new ObjectFile( );         Scene loadedScene = f.load(fn);         if(loadedScene == null) {           System.out.println("Scene not found in: " + fn);           System.exit(0);         }         landBG = loadedScene.getSceneGroup( );    // the land's BG         if(landBG == null ) {           System.out.println("No land branch group found");           System.exit(0);         }       }       catch(IOException ioe)       { System.err.println("Terrain mesh load error: " + fn);         System.exit(0);       }     }

getLandShape( ) checks that the subgraph below landBG has a Shape3D node and the Shape3D is holding a single GeometryArray. The Shape3D node is assigned to the landShape3D global:

     private void getLandShape(BranchGroup landBG)     {       if (landBG.numChildren( ) > 1)         System.out.println("More than one child in land branch group");       Node node = landBG.getChild(0);       if (!(node instanceof Shape3D)) {         System.out.println("No Shape3D found in land branch group");         System.exit(0);       }       landShape3D = (Shape3D) node;       if (landShape3D == null) {         System.out.println("Land Shape3D has no value");         System.exit(0);       }       if (landShape3D.numGeometries( ) > 1)         System.out.println("More than 1 geometry in land BG");       Geometry g = landShape3D.getGeometry( );       if (!(g instanceof GeometryArray)) {         System.out.println("No Geometry Array found in land Shape3D");         System.exit(0);       }     }

getLandDimensions( ) is called from Landscape's constructor to initialize four globals related to the size of the mesh:


landLength

The length of the X (and Y) sides of the floor of the landscape.


scaleLen

The scaling necessary to fit landLength into LAND_LEN units in the world. scaleLen will be used to scale the landscape.


minHeight


maxHeight

Minimum and maximum heights of the landscape.

The underlying assumptions are that the floor runs across the XY plane, is square, with its lower lefthand corner at (0,0), and the positive z-axis holds the height values:

     private void getLandDimensions(Shape3D landShape3D)     {       // get the bounds of the shape       BoundingBox boundBox = new BoundingBox(landShape3D.getBounds( ));       Point3d lower = new Point3d( );       Point3d upper = new Point3d( );       boundBox.getLower(lower); boundBox.getUpper(upper);       System.out.println("lower: " + lower + "\nupper: " + upper );      if ((lower.y == 0) && (upper.x == upper.y)) {        // System.out.println("XY being used as the floor");      }      else if ((lower.z == 0) && (upper.x == upper.z)) {        System.out.println("Error: XZ set as the floor; change to XY in Terragen");        System.exit(0);      }      else {        System.out.println("Cannot determine floor axes");        System.out.println("Y range should == X range, and start at 0");        System.exit(0);      }      landLength = upper.x;      scaleLen = LAND_LEN/landLength;      System.out.println("scaleLen: " + scaleLen);      minHeight = lower.z;      maxHeight = upper.z;     }  // end of getLandDimensions( )

The lower and upper corners of the mesh can be obtained easily by extracting the BoundingBox for the shape. However, this approach only works correctly if the shape contains a single geometry, which is checked by getLandShape( ) before getLandDimensions( ) is called.

Placing the Terrain in the World

The floor of the landscape runs across the XY plane, starting at (0, 0), with sides of landLength units and heights in the z-direction. The world's floor is the XZ plane, with sides of LAND_LEN units and the y-axis corresponding to up and down.

Consequently, the landscape (stored in landBG) must be rotated to lie on the XZ plane and must be scaled to have floor sides of length LAND_LEN. The scaling is a matter of applying the scaleLen global, which equals LAND_LEN/landLength. In addition, the terrain is translated so the center of its floor is at (0, 0) in the world's XZ plane. These changes are illustrated by Figure 27-11.

Figure 27-11. Placing the terrain


Here's the relevant code:

     private void addLandtoScene(BranchGroup landBG)     {        Transform3D t3d = new Transform3D( );        t3d.rotX( -Math.PI/2.0 );    // so land's XY resting on XZ plane        t3d.setScale( new Vector3d(scaleLen, scaleLen, scaleLen) );        TransformGroup sTG = new TransformGroup(t3d);        sTG.addChild(landBG);        // center the land, which starts at (0,0) on the XZ plane,        // so move it left and forward        Transform3D t3d1 = new Transform3D( );        t3d1.set( new Vector3d(-LAND_LEN/2, 0, LAND_LEN/2));        TransformGroup posTG = new TransformGroup(t3d1);        posTG.addChild( sTG );        sceneBG.addChild(posTG);  // add to the world     }

The subgraph added to sceneBG is shown in Figure 27-12.

Figure 27-12. Subgraph for the landscape


An essential point is that any nodes added to landBG will be affected by the translation, rotation, and scaling applied to the landscape. This includes the scenery nodes (i.e., the 3D models and ground cover), but the landscape walls are connected to sceneBG and aren't transformed.

The principal reason for connecting nodes to landBG is so their positioning in space can utilize the local coordinate system in landBG. These are the coordinates specified in Terragen: the floor in the XY plane and heights along the z-axis.

Adding Texture to the Terrain

The texture is stretched to fit the terrain stored in landShape3D below landBG. The texture coordinates (s, t), which define a unit square, must be mapped to the (x, y) coordinates of the terrain whose lower lefthand corner is at (0, 0), and the top righthand corner at landLength, landLength. The intended mapping is captured by Figure 27-13. The simplest way of doing this is define generation planes to translate (x, y) coordinates to (s, t) values.

Figure 27-13. Mapping the terrain to the texture


Generation planes were first seen in Chapter 16.


addLandTexture( ) sets up the generation planes via a call to stampTexCoords( ). It creates an Appearance node for landShape3Dand loads the texture:

     private void addLandTexture(Shape3D shape, String fname)     {       Appearance app = shape.getAppearance( );       // generate texture coords       app.setTexCoordGeneration( stampTexCoords(shape) );       // combine texture with colour and lighting of underlying surface       TextureAttributes ta = new TextureAttributes( );       ta.setTextureMode( TextureAttributes.MODULATE );       app.setTextureAttributes( ta );       // apply texture to shape       Texture2D tex = loadLandTexture(fname);       if (tex != null) {         app.setTexture(tex);         shape.setAppearance(app);       }     }

The generation planes are specified using the following equations:

     s = x/landLength     t = y/landLength

Here's the code that puts this into action:

     private TexCoordGeneration stampTexCoords(Shape3D shape)     {       Vector4f planeS = new Vector4f( (float)(1.0/landLength), 0.0f, 0.0f, 0.0f);       Vector4f planeT = new Vector4f( 0.0f, (float)(1.0/landLength), 0.0f, 0.0f);       // generate new texture coordinates for GeometryArray       TexCoordGeneration texGen = new TexCoordGeneration( );       texGen.setPlaneS(planeS);       texGen.setPlaneT(planeT);       return texGen;     }



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