October 24, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Venturing into a 3D World: Math for Java Game Programmers

  • August 12, 2008
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Java Programming Notes # 1710


Preface

General

Next in a series

This tutorial is the next lesson in a series designed to teach you some of the mathematical skills that you will need (in addition to good programming skills) to become a successful game programmer. The first lesson was titled Math for Java Game Programmers, Getting Started. The previous lesson was titled Math for Java Game Programmers, Putting the Game-Math Library to Work (see Resources).

In addition to helping you with your math skills, I will also teach you how to incorporate those skills into object-oriented programming using Java. If you are familiar with other object-oriented programming languages such as C#, you should have no difficulty porting this material from Java to those other programming languages.

Lots of graphics

Since most computer games make heavy use of either 2D or 3D graphics, you will need skills in the mathematical areas that are required for success in 2D and 3D graphics programming. As a minimum, this includes but is not limited to skills in:

  • Geometry
  • Trigonometry
  • Vectors
  • Matrices
  • 2D and 3D transforms
  • Transformations between coordinate systems
  • Projections

Of course, game programming requires mathematical skills beyond those required for graphics. However, for starters, this series will concentrate on the last five items in the above list.

What you have learned

In the previous lesson, I presented and explained four programs that used the game-math library named GM2D04. One of those programs taught you how to use the addVectorToPoint method of the GM2D04.Point class to translate a geometric object from one location in space to a different location in space.

Another program taught you how to do the same thing but in a possibly more efficient manner.

The third program taught you how to do rudimentary animation using the game-math library.

The fourth program taught you how to use methods of the game-math library to produce relatively complex drawings.

All of the programs were interactive in that they provide a GUI that allows the user to modify certain aspects of the behavior of the program.

What you will learn

The most important thing that you will learn in this lesson is how to update the game-math library to support 3D math and how to produce 3D images similar to that shown in Figure 1.

Figure 1. A 3D image produced using the game-math library.

And a whole lot more...

You will learn much more than that however. Some highlights of the things you will learn are:

  • How to program the equations for projecting a 3D world onto a 2D plane for display on a computer screen.
  • How to cause the direction of the positive y-axis to be up the screen instead of down the screen.
  • How to add vectors in 3D and how to confirm that the head-to-tail and trapezoidal rules apply to 3D as well as to 2D.
  • About scaling, translation, and rotation of a point in both 2D and 3D.
  • About the rotation equations and how to implement them in both 2D and 3D.
  • How to rotate a point around an anchor point other than the origin.
  • About the right-hand rule.

Viewing tip

I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.

Figures

  • Figure 1. A 3D image produced using the game-math library.
  • Figure 2. Graphics output from the program named GM01test02.
  • Figure 3. Command-line output from the program named GM01test02.
  • Figure 4. Graphic output from the program named GM01test05.
  • Figure 5. Graphic output from the program named GM01test06.
  • Figure 6. Rotation equations for a point in 2D space.
  • Figure 7. Initial graphic output from the program named StringArt02.
  • Figure 8. Graphic output from the program named StringArt02 with Loops set to 3.
  • Figure 9. Rotation by 30 degrees around the origin.
  • Figure 10. Rotation around an anchor point that is not at the origin.
  • Figure 11. Rotation around a point further out in space.
  • Figure 12. The six 3D rotation equations.
  • Figure 13. Graphic output from the program named StringArt03 at startup.
  • Figure 14. Geometric object with 12 vertices, 4 loops, and no rotations.
  • Figure 15. Rotation around the z-axis only.
  • Figure 16. Rotation around the x-axis only.
  • Figure 17. Rotation around the y-axis only.
  • Figure 18. Rotation around all three axes with the anchor point at the origin.

Listings

  • Listing 1. The static method named convert3Dto2D.
  • Listing 2. Transform equations for an oblique parallel projection from 3D to 2D.
  • Listing 3. Code in the constructor for the GUI class in the program named GM01test02.
  • Listing 4. The method named GM01.Point3D.draw.
  • Listing 5. The method named GM01.Vector3D.draw.
  • Listing 6. The GM01.Line3D.draw method.
  • Listing 7. Beginning of the drawOffScreen method in GM01test01.
  • Listing 8. Project eight points onto the 2D plane and draw them.
  • Listing 9. Draw twelve lines that connect the corners of the box.
  • Listing 10. Instantiate and draw a GM01.Vector3D object onto the 2D off-screen image.
  • Listing 11. The setCoordinateFrame method.
  • Listing 12. The drawOffScreen method of the program named GM01test05.
  • Listing 13. The game-math library method named GM01.Point3D.scale.
  • Listing 14. Beginning of the game-math library method named GM01.Point2D.rotate.
  • Listing 15. Translate the anchor point to the origin.
  • Listing 16. Rotate the translated newPoint object around the origin.
  • Listing 17. Translate the rotated newPoint object back to the anchor point.
  • Listing 18. Abbreviated listing of the drawOffScreen method.
  • Listing 19. Beginning of the method named GM01.Point3D.rotate.
  • Listing 20. Get the rotation angle values.
  • Listing 21. Rotate the Point 3D object around the z-axis.
  • Listing 22. Rotate the Point 3D object around the x-axis.
  • Listing 23. Rotate the Point 3D object around the y-axis.
  • Listing 24. Translate the object back to the anchor point.
  • Listing 25. Interesting code from the drawOffScreen method.
  • Listing 26. Source code for the updated game-math library named GM01.
  • Listing 27. Source code for the program named GM01test02.
  • Listing 28. Source code for the program named GM01test01.
  • Listing 29. Source code for the program named GM01test05.
  • Listing 30. Source code for the program named GM01test06.
  • Listing 31. Source code for the program named StringArt02.
  • Listing 32. Source code for the program named StringArt03.

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online Java tutorials. You will find a consolidated index at www.DickBaldwin.com.

General background information

In this series, I will frequently refer you to an excellent interactive tutorial on Vector Math for 3D Computer Graphics by Dr. Bradley P. Kjell (see Resources) for the required technical background.  I will then teach you how to incorporate the knowledge that you gain from Kjell's tutorial into Java code with a heavy emphasis on object-oriented programming.

In the process, I will develop and explain a game-math library that you can use to experiment with and to confirm what you learn about points, vectors, and matrices from the Kjell tutorial. The library will start out small and will grow as we progress through more and more material in subsequent lessons.

For this lesson, I recommend that you concurrently study the Kjell tutorial through Chapter6 - Scaling and Unit Vectors.

Preview

I will present and explain a significantly updated game-math library in this lesson. I will also present and explain six different sample programs that show how to use the new features in the updated library. By studying the library and the sample programs, you will learn how to update the game-math library to support 3D math, how to program the equations for projecting a 3D world onto a 2D plane, how to add vectors in 3D, about scaling, translation, and rotation of a point in both 2D and 3D, about the rotation equations and how to implement them in both 2D and 3D, and much more.

Discussion and sample code

 
Testing
All of the programs in this lesson were tested using JDK 1.6 running under Windows XP.

In this section, I will present and explain a significantly updated version of the game-math library named GM01.

In addition, I will present and explain the following programs that use the update game-math library.

The game-math library named GM01

A complete listing of the updated game-math library named GM01 is provided in Listing 26 near the end of the lesson.

A major update to add 3D capability

This is a major update to the game-math library. This version updates the earlier version named GM2D04 to a new version named simply GM01. The primary purpose of the update was to add 3D capability for all of the 2D features provided by the previous version. Because both 2D and 3D capabilities are now included in the library, it is no longer necessary to differentiate between the two in the name of the class. Therefore, this version is simply named GM01.

3D to 2D projections

Adding 3D capability was tedious, but not particularly difficult in most areas of the library. However, adding 3D capability entailed major complexity in one particular area: drawing the objects. It is difficult to draw a 3D object on a 2D screen and have the drawing appear to be 3D. This requires a projection process to project each point in the 3D object onto the correct location on a 2D screen. There are a variety of ways to do this. This 3D library uses an approach often referred to as an oblique parallel projection. See the web page titled Classification of 3D to 2D Projections in Resources for technical information on the projection process.

Eliminating the y-axis confusion

In addition to adding 3D capability, this version also eliminates the confusion surrounding the fact that the default direction of the positive y-axis is down the screen instead of up the screen as viewers have become accustomed to. If you funnel all of your drawing tasks through the library and don't draw directly on the screen, you can program under the assumption that the positive direction of the y-axis is up.

The name of the library

The name GM01 is an abbreviation for GameMath01. See the file named GM2D01 from an earlier lesson (see Resources) for a general description of the game-math library. The library has been updated several times. This version updates the file named GM2D04.

Improving efficiency

In addition to the updates mentioned above, this update cleaned up some lingering areas of code inefficiency by using the simplest available method to draw on an off-screen image.

New static methods

Also, the following new static methods were added to the class named GM01. The first method in the following list deals with the problem of displaying a 3D image on a 2D screen. The last five methods in the list wrap around the standard graphics methods for the purpose of eliminating the issue of the direction of the positive Y-axis.

  • GM01.convert3Dto2D
  • GM01.translate
  • GM01.drawLine
  • GM01.fillOval
  • GM01.drawOval
  • GM01.fillRect

It will probably be necessary for me to add more wrapper methods to the library in future lessons. With the exception of the first method in the above list, the coding of these methods was straightforward and explanations of that code are not warranted. Note, however, that it is the code in the wrapper methods that resolves the issue regarding the direction of the positive y-axis. You will see comments in this regard if you examine the source code for the wrapper methods in Listing 27. I will explain the code for the method named GM01.convert3Dto2D shortly.

Other new methods

In addition to the new static methods listed above, a number of new methods were added to the existing static top-level 2D classes and also included in the new static top-level 3D classes. A list of those new methods follows:

  • GM01.Vector2D.scale
  • GM01.Vector2D.negate
  • GM01.Point2D.clone
  • GM01.Vector2D.normalize
  • GM01.Point2D.rotate
  • GM01.Point2D.scale
  • GM01.Vector3D.scale
  • GM01.Vector3D.negate
  • GM01.Point3D.clone
  • GM01.Vector3D.normalize
  • GM01.Point3D.rotate
  • GM01.Point3D.scale

With the exception of the two rotate methods, the coding of the methods in the above list was also straightforward and an explanation of that code is not warranted. You can view all of the new code in Listing 26.

The two rotate methods are not straightforward at all. They are quite complicated (particularly the 3D method) and require quite a lot of background information to understand. I will dedicate a large portion of a future lesson to the task of rotating geometric objects in 2D and 3D worlds and will defer an explanation of the two rotate methods until that lesson.

The static method named GM01convert3Dto2D

Note first that this is a static method of the class named GM01. Among other things, this means that the method can be called simply by joining the name of the method to the name of the class. In other words, an object of the class named GM01 is not necessary to make the method accessible.

A complete listing of the method is provided in Listing 1.

Listing 1. The static method named convert3Dto2D.

  public static GM01.ColMatrix2D convert3Dto2D(
                                   GM01.ColMatrix3D data){
    return new GM01.ColMatrix2D(
                  data.getData(0) - 0.866*data.getData(2),
                  data.getData(1) - 0.50*data.getData(2));
  }//end convert3Dto2D

As you can see, the method is quite short, and once you know how it is required to behave, coding the method is not difficult. The complexity comes in understanding the required behavior.

Keeping 2D and 3D classes separate
All of the static top-level 2D classes in the existing game-math library were renamed with a suffix of 2D to distinguish them from the new top-level 3D classes. All of the new top-level 3D class names have a 3D suffix.

The ColMatrix2D and ColMatrix3D classes

From the very beginning, the game-math library has contained a static top-level class named ColMatrix. In the updated version of the library, that class has been renamed ColMatrix2D.

Basically, the ColMatrix2D class provides a container for a pair of values of type double with appropriate methods for accessing those values. (I explained the original ColMatrix class in detail in an earlier lesson. See Resources.) Similarly, the new ColMatrix3D class provides a container for three values of type double with appropriate methods for accessing those values.

Objects of the ColMatrix2D class are the fundamental building blocks for several of the other 2D classes in the library, and objects of the ColMatrix3D class are the fundamental building blocks for several of the other 3D classes in the library.

Behavior of the GM01.convert3Dto2D method

The convert3Dto2D method converts a ColMatrix3D object that represents a point in 3D space into a ColMatrix2D object that represents the projection of that 3D point onto a 2D plane. The purpose of the method is to accept x, y, and z coordinate values describing a point in 3D space and to transform those values into a pair of coordinate values suitable for being displayed in two dimensions. The math that is implemented by this method to do the projection is described on the web page titled Classification of 3D to 2D Projections (see Resources).

The transform equations

I won't attempt to justify or to explain the transform equations that are used to accomplish the projection in this lesson. Rather, I will simply use them as presented in the above resource. I will explain the transform equations in a future lesson. In the meantime, if you are interested in more information on the topic, you will find a wealth of information on 3D to 2D projections by performing a Google search for the topic.

The transform equations along with some of the assumptions that I made in the use of the equations are shown in Listing 2.

Listing 2. Transform equations for an oblique parallel projection from 3D to 2D.

The transform equations are:
 x2d = x3d + z3d * cos(theta)/tan(alpha)
 y2d = y3d + z3d * sin(theta)/tan(alpha);

Let alpha = 45 degrees and theta = 30 degrees
Then:  cos(theta) = 0.866
       sin(theta) = 0.5
       tan(alpha) = 1;

Note that the signs in the above equations depend
 on the assumed directions of the angles as well as
 the assumed positive directions of the axes. The
 signs used in this method assume the following:
   Positive x is to the right.
   Positive y is up the screen.
   Positive z is protruding out the front of the
     screen.
   The viewing position is above the x axis and to the
     right of the z-y plane.

The terms in the equations

In Listing 2, the terms x2d and y2d refer to drawing coordinates on the 2D screen, while x3d, y3d, and z3d refer to the coordinates of a point in 3D space. Obviously, sin, cos, and tan refer to the sine, cosine, and tangent of angles named alpha and theta.

Output in two dimensions

These equations and the assumptions that I made in using them produce displays such as the one shown in Figure 1. In that image, the red horizontal line is the x-axis with the positive direction to the right. The green vertical line is the y-axis with the positive direction pointing up. The blue sloping line is the z-axis with the positive direction protruding from the screen towards the viewer. The three axes intersect at the origin in 3D space. (The sloping magenta line going from the corner of the box to the origin is a 3D vector. I will have more to say about the projections of 3D vectors later.)

No perspective in this projection

The sloping black lines in Figure 1 represent the edges of a rectangular box projected onto the 2D screen. Note in particular that there is no perspective in this type of projection. In other words, lines that are parallel in the 3D space remain parallel in the projection of those lines onto the 2D screen. (Hence the word parallel in the name oblique parallel projection.) Objects don't appear to be smaller simply because they are further away from the viewer.

Options involving the angles

I could have produced a somewhat different display by assuming different values for the angles named alpha and theta. However, the values chosen are commonly used values that produce reasonably good results, so I decided to use them. You may find it interesting to experiment with other values for one or both angles to see the results produced by those other values.

The proper algebraic signs

Note in particular that the proper signs for the equations in Listing 2 depend on the assumed positive directions of the angles as well as the assumed positive directions of the axes. The signs used in the method make the assumptions shown in Listing 2. (These assumptions will be particularly important in future lessons where we will be rotating objects in 3D space.)

The viewing position

Also as indicated in Listing 2 and shown in Figure 1, the viewing position is above the x-axis and to the right of the z-y plane.

The code in the method is straightforward

As I mentioned earlier, once you understand the requirements, the code for the method named convert3Dto2D (see Listing 1) is relatively straightforward. If you have studied the code in the previous lessons in this series, the code in Listing 1 shouldn't require further explanation.

The program named GM01test02

Because I made some changes to the existing 2D classes in the game-math library and added some new methods to some of the existing 2D classes, I felt the need to write a program that would step through and test the behavior of most of the 2D methods in the library. That was the purpose of the program named GM01test02.

This program produces both a graphic screen output and lots of text on the command-line screen. The graphic output is shown in Figure 2.

Figure 2. Graphics output from the program named GM01test02.

Command-line output from the program named GM01test02

The command-line output is shown in Figure 3.

Figure 3. Command-line output from the program named GM01test02.

Test overridden toString of ColMatrix2D
1.5,2.5

Test setData and getData of ColMatrix2D
4.5
5.5

Test equals method of ColMatrix2D
false
true

Test add method of ColMatrix2D
9.0,11.0

Test subtract method of ColMatrix2D
0.0,0.0

Test toString method of Point2D
4.5,5.5

Test setData and getData of Point2D
1.1
2.2

Test getColMatrix method of Point2D
1.1,2.2

Test equals method of Point2D
false
true

Test getDisplacementVector method of Point2D
1.5,2.5
4.5,5.5
3.0,3.0

Test addVectorToPoint method of Point2D
1.5,2.5
7.0,9.0

Test toString method of Vector2D
1.5,2.5

Test setData and getData methods of Vector2D
4.5
5.5

Test getColMatrix method of Vector2D
4.5,5.5

Test equals method of Vector2D
false
true
Test add method of Vector2D
4.5,5.5
-1.5,3.5
3.0,9.0

Test getLength method of Vector2D
3.0,4.0
5.0

Test toString method of Line2D
Tail = 1.5,2.5
Head = 4.5,5.5

Test setTail, setHead, getTail,
and getHead methods of Line2D
4.5,5.5
1.5,2.5

Not too exciting

Neither the graphics output in Figure 2 nor the command-line output in Figure 3 are terribly exciting, and neither will mean much to you unless you are willing to examine the code and compare the output with the code.

Source code for the program named GM01test02

A complete listing of the program named GM01test02 is shown in Listing 27 near the end of the lesson.

Listing 3 shows two of the statements in the constructor for the GUI class in the program named GM01test02. The call to the method named testUsingText produces the command-line output shown in Figure 3, while the call to the method named drawOffScreen produces the graphic output shown in Figure 2.

Listing 3. Code in the constructor for the GUI class in the program named GM01test02.

    //Perform tests using text.
    testUsingText();

    //Perform tests using graphics.
    drawOffScreen(g2D);

If you examine the source code for those two methods in Listing 27, you will see that each of the methods contains calls to library methods for the purpose of confirming that the library methods behave as expected. The code in most of those methods is straightforward and should not require further explanation beyond the embedded comments in Listing 27.

End of the discussion for the program named GM01test02

That concludes the discussion of the program named GM01test02. You will find a complete listing of this program in Listing 27.

The program named GM01test01

Code in the program named GM01test01 makes calls to library methods, which in turn make calls to the method named convert3Dto2D shown in Listing 1. Because of the complexity of projecting 3D points onto a 2D screen for display, it will be useful to discuss the three library methods that make calls to the library method named convert3Dto2D. Those three methods are shown in the following list:

  • GM01.Point3D.draw
  • GM01.Vector3D.draw
  • GM01.Line3D.draw

The method named GM01.Point3D.draw

Recall that a point simply represents a location in space and has no width, height, or depth. Therefore a point is not visible to the human eye. However, it is sometimes useful to draw a small circle around a point to mark its location for human consumption. That is the purpose of the draw method of the GM01.Point3D class, which is shown in its entirety in Listing 4.

Listing 4. The method named GM01.Point3D.draw.

    public void draw(Graphics2D g2D){

      //Get 2D projection coordinate values.
      ColMatrix2D temp = convert3Dto2D(point);
      drawOval(g2D,temp.getData(0)-3,
                   temp.getData(1)+3,
                   6,
                   6);
    }//end draw

Purpose of the method

This method draws a small circle around the location of a point in 3D space. The 3D location of the circle is projected onto the 2D plane of the specified graphics context for display later on a 2D screen. The code in Listing 4 projects that location in 3D space onto the 2D plane by calling the static method named convert3Dto2D.

Behavior of the GM01.Point3D.draw method

The location of a point in 3D space, as represented by an object of the GM01.Point3D class, is actually stored in an object of the GM01.ColMatrix3D class. A reference to that ColMatrix3D object is stored in the instance variable named point belonging to the Point3D object. This reference is passed as a parameter to the convert3Dto2D method in Listing 4.

Recall from Listing 1 that the convert3Dto2D method receives an incoming reference to an object of the class GM01.ColMatrix3D and returns a reference to an object of the class GM01.ColMatrix2D, which contains the horizontal and vertical components of the projection of the 3D point onto a 2D plane.

