Preface

This lesson is part of a series designed to start with Java 3D basics and work up to some very complicated programs, such as the program that I explained in the earlier lesson titled "Understanding Lighting in the Java 3D API" (see Resources).

The first lesson in this series was titled "Back to Basics in the Java 3D API". The previous lesson was titled "Understanding Transforms in Java 2D." This lesson is titled "Understanding Transforms in Java 3D."

My current plan is for future lessons to deal with user and object interaction, advanced animation, and textures in Java 3D.

What you will learn

In this lesson, you will learn to understand transforms in Java 3D. You will also learn how to write Java 3D code that makes use of that understanding.

Compiling and running Java 3D programs

In order to compile and run programs using the Java 3D API, you will need to download and install the Java 3D API software. As of the date of this writing, version 1.5.0 is available for download.

In addition, you will need to download and install either Microsoft DirectX or OpenGL. All of the sample programs in this series of tutorials were developed and tested using Microsoft DirectX. They were not tested using OpenGL.

To understand transforms in either 2D or 3D (particularly the rotation transform) you need to have some understanding of trigonometry. I'm sorry, but that is simply the way that it is. In addition, it will be helpful if you have some knowledge of matrices and matrix algebra.

While I will do my best to help you understand transforms, trying to teach you the requisite background knowledge is beyond the scope of this tutorial. I will provide only a brief discussion of the requisite background. For technical background, I recommend that you study the previous lesson titled "Understanding Transforms in Java 2D" and the articles titled "Trigonometry", "Planar transformations", "Spatial transformations", and "A little algebra" (see Resources).

Visible objects in Java 3D

Just to get us started, Figure 1 shows some spheres produced using Java 3D. The quality of the rendering of the yellow and white spheres in Figure 1 was purposely kept low to cause the facets to be visible.

Visible objects in 3D are typically constructed by constructing a mesh of inter-connected polygons (usually triangles) and then covering that mesh with a skin. For example, Figure 2 shows a sphere rendered in 3D with its skin intact.

Figure 3 shows the mesh of inter-connecting triangles that I mentioned earlier.

A wireframe drawing

