Displaying Leaves


Building a Tree Limb

Each tree limb is represented by a TReeLimb object, which internally builds a subgraph such as the one in Figure 28-4.

Figure 28-4. The TreeLimb subgraph


Several of the TRansformGroup nodes (those with "TG" in their name) are used to support translation, rotation, and scaling effects, leaving the basic geometry (a cylinder) unaffected. This means that I could avoid using the complicated GeometryUpdater interface.

The startBG BranchGroup is linked to a parent limb via the parent's endLimbTG node. Since the link is made at execution time, Java 3D requires the use of a BranchGroup.

orientTG holds the orientation of the limb (its initial axis of rotation and angle to that axis). Once set, this cannot change during execution. This restriction is in place to simplify the class's implementation.scaleTG manages the scaling of the x-, y-, and z-dimensions of the cylinder. The x- and z-values are kept the same (they represent the radius); the y-axis is for the length.

The center point for Java 3D's Cylinder is its middle, but I want the origin to be its base at the place where the cylinder connects to the parent limb. baseTG moves the cylinder up the y-axis by length/2 to achieve it. The capabilities for the Cylinder's Material node component are set so that its color can be adjusted at runtime.

Child limbs or leaves are attached to this limb through endLimbTG. The transform inside endLimbTG is an offset of almost the cylinder's scaled length. It is a little less than the length, so child limbs will overlap with their parent. This partly hides any gaps between the limbs when a child is oriented at an extreme angle.

The endLimbTG node is unattached to Cylinder since that would make it prone to scaling, which would affect any child limbs attached to endLimbTG. Scaling is restricted to the limb's cylinder.

Storing Tree Limb Information

Aside from the subgraph data structure, TReeLimb maintains various other information related to a tree limb, including a reference to its parent, current color, current age, level in the overall tree (the first branch is at level 1), and whether it is showing leaves.

The age is a counter, set to 0 when the limb is created, and incremented in each time interval by the GrowthBehavior object. Each limb will have a different age depending on when it was added to the tree.