Using the returned GM01.ColMatrix2D object

Listing 4 uses the horizontal and vertical components stored in the returned ColMatrix2D object to construct the proper parameters and call the wrapper method named GM01.drawOval. This wrapper method doesn't know that it is receiving parameters that were originally derived from an object in 3D space. The world of the wrapper method is confined to 2D space. The wrapper method named GM01.drawOval performs the following actions:

  • Casts the four incoming numeric parameters to type int.
  • Flips the sign on the vertical component to resolve the issue regarding the positive value for the y-axis.
  • Passes the parameters to the drawOval method of the standard Graphics class to cause a small circle to be drawn at the correct location on the 2D off-screen image.

The small circle is not converted to an ellipse

Note that even though the location of the point in 3D space is projected onto the 2D plane, the shape of the small circle is not converted to an ellipse to reflect the 3D to 2D conversion nature of the operation. Thus, if the circle were large, it wouldn't necessarily look right. However, in this case, the only purpose of the circle is to mark the location of a point in 3D space. Therefore, I didn't consider the actual shape of the marker to be too important. Figure 1 shows examples of circles marking points in 3D space at the corners of the box and at the ends of the axes.

The method named GM01.Vector3D.draw

The behavior described above for the method named GM01.Point3D.draw is somewhat indicative of the manner in which 3D geometric objects are projected onto a 2D plane and the manner in which the issue regarding the positive direction of the y-axis is resolved by the code in the updated game-math library.

The behavior of the three draw methods

Each of the three draw methods listed earlier needs the ability to project a Point3D object, a Vector3D object, or a Line3D object onto a 2D pane. In each case, the object to be drawn is built up using one or more ColMatrix3D objects asfundamental building blocks.

For the case of the GM01.Point3D.draw method discussed above, the draw method calls the convert3Dto2D method directly to convert the 3D coordinate values of the point to the 2D coordinate values required for the display.

A similar approach for the GM01.Vector3D.draw method

A similar approach is used for the GM01.Vector3D.draw method, which is shown in Listing 5.

Listing 5. The method named GM01.Vector3D.draw.

    public void draw(Graphics2D g2D,GM01.Point3D tail){

      //Get a 2D projection of the tail
      GM01.ColMatrix2D tail2D = convert3Dto2D(tail.point);

      //Get the 3D location of the head
      GM01.ColMatrix3D head = 
                      tail.point.add(this.getColMatrix());

      //Get a 2D projection of the head
      GM01.ColMatrix2D head2D = convert3Dto2D(head);
      drawLine(g2D,tail2D.getData(0),
                   tail2D.getData(1),
                   head2D.getData(0),
                   head2D.getData(1));

      //Draw a small filled circle to identify the head.
      fillOval(g2D,head2D.getData(0)-3,
                   head2D.getData(1)+3,
                   6,
                   6);

    }//end draw

This method draws the 2D visual manifestation of a GM01.Vector3D object on the specified 2D graphics context. Recall that a vector has no location property. Therefore, it can be correctly drawn anywhere. The GM01.Vector3D.draw method requires the drawing location of the tail to be specified by a reference to a GM01.Point3D object received as an incoming parameter.

A small filled circle is drawn at the head of the vector as shown by the magenta filled circle at the origin in Figure 1.

Two calls to the convert3Dto2D method

Two calls are made to the convert3Dto2D method in Listing 5. The first call gets a 2D projection of the 3D location of the tail of the vector. The second call gets a 2D projection of the 3D location of the head of the vector. In both cases, the projected location in 2D space is returned as a reference to an object of the GM01.ColMatrix2D class.

Draw the vector on the specified drawing context

The reference returned by the first call to the convert3Dto2D method is used to call the static GM01.drawLine wrapper method to

  • Handle the issue of the positive direction of the y-axis.
  • Draw a line on the 2D off-screen image representing the body of the vector as shown by the magenta line in Figure 1.

The reference returned by the second call to the convert3Dto2D method is used to call the static GM01.fillOval wrapper method to

  • Handle the issue of the positive direction of the y-axis.
  • Draw a small filled circle on the 2D off-screen image representing the head of the vector as shown by the magenta filled circle at the origin in Figure 1.

The method named GM01.Line3D.draw

The GM01.Line3D.draw method is shown in Listing 6.

Listing 6. The GM01.Line3D.draw method.

    public void draw(Graphics2D g2D){

      //Get 2D projection coordinates.
      GM01.ColMatrix2D tail = 
                           convert3Dto2D(getTail().point);
      GM01.ColMatrix2D head = 
                           convert3Dto2D(getHead().point);

      drawLine(g2D,tail.getData(0),
                   tail.getData(1),
                   head.getData(0),
                   head.getData(1));
    }//end draw

The code in Listing 6 is so similar to the code in Listing 5 that no further explanation should be required.

Now back to the program named GM01test01

A complete listing of the program named GM01test01 is provided in Listing 28 near the end of the lesson.

Because all of the 3D classes in the game-math library are new to this update, I felt the need to write a program that would step through and test the behavior of most of the 3D methods in the library. That was the purpose of the program named GM01test01.

Like the program named GM01test01 discussed earlier, this program produces both a graphic screen output and lots of text on the command-line screen. The graphic output is shown in Figure 1. I won't waste space printing the command-line output in this tutorial. If you want to see it, you can copy, compile, and run the program from Listing 28 and produce that output yourself.

The graphic output

The graphic output shown in Figure 1 is produced by the method named drawOffScreen, which begins in Listing 7. I will briefly walk you through this method because everything in it is new. However, much of the code is very similar to 2D code that I have explained before.

Listing 7. Beginning of the drawOffScreen method in GM01test01.

  void drawOffScreen(Graphics2D g2D){

    //Translate the origin on the off-screen
    // image and draw a pair of orthogonal axes on it.
    setCoordinateFrame(g2D);

    //Define eight points that define the corners of
    // a box in 3D that is centered on the origin.

    GM01.Point3D[] points = new GM01.Point3D[8];
    //Right side
    points[0] = 
         new GM01.Point3D(new GM01.ColMatrix3D(75,75,75));
    points[1] = 
        new GM01.Point3D(new GM01.ColMatrix3D(75,75,-75));
    points[2] = 
       new GM01.Point3D(new GM01.ColMatrix3D(75,-75,-75));
    points[3] = 
        new GM01.Point3D(new GM01.ColMatrix3D(75,-75,75));
    //Left side
    points[4] = 
        new GM01.Point3D(new GM01.ColMatrix3D(-75,75,75));
    points[5] = 
       new GM01.Point3D(new GM01.ColMatrix3D(-75,75,-75));
    points[6] = 
      new GM01.Point3D(new GM01.ColMatrix3D(-75,-75,-75));
    points[7] = 
       new GM01.Point3D(new GM01.ColMatrix3D(-75,-75,75));

After translating the origin to the center of the off-screen image, Listing 7 instantiates eight GM01Point3D objects that define the corners of the 3D box shown in Figure 1. References to the eight objects are stored in the elements of an array object referred to by the variable named points.; Although this code uses three coordinate values instead of two coordinate values to instantiate the objects, the syntax should be very familiar to you by now.

Project eight points onto the 2D plane and draw them

Listing 8 calls the GM01.Point3D.draw method eight times in succession to cause the locations of the eight points in 3D space to be projected onto the 2D off-screen image and to draw the points as small circles on that image. This is the first draw method from the earlier list that I explained above.

Listing 8. Project eight points onto the 2D plane and draw them.

    //Draw seven of the points in BLACK
    g2D.setColor(Color.BLACK);
    for(int cnt = 1;cnt < points.length;cnt++){
      points[cnt].draw(g2D);
    }//end for loop

    //Draw the right top front point in RED to identify
    // it.
    g2D.setColor(Color.RED);
    points[0].draw(g2D);
    g2D.setColor(Color.BLACK);

Seven of the points are drawn in BLACK and one is drawn in RED. The eight small circles appear at the corners of the box in Figure 1.

Draw twelve lines that connect the corners of the box

Listing 9 calls the GM01.Line3D.draw method twelve times in succession to project the lines that connect the corners of the 3D box onto the 2D off-screen image and to draw those lines on that image. This is the second draw method from the earlier list that I explained above.

Listing 9. Draw twelve lines that connect the corners of the box.

    //Draw lines that connect the points to define the
    // twelve edges of the box.
    //Right side
    new GM01.Line3D(points[0],points[1]).draw(g2D);
    new GM01.Line3D(points[1],points[2]).draw(g2D);
    new GM01.Line3D(points[2],points[3]).draw(g2D);
    new GM01.Line3D(points[3],points[0]).draw(g2D);

    //Left side
    new GM01.Line3D(points[4],points[5]).draw(g2D);
    new GM01.Line3D(points[5],points[6]).draw(g2D);
    new GM01.Line3D(points[6],points[7]).draw(g2D);
    new GM01.Line3D(points[7],points[4]).draw(g2D);

    //Front
    new GM01.Line3D(points[0],points[4]).draw(g2D);
    new GM01.Line3D(points[3],points[7]).draw(g2D);

    //Back
    new GM01.Line3D(points[1],points[5]).draw(g2D);
    new GM01.Line3D(points[2],points[6]).draw(g2D);

Instantiate and draw a GM01.Vector3D object onto the 2D off-screen image

Listing 10 instantiates an object of the GM01.Vector3D class and calls the draw method of that class to draw the magenta vector shown in Figure 1.

Listing 10. Instantiate and draw a GM01.Vector3D object onto the 2D off-screen image.

    //Instantiate a vector.
    GM01.Vector3D vecA = new GM01.Vector3D(
                        new GM01.ColMatrix3D(75,-75,-75));

    //Draw the vector with its tail at the upper-left
    // corner of the box. The length and direction of the
    // vector will cause its head to be at the origin.
    g2D.setColor(Color.MAGENTA);
    vecA.draw(g2D,points[4]);

  }//end drawOffScreen

This is the third and final draw method from the earlier list that I explained above. The vector was drawn with its tail at the upper left corner of the box. The length and direction of the vector were such as to cause the head of the vector to be at the origin in 3D space.

Draw the 3D axes

The red, green, and blue 3D axes shown in Figure 1 were produced by the call to the setCoordinateFrame method early in Listing 7. The setCoordinateFrame method is shown in Listing 11.

Listing 11. The setCoordinateFrame method.

  private void setCoordinateFrame(Graphics2D g2D){

    //Translate the origin to the center.
    GM01.translate(g2D,0.5*osiWidth,-0.5*osiHeight);

    //Draw x-axis in RED
    g2D.setColor(Color.RED);
    GM01.Point3D pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(-75,0,0));
    GM01.Point3D pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(75,0,0));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw y-axis in GREEN
    g2D.setColor(Color.GREEN);
    pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(0,-75,0));
    pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(0,75,0));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw z-axis in BLUE
    g2D.setColor(Color.BLUE);
    pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(0,0,-75));
    pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(0,0,75));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

  }//end setCoordinateFrame method

This method is used to set the origin of the off-screen image. It also projects orthogonal 3D axes onto the 2D off-screen image and draws the projected axes on that image. The axes intersect at the origin in 3D space.

The lengths of the axes are set so as to match the interior dimensions of the box shown in Figure 1and points are drawn where the axes intersect the surfaces of the box.

There is nothing in Listing 11 that you haven't seen before, so further explanation should not be required.

End of discussion

That concludes the discussion of the program named GM01test01. You can view the code that was not discussed here in Listing 28 near the end of the lesson.

The program named GM01test05

In an earlier lesson, you learned how to add two or more vectors in 2D. This program will show you how to use the updated game-math library to add vectors in 3D. You also learned about the 2D trapezoidal rule and the 2D head-to-tail rule in the earlier lesson. This program will illustrate that those rules also apply to the addition of 3D vectors.

Figure 4 shows the graphic output produced by this program.

Figure 4. Graphic output from the program named GM01test05.

This output shows the addition of a magenta vector to a light gray vector to produce a black vector as the sum of the other two vectors. As you can see, both the trapezoidal rule and the head-to-tail rule are illustrated by the graphic output in Figure 4.

Very familiar code

Most of the code in this program will be very familiar to you by now. The new code is mostly contained in the method named drawOffScreen, which is shown in Listing 12. A complete listing of this program is provided in Listing 29 near the end of the lesson.

Listing 12. The drawOffScreen method of the program named GM01test05.

  //The purpose of this method is to illustrate vector
  // addition in 3D
  void drawOffScreen(Graphics2D g2D){

    //Translate the origin on the off-screen image, draw a
    // pair of orthogonal axes on it that intersect at the
    // origin, and paint the background white.
    setCoordinateFrame(g2D);

    //Define two vectors that will be added.
    GM01.Vector3D vecA = new GM01.Vector3D(
                         new GM01.ColMatrix3D(75,75,75));

    GM01.Vector3D vecB = new GM01.Vector3D(
                        new GM01.ColMatrix3D(-15,10,-50));

    //Create a ref point at the origin for convenience.
    GM01.Point3D zeroPoint = new GM01.Point3D(
                             new GM01.ColMatrix3D(0,0,0));

    //Draw vecA in MAGENTA with its tail at the origin.
    g2D.setColor(Color.MAGENTA);
    vecA.draw(g2D,zeroPoint);

    //Draw vecB in LIGHT_GRAY with its tail at the head
    // of vecA.
    g2D.setColor(Color.LIGHT_GRAY);
    GM01.Point3D temp = 
                    new GM01.Point3D(vecA.getColMatrix());
    vecB.draw(g2D,temp);

    //Draw vecB in LIGHT_GRAY with its tail at the origin.
    vecB.draw(g2D,zeroPoint);

    //Draw vecA in MAGENTA with its tail at the head
    // of vecB. This completes a trapezoid.
    g2D.setColor(Color.MAGENTA);
    vecA.draw(g2D,new GM01.Point3D(vecB.getColMatrix()));

    //Add the two vectors.
    GM01.Vector3D sum = vecA.add(vecB);
    //Draw sum in BLACK with its tail at the origin.
    g2D.setColor(Color.BLACK);
    sum.draw(g2D,zeroPoint);

  }//end drawOffScreen

At this point, you should not find any code in Listing 12 that you don't understand. I have explained the code in Listing 12 earlier in this lesson or in an earlier lesson, so I won't repeat that explanation here.

End of discussion.

That concludes the discussion of the program named GM01test05. You will find a complete listing of this program in Listing 29.

The program named GM01test06

Homogeneous Coordinates
In a future lesson, we will learn about a mathematical trick commonly known as homogeneous coordinates that can simplify the scaling, translation, and rotation of a point. However, that will have to wait until we have a better grasp of matrix arithmetic.

Scaling, translation, and rotation of a point

Three of the most common and important operations that you will encounter in game programming will be to scale, translate, and/or rotate a geometric object. Since all geometric objects are composed of points that define the vertices, possibly embellished with lines, shading, lighting, etc., if you know how to scale, translate, and/or rotate a point, you also know how to scale, translate, and/or rotate the entire geometric object. You simply apply the required operation to all of the points that comprise the object and you will have applied the operation to the object as a whole. In a previous lesson, you learned how to translate a geometric object. In this program, you will learn how to scale a geometric object. In the next two programs, you will learn how to rotate geometric objects.

The new scaling methods

The additions to the game-math library included the following two methods:

  • GM01.Point2D.scale
  • GM01.Point3D.scale

As the names imply, these two methods can be used to scale a point in either 2D or 3D.

The game-math library method named GM01.Point3D.scale

This method is shown in Listing 13.

Listing 13. The game-math library method named GM01.Point3D.scale.

    public GM01.Point3D scale(GM01.ColMatrix3D scale){
      return new GM01.Point3D(new ColMatrix3D(
                          getData(0) * scale.getData(0),
                          getData(1) * scale.getData(1),
                          getData(2) * scale.getData(2)));
    }//end scale

This method multiplies each coordinate value of the Point3D object on which the method is called by the corresponding values in an incoming ColMatrix3D object to produce and return a new Point3D object. This makes it possible to scale each coordinate value that defines the location in space by a different scale factor.

If a scale factor for a particular coordinate is less than 1.0 but greater than 0.0, the location of the new point will be closer to the origin along that axis than was the location of the original point. If a scale factor is greater than 1.0, the new point will be further from the origin along that axis. If the scale factor is negative, the location of the new point will be on the other side of the origin along the same axis.

Graphic output from the program named GM01test06

The graphic output from the program named GM01test06 is shown in Figure 5. Compare this with the graphic output from the program named GM01test01 shown in Figure 1.

Figure 5. Graphic output from the program named GM01test06.

Modifications to the original program

The program named GM01test06, shown in Listing 30, modifies the earlier program named GM01test01 in three different ways to demonstrate the effects of scaling points in 3D.

  1. Each GM01Point3D object that defines a corner of the box was scaled by a ColMatrix3D object containing the values 0.25, 0.5, and 0.75. Thus, the overall size of the box was reduced in comparison with the box in shown Figure 1.
  2. Each of the red, green, and blue points on the axes was scaled by 0.25, 0.5, or 0.75 respectively to cause the locations of those points to continue to be where the axes intersect the surface of the box.
  3. The magenta vector shown in Figure 1 was removed from the program because it didn't contribute to the objective of illustrating the scaling of a geometric object.

These modifications are so straightforward that no explanation of the actual code is justified.; You can view the modifications in Listing 30.

End of discussion.

That concludes the discussion of the program named GM01test06. You will find a complete listing of this program in Listing 30.

The program named StringArt02

Things are about to get a lot more interesting. This program and the one following it both deal with the rotation of geometric objects, which is a relatively complex topic.

In this program, I will teach you how to use the new GM01.Point2D.rotate method to rotate objects in 2D space. In the next program, I will teach you how to use the new GM01.Point3D.rotate method to rotate objects in 3D space.

Rotation is 3D space is much more complicated than rotation in 2D space, so I will begin my explanation of this topic with the simpler of the two programs.

Equations for rotating an object in 2D space

Once again, we have some equations to deal with, and once we understand the equations, the required code is tedious, but not terribly difficult to write. The new game-math library method named GM01.Point2D.rotate, which begins in Listing 14, implements the required equations for rotating an object in 2D space.

Listing 14. Beginning of the game-math library method named GM01.Point2D.rotate.

    public GM01.Point2D rotate(GM01.Point2D anchorPoint,
                               double angle){
      GM01.Point2D newPoint = this.clone();

      double tempX ;
      double tempY;

The purpose of the GM01.Point2D.rotate method is to rotate a point around a specified anchor point in the x-y plane. The location of the anchor point is passed in as a reference to an object of the class GM01.Point2D. The rotation angle is passed in as a double value in degrees with the positive angle of rotation being counter-clockwise.

Does not modify the original Point 3D object

This method does not modify the contents of the original Point2D object on which the method is called. Rather, it uses the contents of that object to instantiate, rotate, and return a new Point2D object, leaving the original object intact.

Equations for rotating an object in 2D space

By using the information on the Spatial transformations webpage (see Resources) along with other information on the web, we can conclude that the equations required to rotate a point around the origin in 2D space are shown in Figure 6. I won't attempt to derive or justify these equations. I will simply use them. If you need more information on the topic, simply Google 2D transforms and you will probably find more information than you have the time to read.

Figure 6. Rotation equations for a point in 2D space.

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

In Figure 6, the coordinates of the original point are given by x1 and y1, and the coordinates of the rotated point are given by x2 and y2.  The angle alpha is a counter-clockwise angle around the origin.

Houston, we have a problem

We still have a problem however. The equations in Figure 6 are for rotating a point around the origin. Our objective is to rotate a point around any arbitrary anchor point in 2D space.

We could probably modify the equations in Figure 6 to accomplish this. However, there is another way, which is easier to implement. It can be shown that the same objective can be achieved by translating the anchor point to the origin, rotating the object around the origin, and then translating the rotated object back to the anchor point. Since we already know how to translate a point in 2D space, this is the approach that we will use.

You must be very careful

I do want to point out, however, that you really have to think about what you are doing when you rotate geometric objects, particularly when you combine rotation with translation. For example, rotating an object around the origin and then translating it does not produce the same result as translating the object and then rotating the translated object around the origin.

Clone the original Point2D object

Listing 14 begins by calling the new GM01.Point2D.clone method to create a clone of the object on which the rotate method was called. The clone, referred to by newPoint, will be rotated and returned, thus preserving the original object.

Following that, Listing 14 declares a couple of working variables that will be used later.

Incoming parameters

Note that the GM01.Point2D.rotate method requires two incoming parameters. The first parameter is a reference to a GM01.Point2D object that specifies the anchor point around which the geometric object is to be rotated. The second parameter is the rotation angle in degrees, counter-clockwise around the origin.

Translate the anchor point to the origin

Listing 15 gets a Vector2D object that represents the displacement vector from the origin to the anchor point.

Listing 15. Translate the anchor point to the origin.

      GM01.Vector2D tempVec = 
            new GM01.Vector2D(anchorPoint.getColMatrix());
      newPoint = 
              newPoint.addVectorToPoint(tempVec.negate());

The negative of the displacement vector is used to translate the newPoint object, thus translating the anchor point to the origin.

Rotate the translated newPoint object around the origin

Listing 16 implements the two rotation equations shown in Figure 6 to rotate the translated newPoint object around the origin.

Listing 16. Rotate the translated newPoint object around the origin.

      tempX = newPoint.getData(0);
      tempY = newPoint.getData(1);
      newPoint.setData(//new x coordinate
                      0,
                      tempX*Math.cos(angle*Math.PI/180) -
                      tempY*Math.sin(angle*Math.PI/180));

      newPoint.setData(//new y coordinate
                      1,
                      tempX*Math.sin(angle*Math.PI/180) +
                      tempY*Math.cos(angle*Math.PI/180));

Note that the rotation angle is converted from degrees to radians to make it compatible with the sin and cos functions from the standard Java Math library.

Translate the rotated newPoint object back to the anchor point

Finally, Listing 17 uses the displacement vector that was created and saved earlier to translate the rotated newPoint object back to the anchor point.

Listing 17. Translate the rotated newPoint object back to the anchor point.

      //Translate back to anchorPoint
      newPoint = newPoint.addVectorToPoint(tempVec);

      return newPoint;

    }//end rotate