A rendering of the mesh as shown in Figure 3 is often referred to as a wireframe drawing. (Figure 3 is an Alice wireframe rendering. Most wireframe drawings probably don't include the 3D rendering created through the use of the light source in Figure 3. Wireframe drawings are often just black lines on a white background.)

Visual attributes

Various visual attributes of the object (such as shading, color, etc.) are achieved by the color, intensity, and other appearance factors that are applied to the section of skin that is drawn to cover each individual triangle. For example, in Figure 2, the skin sections covering the triangles in the upper right are very light while the skin covering the triangles in the lower left are much darker. This effect is used to create the optical illusion that we refer to as a 3D rendering.

Controlling the quality

Often, it is possible to control the number of triangles that are used to construct the mesh. Up to a point, the quality of the 3D rendering of an object can be improved by constructing the mesh with more triangles.

For example, the rendering quality of the yellow and white spheres in Figure 1 was purposely made low in order to expose the facets on the surface of the two spheres. A wireframe drawing of these spheres would show that it was constructed with a small number of triangles. The sphere in Figures 2 and 3, on the other hand, was constructed using a larger number of triangles, which resulted in better rendering quality. The sphere in Figure 2 looks more like a true sphere than the yellow and white spheres in Figure 1.

Just to show that these concepts apply to more complex visual objects than spheres, Figure 4 shows a solid rendering and a wireframe rendering of the Alice ice skater object. (A good way to get some experience in 3D programming very quickly is to visit my web page titled "Learn to Program using Alice" (see Resources) and study some of the tutorial lessons that you will find there.)

As you can see, the object in Figure 4 is constructed from hundreds of inter-connected triangles.

What is a point?

A point is a location in 3D space defined by three coordinate values (x,y,z). The value for x defines the location of the point relative to an origin along the x-axis (see the red line in Figures 2 and 3.). The value for y defines the location of the point along the y-axis (see the green line in Figures 2 and 3). The value for z defines the location of the point along the z-axis (see the blue line in Figures 2 and 3.). The x, y, and z axes intersect at the origin in 3D space. The sphere in Figures 2 and 3 is centered on that origin.

The locations where the triangles connect to one another in Figures 3 and 4 are referred to as the vertices. The location of each vertex is specified by a point and its three coordinate values. Generally speaking, the transforms that are applied to 3D objects involve modifying the coordinate values that specify each vertex.

The x, y, and z axes

The axes may or may not be visible in the graph. (I purposely made them visible in Figures 2 and 3.) However, even when they are not visible, they are assumed to be present.

The x-axis is typically considered to be the horizontal axis with positive values going from left to right. This is the case in Java 3D. The y-axis is typically considered to be the vertical axis with positive values going from bottom to top. (Fortunately, unlike Java 2D, the positive direction for y in Java 3D is from bottom to top.) The z-axis is typically considered to go from back to front, protruding out of the screen toward the viewer. In Java 3D, the positive z direction is toward the viewer.

Where is the origin?

In Java 3D, the origin is at the center of the universe. While it is possible to change the apparent location of the origin on the screen by working with the ViewingPlatform, the origin will be generally at the center of the Canvas object used to display the universe in this program.

What is a vector?

According to Wikipedia (see Resources), there are several kinds of vectors. A spatial vector is an object defined by both magnitude and direction; in contrast to a scalar, an object with magnitude only.

Also, according to Wikipedia, the word vector is used to describe a one-dimensional, directional matrix; a row vector or a column vector. I will use the word vector in both senses (spatial and matrix) in this lesson. Hopefully the sense in which the word is being used will be clear from the context of the discussion.

What is a transform?

In the sense that it will be used in this lesson, a transform is an operation that converts the coordinate values for every point that defines an object into a different set of coordinate values. The object that is defined by the new coordinate values will probably be recognizable as representing the original object, although it may be a different size (scale), may be in a different location (translation), may have a different orientation (rotation), may be squashed in one dimension or the other, or may be flipped across one or both of the axes.

What is an affine transform?

The transforms that will be used in this lesson are actually affine transforms. According to the Java 3D documentation:

"An affine matrix can translate, rotate, reflect, scale anisotropically, and shear. Lines remain straight, and parallel lines remain parallel, but the angle between intersecting lines can change. In order for a transform to be classified as affine, the 4th row must be: [0, 0, 0, 1]."

Translation

To translate an object means to move the object from one location in 3D space to a different location in 3D space. (You will see how this is accomplished later.) We can translate an object by translating all of the points that define the object. We can translate a point by adding offsets to the x, y, and z coordinate values that define the location of the point.

If we express the original coordinates of a point as x1, y1, and z1, and express the offsets as tx, ty, and tz, we can express the coordinates of the new location of the point as:

x2 = x1 + tx
y2 = y1 + ty
z2 = z1 + tz

Scaling

To scale an object means to change the locations of all the points that represent the object according to a very specific formula. Typically, but not always, a scaling transform will change the size of the object, making it larger or smaller. (Note that scaling by factors of 1 or -1 doesn't change the size.) We can accomplish a scaling transform by multiplying the x, y, and z coordinate values of all the points that define the object by the same or by different scale factors.

If the scale factors used to multiply the coordinate values are the same, the object will simply become larger or smaller (and could be flipped across the axes if the scale factors are negative). If the scale factors applied to each axis are different, the object may be squashed in one dimension or the other, or possibly even flipped across the axes, but will probably still be recognizable as representing the same object.

The scale factors that are used in a scaling transform are expressed relative to the origin. For example, a scale factor of 2.0 will cause a coordinate value for a point to represent the coordinate value for a new point that is twice as far from the origin.

If we express the original coordinates of a point as x1, y1, and z1, and express the scale factors as sx, sy, and sz, then we can express the coordinates of the new location of the point as:

x2 = x1 * sx
y2 = y1 * sy
z2 = z1 * sx

Rotation

Rotation is much less intuitive than either translation or scaling, and requires a fair knowledge of trigonometry to understand. However, it is possible to create and use rotation transforms in a cookbook fashion without understanding why they work.

The following equations for rotating a point around the origin in 2D space are derived in "Planar transformations" (see Resources), where v represents the angle of rotation.

x2 = x1 * cos(v) - y1 * sin(v)
y2 = x1 * sin(v) + y1 * cos(v)

These equations are extended into 3D in "Spatial transformations" (see Resources). Things get a good bit more complicated in 3D. In 2D, you can only rotate within the x-y plane, meaning that you can only rotate around the origin. However, in 3D, you are not constrained to rotate within any one of the x-y, y-z, or z-x planes. Rather, you can rotate around any or all of the three axes. As a result, there are three sets of rotation equations involved. I will show you those three sets of equations later in conjunction with the matrix equations for transforms.

Positive angle of rotation

The author who derived these equations tells us that they assume that a positive rotation angle is counterclockwise around the axis. What this means is that if you stand at the positive end of an axis and face the origin, a positive angle of rotation around that axis will be counterclockwise.

Matrices

In the earlier lesson titled "Understanding Transforms in Java 2D" (see Resources), I taught you about the use of matrices to perform transforms in Java 2D. I also taught you about homogeneous coordinates.

The article titled "Planar transformations" (see Resources) gives us the matrix equations shown in Figure 2, which can be solved to accomplish translation, scaling, and rotation transforms in 2D.

Although not nearly as explicit, in the article titled "Spatial transformations" (see Resources), that same author gives enough information to make it possible for us to extend the 2D equations into a set of 3D equations. The 3D versions of the homogeneous transform matrix equations are shown in Figure 6.

It is probably worth noting that the 3D matrix equation for rotating around the z-axis in Figure 6 is very similar to the 2D rotation equation shown in Figure 5. One way to think about this is that 2D is simply 3D constrained to include only the x-y plane. Therefore, rotation around the origin in 2D is analogous to rotation around the z-axis in 3D.

Compound operations

As you learned in the earlier lesson on 2D, one of the advantages of using the homogeneous form of the matrix equations is that this makes it possible to combine a series of transforms into a single matrix multiplication by first multiplying the individual matrices that represent of the individual transforms to produce a single compound matrix. Instead of having to perform a series of individual transforms on each point that represents an object, a compound transform matrix can be created first, and each point can be transformed by multiplying the column vector that represents that point by the compound matrix that represents the entire set of transforms. The savings in computational requirements can be enormous.

The good news

It is possible to write Java 3D programs where almost your entire thought process revolves around the creation and use of transform matrices down to the element level. However, the good news is that it is possible to write most Java 3D programs without having to think too deeply about matrices. This is because the Java 3D API provides convenience methods that abstract most of the matrix operations behind a fairly common Java programming interface. Even when you use the convenience methods, however, it is a good idea to have some appreciation as to what is actually taking place behind the curtains.

The overall Java 3D program structure

The overall program structure of a Java 3D scene is a hierarchical tree. The universe is the root of the tree. Moving down from the root, we next encounter an object of the class Locale. Here is a little what Sun has to say about the Locale class:

"A Locale object defines a high-resolution position within a VirtualUniverse, and serves as a container for a collection of BranchGroup-rooted subgraphs (branch graphs), at that position...

A Locale object defines methods to set and get its high-resolution coordinates, and methods to add, remove, and enumerate the branch graphs."

Thus, the Locale object is a node in the tree, which may have one or more children of the class BranchGroup.

The BranchGroup class

Here is some of what Sun has to say about the BranchGroup class:

"The BranchGroup serves as a pointer to the root of a scene graph branch; BranchGroup objects are the only objects that can be inserted into a Locale's set of objects."

Thus, the BranchGroup is also a node in the tree, which may have one or more children of the type Node. The Node class is an abstract class that, as of Java 3D version 1.5.0, has two subclasses:

Group

Leaf

This means, therefore, that a BranchGroup object may have any number of children of the types Group and Leaf.

The Leaf class

Here is some of what Sun has to say about the Leaf class:

"The Leaf node is an abstract class for all scene graph nodes that have no children. Leaf nodes specify lights, geometry, and sounds. They specify special linking and instancing capabilities for sharing scene graphs and provide a view platform for positioning and orienting a view in the virtual world."

In this program, we will be using a ColorCube object, which is a subclass of the Leaf class. This tells us that a ColorCube object can be added as a child of a BranchGroup object, which we will do.

The Group class

Here is some of what Sun has to say about the Group class:

"The Group node object is a general-purpose grouping node. Group nodes have exactly one parent and an arbitrary number of children that are rendered in an unspecified order (or in parallel). Null children are allowed; no operation is performed on a null child node. Operations on Group node objects include adding, removing, and enumerating the children of the Group node. The subclasses of Group node add additional semantics."

The subclasses of the Group class are:

BranchGroup

OrderedGroup

Primitive

SharedGroup

Switch

TransformGroup

ViewSpecificGroup

Several of these classes, in turn, have several different subclasses. Thus, there are a fairly large number of types of objects that can be added as children to a BranchGroup object. However, in this program, we will use only Leaf objects and TransformGroup objects as children of a BranchGroup object.

The TransformGroup class

Here is some of what Sun has to say about the TransformGroup class:

"Group node that contains a transform. The TransformGroup node specifies a single spatial transformation, via a Transform3D object, that can position, orient, and scale all of its children.

The specified transformation must be affine. ...

The effects of transformations in the scene graph are cumulative. The concatenation of the transformations of each TransformGroup in a direct path from the Locale to a Leaf node defines a composite model transformation (CMT)..."

Contains a transform...

Although the documentation states that a TransformGroup object "contains a transform," it is important to understand that the transform is not a child of the object. Rather, the transform is a property of the object that is established by calling the setTransform method on the object, passing a reference to an object of the type Transform3D as a parameter. Thus, there can be only one transform associated with a TransformGroup object, whereas the object may have many children of the type Node.

The effects are cumulative...

What this means is that the effect of the transform contained in a TransformGroup object will be applied to all objects that are children, grand children, great grandchildren, etc. of the TransformGroup object in the hierarchy.

A BranchGroup hierarchy

Figure 7 shows the BranchGroup hierarchy for the program that I will present and explain in this lesson.

The user input GUI on the right side of Figure 8 makes it possible for the user to enter parameters to control the behavior of the four transforms that are executed in the program:

Translate

Rotate

Translate

Scale

The Replot button

When the user clicks the Replot button at the bottom of the input GUI, the program constructs and displays a new 3D universe as shown on the left in Figure 8, making use of the user input parameters and the TransformGroup hierarchy shown in Figure 7.

Eight leaf objects

There are eight Leaf objects in the hierarchy and they are all objects of the class ColorCube. Those eight objects are highlighted in Italics in the hierarchy in Figure 7. They are also clearly visible in the universe in Figure 8.

The universe in Figure 8 shows two sets of 3D axes (three objects per set)
constructed from long skinny ColorCube objects. One set of axes is in the upper right portion of the universe image in Figure 8. The other set protrudes from inside a large ColorCube object with a blue face on the front, a red face on the left side, and a violet face on the top.

The remaining ColorCube object is centered on the origin in 3D space with its red face showing.

Transforms highlighted in boldface

The transforms that are executed by the program are shown as boldface words inside parentheses in Figure 7. The four transforms shown in red boldface are the transforms over which the user has control via the entry of parameters in the input GUI in Figure 8. (These are the transforms listed in the leftmost column in the GUI in Figure 8.)

The transforms shown in black boldface in Figure 7 have hard-coded parameters that are outside the control of the user. For example, the two black scale transforms near the top and bottom of Figure 7 are used to scale the dimensions of ColorCube objects to turn them into long skinny objects for use as the 3D axes shown in Figure 8. Similarly, the black rotate transform near the middle of Figure 7 is used to rotate a ColorCube object so that its blue face will be toward the user when it first appears on the screen (perpendicular to the z-axis).

The redCube object

The ColorCube object named redCube near the top of Figure 7 is positioned above all transforms in the hierarchy. Therefore, it is not affected by any of the transforms and it appears at the center of the universe in Figure 8 in its natural form. The natural form has it located at the origin with the red face perpendicular to the z-axis.

The red translate and rotate transforms

The red translate and rotate transforms near the top of Figure 7 correspond to the top two transforms in the list in the leftmost column of the input GUI in Figure 8. These two transforms affect the remaining seven ColorCube objects at the leaves of the hierarchy because all seven of those leaf objects belong to TransformGroup objects that are descendants (children, grand children, etc.) of the group named rotatedGroup.

The topmost black scale transform

However, the black scale transform shown with the plainAxisGroup in Figure 7affects only the three ColorCube objects that are children of the plainAxisGroup. This transform is a property of that group and none of the other ColorCube objects are children of that group.

The red translate and scale transforms

The red translate and scale transforms near the center of Figure 7 correspond to the bottom two transforms in the list in the leftmost column of the input GUI in Figure 8. These two transforms affect the remaining four ColorCube objects because they are all descendants of the group named scaledGroup. However, they don't affect the four ColorCube objects that appear further up the hierarchy.

Keep these hierarchical relationships in mind

You should keep these hierarchical relationships in mind later as you run the program and experiment with the input parameters that control the behavior of the four transforms shown in red boldface in Figure 7.

Enough background, let's see some code

With that as background material, it's time to examine a program that makes it easy to experiment with and hopefully easy to understand how to create and to use transforms in Java 3D.

In this lesson, I will present and explain a Java 3D program named Java3D010. This program is very similar to the Java 2D program named Java2D001 in the earlier lesson titled "Understanding transforms in Java 2D" (see Resources).

Purpose

The purpose of this program is to make it easy to experiment with the following four transforms executed in sequence:

Translate

Rotate

Translate

Scale

Operation

The program creates a user input GUI that can be used to vary the parameters used for the sequence of transforms listed above (see Figure 8). Eight ColorCube objects are contained in the universe.

Two of the ColorCube objects are shown in Figure 8 as the red square and the red and blue cube. The remaining six ColorCube objects are arranged into two sets of three per set. These are long skinny ColorCube objects that are arranged to simulate two visible sets of 3D axes.

The ColorCube objects are inserted as children of TransformGroup objects at different levels of the BranchGroup hierarchy (see Figure 7). Because they occur at different levels of the hierarchy, the different objects are subjected to different transforms depending on their position in the hierarchy. Only the cube with the blue face showing and its associated visible axes are subjected to all four transforms in the above list.

A Replot button allows the user to modify input parameters, re-compute the transforms, and produce a new output by clicking the button.

Clicking the Replot button when one of the input fields contains String data that cannot be converted to a numeric type will cause the program to abort with a NumberFormatException. For example, a blank field falls into this category.

The program was tested using Java SE 6, and Java 3D 1.5.0 running under Windows XP.

One of the main purposes of this program is to provide you with a tool for experimenting with transforms in Java 3D. I will walk you through a few experiments to get you started down that path. Let's begin by taking a look at what happens if you reverse the order of rotation and translation. We will begin by translating first and then rotating.

Recall from Figure 7 that the universe contains eight ColorCube objects. The top left image in Figure 9 shows the result of translating seven of those objects by the distances and in the directions shown in the top row of input parameter values in the GUI to its right. No rotation was applied to those objects at this point.

Referring back to the hierarchy in Figure 7, we see that this translation transform affects all of the objects other than the object referred to by redCube. Thus, the redCube object remains centered on the origin while all of the other objects are translated to the right, up, and forward toward the viewer.

Add a rotation transform

The bottom left image in Figure 9 shows the result of rotating the seven objects by the angles and around the axes shown in the second line of parameters in the GUI in the bottom right after those seven objects have been translated by the distances and in the directions given in the top right GUI.

Prior to this rotation, the axes belonging to each of the seven objects were parallel to the corresponding axes belonging to the universe. However, once this rotation transform has been executed, the axes belonging to those objects are no longer parallel to the axes belonging to the universe. Rather, those axes are now aligned with the little stubs protruding from the large cube. (Each of these stubs is itself a long skinny ColorCube object.)

The stub protruding from the blue face represents the new direction for the positive z axis belonging to the large cube. The stub protruding from the violet face represents the new direction for the positive y axis. The other two stubs represent the new directions for the positive and negative x axes.

Transform matrices for parameters shown in Figure 9

Figure 10 shows the transform matrices for the parameters shown in the two GUIs in Figure 9. The matrices on the left in Figure 10 correspond to the top right GUI in Figure 9. The matrices on the right in Figure 10 correspond to the bottom right GUI in Figure 9.

Note that each matrix shown in Figure 10 is an individual transform matrix. The top matrix shown in each group corresponds to the default transform matrix that is applied if no other transforms are performed. Each of the remaining four matrices in the group corresponds to one of the transforms listed in the user input GUI.

The order of the display of the transform matrices in Figure 10 is reversed relative to the order of the transforms listed in the GUI in Figure 9. The bottom matrix in a group corresponds to the first transform at the top of the GUI. The matrix immediately below the default matrix corresponds to the last transform at the bottom of the GUI.

The compound transform matrix

The actual transform that is applied to seven of the ColorCube objects is the product of some of the matrices in the group.

The interesting values in the matrices in Figure 10 are highlighted in boldface. If you scan down the matrices on the left in Figure 10, you will see that every matrix except the last one is the same as the default matrix at the top. The default matrix is an identity matrix. The product of two or more identity matrices is another identity matrix.

Only the bottom matrix in the group of matrices on the left in Figure 10 will cause the product of the matrices to be different from the identity matrix. In fact, since the bottom matrix is the only matrix that is different from an identity matrix, the product matrix will be identical to the bottom matrix.

The translation values

The three boldface values in the bottom matrix are the translation values: tx, ty, and tz (see Figure 6). You will note that these three values match the three parameter values for the first translation in the top right GUI in Figure 9.

Add the rotation transform

The group of matrices on the right in Figure 10 contains two transform matrices that differ from the identity matrix. One is the translation matrix at the bottom right. It is the same as the translation matrix at the bottom left. The other matrix that differs from an identity matrix is the rotation matrix that is second from the bottom on the right.

This rotation matrix is actually the product of the three individual transform matrices that represent rotation around the x, y, and z-axes shown in Figure 6. If you are of a mind to do so, you should be able to get the sine and cosine values for the angles involved, enter those values into three transform matrices using the matrix formulas shown in Figure 6, multiply them together, and replicate the values shown in Figure 10.

Rotation followed by translation

Now we will reverse the order of translation and rotation. Figure 11 shows the result of rotating and translating the seven objects by the same parameter values as in Figure 9, but reversing the order.

The top left image in Figure 11 shows the result of applying rotation without first applying translation. This image may look strange at first, but it makes sense once you think about it. The image in the top left of Figure 11 shows two cubes of the same size, both centered at the origin in 3D space. (There are also six of the long skinny ColorCube objects centered on the origin as well.)

The cube with the blue face has been rotated around all three axes. The cube with the red face has not been rotated. Therefore, what we see in this image is the intersection of the two cubes. Only the portions of the red cube that protrude outside of the blue cube are visible. (There is nothing to prevent two objects from occupying exactly the same space in Java 3D.)

Now translate seven of the cubes

The bottom left image in Figure 11 shows the result of following the rotation with translation by the same distances and in the same directions as in Figure 9.

If you compare the bottom left image in Figure 11 with the bottom left image in Figure 9, you will see that they are significantly different.

Translation causes each of the cubes (other than the red cube) to be translated along the axes belonging to the cube. Since those axes were pointing in different directions in Figures 9 and 11 when the translation took place, the cubes ended up in a different location in 3D space in each of the two cases.

The transform matrices

I will leave it as exercise for the student to interpret the transform matrices that are displayed on the command line screen.

Translation followed by rotation followed by translation and scale

Figure 12 shows the result of:

Translating the seven cubes

Rotating the seven cubes

Translating four of the seven cubes again

Scaling those four

Therefore, Figure 12 illustrates all four of the types of transforms that are the topic of this lesson.

Figure 12. Translation followed by rotation followed by translation and scale.

Translate and rotate

The top left image in Figure 12 shows the result of executing a translation followed by a rotation. Recall from Figure 7 that this universe contains eight ColorCube objects. One has its red face perpendicular to the z-axis, which is the viewpoint of the viewer. Also, recall from Figure 7 that no transforms are applied to this object. Therefore, it always looks like a red square centered on the origin.

The remaining seven ColorCube objects consist of two groups of three, which are used to simulate two sets of 3D axes, plus one that is initially oriented so that its blue face is perpendicular to the z-axis.

Translation and rotation transforms affect seven of the eight objects

Recall from Figure 7 that the first translation and the rotation affect all seven of these ColorCube objects in the same way. As a result, following the first translation and rotation, all seven objects are located at the same point in 3D space with the same orientation.

At this point, the six ColorCube objects that simulate the 3D axes occupy the same space (in pairs) so they are visually indistinguishable from one another in pairs. Furthermore, because they are centered on the same point as the cube with the blue face, they appear to protrude from the four faces of the cube as shown in the top image in Figure 12.

Translate and scale four of the seven objects

The bottom image in Figure 12 shows the result of applying an additional translation along the x-axis followed by a scaling transform that scales along the x-dimension.

Referring back to Figure 7, we see that these two transforms affect only four of the ColorCube objects. The affected objects are the object with the blue face and one set of three objects that are used to simulate a set of 3D axes. These four objects are affected in exactly the same way. They are translated along the x-axis and then they are scaled along the x-dimension.

Three objects are left behind

Because one set of three ColorCube objects is not affected by these two transforms, those three objects remain in their current location at their current size and orientation as shown in the bottom left image in Figure 12. (These are the three long skinny ColorCube objects in the upper right portion of the image.)

Four objects are translated

The other four objects are translated in the direction of their respective x-axes. Since all four objects have the same orientation, they are all translated by the same distance in the same direction. Thus, they stay together and continue to be centered at the same point in 3D space.

A very important point

It is very important to note that when an object is translated or rotated in Java 3D, that translation or rotation takes place relative to the axes belonging to that object and do not take place relative to the axes belonging to the universe. Thus, an object that has previously been rotated such that its axes are not parallel to the axes belonging to the universe will not move parallel to the x-axis of the universe when it is translated in the x direction.

In this case, the four ColorCube objects move down, to the left, and slightly forward toward the viewer. You will note that the translated long skinny ColorCube object that has its maximum dimension in x appears to be aligned with the corresponding long skinny ColorCube object that was not translated and remains in the upper right portion of the image. This is because the x axes belonging to these two objects coincide. They coincide because they were earlier translated and rotated in an identical fashion.

Discussion and sample code

Will explain in fragments

As is my custom, I will present and explain this program in fragments. A complete listing of the program is provided in Listing 15.

Before getting into the main body of the program, I will present and explain several utility methods that are called throughout the main body of the program. The first utility method that I will explain is a method named translate, which is shown in its entirety in Listing 1.

The method named translate

This is the method that is called in the main body of the program whenever it is necessary to create a TransformGroup object that will translate a specific node in addition to all of its other children.

//Given an incoming node object and a vector object,
// this method will return a transform group designed
// to translate that node according to that vector.
TransformGroup translate(Node node,Vector3f vector){
Transform3D transform3D = new Transform3D();
transform3D.setTranslation(vector);
TransformGroup transformGroup =
new TransformGroup();
transformGroup.setTransform(transform3D);
transformGroup.addChild(node);
return transformGroup;
}//end translate

A parameter of type node

This method receives two incoming parameters. The first parameter is a reference to an object of the type Node. As we learned earlier, Node is the superclass of both Leaf and Group. Therefore, the first parameter could be a reference either to a visible object such as ColorCube, or to another TransformGroup object.

A parameter of type Vector3f

The second parameter is a reference to an object of type Vector3f. This object encapsulates three numeric values of type float, which are the desired translation distances along the x, y, and z axes.

The purpose of the method

The purpose of the method is to create and return a TransformGroup object that will translate that node and all of its children (as well as any other children that may be added to the group later) according to the distances encapsulated in the vector.

A new TransformGroup object

This method begins by creating a new Transform3D object and setting its translation property to the vector object containing the translation distances. Then it creates a new TransformGroup object and sets its transform property to the new Transform3D object.

Two alternatives programming approaches

One alternative would have been to eliminate the first parameter and simply return the new TransformGroup method at this point. If programmed that way, the code that called the method could immediately add the node as a child to the TransformGroup.

However, I elected to incorporate that step into the method. The incoming node object is added as a child to the new TransformGroup object (by calling the addChild method) and the TransformGroup object with the node already added as a child is returned.

The method named scale

This is the method that is called in the main body of the program whenever it is necessary to create a TransformGroup object that will scale a specific node in addition to all of its other children.

//Given an incoming node object and a vector object,
// this method will return a transform group designed
// to scale that node according to that vector.
TransformGroup scale(Node node,Vector3d vector){
Transform3D transform3D = new Transform3D();
transform3D.setScale(vector);
TransformGroup transformGroup =
new TransformGroup();
transformGroup.setTransform(transform3D);
transformGroup.addChild(node);
return transformGroup;
}//end scale

As is the case with the translate method explained above, this method receives a Node parameter and a Vector3f parameter. In this case, the purpose is to create and return a TransformGroup object that will scale that specific node and all of its children in addition to any other children that may also be added as children of the group.

The code in this method is almost identical to the code in the translate method. However, that method calls the setTranslation method on the new Transform3D object to set its transform property, whereas this method calls the setScale method on the new Tarnsform3D object to set its scale property.

The method named tiltTheAxes

The purpose of the method is to create and return a TransformGroup object that will cause all of the leaf objects that are descendants of a specific node (plus all of the leaf objects that are descendants of any other nodes that may later be added as children of TransformGroup object) to be rotated around their x, y, and z axes.

Except for a change in the name of one variable, this method is identical to a method having the same name that I explained in the lesson titled "Combining Rotation and Translation in Java 3d" (see Resources). Therefore, I won't repeat that explanation in this lesson.

The method named displayMatrix

This method receives an incoming parameter that is a reference to a Transform3D object and displays the contents of the 4x4 matrix contained in that Transform3D object.

The code in this method is very straightforward. The comments in Listing 3 should suffice as an explanation.

The method named getAxesGroup

This is a convenience method that constructs and returns a TransformGroup object containing a set of what look like three orthogonal axes. (See the upper right portion of the bottom left image in Figure 12.) Each of the three individual axes is constructed from a long skinny ColorCube object.

The code in this method is straightforward and shouldn't require any explanation beyond the embedded comments.

Beginning of the class named Java3D010

As I mentioned earlier, a complete listing of the program is provided in Listing 15. All of the code down to the beginning of the constructor for the class named Scene is totally straightforward. That code deals with creating the GUI, servicing the Replot button, etc.

Beginning of the constructor for the class named Scene

The Scene class is an inner class from which the universe is instantiated. One of the functions of the Replot button is to dispose of the old Scene object and to create a new Scene object. When the new Scene object is created, it will be created according to the transform parameter values that have been entered by the user into the GUI shown on the right in Figure 8.

Listing 5 begins by creating a temporary TransformGroup object for the sole purpose of displaying the default transform matrix that is contained in a new TransformGroup object. The default transform matrix is the identity shown at the top left of Figure 10.

The big bang!

Then the code in Listing 5 calls the following two methods to create the universe:

createACanvas

createTheUniverse

The code in these two methods is similar or identical to code that I have explained in earlier lessons on Java 3D so I won't repeat that explanation in this lesson.

Create and prepare two ColorCube objects

Listing 6 creates the two different ColorCube objects shown in Figure 8. One of them is rotated around its vertical axes so that its blue face is perpendicular to the z axis. This object is added as a child to the TransformGroup named blueCubeGroup.

//The following ColorCube displays its red face
// without being rotated.
ColorCube redCube = new ColorCube(0.125f);
//Create a cube and rotate it to cause it to display
// its blue face.
TransformGroup blueCubeGroup =
tiltTheAxes(new ColorCube(0.125f),
0.0d,//x-axis
3*Math.PI/2.0d,//y-axis
0.0d);//z-axis

Add visible axes to the blueCubeGroup

Listing 7 calls the getAxesGroup method to get a set of visible axes (constructed from long skinny ColorCube objects). They are added as children to the blueCubeGroup, producing the blue cube with the protruding axes shown in Figure 8.

We have now eaten our way through the appetizers and it's time for the main course. The code that follows constructs the hierarchy of Leaf andTransformGroup objects.

The transforms in the hierarchy are constructed using the parameter values entered by the user in the GUI shown in Figure 8. Note that this code constructs the hierarchy from the leaves toward the root. This is the reverse of the order in which the transforms are listed in the GUI.

Create and display a scaling transform

Listing 8 creates a scaling transform that will be applied to the blueCubeGroup. According to the hierarchy in Figure 7, the blueCubeGroup includes the blue cube (anonymous ColorCube with the blue face in front) and the blueCubeAxes.

Listing 9 creates and displays a translation transform. (This is the translation transform that is listed near the bottom of the GUI in Figure 8.) This transform will be applied to the scaledGroup. According to the hierarchy in Figure 7, the scaledGroup contains the blueCubeGroup, which in turn contains the blue cube and the blueCubeAxes. Only the blue cube and blueCubeAxes will be affected by this transform.

Note that the blue cube will be translated in a direction indicated by the corresponding axes belonging to the blueCubeGroup, even if the axes belonging to the blueCubeGroup have been rotated.

Create and display a rotation transform

Listing 10 creates a rotation transform that will be applied to the secondTransGroup. According to the hierarchy in Figure 7, the secondTransGroup contains the scaledGroup, which in turn contains the blueCubeGroup.

As you will see in Listing 11, the rotation transform will also be applied to a new set of simulated axes named plainAxesGroup. The purpose of adding plainAxesGroup at this point is to show the location and orientation of the blue cube immediately before the final translation is executed. (See Figure 8.) The blue cube, blueCubeAxes, and plainAxesGroup will all be rotated. Rotation takes place around the local axis (rather than the axes belonging to the universe), even if the cube has been translated away from the origin.

Create and add another set of axes

Listing 11 creates the plainAxesGroup and adds it to the rotatedGroup for the purpose described above.

Listing 12 creates and displays the translation transform shown at the top of the GUI in Figure 8. This translation will be applied the rotatedGroup. According to the hierarchy in Figure 7, the rotatedGroup contains the plainAxesGroup and the secondTransGroup, which in turn contains the scaledGroup. The scaledGroup contains the blueCubeGroup. Thus, the plainAxesGroup, the blue cube, and blueCubeAxes will all be translated by this transform.

//Construct the main group.
mainBranchGroup.addChild(firstTransGroup);
//Add the redCube to mark the origin. None of the
// transforms are applied to the redCube.
mainBranchGroup.addChild(redCube);

Finally, the code in Listing 13 adds a ColorCube object with the red face exposed as a child of the BranchGroup object. This cube is not subjected to any of the transforms. Therefore, it is centered on the origin of the universe when the universe is rendered, marking the location of the origin.

Complete the constructor and the classes

Listing 14 completes the constructor for the Scene class. It also signals the end of the Scene class and the class named Java3D010. Hence, Listing 14 is the end of the program.

Run the program

I encourage you to compile and execute the code from Listing 15. Experiment with the code, making changes, and observing the results of your changes.

Remember, you will need to download and install the Java 3D API plus either Microsoft DirectX or OpenGL to compile and execute these programs. See Download for links to the web sites from which this material can be downloaded.

Complete program listing

/*File Java3D010.java
Copyright 2007, R.G.Baldwin
This program is very similar to the Java2D program named
Java2D001.
The purpose of this program is to make it easy to
experiment with the following four transforms executed
in sequence:
translate
rotate
another translate
scale
The program creates a user input GUI that can be used to
vary the parameters used for the sequence of transforms
Two ColorCube objects and two sets of three long skinny
ColorCube objects arranged to simulate a visible set of
3D axes are added as children at different levels of the
branch group hierarchy. The different objects are
subjected to different transforms depending on their
position in the hierarchy. Only the cube with the blue
face showing and its associated visible axes are subjected
to all four transforms in the above list.
A Replot button allows the user to modify input
parameters, recompute the transforms, and produce a new
output by clicking the button. Clicking the Replot button
when one of the input fields contains String data that
can't be converted to a numeric type will cause the
program to abort with a NumberFormatException. For
example, a blank field falls in this category.
Tested using Java SE 6, and Java 3D 1.5.0 running under
Windows XP.
*********************************************************/
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.geometry.ColorCube;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Node;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector3d;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.Label;
import java.awt.TextField;
import java.awt.Button;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
//This is the top-level driver class for this program.
public class Java3D010 extends Frame{
Scene scene = new Scene();
TextField sxTxt = new TextField("1.0");
TextField syTxt = new TextField("1.0");
TextField szTxt = new TextField("1.0");
TextField tx2Txt = new TextField("0.0");
TextField ty2Txt = new TextField("0.0");
TextField tz2Txt = new TextField("0.0");
TextField xAngleTxt = new TextField("0.0");
TextField yAngleTxt = new TextField("0.0");
TextField zAngleTxt = new TextField("0.0");
TextField tx1Txt = new TextField("0.0");
TextField ty1Txt = new TextField("0.0");
TextField tz1Txt = new TextField("0.0");
//These instance variables are accessed by the
// constructor for a Scene object when it is
// instantiated. They are defined in this enclosing
// class rather than in the Scene class because they
// need to be set before the constructor for the Scene
// object is called.
//Scaling parameters.
double sx = 1.5; //scale X
double sy = 1.0; //scale Y
double sz = 1.0; //scale Z
//Translation parameters for first translation.
float tx1 = 0.7f; //translate X
float ty1 = 0.0f; //translate Y
float tz1 = 0.0f; //translate Z
//Rotation parameters.
double xAngle = 0.0;//rotate around X
double yAngle = 0.0;//rotate around Y
double zAngle = Math.PI/4.0d;//rotate around z
//Translation parameters for second translation.
float tx2 = 0.3f;//translate X again
float ty2 = 0.0f;//translate Y again
float tz2 = 0.0f;//translate Z again
//----------------------------------------------------//
public static void main(String[] args){
Java3D010 thisObj = new Java3D010();
}//end main
//----------------------------------------------------//
public Java3D010(){//top-level constructor
setTitle("Copyright 2007, R.G.Baldwin");
setBounds(236,0,235,235);
//Construct the user input panel and add it to the
// CENTER of the Frame. Arrange the fields from top to
// bottom in the order that the transforms are
// actually executed.
Panel inputPanel = new Panel();
inputPanel.setLayout(new GridLayout(0,4));
inputPanel.add(new Label("Xform",Label.CENTER));
inputPanel.add(new Label("X",Label.CENTER));
inputPanel.add(new Label("Y",Label.CENTER));
inputPanel.add(new Label("Z",Label.CENTER));
//Input data for the first translation.
inputPanel.add(new TextField("Trans"));
inputPanel.add(tx1Txt);
inputPanel.add(ty1Txt);
inputPanel.add(tz1Txt);
//Input data for the rotation.
inputPanel.add(new TextField("Rot (deg)"));
inputPanel.add(xAngleTxt);
inputPanel.add(yAngleTxt);
inputPanel.add(zAngleTxt);
//Input data for the second translation.
inputPanel.add(new TextField("Trans"));
inputPanel.add(tx2Txt);
inputPanel.add(ty2Txt);
inputPanel.add(tz2Txt);
//Input scaling data.
inputPanel.add(new TextField("Scale"));
inputPanel.add(sxTxt);
inputPanel.add(syTxt);
inputPanel.add(szTxt);
add(inputPanel,BorderLayout.CENTER);
//Add a button that allows the user to change the
// input parameter values, recompute the transforms,
// and replot the cube.
Button button = new Button("Replot");
button.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
//Get user input values from the text fields,
// convert them to numeric types, and store them
// in instance variables that are accessible to
// the constructor for the Scene object. Note
// that the parse methods will cause the program
// to abort with a NumberFormatException if a
// field contains a string that cannot be
// converted to the appropriate numeric type
// (including an empty string caused by a blank
// field).
//Data for the first translation.
tx1 = Float.parseFloat(tx1Txt.getText());
ty1 = Float.parseFloat(ty1Txt.getText());
tz1 = Float.parseFloat(tz1Txt.getText());
//Data for the rotation. Convert the rotation
// angles from degrees to radians.
xAngle = (Double.parseDouble(
xAngleTxt.getText()) * Math.PI/180.0);
yAngle = (Double.parseDouble(
yAngleTxt.getText()) * Math.PI/180.0);
zAngle = (Double.parseDouble(
zAngleTxt.getText()) * Math.PI/180.0);
//Data for the second translation.
tx2 = Float.parseFloat(tx2Txt.getText());
ty2 = Float.parseFloat(ty2Txt.getText());
tz2 = Float.parseFloat(tz2Txt.getText());
//Scaling data
sx = Double.parseDouble(sxTxt.getText());
sy = Double.parseDouble(syTxt.getText());
sz = Double.parseDouble(szTxt.getText());
//Instantiate a new Scene object. Dispose of
// the old object to conserve resources.
// Otherwise the program aborts after clicking
// the Replot button 32 times with a message
// regarding a limit on the number of allowable
// Canvas objects that can be rendered.
scene.dispose();
scene = new Scene();
}//end actionPerformed
}//end new ActionListener
);//end addActionListener
//Finish constructing the GUI.
add(button,BorderLayout.SOUTH);
setVisible(true);
//This window listener is used to terminate the
// program when the user clicks the X button.
addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}//end windowClosing
}//end new WindowAdapter
);//end addWindowListener
}//end constructor
//----------------------------------------------------//
//This is an inner class, from which the universe will
// be instantiated.
class Scene extends Frame{
//Declare instance variables that are used later by
// the program.
Canvas3D canvas3D;
SimpleUniverse simpleUniverse;
BranchGroup mainBranchGroup = new BranchGroup();
//--------------------------------------------------//
Scene(){//constructor
//Create a temporary TransformGroup object for the
// sole purpose of displaying the default transform
// matrix that is contained in a new TransformGroup
// object.
System.out.println("\nDefault xform matrix.");
TransformGroup tempXformGroup =
new TransformGroup();
Transform3D tempXform = new Transform3D();
tempXformGroup.getTransform(tempXform);
displayMatrix(tempXform);
//Construct the universe.
createACanvas();
createTheUniverse();
//Create two different ColorCube objects. Rotate
// one of them around its vertical axes so that it
// will display its blue face. The names given to
// the cubes are based on the color of the face that
// is displayed on the front following this
// rotation transform.
//The following ColorCube displays its red face
// without being rotated.
ColorCube redCube = new ColorCube(0.125f);
//Create a cube and rotate it to cause it to display
// its blue face.
TransformGroup blueCubeGroup =
tiltTheAxes(new ColorCube(0.125f),
0.0d,//x-axis
3*Math.PI/2.0d,//y-axis
0.0d);//z-axis
//Add a set of axes to the blueCubeGroup .
TransformGroup blueCubeAxes = getAxesGroup();
blueCubeGroup.addChild(blueCubeAxes);
//Now perform a series of transforms based on the
// user input data. Note that this code constructs
// the branch group hierarchy from the leaves toward
// the root.
//Create a scaling transform, which will be applied
// to the blueCubeGroup. The blueCubeGroup includes
// the blue cube and the blueCubeAxes.
TransformGroup scaledGroup = scale(
blueCubeGroup,
new Vector3d(sx,sy,sz));
//Display the scaling transform matrix.
System.out.println("Scaling xform matrix");
scaledGroup.getTransform(tempXform);
displayMatrix(tempXform);
//Create a translation transform, which will be
// applied to the scaledGroup. The scaledGroup
// contains the blueCubeGroup, which in turn
// contains the blue cube and the blueCubeAxes.
// Only the blue cube and blueCubeAxes will be
// affected by this transform.
//Note that the blue cube is translated in a
// direction indicated by the corresponding axes
// belonging to the blueCubeGroup, even if the axes
// belonging to the blueCubeGroup have been rotated.
TransformGroup secondTransGroup = translate(
scaledGroup,
new Vector3f(tx2,ty2,tz2));
//Display the secondTransGroup transform matrix.
System.out.println("secondTransGroup xform matrix");
secondTransGroup.getTransform(tempXform);
displayMatrix(tempXform);
//Create a rotation transform, which will be applied
// to the secondTransGroup. The secondTransGroup
// contains the scaledGroup, which in turn contains
// the blueCubeGroup. The transform
// will also be applied to a new set of axes named
// plainAxesGroup.
//The purpose of adding plainAxesGroup at this point
// is to show the location and orientation of the
// blue cube immediately before the final
// translation is executed.
//The blue cube, blueCubeAxes, and plainAxesGroup
// will all be rotated. Rotation takes place around
// the local axis (rather than the 3D-space axes),
// even if the cube has been translated away from
// the origin.
TransformGroup rotatedGroup =
tiltTheAxes(secondTransGroup,
xAngle,//x-axis
yAngle,//y-axis
zAngle);//z-axis
//Display the rotation transform matrix.
System.out.println("Rotation xform matrix");
rotatedGroup.getTransform(tempXform);
displayMatrix(tempXform);
//Create and add the plainAxesGroup to the
// rotatedGroup.
TransformGroup plainAxesGroup = getAxesGroup();
rotatedGroup.addChild(plainAxesGroup);
//Create the first translation transform, which will
// be applied the rotatedGroup. The rotatedGroup
// contains the plainAxesGroup and the
// secondTransGroup, which in turn contains the
// scaledGroup. The scaledGroup contains the
// blueCubeGroup. Thus, the
// plainAxesGroup, the blue cube, and blueCubeAxes
// will all be translated by this transform.
TransformGroup firstTransGroup = translate(
rotatedGroup,
new Vector3f(tx1,ty1,tz1));
//Display the firstTransGroup transform matrix.
System.out.println("firstTransGroup xform matrix");
firstTransGroup.getTransform(tempXform);
displayMatrix(tempXform);
//Construct the main group.
mainBranchGroup.addChild(firstTransGroup);
//Add the redCube to mark the origin. None of the
// transforms are applied to the redCube.
mainBranchGroup.addChild(redCube);
//Populate the universe by adding the branch group
// that contains the objects.
simpleUniverse.addBranchGraph(mainBranchGroup);
//Do the normal GUI stuff.
setTitle("Copyright 2007, R.G.Baldwin");
setBounds(0,0,235,235);
setVisible(true);
//This listener is used to terminate the program
// when the user clicks the X-button on the Frame.
addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}//end windowClosing
}//end new WindowAdapter
);//end addWindowListener
}//end constructor
//--------------------------------------------------//
//Create a Canvas3D object to be used for rendering
// the Java 3D universe. Place it in the CENTER of
// the Frame.
void createACanvas(){
canvas3D = new Canvas3D(
SimpleUniverse.getPreferredConfiguration());
add(BorderLayout.CENTER,canvas3D);
}//end createACanvas
//--------------------------------------------------//
//Create an empty Java 3D universe and associate it
// with the Canvas3D object in the CENTER of the
// frame. Also specify the apparent location of the
// viewer's eye.
void createTheUniverse(){
simpleUniverse = new SimpleUniverse(canvas3D);
simpleUniverse.getViewingPlatform().
setNominalViewingTransform();
}//end createTheUniverse
//--------------------------------------------------//
//Given an incoming node object and a vector object,
// this method will return a transform group designed
// to translate that node according to that vector.
TransformGroup translate(Node node,Vector3f vector){
Transform3D transform3D = new Transform3D();
transform3D.setTranslation(vector);
TransformGroup transformGroup =
new TransformGroup();
transformGroup.setTransform(transform3D);
transformGroup.addChild(node);
return transformGroup;
}//end translate
//--------------------------------------------------//
//Given an incoming node object and a vector object,
// this method will return a transform group designed
// to scale that node according to that vector.
TransformGroup scale(Node node,Vector3d vector){
Transform3D transform3D = new Transform3D();
transform3D.setScale(vector);
TransformGroup transformGroup =
new TransformGroup();
transformGroup.setTransform(transform3D);
transformGroup.addChild(node);
return transformGroup;
}//end scale
//--------------------------------------------------//
//The purpose of this method is to create and return
// a transform group designed to perform a counter-
// clockwise rotation about the x, y, and z axes
// belonging to an incoming node. The three incoming
// angle values must be specified in radians. Don't
// confuse this with a RotationInterpolator. This is
// not an interpolation operation. Rather, it is a
// one-time transform.
TransformGroup tiltTheAxes(Node node,
double xAngle,
double yAngle,
double zAngle){
Transform3D tiltAxisXform = new Transform3D();
Transform3D tempTiltAxisXform = new Transform3D();
//Construct and then multiply two rotation transform
// matrices.
tiltAxisXform.rotX(xAngle);
tempTiltAxisXform.rotY(yAngle);
tiltAxisXform.mul(tempTiltAxisXform);
//Construct the third rotation transform matrix and
// multiply it by the result of previously
// multiplying the two earlier matrices.
tempTiltAxisXform.rotZ(zAngle);
tiltAxisXform.mul(tempTiltAxisXform);
TransformGroup rotatedGroup = new TransformGroup(
tiltAxisXform);
rotatedGroup.addChild(node);
return rotatedGroup;
}//end tiltTheAxes
//--------------------------------------------------//
//This method displays the contents of the 4x4 matrix
// contained in a Transform3D object.
void displayMatrix(Transform3D transform){
//Retrieve the contents of the matrix into an array.
// The first four elements of the array contain the
// first row of the matrix, etc.
double[] matrixData = new double[16];
transform.get(matrixData);
for(int outCnt = 0;outCnt < 16;outCnt += 4){
for(int cnt = 0;cnt < 4;cnt++){
System.out.printf(
"%6.2f ",matrixData[cnt + outCnt]);
}//end inner loop
System.out.println();
}//end outer loop
}//end displayMatrix
//--------------------------------------------------//
//This method constructs and returns a TransformGroup
// object containing a set of what look like three
// orthogonal axes. Each of the three individual axes
// is constructed from a long skinny ColorCube
// object.
TransformGroup getAxesGroup(){
ColorCube xAxis = new ColorCube();
TransformGroup xGroup = scale(
xAxis,
new Vector3d(0.25,0.01,0.01));
ColorCube yAxis = new ColorCube();
TransformGroup yGroup = scale(
yAxis,
new Vector3d(0.01,0.25,0.01));
ColorCube zAxis = new ColorCube();
TransformGroup zGroup = scale(
zAxis,
new Vector3d(0.01,0.01,0.25));
TransformGroup axesGroup = new TransformGroup();
axesGroup.addChild(xGroup);
axesGroup.addChild(yGroup);
axesGroup.addChild(zGroup);
return axesGroup;
}//end getAxesGroup
//--------------------------------------------------//
}//end inner class Scene
}//end class Java3D010

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.