The large number of public methods in TReeLimb can be roughly classified into five groups:

  • Scaling of the cylinder's radius or length

  • Color adjustment

  • Parent and children methods

  • Leaf-related

  • Others (e.g., for accessing the limb's current age)

To simplify matters, many of the parameters used by these methods (e.g., scale factors, color changes) are hardwired into the rules found in the GrowthBehavior object (which I explain later). The arguments passed to the treeLimb constructor are concerned with the new limb's size, position, and orientation relative to its parent:

     public TreeLimb(int axis, double angle, float rad, float len,                            TransformGroup startLimbTG, TreeLimb par)

axis and angle are used by orientTG; rad and len become the radius and length of the new cylinder. startLimbTG will be assigned the transformGroup of the parent limb where this limb will be attached. par is a reference to the parent as a treeLimb object.

Subgraph Creation

The subgraph in Figure 28-4 is constructed by buildSubgraph( ):

     private void buildSubgraph(TransformGroup startLimbTG)     /* Create the scene graph.        startLimbTG is the parent's endLimbTG. */     {       BranchGroup startBG = new BranchGroup( );       // set the limb's orientation       TransformGroup orientTG = new TransformGroup( );       if (orientAngle != 0) {         Transform3D trans = new Transform3D( );         if (orientAxis == X_AXIS)           trans.rotX( Math.toRadians(orientAngle));         else if (orientAxis == Y_AXIS)            trans.rotY( Math.toRadians(orientAngle));         else    // must be z-axis           trans.rotZ( Math.toRadians(orientAngle));         orientTG.setTransform(trans);       }       // scaling node       scaleTG = new TransformGroup( );       scaleTG.setCapability( TransformGroup.ALLOW_TRANSFORM_READ);       scaleTG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE);       // limb subgraph's sequence of TGs       startBG.addChild(orientTG);       orientTG.addChild(scaleTG);       scaleTG.addChild( makeLimb( ) );       TransformGroup endLimbTG = locateEndLimb( );       orientTG.addChild(endLimbTG);       startBG.compile( );       startLimbTG.addChild(startBG);   //connect to parent's endLimbTG     } // end of buildSubgraph( )

The cylinder and its components are built inside makeLimb( ):

     private TransformGroup makeLimb( )     // a green cylinder whose base is at (0,0,0)     {       // fix limb's start position       TransformGroup baseTG = new TransformGroup( );       Transform3D trans1 = new Transform3D( );       trans1.setTranslation( new Vector3d(0, limbLen/2, 0) );                                             // move up length/2       baseTG.setTransform(trans1);       Appearance app = new Appearance( );       limbMaterial = new Material(black, black, green, brown, 50.f);       limbMaterial.setCapability( Material.ALLOW_COMPONENT_READ);       limbMaterial.setCapability( Material.ALLOW_COMPONENT_WRITE);          // can change colors; only the diffuse color will be altered       limbMaterial.setLightingEnable(true);       app.setMaterial( limbMaterial );       Cylinder cyl = new Cylinder( radius, limbLen, app);       baseTG.addChild( cyl );       return baseTG;     }  // end of makeLimb( )

The Material's capabilities must be set since color change will be carried out at runtime. Initially, the limb is green.

The radius and length of the cylinder are stored in the radius and limbLen globals. They will never change (the cylinder's geometry isn't updated). Instead, scaling is applied to the cylinder through the scaleTG node.

The endLimbTG node is connected to the subgraph in locateEndLimb( ):

     private TransformGroup locateEndLimb( )     {       // fix limb's end position, and store in endLimbTG       endLimbTG = new TransformGroup( );       endLimbTG.setCapability( TransformGroup.ALLOW_CHILDREN_EXTEND );       endLimbTG.setCapability( TransformGroup.ALLOW_TRANSFORM_READ );       endLimbTG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE );       Transform3D trans2 = new Transform3D( );       trans2.setTranslation(new Vector3d(0, limbLen*(1.0-OVERLAP), 0));       /* The end position is just short of the actual length of the          limb so that any child limbs will be placed so they overlap          with this one. */       endLimbTG.setTransform(trans2);       return endLimbTG;     }  // end of locateEndLimb( )

It's important to set the necessary capabilities in locateEndLimb( ). The ALLOW_CHILDREN_EXTEND bit will permit BranchGroup nodes to be attached to this node.

Scaling

The public scaling methods employ a scale factor or scale so the radius (or length) becomes a desired value. Consider setLength( ), which scales the cylinder's length until it's the specified amount:

     // global for storing scaling info     private Vector3d scaleLimb;     public void setLength(float newLimbLen)     // change the cylinder's length to newLimbLen     // (by changing the scaling)     { double scaledLimbLen = ((double) limbLen) * scaleLimb.y;       double lenChange = ((double) newLimbLen) / scaledLimbLen;       scaleLength( lenChange );     }

To simplify the calculations, the current scaling factors (in the x-, y-, and z-directions) are maintained in scaleLimb. The current scaled limb length is stored in scaledLimbLen. The desired scaling is the factor to take the length from scaledLimbLen to the required newLimbLen value.

The scaling is done by scaleLength( ):

     public void scaleLength(double yChange)     { scaleLimb.y *= yChange;       applyScale( );     }     private void applyScale( )     {       moveEndLimbTG( scaleLimb.y);       scaleTG.getTransform(currTrans);       currTrans.setScale(scaleLimb);       scaleTG.setTransform(currTrans);     }

applyScale( ) applies the new scaling to scaleTG, which changes the perceived length of the cylinder. However, this change won't automatically affect the endLimbTG node (the node that represents the end of the limb) since it's unattached to the graph below scaleTG. The call to moveEndLimbTG( ) adjusts the node's position so it stays located at the end of the cylinder:

     private void moveEndLimbTG( double yScale)     /* yScale is the amount that the Cylinder is about to        be scaled. Apply it to the y- value in endLimbTG  */     {       endLimbTG.getTransform( currTrans );       currTrans.get( endPos );         // current posn of endLimbTG       double currLimbLen = endPos.y;         // current y-posn, the cylinder length including scaling       double changedLen =             ((double) limbLen*(1.0-OVERLAP) * yScale) - currLimbLen;         // change in the y- value after scaling has been applied       endPos.set(0, changedLen, 0);       // store the length change       toMove.setTranslation( endPos );    // overwrite previous trans       currTrans.mul(toMove);       endLimbTG.setTransform(currTrans);  // move endLimbTG     }  // end of moveEndLimbTG( )

endLimbTG's (x, y, z) position is extracted to the endPos vector. This position corresponds to the end of the scaled cylinder.

The necessary position change is calculated by multiplying the cylinder's physical length by the new scale factor in yScale, and subtracting the endPos y-value. I factor in an overlap, which cause limbs to overlap when linked together.

Changing the Limb's Color

The capabilities for changing the limb's Material were set up in makeLimb( ), described earlier. A global, limbMaterial, stores a reference to the node to make it simple to change:

     public void setCurrColour(Color3f c)     // Change the limb's color to c.     { currColor.x = c.x;       currColor.y = c.y;       currColor.z = c.z;       limbMaterial.setDiffuseColor( currColour );     }

To achieve an aging effect, the limb's color is changed from green to brown incrementally, spread out over several frames. This is achieved by precalculating red, green, and blue transitions that will change the RGB values for green to brown over the course of MAX_COLOR_STEP steps:

     // globals     private final static int MAX_COLOR_STEP = 15;     private final static Color3f green = new Color3f(0.0f, 1.0f, 0.1f);     private final static Color3f brown = new Color3f(0.35f, 0.29f, 0.0f);     // incremental change in terms of RGB to go from green to brown     private float redShift = (brown.x - green.x)/((float) MAX_COLOR_STEP);     private float greenShift = (brown.y - green.y)/((float) MAX_COLOR_STEP);     private float blueShift = (brown.z - green.z)/((float) MAX_COLOR_STEP);

The redShift, greenShift, and blueShift values are utilized in stepToBrown( ):

     public void stepToBrown( )     // Incrementally change the limb's color from green to brown     {       if (colorStep <= MAX_COLOR_STEP) {         currColor.x += redShift;         currColor.y += greenShift;         currColor.z += blueShift;         limbMaterial.setDiffuseColor( currColour );         colorStep++;       }     }

stepToBrown( ) will be repeatedly called until the limb has turned brown.

Leaves on Trees

Two ImageCsSeries objects display leaves at the end of a branch. This makes the mass of the leaves seem greater, especially since the images are offset from each other. The two objects are connected to the limb via BranchGroup nodes since the links are formed at runtime:

     public void addLeaves(ImageCsSeries fls, ImageCsSeries bls)     // Leaves are represented by two ImageCsSeries 'screens'     {       if (!hasLeaves) {         frontLeafShape = fls;         backLeafShape = bls;         // add the screens to endLimbTG, via BranchGroups         BranchGroup leafBG1 = new BranchGroup( );         leafBG1.addChild(frontLeafShape);         endLimbTG.addChild(leafBG1);         BranchGroup leafBG2 = new BranchGroup( );         leafBG2.addChild(backLeafShape);         endLimbTG.addChild(leafBG2);         hasLeaves = true;       }     }

The positioning of the ImageCsSeries objects is done when they are created, inside GrowthBehavior.

The other leaf-related methods in treeLimbs pass requests to the ImageCsSeries objects; the requests change the image currently being displayed:

     public void showNextLeaf( )     // show the next leaf image     { if (hasLeaves) {         frontLeafShape.showNext( );         backLeafShape.showNext( );       }     }



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