Then Listing 17 returns a reference to the rotated newPoint object.

Now back to the program named StringArt02

This is a 2D version of a string art program that supports rotation in two dimensions. This program produces a 2D string art image by connecting various points that are equally spaced on the circumference of a circle as shown in Figure 7.

Figure 7. Initial graphic output from the program named StringArt02.

Initial conditions

Initially, the circle is centered on the origin and there are six points on the circle connected by lines forming a hexagon. The lines that connect the points are different colors. The radius of the circle is 50 units. The points at the vertices of the hexagon are not drawn, but the lines that connect the vertices are drawn. The anchor point is drawn in black, resulting in the small black circle at the origin in Figure 7.

A graphical user interface

A GUI is provided that allows the user to specify the following items and click a Replot button to cause the drawing to change:

  • Number Points
  • Number Loops
  • Rotation angle (deg)
  • X anchor point
  • Y anchor point

Changing the number of points causes the number of vertices that describe the geometric object to change. Changing the number of loops causes the number of lines that are drawn to connect the vertices to change. For a value of 1, each vertex is connected to the one next to it. For a value of 2, additional lines are drawn connecting every other vertex. For a value of 3, additional lines are drawn connecting every third vertex as shown in Figure 8.

Figure 8. Graphic output from the program named StringArt02 with Loops set to 3.

Rotating the geometric object around the origin

The geometric object can be rotated around an anchor point. Entering a non-zero value in the Rotation field causes the geometric object to be rotated by the specified angle around the anchor point. The anchor point is initially located at the origin, but the location of the anchor point can be changed by the user. If the anchor point is at the origin, the geometric object is rotated around the origin as shown in Figure 9. (Compare the colors and the locations of the vertices in Figure 8 and Figure 9 to discern the result of the rotation in Figure 9.)

Figure 9. Rotation by 30 degrees around the origin.

Rotating the geometric object around an anchor point away from the origin

Figure 10 shows the result of rotating the geometric object around an anchor point that is not located at the origin.

Figure 10. Rotation around an anchor point that is not at the origin.

The rotation angle is specified in degrees with a positive angle being counter-clockwise. For Figure 10, I purposely located the anchor point at the upper-right vertex in Figure 8 and rotated the geometric object by 90 degrees around that anchor point. Compare Figure 10 with Figure 8 to see the result of rotating the geometric object around the anchor point.

Rotating around an anchor point further out in space

In Figure 11, I moved the anchor point further out in space, but still on a line that runs through the origin and the upper-right vertex in Figure 8. Then I rotated the geometric object by 30 degrees around the anchor point.

Figure 11. Rotation around a point further out in space.

By now, you should have been able to predict in advance what you would see when the program was run with these parameters.

Let's see some code

Given what you have already learned, the only interesting new code in this program is in the drawOffScreen method. An abbreviated listing of that method is shown in Listing 18. A complete listing of the StringArt02 program is provided in Listing 31 near the end of the lesson.

Listing 18. Abbreviated listing of the drawOffScreen method.

  void drawOffScreen(Graphics2D g2D){
    setCoordinateFrame(g2D,false);

    //Create a set of Point2D objects that specify
    // locations on the circumference of a circle that
    // is in the x-y plane with a radius of 50 units. Save
    // references to the Point2D objects in an array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM01.Point2D(new GM01.ColMatrix2D(
        50*Math.cos((cnt*360/numberPoints)*Math.PI/180),
        50*Math.sin((cnt*360/numberPoints)*Math.PI/180)));

      //The following object is populated with the 2D 
      // coordinates of the point around which the 
      //rotations will take place.
      GM01.Point2D anchorPoint = new GM01.Point2D(
                            new GM01.ColMatrix2D(
                              xAnchorPoint,yAnchorPoint));
      //Draw the anchorPoint in BLACK.
      g2D.setColor(Color.BLACK);
      anchorPoint.draw(g2D);

      //The following statement causes the rotation to be 
      //performed.
      points[cnt] = 
                 points[cnt].rotate(anchorPoint,rotation);

    }//end for loop

//Code deleted for brevity

  }//end drawOffScreen

Within the method named drawOffScreen, the only really interesting code is the statement that calls the rotate method of the game-math library on each Point2D object inside a for loop. That code is highlighted in boldface. Knowing what you do about the rotate method, you should have no problem understanding the code in Listing 18.

End of the discussion

That concludes the discussion of the program named StringArt02. You will find a complete listing of this program in Listing 31.

The program named StringArt03

I saved the best for last, or at least I saved the most difficult program until last. In this program, I will teach you how to use the new GM01.Point3D.rotate method to rotate objects in 3D space. Not only is the code for doing rotations in 3D space much more complicated than the rotation code in 2D, it is also more difficult to examine the graphic output produced by rotating an object in 3D space and be certain that the program is working as it should. Therefore, we need to start this discussion with an explanation of the game-math library method named GM01.Point3D.rotate. Before we can get to that method, however, we must deal with the rotation equations for rotation of a point in 3D space.

The six 3D rotation equations

Unlike with 2D rotation where things were less complicated, we now have to deal with three coordinate values, three rotation angles, and six equations. Using the Spatial Transformations webpage (see Resources) and other online material as well, we can conclude that our 3D rotation method must implement the six equations shown in Figure 12.

Figure 12. The six 3D rotation equations.

Let rotation angle around z-axis be zAngle
Let rotation angle around z-axis be xAngle
Let rotation angle around z-axis be yAngle

Rotation around the z-axis
x2 = x1*cos(zAngle) - y1*sin(zAngle)
y2 = x1*sin(zAngle) + y1*cos(zAngle)

Rotation around the x-axis
y2 = y1*cos(xAngle) - z1*sin(xAngle)
z2 = y1*sin(xAngle) + z1* cos(xAngle) 

Rotation around the y-axis
x2 = x1*cos(yAngle) + z1*sin(yAngle)
z2 = -x1*sin(yAngle) + z1*cos(yAngle)

Where:
  x1, y1, and z1 are cordinates of original point
  x2, y2, and z2 are coordinates of rotated point

Also, as before, these six equations are only good for rotation around the origin, but our objective is to be able to rotate a point about any arbitrary anchor point in 3D space. Once again, we will use the trick of translating the anchor point to the origin, rotating the object around the origin, and then translating the object back to the anchor point.

Beginning of the method named GM01.Point3D.rotate

The method named GM01.Point3D.rotate begins in Listing 19.

Listing 19. Beginning of the method named GM01.Point3D.rotate.

    public GM01.Point3D rotate(GM01.Point3D anchorPoint,
                               GM01.ColMatrix3D angles){
      GM01.Point3D newPoint = this.clone();

      double tempX ;
      double tempY;
      double tempZ;
 
      //Translate anchorPoint to the origin
      GM01.Vector3D tempVec = 
            new GM01.Vector3D(anchorPoint.getColMatrix());
      newPoint = 
              newPoint.addVectorToPoint(tempVec.negate());

The purpose of this method is to rotate a point around a specified anchor point in 3D space in the following order:

  • Rotate around z - rotation in x-y plane.
  • Rotate around x - rotation in y-z plane.
  • Rotate around y - rotation in x-z plane.

A useful upgrade
A useful upgrade to the game-math library might be to write three separate rotation methods, each designed to rotate a Point3D object around only one of the three axes.

It is very important to understand that the order of the rotations is critical. You cannot change the order of rotations and expect to end up with the same results. This method is designed to allow you to rotate an object around all three axes with a single method call in the order given above. If you need to rotate your object in some different order, you should call the method up to three times in succession, rotating around only one axis with each call to the method.

Incoming parameters

The anchor point is passed in as a reference to an object of the GM01.Point3D class.

The rotation angles are passed in as double values in degrees (based on the right-hand rule) in the order given above, packaged in an object of the class GM01.ColMatrix3D. (Note that in this case, the ColMatrix3D object is simply a convenient container for the three double angle values and it has no significance from a matrix arithmetic viewpoint.)

The right-hand rule

The right-hand rule states that if you point the thumb of your right hand in the positive direction of an axis and curl your fingers to make a fist, the direction of positive rotation around that axis is given by the direction that your fingers will be pointing.

Original object is not modified

This method does not modify the contents of the Point3D object on which the method is called.  Rather, it uses the contents of that object to instantiate, rotate, and return a new Point3D object.

Rotation around the anchor point

For simplicity, this method translates the anchor point to the origin, rotates around the origin, and then translates the object back to the anchor point.

Very familiar code

The code in Listing 19 is very similar to the code that I explained earlier in Listing 14 and Listing 14.  Therefore, this code should not require an explanation beyond the embedded comments.

Get the rotation angle values

Listing 20 extracts the rotation angle values from the GM01.ColMatrix3D object in which they are contained.

Listing 20. Get the rotation angle values.

      double zAngle = angles.getData(0);
      double xAngle = angles.getData(1);
      double yAngle = angles.getData(2);

Rotate the Point3D object around the z-axis

By this point in the execution of the method, the object has been translated to the origin using the negative of the anchor-point displacement vector. The object will be rotated around the origin and will then be translated back to the anchor point.

Listing 21 implements the first two equations from Figure 12 to rotate the Point3D object around the z-axis. By this, I mean that the object is rotated in a plane that is perpendicular to the z-axis modifying only x and y coordinate values from the object being rotated.

Listing 21. Rotate the Point 3D object around the z-axis.

      //Rotate around z-axis
      tempX = newPoint.getData(0);
      tempY = newPoint.getData(1);
      newPoint.setData(//new x coordinate
                      0,
                      tempX*Math.cos(zAngle*Math.PI/180) -
                      tempY*Math.sin(zAngle*Math.PI/180));

      newPoint.setData(//new y coordinate
                      1,
                      tempX*Math.sin(zAngle*Math.PI/180) +
                      tempY*Math.cos(zAngle*Math.PI/180));

This is the only rotation possibility in 2D rotation and the code in Listing 21 is essentially the same as the code in Listing 16 for 2D rotation.

Rotate the Point 3D object around the x-axis

Before translating the partially rotated object back to the anchor point, it must still be rotated around the x and y-axes. Listing 22 implements the middle two equations in Figure 12 to rotate the Point3D object in a plane that is perpendicular to the x-axis, modifying only the y and z coordinate values.

Listing 22. Rotate the Point 3D object around the x-axis.

      //Rotate around x-axis
      tempY = newPoint.getData(1);
      tempZ = newPoint.getData(2);
      newPoint.setData(//new y coordinate
                      1,
                      tempY*Math.cos(xAngle*Math.PI/180) -
                      tempZ*Math.sin(xAngle*Math.PI/180));

      newPoint.setData(//new z coordinate
                      2,
                      tempY*Math.sin(xAngle*Math.PI/180) +
                      tempZ*Math.cos(xAngle*Math.PI/180));

Rotate the Point 3D object around the y-axis

Listing 23 implements the last two equations in Figure 12 to rotate the Point3D object in a plane that is perpendicular to the y-axis, modifying only the x and z coordinate values.

Listing 23. Rotate the Point 3D object around the y-axis.

      //Rotate around y-axis
      tempX = newPoint.getData(0);
      tempZ = newPoint.getData(2);
      newPoint.setData(//new x coordinate
                      0,
                      tempX*Math.cos(yAngle*Math.PI/180) +
                      tempZ*Math.sin(yAngle*Math.PI/180));

      newPoint.setData(//new z coordinate
                     2,
                     -tempX*Math.sin(yAngle*Math.PI/180) +
                     tempZ*Math.cos(yAngle*Math.PI/180));

Translate the object back to the anchor point

Listing 24 translates the rotated object back to the anchor point, thus completing the 3D rotation of a single GM01.Point3D object.

Listing 24. Translate the object back to the anchor point.

      //Translate back to anchorPoint
      newPoint = newPoint.addVectorToPoint(tempVec);

      return newPoint;

    }//end rotate

In order to rotate an entire 3D geometric object, such as the hexagon in Figure 13, every point that comprises the geometric object must be rotated using the same set of rotation angles and the same anchor point.

Now back to the program named StringArt03

This is a 3D version of a string art program that demonstrates rotation in three dimensions. This program produces a 3D string-art image by connecting various points that are equally spaced on the circumference of a circle. Initially, the circle is on the x-y plane centered on the origin as shown in Figure 13.

Figure 13. Graphic output from the program named StringArt03 at startup.

At startup, there are six points (vertices) on the circle connected by lines forming a hexagon. The lines that connect the points are different colors. The radius of the circle is 50 units. The points at the vertices of the hexagon are not drawn, but the lines that connect the vertices are drawn.

You may have noticed that the startup graphic output in Figure 13 looks a lot like the startup graphic output of the 2D program in Figure 7. There is a significant difference however. Figure 7 shows only two orthogonal axes whereas Figure 13 shows three orthogonal axes using oblique parallel projection to transform the 3D image to a 2D display plane.

A graphical user interface

A GUI is provided that allows the user to specify the following items and click a Replot button to cause the drawing to change:

  • Number Points
  • Number Loops
  • Rotate around Z (deg)
  • Rotate around X (deg)
  • Rotate around Y (deg)
  • X Anchor point
  • Y Anchor point
  • Z Anchor point

Again, the 3D GUI in Figure 13 looks similar to the 2D GUI in Figure 7. The big difference is that the 2D GUI in Figure 7 allows only for rotation around one axis, and only two coordinate values can be specified for the location of the anchor point.

As before, changing the number of points causes the number of vertices that describe the geometric object to change. Changing the number of loops causes the number of lines that are drawn to connect the vertices to change.

The geometric object can be rotated in any or all of three dimensions around an anchor point. Entering a non-zero value in one or more of the Rotate fields causes the object to be rotated by the specified angle or angles around the anchor point.

The anchor point is initially specified to be at the origin, but the location of the anchor point can be changed by the user. If the anchor point is at the origin, the image is rotated around the origin.

Geometric object with 12 vertices, 4 loops, and no rotations

As a baseline case, Figure 14 shows the string-art geometric object with 12 vertices, 4 loops, and no rotations. At this point, the geometric object is an infinitely thin disk in the x-y plane centered on the origin. Note the break in color between yellow and blue that occurs where the circle crosses the positive x-axis.

Figure 14. Geometric object with 12 vertices, 4 loops, and no rotations.

The rotation angle must be specified in degrees with a positive angle being given by the right-hand rule as applied to the axis around which the image is being rotated.

Rotation around one axis only

Figure 15, Figure 16, and Figure 17 show the results of rotating the object around only one axis at a time with the anchor point at the origin. Figure 15 shows the result of rotating the object around the z-axis only by an angle of 60 degrees.

Figure 15. Rotation around the z-axis only.

This results in the object still being in the x-y plane, but it has been rotated counter-clockwise by 60 degrees. Compare Figure 15 with Figure 14 and note how the color break between yellow and blue has moved around to be near the intersection of the circle and the positive y-axis.

Rotation around the x-axis only

Figure 16 shows the result of rotating the object around only the x-axis with a rotation angle of -60 degrees.

Figure 16. Rotation around the x-axis only.

The object is still a disk, but that disk is no longer in the x-y plane. Instead, it has been tilted so that it is now closer to the x-z plane than to the x-y plane. Unfortunately, the oblique parallel projection does not make it practical to do any quantitative measurements on the image.

Rotation around the y-axis only

Figure 17 shows the result of rotating the object around only the y-axis with a rotation angle of -60 degrees.

Figure 17. Rotation around the y-axis only.

I will let you interpret what you see there.

Rotation around all three axes with the anchor point at the origin

When more than one rotation angle has a non-zero value, the rotational effects are cumulative. The object is first rotated around the anchor point in a direction consistent with rotation around the z-axis (rotation in the x-y plane). Then that rotated object is rotated in a direction consistent with rotation around the x-axis (rotation in the y-z plane).; Finally, the previously rotated object is rotated in a direction consistent with rotation around the y-axis (rotation in the x-z plane). It is important to note, however, that the actual rotation is around the anchor point and not around the origin unless the anchor point is at the origin.

Figure 18 shows the result of applying all three of the rotations described above with the anchor point at the origin.

Figure 18. Rotation around all three axes with the anchor point at the origin.

Once again, I will let you interpret what you see there.

Perform all three rotations with the anchor point away from the origin

Figure 19 performs the same three rotations as were performed in Figure 18. However, in Figure 19, the anchor point was at a location defined by the coordinate values (50,50,50).

Figure 19. Perform all three rotations with the anchor point away from the origin.

At the risk of being boring, I will state once again that I will let you interpret what you see there.

Will animate the process later

For me, at least, it isn't easy to visualize the process of rotating around an arbitrary anchor point in 3D. In a future lesson, I will animate the rotation process and run it in slow motion so that you can see the progress of each individual rotation from the beginning until the point where all three rotations are complete. Hopefully, that will make it easier for us to visualize rotation around an arbitrary anchor point in 3D. As a bonus, it will also give you some experience in using the game-math library for a non-trivial animation project.

Let's see some code

As was the case with the previous program, given what you have already learned, the only interesting new code in this program is in the drawOffScreen method. Furthermore, only a small portion of the code in that method is interesting. Listing 25 contains some code that was extracted from the drawOffScreen method.

A complete listing of the program named StringArt03 is provided in Listing 32.

Listing 25. Interesting code from the drawOffScreen method.

    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM01.Point3D(
        new GM01.ColMatrix3D(
          50*Math.cos((cnt*360/numberPoints)*Math.PI/180),
          50*Math.sin((cnt*360/numberPoints)*Math.PI/180),
          0.0));

      //Populate a ColMatrix3D object with rotation values
      GM01.ColMatrix3D angles = new GM01.ColMatrix3D(
                           zRotation,xRotation,yRotation);

      //Populate a Point3D object with anchor point 
      // coordinates.
      GM01.Point3D anchorPoint = new GM01.Point3D(
               new GM01.ColMatrix3D(
                 xAnchorPoint,yAnchorPoint,zAnchorPoint));
      //Draw the anchorPoint in BLACK.
      g2D.setColor(Color.BLACK);
      anchorPoint.draw(g2D);

      //The following statement causes the rotation to be 
      //performed.
      points[cnt] = 
                   points[cnt].rotate(anchorPoint,angles);

    }//end for loop

The only really interesting code in Listing 25 is the statement that calls the rotate method of the game-math library on each Point3D object inside a for loop. That code is highlighted in boldface. Knowing what you do about the rotate method, you should have no problem understanding the code in Listing 25.

End of the discussion

