Chapter 17. Using a Lathe to Make Shapes


Building the Model's Scene Graph

Figure 16-22 shows the scene graph after dolphins.3ds has been loaded (the three dolphins model). The PropManager object creates the long branch shown on the right of the figure, consisting of a chain of four TRansformGroup nodes and a BranchGroup with three Shape3D children. The loaded model was translated into the BranchGroup and its children (each dolphin in dolphins.3ds is represented by a Shape3D node).

PropManager utilizes four transformGroups to deal with different aspects of the model's configuration:


moveTG

Handles the translations


rotTG

For rotations


scaleTG

For scaling


objBoundsTG

Carries out the scaling and possible rotation of the model when it's first loaded

Figure 16-22. Scene graph for loaded dolphins


The reason for this separation is to process distinct operations in different nodes in the graph. This reduces the overall complexity of the coding because I can take advantage of the hierarchy of local coordinate systems used by the transformGroup nodes.

A transformGroup's local coordinate system means it always starts at (0, 0, 0) with no rotation or scaling. However, when Java 3D renders the node into the virtual world, it must obtain its global coordinates (i.e., its virtual world position). It does this by calculating the combined effects of all the ancestor transformGroup nodes operations upon the node.

For example, if the moveTG node is moved to coordinate (1, 5, 0) from its starting point of (0, 0, 0), then all the TRansformGroup nodes below it in the scene graph are repositioned as well. Java 3D generates this effect at render time but, as far as the child nodes are concerned, they are still at (0, 0, 0) in their local coordinate systems.

This mechanism greatly simplifies the programmer's task of writing transformGroup transformations. For instance, a rotation of 70 degrees around the y-axis for rotTG is applied in its local coordinate system, so it is a straightforward rotation around the center. If the transformations of its parent (grandparent, great-grandparent, etc.) had to be taken into account, then the rotation operation would be much more complicated since the code would need to undo all the transformations, rotate around the center, and then apply the transformations again. The advantage of splitting the translation and rotation effects so the translation component (in moveTG) is above the rotation (in rotTG) is that rotational changes only apply to rotTG and its children.

For instance, a positive rotation of 90 degrees around the y-axis turns the XZ plane so the x- and z-axes are pointing in new directions. Subsequently, if a child of rotTG moves two units in the positive x direction, it will appear on screen as a 2-unit move in the negative z direction!

Fortunately, since moveTG is above rotTG, the axes' changes made by rotTG don't trouble moveTG: an x direction move applied to moveTG is always carried out along the x-axis as expected by the user.

Loading the Model

The scene graph is created in PropManager's loadFile( ). First, the model is loaded with ModelLoader, and its BranchGroup is extracted into the sceneGroup variable. The chain of four transformGroups are then created. The following code snippet shows the creation of objBoundsTG and scaleTG, and how scaleTG is linked to objBoundsTG:

     // create a transform group for the object's bounding sphere     TransformGroup objBoundsTG = new TransformGroup( );     objBoundsTG.addChild( sceneGroup );     // resize loaded object's bounding sphere (and maybe rotate)     String ext = getExtension(fnm);     BoundingSphere objBounds = (BoundingSphere) sceneGroup.getBounds( );     setBSPosn(objBoundsTG, objBounds.getRadius( ), ext);     // create a transform group for scaling the object     scaleTG = new TransformGroup( );     scaleTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);     scaleTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);     scaleTG.addChild( objBoundsTG );  // link TGs

The capability bits of scaleTG (and rotTG and moveTG) must be set to allow these nodes to be adjusted after the scene has been made live.


Included in the code fragment is the call to setBSPosn( ). It scales the model so its bounding sphere has a unit radius; this avoids problems with the model being too big or small.

This is a variant of the bounding box technique used in LoaderInfo3D.


If the file extension is .3ds, then the model is rotated -90 degrees around the x-axis to compensate for the axes differences between 3DS and Java 3D, as outlined earlier.

Loading and Applying the Coords Data

The coords datafile requires parsing to extract its translation, rotation, and scaling values. getFileCoords( ) opens the file and reads in lines of text. These are passed to setCurrentPosn( ), setCurrentRotation( ), or setCurrentScale( ) depending on the character following the - at the start of a line.