That concludes the discussion of the program named StringArt03. You will find a complete listing of this program in Listing 32.

Run the programs

I encourage you to copy the code from Listing 27 through Listing 32, compile the code, and execute it in conjunction with the game-math library provided in Listing 26. Experiment with the code, making changes, and observing the results of your changes. Make certain that you can explain why your changes behave as they do.

Summary

The most important thing that you learned in this lesson was how to update the game-math library to support 3D math and how to produce 3D images similar to that shown in Figure 1. You learned much more than that however. Some highlights of the things you learned are:

  • You learned how to program the equations for projecting a 3D world onto a 2D plane for display on a computer screen.
  • You learned how to cause the direction of the positive y-axis to be up the screen instead of down the screen.
  • You learned how to add vectors in 3D and confirmed that the head-to-tail and trapezoidal rules apply to 3D as well as to 2D.
  • You learned about scaling, translation, and rotation of a point in both 2D and 3D.
  • You learned about the rotation equations and how to implement them in both 2D and 3D.
  • You learned how to rotate a point around an anchor point other than the origin.
  • You learned about the right-hand rule.

What's next?

In the next lesson in this series, you will learn how to write your first interactive 3D game using the game-math library. You will also learn how to write a Java program that simulates flocking behavior such as that exhibited by birds and fish and how to incorporate that behavior in a game.

Resources

Complete program listings

Complete listings of the programs discussed in this lesson are shown in Listing 26 through Listing 32 below.

Listing 26. Source code for the updated game-math library named GM01.

/*GM01.java
Copyright 2008, R.G.Baldwin
Revised 02/24/08

This is a major upgrade to the game-math library. This
version upgrades the version named GM2D04 to a new version
named simply GM01.

The primary purpose of the upgrade was to add 3D 
capability for all of the 2D features provided by the
previous version. Because both 2D and 3D capabilities
are included, it is no longer necessary to differentiate
between the two in the name of the class. Therefore, this
version is named GM01.

Adding 3D capability entailed major complexity in one
particular area: drawing the objects. It is difficult to
draw a 3D object on a 2D screen. This requires a 
projection process to project each point in the 3D object
onto the correct location on a 2D plane. There are a 
variety of ways to do this. This 3D library uses an
approach often referred to as an oblique parallel
projection. See the following URL for technical 
information on the projection process:

http://local.wasp.uwa.edu.au/~pbourke/geometry/
classification/


In addition to adding 3D capability, this version also
eliminates the confusion surrounding the fact that the
default direction of the positive y-axis is going down
the screen instead of up the screen as viewers have become
accustomed to. When you use this library, you can program
under the assumption that the positive direction of the
y-axis is up the screen, provided you funnel all of your
drawing tasks through the library and don't draw
directly on the screen.

The name GMnn is an abbreviation for GameMathnn.

See the file named GM2D01.java for a general description 
of the game-math library. The library has been updated
several times. This file is an update of GM2D04.

In addition to the updates mentioned above, this update
cleaned up some lingering areas of code inefficiency,
using the simplest available method to draw on an 
off-screen image. In addition, the following new methods 
were added:

The following methods are new static methods of the class
named GM01. The first method in the list deals with the
problem of displaying a 3D image on a 3D screen.

The last five methods in the list wrap the standard
graphics methods for the purpose of eliminating the issue
of the direction of the positive Y-axis.

GM01.convert3Dto2D
GM01.translate
GM01.drawLine
GM01.fillOval
GM01.drawOval
GM01.fillRect

The following methods are new instance methods of the
indicated static top-level classes belonging to the class
named GM01.

GM01.Vector2D.scale
GM01.Vector2D.negate
GM01.Point2D.clone
GM01.Vector2D.normalize
GM01.Point2D.rotate
GM01.Point2D.scale

GM01.Vector3D.scale
GM01.Vector3D.negate
GM01.Point3D.clone
GM01.Vector3D.normalize
GM01.Point3D.rotate
GM01.Point3D.scale

Tested using JDK 1.6 under WinXP.
*********************************************************/
import java.awt.geom.*;
import java.awt.*;

public class GM01{
  //----------------------------------------------------//

  //This method converts a ColMatrix3D object to a
  // ColMatrix2D object. The purpose is to accept
  // x, y, and z coordinate values and transform those
  // values into a pair of coordinate values suitable for
  // display in two dimensions.
  //See http://local.wasp.uwa.edu.au/~pbourke/geometry/
  // classification/ for technical background on the
  // transform from 3D to 2D.
  //The transform equations are:
  // x2d = x3d + z3d * cos(theta)/tan(alpha)
  // y2d = y3d + z3d * sin(theta)/tan(alpha);
  //Let theta = 30 degrees and alpha = 45 degrees
  //Then:cos(theta) = 0.866
  //     sin(theta) = 0.5
  //     tan(alpha) = 1;
  //Note that the signs in the above equations depend
  // on the assumed directions of the angles as well as
  // the assumed positive directions of the axes. The
  // signs used in this method assume the following:
  //   Positive x is to the right.
  //   Positive y is up the screen.
  //   Positive z is protruding out the front of the
  //     screen.
  //   The viewing position is above the x axis and to the
  //     right of the z-y plane.
  public static GM01.ColMatrix2D convert3Dto2D(
                                   GM01.ColMatrix3D data){
    return new GM01.ColMatrix2D(
                  data.getData(0) - 0.866*data.getData(2),
                  data.getData(1) - 0.50*data.getData(2));
  }//end convert3Dto2D 
  //----------------------------------------------------//

  //This method wraps around the translate method of the
  // Graphics2D class. The purpose is to cause the
  // positive direction for the y-axis to be up the screen
  // instead of down the screen. When you use this method,
  // you should program as though the positive direction
  // for the y-axis is up.
  public static void translate(Graphics2D g2D,
                               double xOffset,
                               double yOffset){
    //Flip the sign on the y-coordinate to change the
    // direction of the positive y-axis to go up the
    // screen.
    g2D.translate(xOffset,-yOffset);
  }//end translate
  //----------------------------------------------------//

  //This method wraps around the drawLine method of the
  // Graphics class. The purpose is to cause the positive
  // direction for the y-axis to be up the screen instead
  // of down the screen. When you use this method, you
  // should program as though the positive direction for
  // the y-axis is up.
  public static void drawLine(Graphics2D g2D,
                              double x1,
                              double y1,
                              double x2,
                              double y2){
    //Flip the sign on the y-coordinate value.
    g2D.drawLine((int)x1,-(int)y1,(int)x2,-(int)y2);
  }//end drawLine
  //----------------------------------------------------//

  //This method wraps around the fillOval method of the
  // Graphics class. The purpose is to cause the positive
  // direction for the y-axis to be up the screen instead
  // of down the screen. When you use this method, you
  // should program as though the positive direction for
  // the y-axis is up.
  public static void fillOval(Graphics2D g2D,
                              double x,
                              double y,
                              double width,
                              double height){
    //Flip the sign on the y-coordinate value.
    g2D.fillOval((int)x,-(int)y,(int)width,(int)height);
  }//end fillOval
  //----------------------------------------------------//

  //This method wraps around the drawOval method of the
  // Graphics class. The purpose is to cause the positive
  // direction for the y-axis to be up the screen instead
  // of down the screen. When you use this method, you
  // should program as though the positive direction for
  // the y-axis is up.
  public static void drawOval(Graphics2D g2D,
                              double x,
                              double y,
                              double width,
                              double height){
    //Flip the sign on the y-coordinate value.
    g2D.drawOval((int)x,-(int)y,(int)width,(int)height);
  }//end drawOval
  //----------------------------------------------------//

  //This method wraps around the fillRect method of the
  // Graphics class. The purpose is to cause the positive
  // direction for the y-axis to be up the screen instead
  // of down the screen. When you use this method, you
  // should program as though the positive direction for
  // the y-axis is up.
  public static void fillRect(Graphics2D g2D,
                              double x,
                              double y,
                              double width,
                              double height){
    //Flip the sign on the y-coordinate value.
    g2D.fillRect((int)x,-(int)y,(int)width,(int)height);
  }//end fillRect
  //----------------------------------------------------//



  //An object of this class represents a 2D column matrix.
  // An object of this class is the fundamental building
  // block for several of the other classes in the
  // library.
  public static class ColMatrix2D{
    double[] data = new double[2];

    public ColMatrix2D(double data0,double data1){
      data[0] = data0;
      data[1] = data1;
    }//end constructor
    //--------------------------------------------------//

    //Overridden toString method.
    public String toString(){
      return data[0] + "," + data[1];
    }//end overridden toString method
    //--------------------------------------------------//

    public double getData(int index){
      if((index < 0) || (index > 1)){ 
        throw new IndexOutOfBoundsException();
      }else{
        return data[index];
      }//end else
    }//end getData method
    //--------------------------------------------------//
    
    public void setData(int index,double data){
      if((index < 0) || (index > 1)){ 
        throw new IndexOutOfBoundsException();
      }else{
        this.data[index] = data;
      }//end else
    }//end setData method
    //--------------------------------------------------//

    //This method overrides the equals method inherited
    // from the class named Object. It compares the values
    // stored in two matrices and returns true if the
    // values are equal or almost equal and returns false
    // otherwise. 
    public boolean equals(Object obj){
      if(obj instanceof GM01.ColMatrix2D &&
         Math.abs(((GM01.ColMatrix2D)obj).getData(0) - 
                                 getData(0)) <= 0.00001 &&
         Math.abs(((GM01.ColMatrix2D)obj).getData(1) - 
                                  getData(1)) <= 0.00001){
        return true;
      }else{
        return false;
      }//end else
     
    }//end overridden equals method
    //--------------------------------------------------//

    //Adds one ColMatrix2D object to another ColMatrix2D
    // object, returning a ColMatrix2D object.
    public GM01.ColMatrix2D add(GM01.ColMatrix2D matrix){
      return new GM01.ColMatrix2D(
                            getData(0)+matrix.getData(0),
                            getData(1)+matrix.getData(1));
    }//end add
    //--------------------------------------------------//

    //Subtracts one ColMatrix2D object from another
    // ColMatrix2D object, returning a ColMatrix2D object.
    // The object that is received as an incoming
    // parameter  is subtracted from the object on which
    // the method is called.
    public GM01.ColMatrix2D subtract(
                                 GM01.ColMatrix2D matrix){
      return new GM01.ColMatrix2D(
                            getData(0)-matrix.getData(0),
                            getData(1)-matrix.getData(1));
    }//end subtract
    //--------------------------------------------------//
  }//end class ColMatrix2D
  //====================================================//


  //An object of this class represents a 3D column matrix.
  // An object of this class is the fundamental building
  // block for several of the other classes in the
  // library.
  public static class ColMatrix3D{
    double[] data = new double[3];

    public ColMatrix3D(
                  double data0,double data1,double data2){
      data[0] = data0;
      data[1] = data1;
      data[2] = data2;
    }//end constructor
    //--------------------------------------------------//

    public String toString(){
      return data[0] + "," + data[1] + "," + data[2];
    }//end overridden toString method
    //--------------------------------------------------//

    public double getData(int index){
      if((index < 0) || (index > 2)){ 
        throw new IndexOutOfBoundsException();
      }else{
        return data[index];
      }//end else
    }//end getData method
    //--------------------------------------------------//

    public void setData(int index,double data){
      if((index < 0) || (index > 2)){ 
        throw new IndexOutOfBoundsException();
      }else{
        this.data[index] = data;
      }//end else
    }//end setData method
    //--------------------------------------------------//
    
    //This method overrides the equals method inherited
    // from the class named Object. It compares the values
    // stored in two matrices and returns true if the
    // values are equal or almost equal and returns false
    // otherwise.
    public boolean equals(Object obj){
      if(obj instanceof GM01.ColMatrix3D &&
         Math.abs(((GM01.ColMatrix3D)obj).getData(0) - 
                                 getData(0)) <= 0.00001 &&
         Math.abs(((GM01.ColMatrix3D)obj).getData(1) - 
                                 getData(1)) <= 0.00001 &&
         Math.abs(((GM01.ColMatrix3D)obj).getData(2) - 
                                  getData(2)) <= 0.00001){
        return true;
      }else{
        return false;
      }//end else
     
    }//end overridden equals method
    //--------------------------------------------------//

    //Adds one ColMatrix3D object to another ColMatrix3D
    // object, returning a ColMatrix3D object.
    public GM01.ColMatrix3D add(GM01.ColMatrix3D matrix){
      return new GM01.ColMatrix3D(
                            getData(0)+matrix.getData(0),
                            getData(1)+matrix.getData(1),
                            getData(2)+matrix.getData(2));
    }//end add
    //--------------------------------------------------//

    //Subtracts one ColMatrix3D object from another
    // ColMatrix3D object, returning a ColMatrix3D object.
    // The object that is received as an incoming
    // parameter is subtracted from the object on which
    // the method is called.
    public GM01.ColMatrix3D subtract(
                                 GM01.ColMatrix3D matrix){
      return new GM01.ColMatrix3D(
                            getData(0)-matrix.getData(0),
                            getData(1)-matrix.getData(1),
                            getData(2)-matrix.getData(2));
    }//end subtract
    //--------------------------------------------------//
  }//end class ColMatrix3D
  //====================================================//
  //====================================================//


  public static class Point2D{
    GM01.ColMatrix2D point;
    
    public Point2D(GM01.ColMatrix2D point){//constructor
      //Create and save a clone of the ColMatrix2D object
      // used to define the point to prevent the point
      // from being corrupted by a later change in the
      // values stored in the original ColMatrix2D object
      // through use of its set method.
      this.point = new ColMatrix2D(
                       point.getData(0),point.getData(1));
    }//end constructor
    //--------------------------------------------------//

    public String toString(){
      return point.getData(0) + "," + point.getData(1);
    }//end toString
    //--------------------------------------------------//

    public double getData(int index){
      if((index < 0) || (index > 1)){ 
        throw new IndexOutOfBoundsException();
      }else{
        return point.getData(index);
      }//end else
    }//end getData
    //--------------------------------------------------//

    public void setData(int index,double data){
      if((index < 0) || (index > 1)){ 
        throw new IndexOutOfBoundsException();
      }else{
        point.setData(index,data);
      }//end else
    }//end setData
    //--------------------------------------------------//

    //This method draws a small circle around the location
    // of the point on the specified graphics context.
    public void draw(Graphics2D g2D){
      drawOval(g2D,getData(0)-3,
                   getData(1)+3,6,6);
    }//end draw

    //--------------------------------------------------//

    //Returns a reference to the ColMatrix2D object that
    // defines this Point2D object.
    public GM01.ColMatrix2D getColMatrix(){
      return point;
    }//end getColMatrix
    //--------------------------------------------------//

    //This method overrides the equals method inherited
    // from the class named Object. It compares the values
    // stored in the ColMatrix2D objects that define two
    // Point2D objects and returns true if they are equal
    // and false otherwise. 
    public boolean equals(Object obj){
      if(point.equals(((GM01.Point2D)obj).
                                         getColMatrix())){
        return true;
      }else{
        return false;
      }//end else
     
    }//end overridden equals method
    //--------------------------------------------------//

    //Gets a displacement vector from one Point2D object
    // to a second Point2D object. The vector points from
    // the object on which the method is called to the
    // object passed as a parameter to the method. Kjell
    // describes this as the distance you would have to
    // walk along the x and then the y axes to get from
    // the first point to the second point.
    public GM01.Vector2D getDisplacementVector(
                                      GM01.Point2D point){
      return new GM01.Vector2D(new GM01.ColMatrix2D(
                            point.getData(0)-getData(0),
                            point.getData(1)-getData(1)));
    }//end getDisplacementVector
    //--------------------------------------------------//

    //Adds a Vector2D to a Point2D producing a
    // new Point2D.
    public GM01.Point2D addVectorToPoint(
                                      GM01.Vector2D vec){
      return new GM01.Point2D(new GM01.ColMatrix2D(
                          getData(0) + vec.getData(0),
                          getData(1) + vec.getData(1)));
    }//end addVectorToPoint
    //--------------------------------------------------//

    //Returns a new Point2D object that is a clone of
    // the object on which the method is called.
    public Point2D clone(){
      return new Point2D(
                  new ColMatrix2D(getData(0),getData(1)));
    }//end clone
    //--------------------------------------------------//

    //The purpose of this method is to rotate a point
    // around a specified anchor point in the x-y plane.
    //The rotation angle is passed in as a double value
    // in degrees with the positive angle of rotation
    // being counter-clockwise.
    //This method does not modify the contents of the
    // Point2D object on which the method is called.
    // Rather, it uses the contents of that object to
    // instantiate, rotate, and return a new Point2D
    // object.
    //For simplicity, this method translates the
    // anchorPoint to the origin, rotates around the
    // origin, and then translates back to the
    // anchorPoint.
    /*
    See http://www.ia.hiof.no/~borres/cgraph/math/threed/
    p-threed.html for a definition of the equations
    required to do the rotation.

    x2 = x1*cos - y1*sin
    y2 = x1*sin + y1*cos
    */ 
    public GM01.Point2D rotate(GM01.Point2D anchorPoint,
                               double angle){
      GM01.Point2D newPoint = this.clone();

      double tempX ;
      double tempY;
 
      //Translate anchorPoint to the origin
      GM01.Vector2D tempVec = 
            new GM01.Vector2D(anchorPoint.getColMatrix());
      newPoint = 
              newPoint.addVectorToPoint(tempVec.negate());

      //Rotate around the origin.
      tempX = newPoint.getData(0);
      tempY = newPoint.getData(1);
      newPoint.setData(//new x coordinate
                      0,
                      tempX*Math.cos(angle*Math.PI/180) -
                      tempY*Math.sin(angle*Math.PI/180));

      newPoint.setData(//new y coordinate
                      1,
                      tempX*Math.sin(angle*Math.PI/180) +
                      tempY*Math.cos(angle*Math.PI/180));

      //Translate back to anchorPoint
      newPoint = newPoint.addVectorToPoint(tempVec);
      
      return newPoint;

    }//end rotate
    //--------------------------------------------------//

    //Multiplies this point by a scaling matrix received
    // as an incoming parameter and returns the scaled
    // point.
    public GM01.Point2D scale(GM01.ColMatrix2D scale){
      return new GM01.Point2D(new ColMatrix2D(
                          getData(0) * scale.getData(0),
                          getData(1) * scale.getData(1)));
    }//end scale
    //--------------------------------------------------//
  }//end class Point2D
  //====================================================//


  public static class Point3D{
    GM01.ColMatrix3D point;

    public Point3D(GM01.ColMatrix3D point){//constructor
      //Create and save a clone of the ColMatrix3D object
      // used to define the point to prevent the point
      // from being corrupted by a later change in the
      // values stored in the original ColMatrix3D object
      // through use of its set method.
      this.point = 
         new ColMatrix3D(point.getData(0),
                         point.getData(1),
                         point.getData(2));
    }//end constructor
    //--------------------------------------------------//

    public String toString(){
      return point.getData(0) + "," + point.getData(1) 
                                 + "," + point.getData(2);
    }//end toString
    //--------------------------------------------------//

    public double getData(int index){
      if((index < 0) || (index > 2)){ 
        throw new IndexOutOfBoundsException();
      }else{
        return point.getData(index);
      }//end else
    }//end getData
    //--------------------------------------------------//

    public void setData(int index,double data){
      if((index < 0) || (index > 2)){ 
        throw new IndexOutOfBoundsException();
      }else{
        point.setData(index,data);
      }//end else
    }//end setData
    //--------------------------------------------------//

    //This method draws a small circle around the location
    // of the point on the specified graphics context.
    public void draw(Graphics2D g2D){

      //Get 2D projection coordinate values.
      ColMatrix2D temp = convert3Dto2D(point);
      drawOval(g2D,temp.getData(0)-3,
                   temp.getData(1)+3,
                   6,
                   6);
    }//end draw
    //--------------------------------------------------//

    //Returns a reference to the ColMatrix3D object that
    // defines this Point3D object.
    public GM01.ColMatrix3D getColMatrix(){
      return point;
    }//end getColMatrix
    //--------------------------------------------------//

    //This method overrides the equals method inherited
    // from the class named Object. It compares the values
    // stored in the ColMatrix3D objects that define two
    // Point3D objects and returns true if they are equal
    // and false otherwise. 
    public boolean equals(Object obj){
      if(point.equals(((GM01.Point3D)obj).
                                         getColMatrix())){
        return true;
      }else{
        return false;
      }//end else

    }//end overridden equals method
    //--------------------------------------------------//