setCurrentPosn( ) extracts the (x, y, z) values and calls doMove( ) with the values packaged as a Vector3d object. doMove( ) adds the translation to the current value:

     private void doMove(Vector3d theMove)     { moveTG.getTransform(t3d);        // get current posn from TG       chgT3d.setIdentity( );               // reset change TG       chgT3d.setTranslation(theMove);  // setup move       t3d.mul(chgT3d);                 // 'add' move to current posn       moveTG.setTransform(t3d);        // update TG     }

chgT3d is a global transform3D and is reinitialized before use by setting it to be an identity matrix.


The addition of the new translation is done using multiplication since I'm dealing with matrices inside the transform3D objects.

setCurrentScale( ) is similar in that it extracts a single value and then calls scale( ) to apply that value to the scene graph:

     public void scale(double d)     { scaleTG.getTransform(t3d);    // get current scale from TG       chgT3d.setIdentity( );            // reset change Trans       chgT3d.setScale(d);           // set up new scale       t3d.mul(chgT3d);              // multiply new scale to current one       scaleTG.setTransform(t3d);    // update the TG       scale *= d;                   // update scale variable     }

The coding style of scale( ) is the same as doMove( ).

Handling Rotation

Dealing with rotation is more complicated due to the mathematical property that rotations about different axes are noncommutative. For example, a rotation of 80 degrees around the x-axis followed by 80 degrees about the z-axis (see Figure 16-23) produces a different result if carried out in the opposite order (see Figure 16-24).

Figure 16-23. Rotation order: x-axis rotation then z-axis


Figure 16-24. Rotation order: z-axis rotation then x-axis


Though the GUI displays are too small to read in the figures, they show the rotation values to be (80, 0, 80).

This rotation property means that the coords datafile cannot store the rotation information as three total rotations. Storing the order in which the rotations were carried out is necessary. The solution to this problem relies on a simplification of the user interface: a click of a rotation button always results in a rotation of 10 degrees (negative or positive, around the x-, y-, or z-axes). Then the user's rotation commands can be represented by a sequence of rotation numbers, which must be executed in sequence order to duplicate the desired final orientation. The rotation numbers range between 1 and 6:

  1. Positive ROT_INCR around the x-axis

  2. Negative ROT_INCR around the x-axis

  3. Positive ROT_INCR around the y-axis

  4. Negative ROT_INCR around the y-axis

  5. Positive ROT_INCR around the z-axis

  6. Negative ROT_INCR around the z-axis

ROT_INCR is a constant defined in PropManager (10 degrees).

This approach means that the rotation information for Figure 16-23 is encoded as:

     -r 1111111155555555

Figure 16-24 is represented by:

     -r 5555555511111111

The eight 1s mean 80 degrees around the x-axis, and the eight 5s mean 80 degrees around the z-axis.

This representation has the drawback that it may lead to long strings, but this is unlikely considering the application. Usually, a model only needs turning through 90 or 180 degrees along one or perhaps two axes. However, if the user makes lots of adjustments to the rotation, they are all stored; in that case, it's probably better to exit the application and start over.

An advantage of the representation is the simple way that the sequence can be modified manually through editing the coords datafile in a text editor. This holds true for the position and scaling data, which can be changed to any value.

Applying the rotation

The sequence of rotation numbers is extracted form the coords datafile in PropManager's setCurrentRotation( ). The method calls rotate( ) to carry out a rotation for each rotation number.

rotate( ) calls doRotate( ) to change the scene graph and one of storeRotateX( ), storeRotateY( ), or storeRotateZ( ) to record the rotation in an ArrayList of rotation numbers and to update the total rotations for the x-, y-, or z-axes. The doRotate( ) method is shown here:

     private void doRotate(int axis, int change)     {       double radians = (change == INCR) ? ROT_AMT : -ROT_AMT;       rotTG.getTransform(t3d);     // get current rotation from TG       chgT3d.setIdentity( );        // reset change Trans       switch (axis) {              // setup new rotation         case X_AXIS: chgT3d.rotX(radians); break;         case Y_AXIS: chgT3d.rotY(radians); break;         case Z_AXIS: chgT3d.rotZ(radians); break;         default: System.out.println("Unknown axis of rotation"); break;       }       t3d.mul(chgT3d);     // 'add' new rotation to current one       rotTG.setTransform(t3d);     // update the TG     }

The coding style is similar to doMove( ) and scale( ): the existing TRanform3D value is extracted from the transformGroup node, updated to reflect the change and then stored back in the node.

Making the Model Available

As Figure 16-22 shows, the top level of the model's scene graph is the moveTG TransformGroup. This can be accessed by calling getTG( ):

     public TransformGroup getTG( )     {  return moveTG; }

The one subtlety here is that the moveTG, rotTG, and scaleTG nodes will almost certainly be modified after the model's graph has been added to the scene. This means that their capability bits must be set to permit runtime access and change when the nodes are created:

     // create a transform group for scaling the object     scaleTG = new TransformGroup( );     scaleTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);     scaleTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);     scaleTG.addChild( objBoundsTG );     // create a transform group for rotating the object     rotTG = new TransformGroup( );     rotTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);     rotTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);     rotTG.addChild( scaleTG );     // create a transform group for moving the object     moveTG = new TransformGroup( );     moveTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);     moveTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);     moveTG.addChild( rotTG );

Modifying the Model's Configuration at Runtime

User requests to move, rotate, scale, or save the coords data are passed from the GUI in Loader3D tHRough WrapLoader3D to the PropManager object. The relevant methods are move( ), rotate( ), scale( ), and saveCoordFile( ).

I've described rotate( ) and scale( ); they're employed when the coords data are being applied to the model. move( )'s main purpose is to translate the data supplied by the GUI (an axis and direction) into a vector, which is passed to doMove( | ).

saveCoordFile( ) is straightforward, but relies on global variables holding the current configuration information.

Another aspect of Loader3D's GUI is that it displays the current configuration. This is achieved by calling getLoc( ), geTRotations( ), and getScale( ) in PropManager via WrapLoader3D. For example, in Loader3D:

     // global     private WrapLoader3D w3d;     private void showPosInfo( )     { Vector3d loc = w3d.getLoc( );       xyzTF.setText("( " + df.format(loc.x) + ", " +           df.format(loc.y) + ", " + df.format(loc.z) + " )");     }

In WrapLoader3D:

 // global     private PropManager propMan;     public Vector3d getLoc( )     {  return propMan.getLoc( );  }

In PropManager:

     public Vector3d getLoc( )     { moveTG.getTransform(t3d);       Vector3d trans = new Vector3d( );       t3d.get(trans);       return trans;     }



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