    //Gets a displacement vector from one Point3D object
    // to a second Point3D object. The vector points from
    // the object on which the method is called to the
    // object passed as a parameter to the method. Kjell
    // describes this as the distance you would have to
    // walk along the x and then the y axes to get from
    // the first point to the second point.
    public GM01.Vector3D getDisplacementVector(
                                      GM01.Point3D point){
      return new GM01.Vector3D(new GM01.ColMatrix3D(
                            point.getData(0)-getData(0),
                            point.getData(1)-getData(1),
                            point.getData(2)-getData(2)));
    }//end getDisplacementVector
    //--------------------------------------------------//

    //Adds a Vector3D to a Point3D producing a
    // new Point3D.
    public GM01.Point3D addVectorToPoint(
                                      GM01.Vector3D vec){
      return new GM01.Point3D(new GM01.ColMatrix3D(
                          getData(0) + vec.getData(0),
                          getData(1) + vec.getData(1),
                          getData(2) + vec.getData(2)));
    }//end addVectorToPoint
    //--------------------------------------------------//

    //Returns a new Point3D object that is a clone of
    // the object on which the method is called.
    public Point3D clone(){
      return new Point3D(new ColMatrix3D(getData(0),
                                         getData(1),
                                         getData(2)));
    }//end clone
    //--------------------------------------------------//

    //The purpose of this method is to rotate a point
    // around a specified anchor point in the following
    // order:
    // Rotate around z - rotation in x-y plane.
    // Rotate around x - rotation in y-z plane.
    // Rotate around y - rotation in x-z plane.
    //The rotation angles are passed in as double values
    // in degrees (based on the right-hand rule) in the
    // order given above, packaged in an object of the
    // class GM01.ColMatrix3D. (Note that in this case,
    // the ColMatrix3D object is simply a convenient
    // container and it has no significance from a matrix
    // viewpoint.)
    //The right-hand rule states that if you point the
    // thumb of your right hand in the positive direction
    // of an axis, the direction of positive rotation
    // around that axis is given by the direction that
    // your fingers will be pointing.
    //This method does not modify the contents of the
    // Point3D object on which the method is called.
    // Rather, it uses the contents of that object to
    // instantiate, rotate, and return a new Point3D
    // object.
    //For simplicity, this method translates the
    // anchorPoint to the origin, rotates around the
    // origin, and then translates back to the
    // anchorPoint.
    /*
    See http://www.ia.hiof.no/~borres/cgraph/math/threed/
    p-threed.html for a definition of the equations
    required to do the rotation.
    z-axis 
    x2 = x1*cos - y1*sin
    y2 = x1*sin + y1*cos

    x-axis
    y2 = y1*cos(v) - z1*sin(v)
    z2 = y1*sin(v) + z1* cos(v) 

    y-axis
    x2 = x1*cos(v) + z1*sin(v)
    z2 = -x1*sin(v) + z1*cos(v)
    */ 
    public GM01.Point3D rotate(GM01.Point3D anchorPoint,
                               GM01.ColMatrix3D angles){
      GM01.Point3D newPoint = this.clone();

      double tempX ;
      double tempY;
      double tempZ;

      //Translate anchorPoint to the origin
      GM01.Vector3D tempVec = 
            new GM01.Vector3D(anchorPoint.getColMatrix());
      newPoint = 
              newPoint.addVectorToPoint(tempVec.negate());

      double zAngle = angles.getData(0);
      double xAngle = angles.getData(1);
      double yAngle = angles.getData(2);

      //Rotate around z-axis
      tempX = newPoint.getData(0);
      tempY = newPoint.getData(1);
      newPoint.setData(//new x coordinate
                      0,
                      tempX*Math.cos(zAngle*Math.PI/180) -
                      tempY*Math.sin(zAngle*Math.PI/180));

      newPoint.setData(//new y coordinate
                      1,
                      tempX*Math.sin(zAngle*Math.PI/180) +
                      tempY*Math.cos(zAngle*Math.PI/180));

      //Rotate around x-axis
      tempY = newPoint.getData(1);
      tempZ = newPoint.getData(2);
      newPoint.setData(//new y coordinate
                      1,
                      tempY*Math.cos(xAngle*Math.PI/180) -
                      tempZ*Math.sin(xAngle*Math.PI/180));

      newPoint.setData(//new z coordinate
                      2,
                      tempY*Math.sin(xAngle*Math.PI/180) +
                      tempZ*Math.cos(xAngle*Math.PI/180));

      //Rotate around y-axis
      tempX = newPoint.getData(0);
      tempZ = newPoint.getData(2);
      newPoint.setData(//new x coordinate
                      0,
                      tempX*Math.cos(yAngle*Math.PI/180) +
                      tempZ*Math.sin(yAngle*Math.PI/180));

      newPoint.setData(//new z coordinate
                     2,
                     -tempX*Math.sin(yAngle*Math.PI/180) +
                     tempZ*Math.cos(yAngle*Math.PI/180));

      //Translate back to anchorPoint
      newPoint = newPoint.addVectorToPoint(tempVec);

      return newPoint;

    }//end rotate
    //--------------------------------------------------//

    //Multiplies this point by a scaling matrix received
    // as an incoming parameter and returns the scaled
    // point.
    public GM01.Point3D scale(GM01.ColMatrix3D scale){
      return new GM01.Point3D(new ColMatrix3D(
                          getData(0) * scale.getData(0),
                          getData(1) * scale.getData(1),
                          getData(2) * scale.getData(2)));
    }//end scale
    //--------------------------------------------------//
  }//end class Point3D
  //====================================================//
  //====================================================//


  public static class Vector2D{
    GM01.ColMatrix2D vector;
    
    public Vector2D(GM01.ColMatrix2D vector){//constructor
      //Create and save a clone of the ColMatrix2D object
      // used to define the vector to prevent the vector
      // from being corrupted by a later change in the
      // values stored in the original ColVector2D object.
      this.vector = new ColMatrix2D(
                     vector.getData(0),vector.getData(1));
    }//end constructor
    //--------------------------------------------------//

    public String toString(){
      return vector.getData(0) + "," + vector.getData(1);
    }//end toString
    //--------------------------------------------------//

    public double getData(int index){
      if((index < 0) || (index > 1)){
        throw new IndexOutOfBoundsException();
      }else{
        return vector.getData(index);
      }//end else
    }//end getData
    //--------------------------------------------------//

    public void setData(int index,double data){
      if((index < 0) || (index > 1)){ 
        throw new IndexOutOfBoundsException();
      }else{
        vector.setData(index,data);
      }//end else
    }//end setData
    //--------------------------------------------------//

    //This method draws a vector on the specified graphics
    // context, with the tail of the vector located at a
    // specified point, and with a small filled circle at
    // the head.
    public void draw(Graphics2D g2D,GM01.Point2D tail){

      drawLine(g2D,
               tail.getData(0),
               tail.getData(1),
               tail.getData(0)+vector.getData(0),
               tail.getData(1)+vector.getData(1));

      fillOval(g2D,
               tail.getData(0)+vector.getData(0)-3,
               tail.getData(1)+vector.getData(1)+3,
               6,
               6);
    }//end draw
    //--------------------------------------------------//

    //Returns a reference to the ColMatrix2D object that
    // defines this Vector2D object.
    public GM01.ColMatrix2D getColMatrix(){
      return vector;
    }//end getColMatrix
    //--------------------------------------------------//

    //This method overrides the equals method inherited
    // from the class named Object. It compares the values
    // stored in the ColMatrix2D objects that define two
    // Vector2D objects and returns true if they are equal
    // and false otherwise. 
    public boolean equals(Object obj){
      if(vector.equals((
                     (GM01.Vector2D)obj).getColMatrix())){
        return true;
      }else{
        return false;
      }//end else

    }//end overridden equals method
    //--------------------------------------------------//

    //Adds this vector to a vector received as an incoming
    // parameter and returns the sum as a vector.
    public GM01.Vector2D add(GM01.Vector2D vec){
      return new GM01.Vector2D(new ColMatrix2D(
                       vec.getData(0)+vector.getData(0),
                       vec.getData(1)+vector.getData(1)));
    }//end add
    //--------------------------------------------------//

    //Returns the length of a Vector2D object.
    public double getLength(){
      return Math.sqrt(
           getData(0)*getData(0) + getData(1)*getData(1));
    }//end getLength
    //--------------------------------------------------//

    //Multiplies this vector by a scale factor received as
    // an incoming parameter and returns the scaled
    // vector.
    public GM01.Vector2D scale(Double factor){
      return new GM01.Vector2D(new ColMatrix2D(
                                    getData(0) * factor,
                                    getData(1) * factor));
    }//end scale
    //--------------------------------------------------//

    //Changes the sign on each of the vector components
    // and returns the negated vector.
    public GM01.Vector2D negate(){
      return new GM01.Vector2D(new ColMatrix2D(
                                            -getData(0),
                                            -getData(1)));
    }//end negate
    //--------------------------------------------------//

    //Returns a new vector that points in the same
    // direction but has a length of one unit.
    public GM01.Vector2D normalize(){
      double length = getLength();
      return new GM01.Vector2D(new ColMatrix2D(
                                      getData(0)/length,
                                      getData(1)/length));
    }//end normalize
    //--------------------------------------------------//
  }//end class Vector2D
  //====================================================//


  public static class Vector3D{
    GM01.ColMatrix3D vector;

    public Vector3D(GM01.ColMatrix3D vector){//constructor
      //Create and save a clone of the ColMatrix3D object
      // used to define the vector to prevent the vector
      // from being corrupted by a later change in the
      // values stored in the original ColMatris3D object.
      this.vector = new ColMatrix3D(vector.getData(0),
                                    vector.getData(1),
                                    vector.getData(2));
    }//end constructor
    //--------------------------------------------------//

    public String toString(){
      return vector.getData(0) + "," + vector.getData(1) 
                                + "," + vector.getData(2);
    }//end toString
    //--------------------------------------------------//

    public double getData(int index){
      if((index < 0) || (index > 2)){
        throw new IndexOutOfBoundsException();
      }else{
        return vector.getData(index);
      }//end else
    }//end getData
    //--------------------------------------------------//

    public void setData(int index,double data){
      if((index < 0) || (index > 2)){ 
        throw new IndexOutOfBoundsException();
      }else{
        vector.setData(index,data);
      }//end else
    }//end setData
    //--------------------------------------------------//

    //This method draws a vector on the specified graphics
    // context, with the tail of the vector located at a
    // specified point, and with a small circle at the
    // head.
    public void draw(Graphics2D g2D,GM01.Point3D tail){

      //Get a 2D projection of the tail
      GM01.ColMatrix2D tail2D = convert3Dto2D(tail.point);

      //Get the 3D location of the head
      GM01.ColMatrix3D head = 
                      tail.point.add(this.getColMatrix());

      //Get a 2D projection of the head
      GM01.ColMatrix2D head2D = convert3Dto2D(head);
      drawLine(g2D,tail2D.getData(0),
                   tail2D.getData(1),
                   head2D.getData(0),
                   head2D.getData(1));

      //Draw a small filled circle to identify the head.
      fillOval(g2D,head2D.getData(0)-3,
                   head2D.getData(1)+3,
                   6,
                   6);

    }//end draw
    //--------------------------------------------------//

    //Returns a reference to the ColMatrix3D object that
    // defines this Vector3D object.
    public GM01.ColMatrix3D getColMatrix(){
      return vector;
    }//end getColMatrix
    //--------------------------------------------------//

    //This method overrides the equals method inherited
    // from the class named Object. It compares the values
    // stored in the ColMatrix3D objects that define two
    // Vector3D objects and returns true if they are equal
    // and false otherwise. 
    public boolean equals(Object obj){
      if(vector.equals((
                     (GM01.Vector3D)obj).getColMatrix())){
        return true;
      }else{
        return false;
      }//end else
     
    }//end overridden equals method
    //--------------------------------------------------//
    
    //Adds this vector to a vector received as an incoming
    // parameter and returns the sum as a vector.
    public GM01.Vector3D add(GM01.Vector3D vec){
      return new GM01.Vector3D(new ColMatrix3D(
                       vec.getData(0)+vector.getData(0),
                       vec.getData(1)+vector.getData(1),
                       vec.getData(2)+vector.getData(2)));
    }//end add
    //--------------------------------------------------//

    //Returns the length of a Vector3D object.
    public double getLength(){
      return Math.sqrt(getData(0)*getData(0) + 
                       getData(1)*getData(1) + 
                       getData(2)*getData(2));
    }//end getLength
    //--------------------------------------------------//

    //Multiplies this vector by a scale factor received as
    // an incoming parameter and returns the scaled
    // vector.
    public GM01.Vector3D scale(Double factor){
      return new GM01.Vector3D(new ColMatrix3D(
                                    getData(0) * factor,
                                    getData(1) * factor,
                                    getData(2) * factor));
    }//end scale
    //--------------------------------------------------//

    //Changes the sign on each of the vector components
    // and returns the negated vector.
    public GM01.Vector3D negate(){
      return new GM01.Vector3D(new ColMatrix3D(
                                            -getData(0),
                                            -getData(1),
                                            -getData(2)));
    }//end negate
    //--------------------------------------------------//

    //Returns a new vector that points in the same
    // direction but has a length of one unit.
    public GM01.Vector3D normalize(){
      double length = getLength();
      return new GM01.Vector3D(new ColMatrix3D(
                                      getData(0)/length,
                                      getData(1)/length,
                                      getData(2)/length));
    }//end normalize
    //--------------------------------------------------//
  }//end class Vector3D
  //====================================================//
  //====================================================//


  //A line is defined by two points. One is called the
  // tail and the other is called the head. Note that this
  // class has the same name as one of the classes in
  // the Graphics2D class. Therefore, if the class from
  // the Graphics2D class is used in some future upgrade
  // to this program, it will have to be fully qualified.
  public static class Line2D{
    GM01.Point2D[] line = new GM01.Point2D[2];
    
    public Line2D(GM01.Point2D tail,GM01.Point2D head){
      //Create and save clones of the points used to
      // define the line to prevent the line from being 
      // corrupted by a later change in the coordinate
      // values of the points.
      this.line[0] = new Point2D(new GM01.ColMatrix2D(
                        tail.getData(0),tail.getData(1)));
      this.line[1] = new Point2D(new GM01.ColMatrix2D(
                        head.getData(0),head.getData(1)));
    }//end constructor
    //--------------------------------------------------//

    public String toString(){
      return "Tail = " + line[0].getData(0) + "," 
             + line[0].getData(1) + "\nHead = " 
             + line[1].getData(0) + "," 
             + line[1].getData(1);
    }//end toString
    //--------------------------------------------------//

    public GM01.Point2D getTail(){
      return line[0];
    }//end getTail
    //--------------------------------------------------//

    public GM01.Point2D getHead(){
      return line[1];
    }//end getHead
    //--------------------------------------------------//

    public void setTail(GM01.Point2D newPoint){
      //Create and save a clone of the new point to
      // prevent the line from being corrupted by a
      // later change in the coordinate values of the
      // point.
      this.line[0] = new Point2D(new GM01.ColMatrix2D(
                newPoint.getData(0),newPoint.getData(1)));
    }//end setTail
    //--------------------------------------------------//

    public void setHead(GM01.Point2D newPoint){
      //Create and save a clone of the new point to
      // prevent the line from being corrupted by a
      // later change in the coordinate values of the
      // point.
      this.line[1] = new Point2D(new GM01.ColMatrix2D(
                newPoint.getData(0),newPoint.getData(1)));
    }//end setHead
    //--------------------------------------------------//

    public void draw(Graphics2D g2D){
      drawLine(g2D,getTail().getData(0),
                   getTail().getData(1),
                   getHead().getData(0),
                   getHead().getData(1));
    }//end draw
    //--------------------------------------------------//
  }//end class Line2D
  //====================================================//


  //A line is defined by two points. One is called the
  // tail and the other is called the head.
  public static class Line3D{
    GM01.Point3D[] line = new GM01.Point3D[2];

    public Line3D(GM01.Point3D tail,GM01.Point3D head){
      //Create and save clones of the points used to
      // define the line to prevent the line from being 
      // corrupted by a later change in the coordinate
      // values of the points.
      this.line[0] = new Point3D(new GM01.ColMatrix3D(
                                        tail.getData(0),
                                        tail.getData(1),
                                        tail.getData(2)));
      this.line[1] = new Point3D(new GM01.ColMatrix3D(
                                        head.getData(0),
                                        head.getData(1),
                                        head.getData(2)));
    }//end constructor
    //--------------------------------------------------//

    public String toString(){
      return "Tail = " + line[0].getData(0) + "," 
                       + line[0].getData(1)  + "," 
                       + line[0].getData(2) 
                       + "\nHead = " 
                       + line[1].getData(0) + "," 
                       + line[1].getData(1) + ","      
                       + line[1].getData(2);
    }//end toString
    //--------------------------------------------------//

    public GM01.Point3D getTail(){
      return line[0];
    }//end getTail
    //--------------------------------------------------//

    public GM01.Point3D getHead(){
      return line[1];
    }//end getHead
    //--------------------------------------------------//

    public void setTail(GM01.Point3D newPoint){
      //Create and save a clone of the new point to
      // prevent the line from being corrupted by a
      // later change in the coordinate values of the
      // point.
      this.line[0] = new Point3D(new GM01.ColMatrix3D(
                                    newPoint.getData(0),
                                    newPoint.getData(1),
                                    newPoint.getData(2)));
    }//end setTail
    //--------------------------------------------------//

    public void setHead(GM01.Point3D newPoint){
      //Create and save a clone of the new point to
      // prevent the line from being corrupted by a
      // later change in the coordinate values of the
      // point.
      this.line[1] = new Point3D(new GM01.ColMatrix3D(
                                    newPoint.getData(0),
                                    newPoint.getData(1),
                                    newPoint.getData(2)));
    }//end setHead
    //--------------------------------------------------//

    public void draw(Graphics2D g2D){

      //Get 2D projection coordinates.
      GM01.ColMatrix2D tail = 
                           convert3Dto2D(getTail().point);
      GM01.ColMatrix2D head = 
                           convert3Dto2D(getHead().point);

      drawLine(g2D,tail.getData(0),
                   tail.getData(1),
                   head.getData(0),
                   head.getData(1));
    }//end draw
    //--------------------------------------------------//
  }//end class Line3D
  //====================================================//

}//end class GM01
//======================================================//

 

Listing 27. Source code for the program named GM01test02.

/*GM01test02.java
Copyright 2008, R.G.Baldwin
Revised 02/18/08

This program tests many 2D aspects of the GM01 library
using both text and graphics.

Tested using JDK 1.6 under WinXP.
*********************************************************/
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;

class GM01test02{
  public static void main(String[] args){
    GUI guiObj = new GUI();
  }//end main
}//end controlling class GM01test02
//======================================================//

class GUI extends JFrame{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 400;
  int vSize = 400;
  Image osi;//an off-screen image
  int osiWidth;//off-screen image width
  int osiHeight;//off-screen image height
  MyCanvas myCanvas;//a subclass of Canvas

  GUI(){//constructor
    //Set JFrame size, title, and close operation.
    setSize(hSize,vSize);
    setTitle("Copyright 2008,R.G.Baldwin");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Create a new drawing canvas and add it to the
    // center of the JFrame.
    myCanvas = new MyCanvas();
    this.getContentPane().add(myCanvas);

    //This object must be visible before you can get an
    // off-screen image.  It must also be visible before
    // you can compute the size of the canvas.
    setVisible(true);
    osiWidth = myCanvas.getWidth();
    osiHeight = myCanvas.getHeight();

    //Create an off-screen image and get a graphics
    // context on it.
    osi = createImage(osiWidth,osiHeight);
    Graphics2D g2D = (Graphics2D)(osi.getGraphics());

    //Perform tests using text.
    testUsingText();

    //Perform tests using graphics.
    drawOffScreen(g2D);

    //Cause the overridden paint method belonging to
    // myCanvas to be executed.
    myCanvas.repaint();

  }//end constructor
  //----------------------------------------------------//

  //The purpose of this method is to test various
  // 2D aspects of the library using text.
  public void testUsingText(){
    System.out.println(
               "Test overridden toString of ColMatrix2D");
    GM01.ColMatrix2D tempA = 
                            new GM01.ColMatrix2D(1.5,2.5);
    System.out.println(tempA);
    System.out.println();

    System.out.println(
               "Test setData and getData of ColMatrix2D");
    tempA.setData(0,4.5);
    System.out.println(tempA.getData(0));
    tempA.setData(1,5.5);
    System.out.println(tempA.getData(1));
    System.out.println();

    System.out.println(
                     "Test equals method of ColMatrix2D");
    GM01.ColMatrix2D tempB = 
                            new GM01.ColMatrix2D(1.5,2.5);
    System.out.println(tempA.equals(tempB));
    tempB.setData(0,4.5);
    tempB.setData(1,5.5);
    System.out.println(tempA.equals(tempB));
    System.out.println();

    System.out.println("Test add method of ColMatrix2D");
    System.out.println(tempA.add(tempB));
    System.out.println();

    System.out.println(
                   "Test subtract method of ColMatrix2D");
    System.out.println(tempA.subtract(tempB));
    System.out.println();

    System.out.println("Test toString method of Point2D");
    GM01.Point2D pointA = new GM01.Point2D(tempA);
    System.out.println(pointA);
    System.out.println();

    System.out.println(
                   "Test setData and getData of Point2D");
    pointA.setData(0,1.1);
    System.out.println(pointA.getData(0));
    pointA.setData(1,2.2);
    System.out.println(pointA.getData(1));
    System.out.println();

    System.out.println(
                   "Test getColMatrix method of Point2D");
    System.out.println(pointA.getColMatrix());
    System.out.println();

    System.out.println("Test equals method of Point2D");
    GM01.Point2D pointB = new GM01.Point2D(tempB);
    System.out.println(pointA.equals(pointB));
    pointA = new GM01.Point2D(tempB);
    System.out.println(pointA.equals(pointB));
    System.out.println();

    System.out.println(
          "Test getDisplacementVector method of Point2D");
    pointA = 
          new GM01.Point2D(new GM01.ColMatrix2D(1.5,2.5));
    System.out.println(pointA);
    System.out.println(pointB);
    System.out.println(
                    pointA.getDisplacementVector(pointB));
    System.out.println();

    System.out.println(
               "Test addVectorToPoint method of Point2D");
    System.out.println(pointA);
    System.out.println(pointA.addVectorToPoint(
      new GM01.Vector2D(new GM01.ColMatrix2D(5.5,6.5))));
    System.out.println();

    //See the method named drawOffScreen for a test of
    // the draw method of the Point2D class.

    System.out.println(
                      "Test toString method of Vector2D");
    GM01.Vector2D vecA = 
         new GM01.Vector2D(new GM01.ColMatrix2D(1.5,2.5));
    System.out.println(vecA);
    System.out.println();

    System.out.println(
          "Test setData and getData methods of Vector2D");
    vecA.setData(0,4.5);
    System.out.println(vecA.getData(0));
    vecA.setData(1,5.5);
    System.out.println(vecA.getData(1));
    System.out.println();

    //See the method named drawOffScreen for a test of
    // the draw method of the Vector2D class.

    System.out.println(
                  "Test getColMatrix method of Vector2D");
    System.out.println(vecA.getColMatrix());
    System.out.println();

    System.out.println("Test equals method of Vector2D");
    GM01.Vector2D vecB = 
         new GM01.Vector2D(new GM01.ColMatrix2D(1.5,2.5));
    System.out.println(vecA.equals(vecB));
    vecB.setData(0,4.5);
    vecB.setData(1,5.5);
    System.out.println(vecA.equals(vecB));

    System.out.println("Test add method of Vector2D");
    System.out.println(vecA);
    vecB = 
        new GM01.Vector2D(new GM01.ColMatrix2D(-1.5,3.5));
    System.out.println(vecB);
    System.out.println(vecA.add(vecB));
    System.out.println();


    System.out.println(
                     "Test getLength method of Vector2D");
    vecA = 
         new GM01.Vector2D(new GM01.ColMatrix2D(3.0,4.0));
    System.out.println(vecA);
    System.out.println(vecA.getLength());
    System.out.println();

    System.out.println("Test toString method of Line2D");
    GM01.Line2D lineA = new GM01.Line2D(pointA,pointB);
    System.out.println(lineA);
    System.out.println();

    System.out.println("Test setTail, setHead, getTail, "
                     + "\nand getHead methods of Line2D");
    lineA.setTail(pointB);
    lineA.setHead(pointA);
    System.out.println(lineA.getTail());
    System.out.println(lineA.getHead());
    System.out.println();

    //See the method named drawOffScreen for a test of
    // the draw method of the Line2D class.
    
  }//end testUsingText
  //----------------------------------------------------//

  //The purpose of this method is test various 2D aspects
  // of the game-math library using graphics.
  void drawOffScreen(Graphics2D g2D){

    //Translate the origin on the off-screen
    // image and draw a pair of orthogonal axes on it.
    setCoordinateFrame(g2D);

    //Define the corners of a square in 2D that is
    // centered on the origin.
    GM01.Point2D pointA = 
           new GM01.Point2D(new GM01.ColMatrix2D(75,-75));
    GM01.Point2D pointB = 
          new GM01.Point2D(new GM01.ColMatrix2D(-75,-75));
    GM01.Point2D pointC = 
           new GM01.Point2D(new GM01.ColMatrix2D(-75,75));
    GM01.Point2D pointD = 
            new GM01.Point2D(new GM01.ColMatrix2D(75,75));

    //Draw three of the points in BLACK. Draw the fourth
    // point in RED to identify it.
    g2D.setColor(Color.BLACK);
    pointA.draw(g2D);
    pointB.draw(g2D);
    pointC.draw(g2D);
    g2D.setColor(Color.RED);
    pointD.draw(g2D);
    g2D.setColor(Color.BLACK);


    //Draw four lines connecting the points to produce
    // the outline of a square.
    GM01.Line2D lineAB = new GM01.Line2D(pointA,pointB);
    lineAB.draw(g2D);

    GM01.Line2D lineBC = new GM01.Line2D(pointB,pointC);
    lineBC.draw(g2D);

    GM01.Line2D lineCD = new GM01.Line2D(pointC,pointD);
    lineCD.draw(g2D);

    GM01.Line2D lineDA = new GM01.Line2D(pointD,pointA);
    lineDA.draw(g2D);

    //Define a vector.
    GM01.Vector2D vecA = new GM01.Vector2D(
                            new GM01.ColMatrix2D(75,75));

    //Draw the vector with its tail at pointB. The length
    // and direction of the vector will cause its head
    // to be at the origin.
    g2D.setColor(Color.MAGENTA);
    vecA.draw(g2D,pointB);

  }//end drawOffScreen
  //----------------------------------------------------//

  //This method is used to set the origin of the
  // off-screen image and to draw orthogonal 2D axes on
  // the image that intersect at the origin.
  private void setCoordinateFrame(Graphics2D g2D){

    //Translate the origin to the center.
    GM01.translate(g2D,0.5*osiWidth,-0.5*osiHeight);

    //Draw x-axis in RED
    g2D.setColor(Color.RED);
    GM01.drawLine(g2D,-75,0,75,0);

    //Draw y-axis in GREEN
    g2D.setColor(Color.GREEN);
    GM01.drawLine(g2D,0,75,0,-75);

  }//end setCoordinateFrame method
  //====================================================//


  //This is an inner class of the GUI class.
  class MyCanvas extends Canvas{
    //Override the paint() method. This method will be
    // called when the JFrame and the Canvas appear on the
    // screen or when the repaint method is called on the
    // Canvas object.
    //The purpose of this method is to display the
    // off-screen image on the screen.
    public void paint(Graphics g){
      g.drawImage(osi,0,0,this);
    }//end overridden paint()
    
  }//end inner class MyCanvas

}//end class GUI
//======================================================//

 

Listing 28. Source code for the program named GM01test01.

tr>
/*GM01test01.java 
Copyright 2008, R.G.Baldwin
Revised 02/18/08

This program tests many 3D aspects of the GM01 library
using both text and graphics.

Tested using JDK 1.6 under WinXP.
*********************************************************/
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;

class GM01test01{
  public static void main(String[] args){
    GUI guiObj = new GUI();
  }//end main
}//end controlling class GM01test01
//======================================================//

class GUI extends JFrame{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 400;
  int vSize = 400;
  Image osi;//an off-screen image
  int osiWidth;//off-screen image width
  int osiHeight;//off-screen image height
  MyCanvas myCanvas;//a subclass of Canvas

  GUI(){//constructor
    //Set JFrame size, title, and close operation.
    setSize(hSize,vSize);
    setTitle("Copyright 2008,R.G.Baldwin");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Create a new drawing canvas and add it to the
    // center of the JFrame.
    myCanvas = new MyCanvas();
    this.getContentPane().add(myCanvas);

    //This object must be visible before you can get an
    // off-screen image.  It must also be visible before
    // you can compute the size of the canvas.
    setVisible(true);
    osiWidth = myCanvas.getWidth();
    osiHeight = myCanvas.getHeight();

    //Create an off-screen image and get a graphics
    // context on it.
    osi = createImage(osiWidth,osiHeight);
    Graphics2D g2D = (Graphics2D)(osi.getGraphics());

    //Test many 3D library features using text.
    testUsingText();

    //Test many 3D library features using graphics.
    drawOffScreen(g2D);

    //Cause the overridden paint method belonging to
    // myCanvas to be executed.
    myCanvas.repaint();

  }//end constructor
  //----------------------------------------------------//

    //The purpose of this method is to test various
    // 3D aspects of the library using text.
  public void testUsingText(){
    System.out.println(
               "Test overridden toString of ColMatrix3D");
    GM01.ColMatrix3D tempA = 
                        new GM01.ColMatrix3D(1.5,2.5,3.5);
    System.out.println(tempA);
    System.out.println();

    System.out.println(
               "Test setData and getData of ColMatrix3D");
    tempA.setData(0,4.5);
    System.out.println(tempA.getData(0));
    tempA.setData(1,5.5);
    System.out.println(tempA.getData(1));
    tempA.setData(2,6.5);
    System.out.println(tempA.getData(2));
    System.out.println();

    System.out.println(
                     "Test equals method of ColMatrix3D");
    GM01.ColMatrix3D tempB = 
                        new GM01.ColMatrix3D(1.5,2.5,3.5);
    System.out.println(tempA.equals(tempB));
    tempB.setData(0,4.5);
    tempB.setData(1,5.5);
    tempB.setData(2,6.5);
    System.out.println(tempA.equals(tempB));
    System.out.println();

    System.out.println("Test add method of ColMatrix3D");
    System.out.println(tempA.add(tempB));
    System.out.println();

    System.out.println(
                   "Test subtract method of ColMatrix3D");
    System.out.println(tempA.subtract(tempB));
    System.out.println();

    System.out.println("Test toString method of Point3D");
    GM01.Point3D pointA = new GM01.Point3D(tempA);
    System.out.println(pointA);
    System.out.println();

    System.out.println(
                   "Test setData and getData of Point3D");
    pointA.setData(0,1.1);
    System.out.println(pointA.getData(0));
    pointA.setData(1,2.2);
    System.out.println(pointA.getData(1));
    pointA.setData(2,3.3);
    System.out.println(pointA.getData(2));
    System.out.println();

    System.out.println("Test getColMatrix of Point3D");
    System.out.println(pointA.getColMatrix());
    System.out.println();

    System.out.println("Test equals method of Point3D");
    GM01.Point3D pointB = new GM01.Point3D(tempB);
    System.out.println(pointA.equals(pointB));
    pointA = new GM01.Point3D(tempB);
    System.out.println(pointA.equals(pointB));
    System.out.println();

    //See the method named drawOffScreen for a test of
    // the draw method of the Point3D class.

    System.out.println(
          "Test getDisplacementVector method of Point3D");
    pointA = 
      new GM01.Point3D(new GM01.ColMatrix3D(1.5,2.5,3.5));
    System.out.println(pointA);
    System.out.println(pointB);
    System.out.println(
                    pointA.getDisplacementVector(pointB));
    System.out.println();

    System.out.println(
               "Test addVectorToPoint method of Point3D");
    System.out.println(pointA);
    System.out.println(pointA.addVectorToPoint(
                   new GM01.Vector3D(
                     new GM01.ColMatrix3D(5.5,6.5,7.5))));
    System.out.println();

    System.out.println(
                      "Test toString method of Vector3D");
    GM01.Vector3D vecA = new GM01.Vector3D(
                       new GM01.ColMatrix3D(1.5,2.5,3.5));
    System.out.println(vecA);
    System.out.println();

    System.out.println(
          "Test setData and getData methods of Vector3D");
    vecA.setData(0,4.5);
    System.out.println(vecA.getData(0));
    vecA.setData(1,5.5);
    System.out.println(vecA.getData(1));
    vecA.setData(2,6.5);
    System.out.println(vecA.getData(2));
    System.out.println();

    //See the method named drawOffScreen for a test of
    // the draw method of the Vector3D class.

    System.out.println(
                  "Test getColMatrix method of Vector3D");
    System.out.println(vecA.getColMatrix());
    System.out.println();

    System.out.println("Test equals method of Vector3D");
    GM01.Vector3D vecB = new GM01.Vector3D(
                       new GM01.ColMatrix3D(1.5,2.5,3.5));
    System.out.println(vecA.equals(vecB));
    vecB.setData(0,4.5);
    vecB.setData(1,5.5);
    vecB.setData(2,6.5);
    System.out.println(vecA.equals(vecB));

    System.out.println("Test add method of Vector3D");
    System.out.println(vecA);
    vecB = new GM01.Vector3D(
                      new GM01.ColMatrix3D(-1.5,2.5,3.5));
    System.out.println(vecB);
    System.out.println(vecA.add(vecB));
    System.out.println();

    System.out.println(
                     "Test getLength method of Vector3D");
    vecA = new GM01.Vector3D(
                       new GM01.ColMatrix3D(3.0,4.0,5.0));
    System.out.println(vecA);
    System.out.println(vecA.getLength());
    System.out.println();

    System.out.println("Test toString method of Line3D");
    GM01.Line3D lineA = new GM01.Line3D(pointA,pointB);
    System.out.println(lineA);
    System.out.println();

    System.out.println("Test setTail, setHead, getTail, "
                     + "\nand getHead methods of Line3D");
    lineA.setTail(pointB);
    lineA.setHead(pointA);
    System.out.println(lineA.getTail());
    System.out.println(lineA.getHead());
    System.out.println();
  }//end testUsingText
  //----------------------------------------------------//

  //The purpose of this method is to test various
  // 3D aspects of the library using graphics.
  void drawOffScreen(Graphics2D g2D){

    //Translate the origin on the off-screen
    // image and draw a pair of orthogonal axes on it.
    setCoordinateFrame(g2D);

    //Define eight points that define the corners of
    // a box in 3D that is centered on the origin.

    GM01.Point3D[] points = new GM01.Point3D[8];
    //Right side
    points[0] = 
         new GM01.Point3D(new GM01.ColMatrix3D(75,75,75));
    points[1] = 
        new GM01.Point3D(new GM01.ColMatrix3D(75,75,-75));
    points[2] = 
       new GM01.Point3D(new GM01.ColMatrix3D(75,-75,-75));
    points[3] = 
        new GM01.Point3D(new GM01.ColMatrix3D(75,-75,75));
    //Left side
    points[4] = 
        new GM01.Point3D(new GM01.ColMatrix3D(-75,75,75));
    points[5] = 
       new GM01.Point3D(new GM01.ColMatrix3D(-75,75,-75));
    points[6] = 
      new GM01.Point3D(new GM01.ColMatrix3D(-75,-75,-75));
    points[7] = 
       new GM01.Point3D(new GM01.ColMatrix3D(-75,-75,75));

    //Draw seven of the points in BLACK
    g2D.setColor(Color.BLACK);
    for(int cnt = 1;cnt < points.length;cnt++){
      points[cnt].draw(g2D);
    }//end for loop

    //Draw the right top front point in RED to identify
    // it.
    g2D.setColor(Color.RED);
    points[0].draw(g2D);
    g2D.setColor(Color.BLACK);

    //Draw lines that connect the points to define the
    // twelve edges of the box.
    //Right side
    new GM01.Line3D(points[0],points[1]).draw(g2D);
    new GM01.Line3D(points[1],points[2]).draw(g2D);
    new GM01.Line3D(points[2],points[3]).draw(g2D);
    new GM01.Line3D(points[3],points[0]).draw(g2D);

    //Left side
    new GM01.Line3D(points[4],points[5]).draw(g2D);
    new GM01.Line3D(points[5],points[6]).draw(g2D);
    new GM01.Line3D(points[6],points[7]).draw(g2D);
    new GM01.Line3D(points[7],points[4]).draw(g2D);

    //Front
    new GM01.Line3D(points[0],points[4]).draw(g2D);
    new GM01.Line3D(points[3],points[7]).draw(g2D);

    //Back
    new GM01.Line3D(points[1],points[5]).draw(g2D);
    new GM01.Line3D(points[2],points[6]).draw(g2D);


    //Define a vector.
    GM01.Vector3D vecA = new GM01.Vector3D(
                        new GM01.ColMatrix3D(75,-75,-75));

    //Draw the vector with its tail at the upper-left
    // corner of the box. The length and direction of the
    // vector will cause its head to be at the origin.
    g2D.setColor(Color.MAGENTA);
    vecA.draw(g2D,points[4]);

  }//end drawOffScreen
  //----------------------------------------------------//

  //This method is used to set the origin of the
  // off-screen image and to draw orthogonal 3D axes on
  // the off-screen image that intersect at the origin.
  // The lengths of the axes are set so as to match the
  // interior dimensions of the box and points are drawn
  // where the axes intersect the surfaces of the box.
  private void setCoordinateFrame(Graphics2D g2D){

    //Translate the origin to the center.
    GM01.translate(g2D,0.5*osiWidth,-0.5*osiHeight);

    //Draw x-axis in RED
    g2D.setColor(Color.RED);
    GM01.Point3D pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(-75,0,0));
    GM01.Point3D pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(75,0,0));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw y-axis in GREEN
    g2D.setColor(Color.GREEN);
    pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(0,-75,0));
    pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(0,75,0));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw z-axis in BLUE
    g2D.setColor(Color.BLUE);
    pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(0,0,-75));
    pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(0,0,75));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

  }//end setCoordinateFrame method
  //====================================================//


  //This is an inner class of the GUI class.
  class MyCanvas extends Canvas{
    //Override the paint() method. This method will be
    // called when the JFrame and the Canvas appear on the
    // screen or when the repaint method is called on the
    // Canvas object.
    //The purpose of this method is to display the
    // off-screen image on the screen.
    public void paint(Graphics g){
      g.drawImage(osi,0,0,this);
    }//end overridden paint()
    
  }//end inner class MyCanvas

}//end class GUI
//======================================================//

 

Listing 29. Source code for the program named GM01test05.

/*GM01test05.java
Copyright 2008, R.G.Baldwin
Revised 02/24/08

This program illustrates vector addition in 3D.

Tested using JDK 1.6 under WinXP.
*********************************************************/
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;

class GM01test05{
  public static void main(String[] args){
    GUI guiObj = new GUI();
  }//end main
}//end controlling class GM01test05
//======================================================//

class GUI extends JFrame{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 300;
  int vSize = 200;
  Image osi;//an off-screen image
  int osiWidth;//off-screen image width
  int osiHeight;//off-screen image height
  MyCanvas myCanvas;//a subclass of Canvas

  GUI(){//constructor
    //Set JFrame size, title, and close operation.
    setSize(hSize,vSize);
    setTitle("Copyright 2008,R.G.Baldwin");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Create a new drawing canvas and add it to the
    // center of the JFrame.
    myCanvas = new MyCanvas();
    this.getContentPane().add(myCanvas);

    //This object must be visible before you can get an
    // off-screen image.  It must also be visible before
    // you can compute the size of the canvas.
    setVisible(true);
    osiWidth = myCanvas.getWidth();
    osiHeight = myCanvas.getHeight();

    //Create an off-screen image and get a graphics
    // context on it.
    osi = createImage(osiWidth,osiHeight);
    Graphics2D g2D = (Graphics2D)(osi.getGraphics());

    //Draw on the off-screen image.
    drawOffScreen(g2D);

    //Cause the overridden paint method belonging to
    // myCanvas to be executed.
    myCanvas.repaint();

  }//end constructor
  //----------------------------------------------------//

  //The purpose of this method is to illustrate vector
  // addition in 3D
  void drawOffScreen(Graphics2D g2D){

    //Translate the origin on the off-screen image, draw a
    // pair of orthogonal axes on it that intersect at the
    // origin, and paint the background white.
    setCoordinateFrame(g2D);

    //Define two vectors that will be added.
    GM01.Vector3D vecA = new GM01.Vector3D(
                         new GM01.ColMatrix3D(75,75,75));
                        
    GM01.Vector3D vecB = new GM01.Vector3D(
                        new GM01.ColMatrix3D(-15,10,-50));

    //Create a ref point at the origin for convenience.
    GM01.Point3D zeroPoint = new GM01.Point3D(
                             new GM01.ColMatrix3D(0,0,0));

    //Draw vecA in MAGENTA with its tail at the origin.
    g2D.setColor(Color.MAGENTA);
    vecA.draw(g2D,zeroPoint);

    //Draw vecB in LIGHT_GRAY with its tail at the head
    // of vecA.
    g2D.setColor(Color.LIGHT_GRAY);
    GM01.Point3D temp = 
                    new GM01.Point3D(vecA.getColMatrix());
    vecB.draw(g2D,temp);

    //Draw vecB in LIGHT_GRAY with its tail at the origin.
    vecB.draw(g2D,zeroPoint);

    //Draw vecA in MAGENTA with its tail at the head
    // of vecB. This completes a trapezoid.
    g2D.setColor(Color.MAGENTA);
    vecA.draw(g2D,new GM01.Point3D(vecB.getColMatrix()));

    //Add the two vectors.
    GM01.Vector3D sum = vecA.add(vecB);
    //Draw sum in BLACK with its tail at the origin.
    g2D.setColor(Color.BLACK);
    sum.draw(g2D,zeroPoint);

  }//end drawOffScreen
  //----------------------------------------------------//

  //This method is used to set the origin of the
  // off-screen image, set the background color to WHITE,
  // and draw orthogonal 3D axes on the off-screen image
  // that intersect at the origin.
  private void setCoordinateFrame(Graphics2D g2D){

    //Translate the origin to the center.
    GM01.translate(g2D,0.5*osiWidth,-0.5*osiHeight);
    
    //Set background color to WHITE.
    g2D.setColor(Color.WHITE);
    GM01.fillRect(
          g2D,-osiWidth/2,osiHeight/2,osiWidth,osiHeight);

    //Draw x-axis in RED
    g2D.setColor(Color.RED);
    GM01.Point3D pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(-75,0,0));
    GM01.Point3D pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(75,0,0));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw y-axis in GREEN
    g2D.setColor(Color.GREEN);
    pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(0,-75,0));
    pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(0,75,0));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw z-axis in BLUE
    g2D.setColor(Color.BLUE);
    pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(0,0,-75));
    pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(0,0,75));
    pointA.draw(g2D);
    pointB.draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

  }//end setCoordinateFrame method
  //====================================================//

  //This is an inner class of the GUI class.
  class MyCanvas extends Canvas{
    //Override the paint() method. This method will be
    // called when the JFrame and the Canvas appear on the
    // screen or when the repaint method is called on the
    // Canvas object.
    //The purpose of this method is to display the
    // off-screen image on the screen.
    public void paint(Graphics g){
      g.drawImage(osi,0,0,this);
    }//end overridden paint()

  }//end inner class MyCanvas
    
}//end class GUI
//======================================================//

 

Listing 30. Source code for the program named GM01test06.

/*GM01test06.java
Copyright 2008, R.G.Baldwin
Revised 02/24/08

This program is an update of the program named GM01test01.
The purpose is to illustrate scaling a geometric object
by calling the method named GM01.Point3D.scale for each
point that makes up the object.

Tested using JDK 1.6 under WinXP.
*********************************************************/
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;

class GM01test06{
  public static void main(String[] args){
    GUI guiObj = new GUI();
  }//end main
}//end controlling class GM01test06
//======================================================//

class GUI extends JFrame{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 400;
  int vSize = 400;
  Image osi;//an off-screen image
  int osiWidth;//off-screen image width
  int osiHeight;//off-screen image height
  MyCanvas myCanvas;//a subclass of Canvas

  GUI(){//constructor
    //Set JFrame size, title, and close operation.
    setSize(hSize,vSize);
    setTitle("Copyright 2008,R.G.Baldwin");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Create a new drawing canvas and add it to the
    // center of the JFrame.
    myCanvas = new MyCanvas();
    this.getContentPane().add(myCanvas);

    //This object must be visible before you can get an
    // off-screen image.  It must also be visible before
    // you can compute the size of the canvas.
    setVisible(true);
    osiWidth = myCanvas.getWidth();
    osiHeight = myCanvas.getHeight();

    //Create an off-screen image and get a graphics
    // context on it.
    osi = createImage(osiWidth,osiHeight);
    Graphics2D g2D = (Graphics2D)(osi.getGraphics());

    //Test many 3D library features using text.
    testUsingText();

    //Test many 3D library features using graphics.
    drawOffScreen(g2D);

    //Cause the overridden paint method belonging to
    // myCanvas to be executed.
    myCanvas.repaint();

  }//end constructor
  //----------------------------------------------------//

    //The purpose of this method is to test various
    // 3D aspects of the library using text.
  public void testUsingText(){
    System.out.println(
               "Test overridden toString of ColMatrix3D");
    GM01.ColMatrix3D tempA = 
                        new GM01.ColMatrix3D(1.5,2.5,3.5);
    System.out.println(tempA);
    System.out.println();

    System.out.println(
               "Test setData and getData of ColMatrix3D");
    tempA.setData(0,4.5);
    System.out.println(tempA.getData(0));
    tempA.setData(1,5.5);
    System.out.println(tempA.getData(1));
    tempA.setData(2,6.5);
    System.out.println(tempA.getData(2));
    System.out.println();

    System.out.println(
                     "Test equals method of ColMatrix3D");
    GM01.ColMatrix3D tempB = 
                        new GM01.ColMatrix3D(1.5,2.5,3.5);
    System.out.println(tempA.equals(tempB));
    tempB.setData(0,4.5);
    tempB.setData(1,5.5);
    tempB.setData(2,6.5);
    System.out.println(tempA.equals(tempB));
    System.out.println();

    System.out.println("Test add method of ColMatrix3D");
    System.out.println(tempA.add(tempB));
    System.out.println();

    System.out.println(
                   "Test subtract method of ColMatrix3D");
    System.out.println(tempA.subtract(tempB));
    System.out.println();

    System.out.println("Test toString method of Point3D");
    GM01.Point3D pointA = new GM01.Point3D(tempA);
    System.out.println(pointA);
    System.out.println();

    System.out.println(
                   "Test setData and getData of Point3D");
    pointA.setData(0,1.1);
    System.out.println(pointA.getData(0));
    pointA.setData(1,2.2);
    System.out.println(pointA.getData(1));
    pointA.setData(2,3.3);
    System.out.println(pointA.getData(2));
    System.out.println();

    System.out.println("Test getColMatrix of Point3D");
    System.out.println(pointA.getColMatrix());
    System.out.println();

    System.out.println("Test equals method of Point3D");
    GM01.Point3D pointB = new GM01.Point3D(tempB);
    System.out.println(pointA.equals(pointB));
    pointA = new GM01.Point3D(tempB);
    System.out.println(pointA.equals(pointB));
    System.out.println();

    //See the method named drawOffScreen for a test of
    // the draw method of the Point3D class.

    System.out.println(
          "Test getDisplacementVector method of Point3D");
    pointA = 
      new GM01.Point3D(new GM01.ColMatrix3D(1.5,2.5,3.5));
    System.out.println(pointA);
    System.out.println(pointB);
    System.out.println(
                    pointA.getDisplacementVector(pointB));
    System.out.println();

    System.out.println(
               "Test addVectorToPoint method of Point3D");
    System.out.println(pointA);
    System.out.println(pointA.addVectorToPoint(
                   new GM01.Vector3D(
                     new GM01.ColMatrix3D(5.5,6.5,7.5))));
    System.out.println();

    System.out.println(
                      "Test toString method of Vector3D");
    GM01.Vector3D vecA = new GM01.Vector3D(
                       new GM01.ColMatrix3D(1.5,2.5,3.5));
    System.out.println(vecA);
    System.out.println();

    System.out.println(
          "Test setData and getData methods of Vector3D");
    vecA.setData(0,4.5);
    System.out.println(vecA.getData(0));
    vecA.setData(1,5.5);
    System.out.println(vecA.getData(1));
    vecA.setData(2,6.5);
    System.out.println(vecA.getData(2));
    System.out.println();

    //See the method named drawOffScreen for a test of
    // the draw method of the Vector3D class.

    System.out.println(
                  "Test getColMatrix method of Vector3D");
    System.out.println(vecA.getColMatrix());
    System.out.println();

    System.out.println("Test equals method of Vector3D");
    GM01.Vector3D vecB = new GM01.Vector3D(
                       new GM01.ColMatrix3D(1.5,2.5,3.5));
    System.out.println(vecA.equals(vecB));
    vecB.setData(0,4.5);
    vecB.setData(1,5.5);
    vecB.setData(2,6.5);
    System.out.println(vecA.equals(vecB));

    System.out.println("Test add method of Vector3D");
    System.out.println(vecA);
    vecB = new GM01.Vector3D(
                      new GM01.ColMatrix3D(-1.5,2.5,3.5));
    System.out.println(vecB);
    System.out.println(vecA.add(vecB));
    System.out.println();

    System.out.println(
                     "Test getLength method of Vector3D");
    vecA = new GM01.Vector3D(
                       new GM01.ColMatrix3D(3.0,4.0,5.0));
    System.out.println(vecA);
    System.out.println(vecA.getLength());
    System.out.println();

    System.out.println("Test toString method of Line3D");
    GM01.Line3D lineA = new GM01.Line3D(pointA,pointB);
    System.out.println(lineA);
    System.out.println();

    System.out.println("Test setTail, setHead, getTail, "
                     + "\nand getHead methods of Line3D");
    lineA.setTail(pointB);
    lineA.setHead(pointA);
    System.out.println(lineA.getTail());
    System.out.println(lineA.getHead());
    System.out.println();
  }//end testUsingText
  //----------------------------------------------------//

  //The purpose of this method is to test various
  // 3D aspects of the library using graphics.
  void drawOffScreen(Graphics2D g2D){

    //Translate the origin on the off-screen
    // image and draw a pair of orthogonal axes on it.
    setCoordinateFrame(g2D);

    //Define eight points that define the corners of
    // a box in 3D that is centered on the origin.

    GM01.Point3D[] points = new GM01.Point3D[8];
    //Right side
    points[0] = 
         new GM01.Point3D(new GM01.ColMatrix3D(75,75,75));
    points[1] = 
        new GM01.Point3D(new GM01.ColMatrix3D(75,75,-75));
    points[2] = 
       new GM01.Point3D(new GM01.ColMatrix3D(75,-75,-75));
    points[3] = 
        new GM01.Point3D(new GM01.ColMatrix3D(75,-75,75));
    //Left side
    points[4] = 
        new GM01.Point3D(new GM01.ColMatrix3D(-75,75,75));
    points[5] = 
       new GM01.Point3D(new GM01.ColMatrix3D(-75,75,-75));
    points[6] = 
      new GM01.Point3D(new GM01.ColMatrix3D(-75,-75,-75));
    points[7] = 
       new GM01.Point3D(new GM01.ColMatrix3D(-75,-75,75));

    //Scale each of the points that define the corners of
    // the box.
    for(int cnt = 0;cnt < points.length;cnt++){
      points[cnt] = points[cnt].scale(
                     new GM01.ColMatrix3D(0.25,0.5,0.75));
    }//end for loop

    //Draw seven of the points in BLACK
    g2D.setColor(Color.BLACK);
    for(int cnt = 1;cnt < points.length;cnt++){
      points[cnt].draw(g2D);
    }//end for loop

    //Draw the right top front point in RED to identify
    // it.
    g2D.setColor(Color.RED);
    points[0].draw(g2D);
    g2D.setColor(Color.BLACK);

    //Draw lines that connect the points to define the
    // twelve edges of the box.
    //Right side
    new GM01.Line3D(points[0],points[1]).draw(g2D);
    new GM01.Line3D(points[1],points[2]).draw(g2D);
    new GM01.Line3D(points[2],points[3]).draw(g2D);
    new GM01.Line3D(points[3],points[0]).draw(g2D);

    //Left side
    new GM01.Line3D(points[4],points[5]).draw(g2D);
    new GM01.Line3D(points[5],points[6]).draw(g2D);
    new GM01.Line3D(points[6],points[7]).draw(g2D);
    new GM01.Line3D(points[7],points[4]).draw(g2D);

    //Front
    new GM01.Line3D(points[0],points[4]).draw(g2D);
    new GM01.Line3D(points[3],points[7]).draw(g2D);

    //Back
    new GM01.Line3D(points[1],points[5]).draw(g2D);
    new GM01.Line3D(points[2],points[6]).draw(g2D);

  }//end drawOffScreen
  //----------------------------------------------------//

  //This method is used to set the origin of the
  // off-screen image and to draw orthogonal 3D axes on
  // the off-screen image that intersect at the origin.
  // Points are drawn where the axes intersect the
  // surfaces of the box.
  private void setCoordinateFrame(Graphics2D g2D){

    //Translate the origin to the center.
    GM01.translate(g2D,0.5*osiWidth,-0.5*osiHeight);

    //Draw x-axis in RED. Scale the points before
    // drawing them so that they will identify the
    // locations where the axes intersect the surface
    // of the box.
    g2D.setColor(Color.RED);
    GM01.Point3D pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(-75,0,0));
    GM01.Point3D pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(75,0,0));
    pointA.scale(
            new GM01.ColMatrix3D(0.25,0.0,0.0)).draw(g2D);
    pointB.scale(
            new GM01.ColMatrix3D(0.25,0.0,0.0)).draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw y-axis in GREEN
    g2D.setColor(Color.GREEN);
    pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(0,-75,0));
    pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(0,75,0));
    pointA.scale(
             new GM01.ColMatrix3D(0.0,0.5,0.0)).draw(g2D);
    pointB.scale(
             new GM01.ColMatrix3D(0.0,0.5,0.0)).draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw z-axis in BLUE
    g2D.setColor(Color.BLUE);
    pointA = 
          new GM01.Point3D(new GM01.ColMatrix3D(0,0,-75));
    pointB = 
           new GM01.Point3D(new GM01.ColMatrix3D(0,0,75));
    pointA.scale(
            new GM01.ColMatrix3D(0.0,0.0,0.75)).draw(g2D);
    pointB.scale(
            new GM01.ColMatrix3D(0.0,0.0,0.75)).draw(g2D);
    new GM01.Line3D(pointA,pointB).draw(g2D);

  }//end setCoordinateFrame method
  //====================================================//


  //This is an inner class of the GUI class.
  class MyCanvas extends Canvas{
    //Override the paint() method. This method will be
    // called when the JFrame and the Canvas appear on the
    // screen or when the repaint method is called on the
    // Canvas object.
    //The purpose of this method is to display the
    // off-screen image on the screen.
    public void paint(Graphics g){
      g.drawImage(osi,0,0,this);
    }//end overridden paint()

  }//end inner class MyCanvas

}//end class GUI
//======================================================//

 

Listing 31. Source code for the program named StringArt02.

/*StringArt02.java
Copyright 2008, R.G.Baldwin
Revised 02/22/08

This is a 2D version of a string art program that supports
rotation in two dimensions.

This program produces a 2D string art image by connecting
various points that are equally spaced on the 
circumference of a circle.

Initially, the circle is centered on the origin. There are
six points on the circle connected by lines forming a 
hexagon, The lines that connect the points are different 
colors. The radius of the circle is 50 units. The points 
at the vertices of the hexagon are not drawn, but the 
lines that connect the vertices are drawn.

A GUI is provided that allows the user to specify the 
following items and click a Replot button to cause the 
drawing to change:

Number Points
Number Loops
Rotation angle (deg)
X anchor point
Y anchor point


Changing the number of points causes the number of 
vertices that describe the geometric object to change.

Changing the number of loops causes the number of lines
that are drawn to connect the vertices to change. For a
value of 1, each vertex is connected to the one next to
it. For a value of 2, additional lines are drawn 
connecting every other vertex. For a value of 3, 
additional lines are drawn connecting every third vertex,
etc.

The image can be rotated around an anchor point. Entering 
a non-zero value in the Rotation field causes the image 
to be rotated by the specified angle around the anchor 
point.

The anchor point is initially located at the origin, but 
the location of the anchor point can be changed by the 
user. If the anchor point is at the origin, the image is 
rotated around the origin. Otherwise, the image is rotated
around the point in 2D space specified by the anchor 
point. The anchor point is drawn in black.

The rotation angle is specified in degrees with a positive
angle being counter-clockwise.

The number of points is initially set to six and the 
number of loops is initially set to one.  Making the 
number of points larger and making the number of loops 
larger produces many interesting patterns.

Tested using JDK 1.6 under WinXP.
*********************************************************/
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import java.awt.event.*;

class StringArt02{
  public static void main(String[] args){
    GUI guiObj = new GUI();
  }//end main
}//end controlling class StringArt02
//======================================================//

class GUI extends JFrame implements ActionListener{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 300;
  int vSize = 470;
  Image osi;//an off-screen image
  int osiWidth;//off-screen image width
  int osiHeight;//off-screen image height
  MyCanvas myCanvas;//a subclass of Canvas 
  Graphics2D g2D;//off-screen graphics context.

  JTextField numberPointsField;//User input field.
  JTextField loopsField;//User input field.
  int numberPoints = 6;//Can be modified by the user.
  int loopLim = 1;//Can be modified by the user.

  JTextField rotationField;//User input field
  double rotation;//Rotation angle in degrees clockwise.

  JTextField xAnchorPointField;//User input field
  JTextField yAnchorPointField;//User input field
  double xAnchorPoint;//Rotation anchor point.
  double yAnchorPoint;//Rotation anchor point.

  //The following variable is used to refer to an array
  // object containing the points that define the
  // vertices of a geometric object.
  GM01.Point2D[] points;

  //----------------------------------------------------//

  GUI(){//constructor
    //Instantiate the array object that will be used to
    // store the points that define the vertices of the
    // geometric object.
    points = new GM01.Point2D[numberPoints];

    //Set JFrame size, title, and close operation.
    setSize(hSize,vSize);
    setTitle("Copyright 2008,R.G.Baldwin");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Instantiate the user input components.
    numberPointsField =new JTextField("6");
    loopsField = new JTextField("1");
    rotationField = new JTextField("0");
    xAnchorPointField = new JTextField("0");
    yAnchorPointField = new JTextField("0");
    JButton button = new JButton("Replot");

    //Instantiate a JPanel that will house the user input
    // components and set its layout manager.
    JPanel controlPanel = new JPanel();
    controlPanel.setLayout(new GridLayout(0,2));

    //Add the user input components and appropriate labels
    // to the control panel.
    controlPanel.add(new JLabel(" Number Points"));    
    controlPanel.add(numberPointsField);
    controlPanel.add(new JLabel(" Number Loops"));
    controlPanel.add(loopsField);
    controlPanel.add(new JLabel(
                               " Rotation angle (deg)"));
    controlPanel.add(rotationField);
    controlPanel.add(new JLabel(" X anchor point"));
    controlPanel.add(xAnchorPointField);
    controlPanel.add(new JLabel(" Y anchor point"));
    controlPanel.add(yAnchorPointField);
    controlPanel.add(button);

    //Add the control panel to the SOUTH position in the
    // JFrame.
    this.getContentPane().add(
                         BorderLayout.SOUTH,controlPanel);

    //Create a new drawing canvas and add it to the
    // CENTER of the JFrame above the control panel.
    myCanvas = new MyCanvas();
    this.getContentPane().add(
                            BorderLayout.CENTER,myCanvas);

    //This object must be visible before you can get an
    // off-screen image.  It must also be visible before
    // you can compute the size of the canvas.
    setVisible(true);

    //Make the size of the off-screen image match the
    // size of the canvas.
    osiWidth = myCanvas.getWidth();
    osiHeight = myCanvas.getHeight();

    //Create an off-screen image and get a graphics
    // context on it.
    osi = createImage(osiWidth,osiHeight);
    g2D = (Graphics2D)(osi.getGraphics());

    //Erase the off-screen image, establish the origin,
    // and draw the axes
    setCoordinateFrame(g2D,true);

    //Create the Point2D objects that define the geometric
    // object and manipulate them to produce the desired
    // results.
    drawOffScreen(g2D);

    //Register this object as an action listener on the
    // button.
    button.addActionListener(this);

    //Cause the overridden paint method belonging to
    // myCanvas to be executed.
    myCanvas.repaint();
    
  }//end constructor
  //----------------------------------------------------//

  //The purpose of this method is to Create the Point2D
  // objects that define the vertices of a geometric
  // object and manipulate them to produce the desired
  // results.
  void drawOffScreen(Graphics2D g2D){
    //Erase the off-screen image and draw new axes, but
    // don't move the origin.
    setCoordinateFrame(g2D,false);

    //Create a set of Point2D objects that specify
    // locations on the circumference of a circle that
    // is in the x-y plane with a radius of 50 units. Save
    // references to the Point2D objects in an array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM01.Point2D(new GM01.ColMatrix2D(
        50*Math.cos((cnt*360/numberPoints)*Math.PI/180),
        50*Math.sin((cnt*360/numberPoints)*Math.PI/180)));

      //The following object is populated with the 2D 
      // coordinates of the point around which the 
      //rotations will take place.
      GM01.Point2D anchorPoint = new GM01.Point2D(
                            new GM01.ColMatrix2D(
                              xAnchorPoint,yAnchorPoint));
      //Draw the anchorPoint in BLACK.
      g2D.setColor(Color.BLACK);
      anchorPoint.draw(g2D);

      //The following statement causes the rotation to be 
      //performed.
      points[cnt] = 
                 points[cnt].rotate(anchorPoint,rotation);

    }//end for loop

    //Implement the algorithm that draws lines connecting
    // points on the geometric object.
    GM01.Line2D line;

    //Begin the outer loop.
    for(int loop = 1;loop <= loopLim;loop++){
      //The following variable specifies the array
      // element containing a point on which a line will
      // start.
      int start = -1;

      //The following variable specifies the number of
      // points that will be skipped between the starting
      // point and the ending point for a line.
      int skip = loop;
      //The following logic causes the element number to
      // wrap around when it reaches the end of the
      // array.
      while(skip >= 2*numberPoints-1){
        skip -= numberPoints;
      }//end while loop

      //The following variable specifies the array
      // element containing a point on which a line will
      // end.
      int end = start + skip;

      //Begin inner loop. This loop actually constructs
      // the GM01.Line2D objects and causes visual
      // manifestations of those objects to be drawn on
      // the off-screen image. Note the requirement to
      // wrap around when the element numbers exceed the
      // length of the array.
      for(int cnt = 0;cnt < numberPoints;cnt++){
        if(start < numberPoints-1){
          start++;
        }else{
          //Wrap around
          start -= (numberPoints-1);
        }//end else

        if(end < numberPoints-1){
          end++;
        }else{
          //Wrap around.
          end -= (numberPoints-1);
        }//end else

        //Create some interesting colors.
        g2D.setColor(new Color(cnt*255/numberPoints,
                               127+cnt*64/numberPoints,
                               255-cnt*255/numberPoints));

        //Create a line that connects points on the
        // geometric object.
        line = new GM01.Line2D(points[start],points[end]);
        line.draw(g2D);
      }//end inner loop
    }//end outer loop

  }//end drawOffScreen
  //----------------------------------------------------//

  //This method is used to set the origin of the
  // off-screen image to the center and to draw orthogonal
  // 2D axes on the image that intersect at the origin.
  //The second parameter is used to determine if the
  // origin should be translated to the center.
  private void setCoordinateFrame(
                        Graphics2D g2D,boolean translate){

    //Translate the origin to the center if translate is
    // true.
    if(translate){
      GM01.translate(g2D,0.5*osiWidth,-0.5*osiHeight);
    }//end if

    //Erase the screen
    g2D.setColor(Color.WHITE);
    GM01.fillRect(g2D,-osiWidth/2,osiHeight/2,
                                      osiWidth,osiHeight);

    //Draw x-axis in RED
    g2D.setColor(Color.RED);
    GM01.Point2D pointA = new GM01.Point2D(
                   new GM01.ColMatrix2D(-osiWidth/2,0));
    GM01.Point2D pointB = new GM01.Point2D(
                    new GM01.ColMatrix2D(osiWidth/2,0));
    new GM01.Line2D(pointA,pointB).draw(g2D);

    //Draw y-axis in GREEN
    g2D.setColor(Color.GREEN);
    pointA = new GM01.Point2D(
                  new GM01.ColMatrix2D(0,-osiHeight/2));
    pointB = new GM01.Point2D(
                   new GM01.ColMatrix2D(0,osiHeight/2));
    new GM01.Line2D(pointA,pointB).draw(g2D);

  }//end setCoordinateFrame method
  //----------------------------------------------------//

  //This method is called to respond to a click on the
  // button.
  public void actionPerformed(ActionEvent e){
    //Get user input values and use them to modify several
    // values that control the drawing.
    numberPoints = Integer.parseInt(
                             numberPointsField.getText());

    loopLim = Integer.parseInt(loopsField.getText());

    //Rotation angle in degrees.
    rotation = 
             Double.parseDouble(rotationField.getText());

    //Rotation anchor points
    xAnchorPoint = 
          Double.parseDouble(xAnchorPointField.getText());
    yAnchorPoint = 
          Double.parseDouble(yAnchorPointField.getText());

    //Instantiate a new array object with a length
    // that matches the new value for numberPoints.    
    points = new GM01.Point2D[numberPoints];

    //Draw a new off-screen image based on user inputs.
    drawOffScreen(g2D);
    myCanvas.repaint();//Copy off-screen image to canvas.
  }//end actionPerformed
  //====================================================//


  //This is an inner class of the GUI class.
  class MyCanvas extends Canvas{
    //Override the paint() method. This method will be
    // called when the JFrame and the Canvas appear on the
    // screen or when the repaint method is called on the
    // Canvas object.
    //The purpose of this method is to display the
    // off-screen image on the screen.
    public void paint(Graphics g){
      g.drawImage(osi,0,0,this);
    }//end overridden paint()

  }//end inner class MyCanvas

}//end class GUI
//======================================================//

 

Listing 32. Source code for the program named StringArt03.

/*StringArt03.java
Copyright 2008, R.G.Baldwin
Revised 02/22/08

This is a 3D version of a string art program that supports
rotation in three dimensions.

This program produces a 3D string art image by connecting
various points that are equally spaced on the 
circumference of a circle.

Initially, the circle is on the x-y plane centered on the 
origin. There are six points on the circle connected by
lines forming a hexagon, The lines that connect the points
are different colors. The radius of the circle is 50 
units. The points at the vertices of the hexagon are not 
drawn, but the lines that connect the vertices are drawn.

A GUI is provided that allows the user to specify the 
following items and click a Replot button to cause the 
drawing to change:

Number Points
Number Loops
Rotate around Z (deg)
Rotate around X (deg)
Rotate around Y (deg)
X Anchor point
Y Anchor point
Z Anchor point

Changing the number of points causes the number of 
vertices that describe the geometric object to change.

Changing the number of loops causes the number of lines
that are drawn to connect the vertices to change. For a
value of 1, each vertex is connected to the one next to
it. For a value of 2, additional lines are drawn 
connecting every other vertex. For a value of 3, 
additional lines are drawn connecting every third vertex,
etc.

The image can be rotated in any or all of three 
dimensions around an anchor point. Entering a non-zero 
value in one or more of the Rotate fields causes the 
image to be rotated by the specified angle or angles 
around the anchor point. The anchor point is initially
specified to be at the origin, but the location of the
anchor point can be changed by the user. If the anchor 
point is at the origin, the image is rotated around the 
origin. Otherwise, the image is rotated around the point
in 3D space specified by the anchor point. The anchor 
point is drawn in black.

The rotation angle is specified in degrees with a positive
angle being given by the right-hand rule as applied to the
axis around which the image is being rotated.

The rotational effects are cumulative. The image is first 
rotated around the anchor point in a direction consistent 
with rotation around the z-axis (rotation in the x-y 
plane). Then that rotated image is rotated in a direction 
consistent with rotation around the x-axis (rotation in 
the y-z plane). Finally, the previously rotated image is 
rotated in a direction consistent with rotation around the
y-axis (rotation in the x-z plane). It is important
to note, however, that the actual rotation is around the 
anchor point and not around the origin unless the anchor
point is at the origin.

The number of points is initially set to six and the 
number of loops is initially set to one.  Making the 
number of points larger and making the number of loops 
larger produces many interesting patterns.

Tested using JDK 1.6 under WinXP.
*********************************************************/
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import java.awt.event.*;

class StringArt03{
  public static void main(String[] args){
    GUI guiObj = new GUI();
  }//end main
}//end controlling class StringArt03
//======================================================//

class GUI extends JFrame implements ActionListener{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 300;
  int vSize = 470;
  Image osi;//an off-screen image
  int osiWidth;//off-screen image width
  int osiHeight;//off-screen image height
  MyCanvas myCanvas;//a subclass of Canvas 
  Graphics2D g2D;//Off-screen graphics context.

  JTextField numberPointsField;//User input field.
  JTextField loopsField;//User input field.
  int numberPoints = 6;//Can be modified by the user.
  int loopLim = 1;//Can be modified by the user.

  JTextField zRotationField;//User input field
  JTextField xRotationField;//User input field
  JTextField yRotationField;//User input field
  double zRotation;//Rotation around z in degrees.
  double xRotation;//Rotation around x in degrees.
  double yRotation;//Rotation around y in degrees

  JTextField xAnchorPointField;//User input field
  JTextField yAnchorPointField;//User input field
  JTextField zAnchorPointField;//User input field
  double xAnchorPoint;//Rotation anchor point.
  double yAnchorPoint;//Rotation anchor point.
  double zAnchorPoint;//Rotation anchor point.


  //The following variable is used to refer to an array
  // object containing the points that define the
  // vertices of a geometric object.
  GM01.Point3D[] points;

  //----------------------------------------------------//

  GUI(){//constructor
    //Instantiate the array object that will be used to
    // store the points that define the vertices of the
    // geometric object.
    points = new GM01.Point3D[numberPoints];

    //Set JFrame size, title, and close operation.
    setSize(hSize,vSize);
    setTitle("Copyright 2008,R.G.Baldwin");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Instantiate the user input components.
    numberPointsField =new JTextField("6");
    loopsField = new JTextField("1");
    zRotationField = new JTextField("0");
    xRotationField = new JTextField("0");
    yRotationField = new JTextField("0");
    xAnchorPointField = new JTextField("0");
    yAnchorPointField = new JTextField("0");
    zAnchorPointField = new JTextField("0");
    JButton button = new JButton("Replot");

    //Instantiate a JPanel that will house the user input
    // components and set its layout manager.
    JPanel controlPanel = new JPanel();
    controlPanel.setLayout(new GridLayout(0,2));

    //Add the user input components and appropriate labels
    // to the control panel.
    controlPanel.add(new JLabel(" Number Points"));    
    controlPanel.add(numberPointsField);
    controlPanel.add(new JLabel(" Number Loops"));
    controlPanel.add(loopsField);
    controlPanel.add(new JLabel(
                               " Rotate around Z (deg)"));
    controlPanel.add(zRotationField);
    controlPanel.add(new JLabel(
                               " Rotate around X (deg)"));
    controlPanel.add(xRotationField);
    controlPanel.add(new JLabel(
                               " Rotate around Y (deg)"));
    controlPanel.add(yRotationField);
    controlPanel.add(new JLabel(" X anchor point"));
    controlPanel.add(xAnchorPointField);
    controlPanel.add(new JLabel(" Y anchor point"));
    controlPanel.add(yAnchorPointField);
    controlPanel.add(new JLabel(" Z anchor point"));
    controlPanel.add(zAnchorPointField);
    controlPanel.add(button);

    //Add the control panel to the SOUTH position in the
    // JFrame.
    this.getContentPane().add(
                         BorderLayout.SOUTH,controlPanel);

    //Create a new drawing canvas and add it to the
    // CENTER of the JFrame above the control panel.
    myCanvas = new MyCanvas();
    this.getContentPane().add(
                            BorderLayout.CENTER,myCanvas);

    //This object must be visible before you can get an
    // off-screen image.  It must also be visible before
    // you can compute the size of the canvas.
    setVisible(true);

    //Make the size of the off-screen image match the
    // size of the canvas.
    osiWidth = myCanvas.getWidth();
    osiHeight = myCanvas.getHeight();

    //Create an off-screen image and get a graphics
    // context on it.
    osi = createImage(osiWidth,osiHeight);
    g2D = (Graphics2D)(osi.getGraphics());

    //Erase the off-screen image, establish the origin,
    // and draw the axes
    setCoordinateFrame(g2D,true);

    //Create the Point3D objects that define the geometric
    // object and manipulate them to produce the desired
    // results.
    drawOffScreen(g2D);

    //Register this object as an action listener on the
    // button.
    button.addActionListener(this);

    //Cause the overridden paint method belonging to
    // myCanvas to be executed.
    myCanvas.repaint();

  }//end constructor
  //----------------------------------------------------//
  
  //The purpose of this method is to Create the Point3D
  // objects that define the vertices of a geometric
  // object and manipulate them to produce the desired
  // results.
  void drawOffScreen(Graphics2D g2D){
    //Erase the off-screen image and draw new axes, but
    // don't move the origin.
    setCoordinateFrame(g2D,false);


    //Create a set of Point3D objects that specify
    // locations on the circumference of a circle that
    // is in the x-y plane with a radius of 50 units. Save
    // references to the Point3D objects in an array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM01.Point3D(
        new GM01.ColMatrix3D(
          50*Math.cos((cnt*360/numberPoints)*Math.PI/180),
          50*Math.sin((cnt*360/numberPoints)*Math.PI/180),
          0.0));

      //The following ColMatrix3D object must be populated
      // with three rotation angles in degrees that
      // specify the following rotational angles in order
      // according to the right-hand rule as applied to 
      // the indicated axis.
      // Rotate around z
      // Rotate around x
      // Rotate around y
      GM01.ColMatrix3D angles = new GM01.ColMatrix3D(
                           zRotation,xRotation,yRotation);

      //The following object contains the 3D coordinates
      // of the point around which the rotations will
      // take place.
      GM01.Point3D anchorPoint = new GM01.Point3D(
               new GM01.ColMatrix3D(
                 xAnchorPoint,yAnchorPoint,zAnchorPoint));
      //Draw the anchorPoint in BLACK.
      g2D.setColor(Color.BLACK);
      anchorPoint.draw(g2D);

      //The following statement causes the rotation to be 
      //performed.
      points[cnt] = 
                   points[cnt].rotate(anchorPoint,angles);

    }//end for loop

    //Implement the algorithm that draws lines connecting
    // points on the geometric object.
    GM01.Line3D line;

    //Begin the outer loop.
    for(int loop = 1;loop <= loopLim;loop++){
      //The following variable specifies the array
      // element containing a point on which a line will
      // start.
      int start = -1;

      //The following variable specifies the number of
      // points that will be skipped between the starting
      // point and the ending point for a line.
      int skip = loop;
      //The following logic causes the element number to
      // wrap around when it reaches the end of the
      // array.
      while(skip >:= 2*numberPoints-1){
        skip -= numberPoints;
      }//end while loop

      //The following variable specifies the array
      // element containing a point on which a line will
      // end.
      int end = start + skip;

      //Begin inner loop. This loop actually constructs
      // the GM01.Line3D objects and causes visual
      // manifestations of those objects to be drawn on
      // the off-screen image. Note the requirement to
      // wrap around when the element numbers exceed the
      // length of the array.
      for(int cnt = 0;cnt < numberPoints;cnt++){
        if(start < numberPoints-1){
          start++;
        }else{
          //Wrap around
          start -= (numberPoints-1);
        }//end else

        if(end < numberPoints-1){
          end++;
        }else{
          //Wrap around.
          end -= (numberPoints-1);
        }//end else

        //Create some interesting colors.
        g2D.setColor(new Color(cnt*255/numberPoints,
                               127+cnt*64/numberPoints,
                               255-cnt*255/numberPoints));

        //Create a line that connects points on the
        // geometric object.
        line = new GM01.Line3D(points[start],points[end]);
        line.draw(g2D);
      }//end inner loop
    }//end outer loop

  }//end drawOffScreen
  //----------------------------------------------------//

  //This method is used to set the origin of the
  // off-screen image to the center and to draw orthogonal
  // 3D axes on the image that intersect at the origin.
  //The second parameter is used to determine if the
  // origin should be translated to the center.
  private void setCoordinateFrame(
                        Graphics2D g2D,boolean translate){

    //Translate the origin to the center if translate is
    // true.
    if(translate){
      GM01.translate(g2D,0.5*osiWidth,-0.5*osiHeight);
    }//end if

    //Erase the screen
    g2D.setColor(Color.WHITE);
    GM01.fillRect(g2D,-osiWidth/2,osiHeight/2,
                                      osiWidth,osiHeight);

    //Draw x-axis in RED
    g2D.setColor(Color.RED);
    GM01.Point3D pointA = new GM01.Point3D(
                   new GM01.ColMatrix3D(-osiWidth/2,0,0));
    GM01.Point3D pointB = new GM01.Point3D(
                    new GM01.ColMatrix3D(osiWidth/2,0,0));
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw y-axis in GREEN
    g2D.setColor(Color.GREEN);
    pointA = new GM01.Point3D(
                  new GM01.ColMatrix3D(0,-osiHeight/2,0));
    pointB = new GM01.Point3D(
                   new GM01.ColMatrix3D(0,osiHeight/2,0));
    new GM01.Line3D(pointA,pointB).draw(g2D);

    //Draw z-axis in BLUE. Make its length the same as the
    // length of the x-axis.
    g2D.setColor(Color.BLUE);
    pointA = new GM01.Point3D(
                   new GM01.ColMatrix3D(0,0,-osiWidth/2));
    pointB = new GM01.Point3D(
                    new GM01.ColMatrix3D(0,0,osiWidth/2));
    new GM01.Line3D(pointA,pointB).draw(g2D);

  }//end setCoordinateFrame method
  //----------------------------------------------------//

  //This method is called to respond to a click on the
  // button.
  public void actionPerformed(ActionEvent e){
    //Get user input values and use them to modify several
    // values that control the drawing.
    numberPoints = Integer.parseInt(
                             numberPointsField.getText());

    loopLim = Integer.parseInt(loopsField.getText());

    //Rotation around z in degrees.
    zRotation = 
             Double.parseDouble(zRotationField.getText());
    //Rotation around x in degrees.
    xRotation = 
             Double.parseDouble(xRotationField.getText());
    //Rotation around y in degrees
    yRotation = 
             Double.parseDouble(yRotationField.getText());

    //Rotation anchor points
    xAnchorPoint = 
          Double.parseDouble(xAnchorPointField.getText());
    yAnchorPoint = 
          Double.parseDouble(yAnchorPointField.getText());
    zAnchorPoint = 
          Double.parseDouble(zAnchorPointField.getText());

    //Instantiate a new array object with a length
    // that matches the new value for numberPoints.
    points = new GM01.Point3D[numberPoints];

    //Draw a new off-screen image based on user inputs.
    drawOffScreen(g2D);
    myCanvas.repaint();//Copy off-screen image to canvas.
  }//end actionPerformed
  //====================================================//


  //This is an inner class of the GUI class.
  class MyCanvas extends Canvas{
    //Override the paint() method. This method will be
    // called when the JFrame and the Canvas appear on the
    // screen or when the repaint method is called on the
    // Canvas object.
    //The purpose of this method is to display the
    // off-screen image on the screen.
    public void paint(Graphics g){
      g.drawImage(osi,0,0,this);
    }//end overridden paint()
    
  }//end inner class MyCanvas

}//end class GUI
//======================================================//

 


Copyright

Copyright 2008, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

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.

Baldwin@DickBaldwin.com






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel