July 31, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Putting the Game-Math Library to Work: Math for Java Game Programmers

  • July 1, 2008
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Java Programming Notes # 1708


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, Vector Addition (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.

The Kjell tutorials

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-programming 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.

What you have learned

In the previous lesson, you learned:

  • How to add two or more vectors
  • About the head-to-tail rule in vector addition
  • About the vector addition parallelogram
  • About the relationship between the length of the sum of two or more vectors and the lengths of the individual vectors in the sum
  • How to add a vector to a point
  • How to get the length of a vector
  • How to represent an object in different coordinate frames

What you will learn

In this lesson we will put the game-math library to work. I will provide and explain four sample programs. The first program will teach you how to translate a geometric object in two dimensions. The second program will teach you how to accomplish the same thing but in a possibly more efficient manner. The third program will use the library to produce animation in two dimensions. (A future lesson will produce translation and animation in three dimensions.) The fourth program will teach you how to use methods of the game-math library to produce relatively complex drawings.

I also recommend that you concurrently study the Kjell tutorial through Chapter 5 - Vector Direction (see Resources).

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. Translation of black hexagon to location of red hexagon.
  • Figure 2. Screen output for geometric object with 50 vertices.
  • Figure 3. Screen output without drawing the points at the vertices.
  • Figure 4. Starting point for the hexagon in VectorAdd06.
  • Figure 5. Possible ending point for the hexagon in VectorAdd06.
  • Figure 6. String art with 15 vertices and 7 loops.
  • Figure 7. String art with 25 vertices and 11 loops.
  • Figure 8. String art with 100 vertices and 100 loops.
  • Figure 9. Output from StringArt01 at startup.

Listings

  • Listing 1. Instance variables in the class named GUI.
  • Listing 2. Abbreviated constructor for the GUI class.
  • Listing 3. Beginning of the drawOffScreen method.
  • Listing 4. To draw or not to draw the lines.
  • Listing 5. Change the drawing color to RED.
  • Listing 6. Translate the geometric object.
  • Listing 7. Draw the lines if drawLines is true.
  • Listing 8. The actionPerformed method.
  • Listing 9. Abbreviated listing of the drawOffScreen method.
  • Listing 10. The class named MyCanvas, the update method, and the paint method.
  • Listing 11. Abbreviated listing of actionPerformed method.
  • Listing 12. The inner Thread class named Animate.
  • Listing 13. Do the animated move.
  • Listing 14. Beginning of the drawOffScreen method in StringArt01.
  • Listing 15. Implement the algorithm that draws the lines.
  • Listing 16. Draw the lines.
  • Listing 17. Source code for the game-math library named GM2D04.
  • Listing 18. Source code for the sample program named VectorAdd05.
  • Listing 19. Source code for the program named VectorAdd05a.
  • Listing 20. Source code for the program named VectorAdd06.
  • Listing 21. Source code for the program named StringArt01.

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.

Preview

In this lesson, I will present and explain four programs that use the game-math library named GM2D04. The purpose of the first program named VectorAdd05 is 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.

The purpose of the program named VectorAdd05a is to accomplish the same translation operation, but in a possibly more efficient manner.

The purpose of the program named VectorAdd06 is to teach you how to do rudimentary animation using the game-math library.

The purpose of the program named StringArt01 is to teach you how to use methods of the game-math library to produce relatively complex drawings.

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

Discussion and sample code

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

The game-math library named GM2D04

The game-math library that we are developing in this series of lessons has undergone several updates since its inception in the first lesson. The version of the library that existed at the end of the previous lesson (see Resources) was named GM2D04. No revisions have been made to the library since that lesson and we won't be making any revisions during this lesson. The source code for the library named GM2D04 is provided for your convenience in Listing 17 near the end of the lesson.

The program named VectorAdd05

The main purpose of this program is to use the addVectorToPoint method of the GM2D04.Point class in the game-math library to translate a geometric object from one location in space to a different location in space. This is illustrated in Figure 1, which shows one hexagon (shown in black) having been translated by 50 units in both the x and y directions and colored red at the new location.

Figure 1. Translation of black hexagon to location of red hexagon.

Various other game-math library methods are also used

Along the way, the program uses various other methods of the classes in the game-math library named GM2D04 to accomplish its purpose.

The program initially constructs and draws a black hexagon centered on the origin as shown in Figure 1. The six points that define the vertices of the hexagon lie on a circle with a radius of 50 units. The points at the vertices and the lines that connect the points are drawn. In addition, the program initially causes the hexagon to be translated by 50 units in the positive X direction and 50 units in the positive Y. The translated hexagon is drawn in red. The original black hexagon is not erased when the translated version is drawn in red.

A graphical user interface (GUI)

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 of points to define the geometric object.
  • X-component of the displacement vector.
  • Y-component of the displacement vector.
  • A checkbox to specify whether points are to be drawn.
  • A checkbox to specify whether lines are to be drawn.

These user-input features are shown at the bottom of Figure 1.

Changing the number of vertices

Changing the number of points causes the number of vertices that describe the geometric object to change.  For a large number of points, the geometric object becomes a circle as shown in Figure 2.

Figure 2. Screen output for geometric object with 50 vertices.

For only three points, the geometric object would become a triangle. For four points, it would become a rectangle.  For two points, it would become a line, etc.

The positive y-axis
The positive direction for the y-axis is down in the figures in this lesson. This is the default for Java graphics. I will resolve that issue and cause the positive direction for the y-axis to be up instead of down in a future lesson.

Translation

Changing the components of the displacement vector causes the geometric object to be translated to a different location before being drawn in red. In addition to increasing the number of vertices, Figure 2 also shows the result of translating by 30 units along the x-axis and -40 units along the y-axis.

Checking and un-checking the checkboxes

Figure 3 shows the result of un-checking one of the checkboxes to prevent the points that define the vertices from being drawn. In this case, only the lines that connect the vertices were drawn, resulting in the two circles shown.

Figure 3. Screen output without drawing the points at the vertices.

Similarly, the GUI can be used to cause only the points that define the vertices to be drawn without the connecting lines.

Will explain the code in fragments

I will explain the code in this program in fragments. A complete listing of the program is provided in Listing 18 for your convenience.

Much of the code in this lesson is very similar to code that I have explained in earlier lessons, so I won't bore you by repeating those explanations. Rather, I will concentrate on code that is new and different in this lesson.

Beginning of the class named GUI

The beginning of the class named GUI is shown in Listing 1.

Listing 1. Instance variables in the class named GUI.

class GUI extends JFrame implements ActionListener{
  //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 
  Graphics2D g2D;//Off-screen graphics context.

  //The following two variables are used to establish the
  // location of the origin.
  double xAxisOffset;
  double yAxisOffset;

  int numberPoints = 6;//Can be modified by the user.
  JTextField numberPointsField; //User input field.
  //The commponents of the following displacement vector
  // can be modified by the user.
  GM2D04.Vector vector = 
           new GM2D04.Vector(new GM2D04.ColMatrix(50,50));
  JTextField vectorX;//User input field.
  JTextField vectorY;//User input field.

  //The following variables are used to determine whether
  // to draw the points and/or the lines.
  boolean drawPoints = true;
  boolean drawLines = true;
  Checkbox drawPointsBox;//User input field
  Checkbox drawLinesBox;//User input field.

  //The following variables are used to refer to array
  // objects containing the points that define the
  // vertices of the geometric object.
  GM2D04.Point[] points;
  GM2D04.Point[] newPoints;

The code in Listing 1 declares a large number of instance variables, initializing some of them. Those variables shouldn't require an explanation beyond the embedded comments. I show them here solely to make it easy for you to refer to them later when I discuss them.

Extends JFrame and implements ActionListener

This class extends the JFrame class and implements the ActionListener interface. As you will see later, implementing the ActionListener interface requires that the class contains a concrete definition of the actionPerformed method. It also makes an object of the GUI class eligible for being registered as a listener object on the Replot button shown in Figure 1.

Abbreviated constructor for the GUI class

An Abbreviated listing of the constructor for the GUI class is shown in Listing 2. Much of the code was deleted from Listing 2 for brevity. You can view the code that was deleted in Listing 18.

Listing 2. Abbreviated constructor for the GUI class.

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

//Code that creates the user interface was deleted
// for brevity.

//Code dealing with the canvas and the off-screen
// was deleted for brevity.


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

    //Create the Point 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

Arrays for storage of vertices

The constructor begins by instantiating two array objects that will be used to store references to the GM2D04.Point objects that define the vertices of the geometric object.

Set the coordinate frame

Later on, the constructor calls the method named setCoordinateFrame to erase the off-screen image, draw orthogonal axes on it, and translate the origin to a new location. The code in the setCoordinateFrame method is straightforward and shouldn't require an explanation beyond the embedded comments. You can view that code in Listing 18.

The fourth parameter to the setCoordinateFrame method is used to determine whether or not to translate the origin by the amount given by the second and third parameters.; If true, the origin is translated. If false, the origin is not translated.

The method named drawOffScreen

After setting the coordinate frame, Listing 2 calls the method named drawOffScreen. I will be discussing that method in some detail shortly.

Register an ActionListener object

Next, the code in Listing 2 registers the object of the GUI class as an action listener on the Replot button shown in Figure 1. This auses the method named actionPerformed to be executed whenever the user clicks the Replot button. I will also discuss the actionPerformed method shortly.

Repaint the canvas

Finally, Listing 2 calls the repaint method to repaint the canvas.; If you have studied the previous lessons in this series (seeResources), you should know what this does and an explanation should not be necessary.

Beginning of the drawOffScreen method

Listing 3 shows the beginning of the method named drawOffScreen. The purpose of this method is to create the GM2D04.Point objects that define the vertices of the geometric object and to manipulate those points to produce the desired results.

Listing 3. Beginning of the drawOffScreen method.

  void drawOffScreen(Graphics2D g2D){
    //Erase the off-screen image and draw new axes, but
    // don't change the coordinate frame.
    setCoordinateFrame(g2D,xAxisOffset,yAxisOffset,false);

    //Create a set of Point objects that specify
    // locations on the circumference of a circle and
    // save references to the Point objects in an array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM2D04.Point(
        new GM2D04.ColMatrix(
                        50*Math.cos((cnt*360/numberPoints)
                                          *Math.PI/180),
                        50*Math.sin((cnt*360/numberPoints)
                                          *Math.PI/180)));
      if(drawPoints){//Draw points if true
        points[cnt].draw(g2D);
      }//end if
    }//end for loop

Erase the image and draw the axes

Listing 3 begins by calling the setCoordinateFrame method to erase the off-screen image and draw orthogonal axes, intersecting at the origin on the erased image. Note however that unlike the call to the setCoordinateFrame method that was made in the constructor in Listing 2, the call to the method in Listing 3 passes false as the fourth parameter, thereby preventing the location of the origin from being modified.

Create the points that define the vertices

Then Listing 3 executes a for loop that instantiates the set of GM2D04.Point objects that define the vertices of the geometric object and saves references to those objects in the array object that was instantiated in Listing 2. Note that during the first pass through the constructor and the method named drawOffScreen, the length of the array object and the number of points instantiated are both specified by the initial value (6) of the instance variable named numberPoints (see Listing 1).

Some knowledge of trigonometry is required

As I told you in the first lesson in this series (see Resources) "I will assume that you either already have, or can gain the required skills in geometry and trigonometry on your own." In order to understand the code in the for loop in Listing 3, you must have at least a rudimentary knowledge of trigonometry.

For now, suffice it to say that this code will instantiate a set of GM2D04.Point objects equally spaced around the circumference of a circle with a radius of 50 units centered on the origin. (When rendered on the off-screen image, these units will be translated to pixels.) For the initial value of six points, the first point will be located at an angle of zero degrees relative to the horizontal, and each of the remaining five points will located on the circumference of the circle at an angle that is an even multiple of 360/6 or 60 degrees.

To draw or not to draw the points

Recall that a point has no width, no height, and no depth and is therefore not visible to the human eye. However, when you call the draw method on an object of the GM2D04.Point class, a small circle is drawn around the location of the point marking that location for human consumption.

The default drawing color
The default drawing color is BLACK. When the points are drawn the first time the drawOffScreen method is called, they will be drawn in BLACK, which is a public static final variable in the Color class.

An if statement embedded in the for loop in Listing 3 tests the value of the boolean instance variable named drawPoints (see Listing 1, which initializes the value to true) to determine whether or not to draw the circle marking the location of each point as it is instantiated and saved in the array object. If true, the circle is drawn as shown in Figure 1. If false, the circle is not drawn as shown in Figure 3. As you will see later, the user can modify the value of the variable named drawPoints using one of the checkboxes and the Replot button in Figure 1.

To draw or not to draw the lines

Listing 4 tests the value of the instance variable named drawLines to determine whether or not to draw lines connecting each of the points. This variable is also initialized to true in Listing 1, and its value can be modified by the user later using one of the checkboxes and the Replot button shown in Figure 1.

Listing 4. To draw or not to draw the lines.

    GM2D04.Line line;
    if(drawLines){//Instantiate and draw lines if true.
      for(int cnt = 0;cnt < numberPoints-1;cnt++){
        //Begin by drawing all of the lines but one.
        line = new GM2D04.Line(points[cnt],points[cnt+1]);
          line.draw(g2D);
      }//end for loop
      //Draw the remaining line required to close the
      // polygon.
      line = new GM2D04.Line(points[numberPoints-1],
                             points[0]);
      line.draw(g2D);
    }//end if

Slightly more complicated

Drawing the lines is only slightly more complicated than drawing the points. A for loop is used in Listing 4 to draw lines connecting successive pairs of points whose references were stored in the array named points in Listing 3. This takes care of all of the required lines but one. Following the for loop, one additional statement is executed to draw a line connecting the points whose references are stored in the first and the last elements in the array. (I will show you another way to accomplish this wraparound in the program named StringArt01 later in this lesson.)

No need to save the GM2D04.Line objects

Note that the GM2D04.Line object that is used to draw each connecting line has no value in this program after the line is drawn on the off-screen image. Therefore, there is no need to consume memory by saving a large number of such objects. A single reference variable of the class GM2D04.Line is used to refer to all the objects of the GM2D04.Line class that are used to draw the connecting lines. As each new object of that class is instantiated, the object previously referred to by the reference variable becomes eligible for garbage collection. (Go to Google and search for the following keywords to learn more about this topic: baldwin java eligible garbage collection.)

Change the drawing color to RED

After the BLACK geometric object shown in Figure 1 has been drawn on the off-screen image, it is time to change the drawing color to RED in preparation for translating the object and drawing the translated version of the object. This is accomplished by the single statement in Listing 5.

Listing 5. Change the drawing color to RED.

    g2D.setColor(Color.RED);//Change drawing color to RED.

Translate the geometric object

That brings us to the most important block of code in the entire program, insofar as achieving the main learning objective of the program is concerned.

Listing 6. Translate the geometric object.

    for(int cnt = 0;cnt < numberPoints;cnt++){
      newPoints[cnt] = 
                     points[cnt].addVectorToPoint(vector);
      if(drawPoints){//Draw points if true.
        newPoints[cnt].draw(g2D);
      }//end if
    }//end for loop

Listing 6 uses a call to the addVectorToPoint method of the GM2D04.Point class embedded in a for loop to translate each of the vertices (points) that define the original geometric object to a second set of vertices that define a geometric object having the same shape in a different location. The new vertices were saved in a different array object referred to by the reference variable named newPoints. It is important to note that in this case, the original geometric object was not moved. Rather, it was replicated in a new location with the new location being defined by a displacement vector added to the value of each original point.

Make it appear to move

In many cases in game programming, you will want to make it appear that the object has actually moved instead of being replicated. That appearance could be achieved by saving the reference to the new GM2D04.Point object back into the same array, thereby replacing the reference that was previously there. Make no mistake about it, however, when using this approach, the translated geometric object is a different object, defined by a new set of GM2D04.Point objects that define the vertices of the geometric object in the new location.

A different approach

A different approach would be to call the setData method of the GM2D04.Point class to modify the coordinate values stored in the existing object that define the location of the point. In that case, it would not be necessary to instantiate a new object, and I suppose it could be argued that such an approach would actually move each vertex, thereby moving the geometric object. If execution speed is an important factor (particularly in animation code) it would probably be useful to run some benchmarks to determine which approach would be best. (I will show you how to implement this approach in the program named VectorAdd05a later in this lesson.)

Drawing the points

Once again, an if statement is embedded in the code in Listing 6 to determine whether or not to draw the new points on the off-screen image as the new GM2D04.Point objects are instantiated and saved in the array. If the points are drawn, they will be drawn in RED due to the code in Listing 5.

Drawing the lines

The code in Listing 7 is essentially the same as the code in Listing 4. This code tests the variable named drawLines to determine whether or not to draw lines connecting the new points in the current drawing color (RED).

Listing 7. Draw the lines if drawLines is true.

    if(drawLines){//Instantiate and draw lines if true.
      for(int cnt = 0;cnt < numberPoints-1;cnt++){
        line = new GM2D04.Line(newPoints[cnt],
                                        newPoints[cnt+1]);
        line.draw(g2D);
      }//end for loop
      //Draw the remaining line required to close the
      // polygon.
      line = new GM2D04.Line(newPoints[numberPoints-1],
                                            newPoints[0]);
      line.draw(g2D);
    }//end if

  }//end drawOffScreen

End of the method

Listing 7 also signals the end of the method named drawOffScreen.

The actionPerformed method

As mentioned earlier, the actionPerformed method, shown in Listing 8, is called to respond to a click on the Replot button shown in Figure 1.

Listing 8. The actionPerformed method.

  public void actionPerformed(ActionEvent e){
    //Get user input values and use them to modify several
    // values that control the translation and the
    // drawing.
    numberPoints = Integer.parseInt(
                             numberPointsField.getText());
    vector.setData(
                 0,Double.parseDouble(vectorX.getText()));
    vector.setData(
                 1,Double.parseDouble(vectorY.getText()));

    if(drawPointsBox.getState()){
      drawPoints = true;
    }else{
      drawPoints = false;
    }//end else

    if(drawLinesBox.getState()){
      drawLines = true;
    }else{
      drawLines = false;
    }//end else

    //Instantiate two new array objects with a length
    // that matches the new value for numberPoints.    
    points = new GM2D04.Point[numberPoints];
    newPoints = new GM2D04.Point[numberPoints];

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

Basically this method:

  • Gets input values from the user input components shown at the bottom of Figure 1.
  • Uses the input values to set the values of certain variables.
  • Instantiates new array objects of the correct length to contain the points that define the vertices of the geometric objects.
  • Calls the drawOffScreen method to generate the new point and line objects and display them on the canvas.

End of the program discussion

That ends the discussion of the program named VectorAdd05.

The program named VectorAdd05a

I promised you earlier that I would explain a different approach to modifying the vertices of the geometric object in order to translate it. That approach is used in the program named VectorAdd05a, which you will find in Listing 19.

The only significant change to this program relative to the program named VectorAdd05 is shown in boldface in Listing 9.

Listing 9. Abbreviated listing of the drawOffScreen method.

  void drawOffScreen(Graphics2D g2D){
//Code deleted for brevity.

    g2D.setColor(Color.RED);//Change drawing color to RED.

    //Translate the geometic object and save the points
    // that define the translated object in the same
    // array object.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt].setData(
            0,points[cnt].getData(0) + vector.getData(0));
      points[cnt].setData(
            1,points[cnt].getData(1) + vector.getData(1));

      if(drawPoints){//Draw points if true.
        points[cnt].draw(g2D);
      }//end if
    }//end for loop

//Code deleted for brevity

  }//end drawOffScreen

Compare this code with an earlier listing

You should compare the boldface code in Listing 9 with the boldface code in Listing 6. The code in Listing 9 does not call the addVectorToPoint method to instantiate a new set of GM2D04.Point objects. Instead, it uses lower-level methods from the game-math library to modify the x and y attribute values that define the locations of the existing GM2D04.Point objects.

As you can see, the code in Listing 9 is somewhat more complex than the code in Listing 6. However, the code in Listing 9 probably entails lower overhead at runtime. First, it doesn't call the addVectorToPoint method that hides the programming details behind a single method call. Second, the code in Listing 9 doesn't instantiate new objects of the GM2D04.Point class, as is the case in Listing 6.

A middle ground

A middle ground that is not illustrated here might be to use a method like the addVectorToPoint method to hide the details, but to have that method change the x and y attribute values in the existing points instead of instantiating new objects. None of the three approaches is either right or wrong. There are pros and cons to all three approaches. This is indicative of the kinds of decisions that must be made by developers who design class libraries containing classes and methods to accomplish common tasks.

End of the program discussion

That ends the discussion of the program named VectorAdd05a.

The program named VectorAdd06

This is an update to the program named VectorAdd05. The behavior of this program is similar to the earlier program except that instead of displaying a static view of the translated geometric object when the Replot button is clicked, this program animates the geometric object causing it to move from its original location shown in Figure 4 to a new location in 100 incremental steps along a straight line path.

Figure 4. Starting point for the hexagon in VectorAdd06.

The final position

The final position for the translated hexagon depends on the user input values for the vector.  Figure 5 shows one possible ending point for the translated hexagon.

Figure 5. Possible ending point for the hexagon in VectorAdd06.

The animation loop

The animation loop sleeps for ten milliseconds during each of the 100 iterations. Therefore, approximately one second (1000 milliseconds and possibly more) is required for the object to make the trip from its initial location to its new location. Once it reaches the new location, the program is ready for the user to change input values and click the Replot button again.

As with the program named VectorAdd05, this program uses the addVectorToPoint method of the GM2D04.Point class to translate a geometric object from one location in space to a different location in space. In this program, however, the translation is performed in 100 incremental steps to produce an animation. As shown in Figure 5, the animated geometric object is drawn in red to make it visually distinct from the original object. The original object is not erased from the display.

The polygon

The program initially constructs and draws a black polygon in the upper-left corner of the canvas as shown in Figure 4. (The polygon is a hexagon in Figure 4.) The six points that define the vertices of the hexagon lie on a circle with a radius of 50 units. The points at the vertices and the lines that connect the points are drawn initially.

Also as shown in Figure 4, a GUI is provided that allows the user to specify the following items and click a Replot button to cause the animation to begin:

  • The number of points that define the geometric object (polygon).
  • X-component of the displacement vector.
  • Y-component of the displacement vector.
  • A checkbox to specify whether points are to be drawn.
  • A checkbox to specify whether lines are to be drawn.

As in the previous program, changing the number of points causes the number of vertices that describe the geometric object to change, allowing you to create triangles, rectangles, pentagons, hexagons, circles, etc.

Changing the components of the displacement vector causes the geometric object to be translated to a different location. Checking and unchecking the checkboxes causes the points and/or the lines to either be drawn or not drawn.

Animation performance

On the computer that I am now using, the animation becomes jerky at about 700 points when both the points and the lines are drawn.

Will explain in fragments

I will explain this program in fragments and will avoid repeating explanations that I have previously given. A complete listing of this program is provided in Listing 20 near the end of the lesson.

Much of the code in this program is very similar to code that I explained earlier. The thing that is new about this program is the animation aspect of the program. Therefore, I will concentrate mainly on the animation code in my explanation of the program.

The class named MyCanvas, the update method, and the paint method

Before getting into the details of the program, I want to explain the inner workings of the class named MyCanvas that I defined for this program. This class, which is an inner class of the GUI class, is shown in its entirety in Listing 10.

Listing 10. The class named MyCanvas, the update method, and the paint method.

  class MyCanvas extends Canvas{
    public void update(Graphics g){
      paint(g);//Call the overridden paint method.
    }//end overridden update()
    //--------------------------------------------------//

    public void paint(Graphics g){
      g.drawImage(osi,0,0,this);
    }//end overridden paint()

  }//end inner class MyCanvas

Different from previous version

If you compare this class with the class named MyCanvas in the earlier program named VectorAdd05 (see Listing 18), you will see that this version of the class is considerably different from the previous version.

The previous version simply overrode the method named paint to cause the off-screen image to be copied to the canvas. That is a perfectly good approach for programs where the image that is displayed on the canvas changes only occasionally. However, that is not a good approach for an animation program where the image displayed on the canvas changes frequently.

Calling the update method

Recall that in Java, when we want to display an image on the computer screen, we don't call the overridden paint method directly. Rather, we call the method named repaint.

Among other tasks, the repaint method acts as a traffic cop to decide which application will be allowed to draw on the screen next, but that isn't the main thrust of our interest here. The main thrust of our interest here is that when we call the repaint method, this eventually results in a call to a method named update. By default, the update method paints a white background on the canvas erasing anything that was previously there. Then the update method calls our overridden paint method to draw whatever it is that we want to draw on the canvas.

Can cause undesirable flashing

Normally this is an adequate approach. However, for an animation program, the repetitive painting of a white background on the canvas can cause an undesirable level of flashing in the visual output of the program. In this case, there is no need to paint a white background on the canvas before copying the off-screen image on the canvas because that image will completely fill the canvas with the new image. Therefore, allowing the canvas to be painted white each time the off-screen image is copied to the canvas is unnecessary and does cause flashing on my computer.

Overriding the update method

Listing 10 overrides the update method to eliminate the painting of a white background on the canvas. In Listing 10, the update method simply calls the overridden paint method to copy the off-screen image onto the canvas. On my computer, this results in a much more pleasing animation output.

The actionPerformed method

The next code fragment that I will explain is the actionPerformed method, which is called in response to each click on the Replot button shown in Figure 4 and Figure 5.

Listing 11 shows an abbreviated listing of the actionPerformed method.

Listing 11. Abbreviated listing of actionPerformed method.

  public void actionPerformed(ActionEvent e){

//Code deleted for brevity

    //Set the animation flag to true to enable animation.
    animation = true;

    //Spawn an animation thread
    Animate animate = new Animate();
    animate.start();

  }//end actionPerformed

Most of the code in this method is the same as code that I explained in conjunction with Listing 8, so I won't repeat that explanation here. (I deleted that code for brevity in Listing 11. You can view the method in its entirety in Listing 20.)

The code in Listing 11 picks up with a statement that sets the value of an instance variable named animation to true. This variable is used simply to prevent the animation from starting until the first time the user clicks the Replot button.

Spawn an animation thread

This is where this program really departs from the previous programs. Listing 11 instantiates an object of an inner Thread class named Animate and saves that object's reference in a local variable named animate. Then Listing 11 uses that variable to call the start method on the Animate object.

I won't attempt to go into all of the technical details involved in this operation. Suffice it to say that this eventually causes a method named run belonging to the Animate object to be executed.

The Animate class and the run method

The Animate class and the run method begin in Listing 12.

Listing 12. The inner Thread class named Animate.

  class Animate extends Thread{
    public void run(){
      //Compute the incremental distances that the
      // geometric object will move during each iteration.
      double xInc = vector.getData(0)/100;
      double yInc = vector.getData(1)/100;

The run method begins by computing incremental x and y displacement values that will be required to move the geometric object from its initial position as shown in Figure 4 to its final position as shown in Figure 5 in 100 equal steps.

Do the animated move

Then Listing 13 executes 100 iterations of a for loop to cause the geometric object to actually move through those 100 incremental steps and to be drawn on the screen once during each incremental step.

Listing 13. Do the animated move.

      for(int cnt = 0;cnt < 100;cnt++){
        vector.setData(0,cnt*xInc);
        vector.setData(1,cnt*yInc);

        //Draw a new off-screen image based on the
        // incremental displacement vector and other user
        // inputs.
        drawOffScreen(g2D);
        //Copy off-screen image to canvas.
        myCanvas.repaint();

        //Sleep for ten milliseconds.
        try{
          Thread.currentThread().sleep(10);
        }catch(InterruptedException ex){
          ex.printStackTrace();
        }//end catch
      }//end for loop
    }//end run
  }//end inner class named Animate

During each iteration of the for loop...

During each iteration of the for loop in Listing 13, an incremental displacement vector is created with X and Y component values that are equal to 1/100 of the user-specified displacement vector multiplied by the loop counter value. The incremental displacement vector is used by the code in the drawOffScreen method to translate the geometric object to a new location on the off-screen image. Then the repaint method is called on the canvas to cause the off-screen image to be copied onto the canvas as described in conjunction with Listing 10 earlier..

At the end of each iteration of the for loop, the animation thread sleeps for ten milliseconds before starting the next iteration.

The bottom line is that this code causes the geometric object to move in incremental steps from its initial location to a new location based on a displacement vector specified by the user. In other words, the geometric object reaches its final location in a straight-line animated manner, taking 100 steps to get there.

The drawOffScreen method

The drawOffScreen method that is called by the Animation thread in Listing 13 is very similar to the method having the same name that I explained in conjunction with Listing 3 through Listing 7. Therefore, I won't repeat that explanation here.

The big difference in the use of that method in the two programs is that in the earlier program, the method was called only once to translate the geometric object according to the values in the displacement vector in one giant step. In this program, the values in the displacement vector are divided by 100 and the drawOffScreen method is called 100 times with ten-millisecond pauses between each call to the method. Each time the drawOffScreen method is called, the geometric object ends up closer to its final position and each of the 100 intermediate positions are displayed on the canvas for ten milliseconds.

End of discussion

That ends the discussion of the program named VectorAdd06.

The program named StringArt01

You may be old enough to remember when people who liked to do handicraft projects were creating pictures using strings and nails. If not, I'm sure that if you Google the topic of string art, you will find more about the topic than you ever wanted to know.

This program was inspired by some programs that I saw at the Son of String Art page (see Resources). I will be the first to admit that the output from this program isn't nearly as elaborate or interesting as the output from some of the projects on display at that web site. Those programs are truly impressive, particularly considering that they were written using a programming language that was "designed to help young people (ages 8 and up) develop 21st century learning skills." (See About Scratch in Resources)

In future lessons, however, I will show you how to improve this string art program including the addition of animated translation and rotation of the string-art images in 3D.

Behavior of the program

This program produces a 2D string art image by drawing lines to connect various points that are equally spaced around the circumference of a circle. At startup, the program constructs and draws a multi-colored hexagon centered on the origin as shown in Figure 9. The six points that define the vertices of the hexagon lie on a circle with a radius of 100 units. The points at the vertices 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

As in earlier programs in this lesson, 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 Loop 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. Making the number of points and loops large produces some interesting patterns.

Some output from the program

Before getting into the technical details of the program, I am going to show you some sample screen output from the program. Figure 6 shows the result of drawing lines to connect the points in the pattern described above among fifteen points that are equally spaced around the circumference of the circle.

Figure 6. String art with 15 vertices and 7 loops.

Figure 7 shows the result of drawing lines in a more complex pattern among 25 points that are equally spaced around the circumference of the circle.

Figure 7. String art with 25 vertices and 11 loops.

Figure 8 shows the result of drawing lines in an extremely complex pattern among 100 points that are equally spaced around the circumference of the circle.

Figure 8. String art with 100 vertices and 100 loops.

As you can see, there were so many lines and they were so close together in Figure 8 that the visual distinction between lines almost completely disappeared.

Finally, Figure 9 shows the program output at startup, with six vertices and one loop.

Figure 9. Output from StringArt01 at startup.

Will discuss in fragments

As is my custom, I will discuss and explain this program in fragments. Much of the code in this program is very similar to code that I have explained earlier, so I won't repeat those explanations. Rather, I will concentrate on the code that is new and different in this program. A complete listing of the program is provided in Listing 21 near the end of the lesson.

Most of the code that is new to this program is in the method named drawOffScreen, which begins in Listing 14.

Listing 14. Beginning of the drawOffScreen method in StringArt01.

  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 Point objects that specify
    // locations on the circumference of a circle and
    // save references to the Point objects in an array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM2D04.Point(
        new GM2D04.ColMatrix(
                       100*Math.cos((cnt*360/numberPoints)
                                            *Math.PI/180),
                       100*Math.sin((cnt*360/numberPoints)
                                          *Math.PI/180)));
    }//end for loop

Even some of the code in the method named drawOffScreen was explained earlier, and that is the case for the code in Listing 14.

Implement the algorithm that draws the lines

Listing 15 begins the outer loop in a pair of nested loops that implement the algorithm to draw the lines shown in Figure 6 through Figure 9.

Listing 15. Implement the algorithm that draws the lines.

    GM2D04.Line 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;

The algorithm

As I mentioned earlier, the algorithm goes something like the following:

  • Draw a line connecting every point to its immediate neighbor on the circumference of the circle.
  • Draw a line connecting every other point on the circumference of the circle.
  • Draw a line connecting every third point on the circumference of the circle.
  • Continue this process until the number of iterations satisfies the number of Loops specified by the user.

The code in the outer loop that begins in Listing 15 is responsible for identifying the beginning and ending points for the lines that will be drawn during one iteration of the outer loop. Given the above information and the embedded comments in Listing 15, you should have no difficulty understanding the logic in Listing 15.

Draw the lines

The inner loop in the pair of nested loops is shown in Listing 16. This loop constructs a series of GM2D04.Line objects and then causes visual manifestations of those objects to be drawn on the off-screen image.

Listing 16. Draw the lines.

      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 and draw it.
        line = new GM2D04.Line(points[start],points[end]);
        line.draw(g2D);
      }//end inner loop
    }//end outer loop
  }//end drawOffScreen

Once again, given what you now know, and given the embedded comments in the code, you should have no difficulty understanding the logic of the code. Note in particular the requirement to wrap around when the element number exceeds the length of the array containing references to the GM2D04.Point objects that specify the locations of the vertices of the geometric object.

End of the discussion of StringArt01

That ends the discussion of the program named StringArt01. It also ends the discussion of all five of the programs that I explained in this lesson.

Run the programs

I encourage you to copy the code from Listing 18 through Listing 21, compile the code, and execute it in conjunction with the game-math library provided in Listing 17. 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

In this lesson, I presented and explained four programs that use the game-math library named GM2D04. The purpose of the program named VectorAdd05 is 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.

The purpose of the program named VectorAdd05a is to accomplish the same translation operation, but to do so in a possibly more efficient manner.

The purpose of the program named VectorAdd06 is to teach you how to do rudimentary animation using the game-math library.

The purpose of the program named StringArt01 is to teach you how to use methods of the game-math library to produce relatively complex drawings.

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

What's next?

In the next lesson, 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, and how to add vectors in 3D. You will also learn 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.

Resources

Complete program listings

Complete listings of the programs discussed in this lesson are shown in Listing 17 through Listing 21 below.

Listing 17. Source code for the game-math library named GM2D04.

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

The name GM2Dnn is an abbreviation for GameMath2Dnn.

See the file named GM2D01.java for a general description 
of this game-math library file. This file is an update of 
GM2D03.

This update added the following new capabilities:

Vector addition - Adds this Vector object to a Vector
object received as an incoming parameter and returns the
sum as a resultant Vector object.

Added a method named getLength that returns the length
of a vector as type double.

Added a method named addVectorToPoint to add a Vector to 
a Point producing a new Point.

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

public class GM2D04{

  //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 ColMatrix{
    double[] data = new double[2];

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

    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 GM2D04.ColMatrix &&
         Math.abs(((GM2D04.ColMatrix)obj).getData(0) - 
                                 getData(0)) <= 0.00001 &&
         Math.abs(((GM2D04.ColMatrix)obj).getData(1) - 
                                  getData(1)) <= 0.00001){
        return true;
      }else{
        return false;
      }//end else

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

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

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

  public static class Point{
    GM2D04.ColMatrix point;

    public Point(GM2D04.ColMatrix point){//constructor
      //Create and save a clone of the ColMatrix object
      // used to define the point to prevent the point
      // from being corrupted by a later change in the
      // values stored in the original ColMatrix object
      // through use of its set method.
      this.point = 
         new ColMatrix(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){
      Ellipse2D.Double circle = 
                        new Ellipse2D.Double(getData(0)-3,
                                             getData(1)-3,
                                             6,
                                             6);
      g2D.draw(circle);
    }//end draw
    //--------------------------------------------------//

    //Returns a reference to the ColMatrix object that
    // defines this Point object.
    public GM2D04.ColMatrix getColMatrix(){
      return point;
    }//end getColMatrix
    //--------------------------------------------------//

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

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

    //Gets a displacement vector from one Point object to
    // a second Point 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 GM2D04.Vector getDisplacementVector(
                                      GM2D04.Point point){
      return new GM2D04.Vector(new GM2D04.ColMatrix(
                            point.getData(0)-getData(0),
                            point.getData(1)-getData(1)));
    }//end getDisplacementVector
    //--------------------------------------------------//

    //Adds a Vector to a Point producing a new Point.
    public GM2D04.Point addVectorToPoint(
                                      GM2D04.Vector vec){
      return new GM2D04.Point(new GM2D04.ColMatrix(
                          getData(0) + vec.getData(0),
                          getData(1) + vec.getData(1)));
    }//end addVectorToPoint
    //--------------------------------------------------//
  }//end class Point
  //====================================================//

  public static class Vector{
    GM2D04.ColMatrix vector;

    public Vector(GM2D04.ColMatrix vector){//constructor
      //Create and save a clone of the ColMatrix object
      // used to define the vector to prevent the vector
      // from being corrupted by a later change in the
      // values stored in the original ColVector object.
      this.vector = new ColMatrix(
                     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 circle at the
    // head.
    public void draw(Graphics2D g2D,GM2D04.Point tail){
      Line2D.Double line = new Line2D.Double(
                       tail.getData(0),
                       tail.getData(1),
                       tail.getData(0)+vector.getData(0),
                       tail.getData(1)+vector.getData(1));

    //Draw a small circle to identify the head.
      Ellipse2D.Double circle = new Ellipse2D.Double(
                      tail.getData(0)+vector.getData(0)-2,
                      tail.getData(1)+vector.getData(1)-2,
                      4,
                      4);
      g2D.draw(circle);
      g2D.draw(line);
    }//end draw
    //--------------------------------------------------//

    //Returns a reference to the ColMatrix object that
    // defines this Vector object.
    public GM2D04.ColMatrix getColMatrix(){
      return vector;
    }//end getColMatrix
    //--------------------------------------------------//

    //This method overrides the equals method inherited
    // from the class named Object. It compares the values
    // stored in the ColMatrix objects that define two
    // Vector objects and returns true if they are equal
    // and false otherwise. 
    public boolean equals(Object obj){
      if(vector.equals((
                     (GM2D04.Vector)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 GM2D04.Vector add(GM2D04.Vector vec){
      return new GM2D04.Vector(new ColMatrix(
                       vec.getData(0)+vector.getData(0),
                       vec.getData(1)+vector.getData(1)));
    }//end add
    //--------------------------------------------------//

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


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

    public Line(GM2D04.Point tail,GM2D04.Point 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 Point(new GM2D04.ColMatrix(
                        tail.getData(0),tail.getData(1)));
      this.line[1] = new Point(new GM2D04.ColMatrix(
                        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 GM2D04.Point getTail(){
      return line[0];
    }//end getTail
    //--------------------------------------------------//

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

    public void setTail(GM2D04.Point 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 Point(new GM2D04.ColMatrix(
              newPoint.getData(0),newPoint.getData(1)));
    }//end setTail
    //--------------------------------------------------//

    public void setHead(GM2D04.Point 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 Point(new GM2D04.ColMatrix(
              newPoint.getData(0),newPoint.getData(1)));
    }//end setHead
    //--------------------------------------------------//

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

}//end class GM2D04
//======================================================//

 

Listing 18. Source code for the sample program named VectorAdd05.

/*VectorAdd05.java 
Copyright 2008, R.G.Baldwin
Revised 02/11/08

The purpose of this program is 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.  Along the way, the program uses various methods
of the classes in the game-math library named GM2D04 to 
accomplish its purpose.

The program initially constructs and draws a black hexagon
centered on the origin. The six points that define the
vertices of the hexagon lie on a circle with a radius of 
50 units. The points at the vertices and the lines that 
connect the points are drawn.

In addition, the program initially causes the hexagon to 
be translated by 50 units in the positive X direction and
50 units in the positive Y. The translated hexagon is
drawn in red. The original black hexagon is not erased 
when the translated version is drawn in red.

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
X-component of the displacement vector.
Y-component of the displacement vector.
A checkbox to specify whether points are to be drawn.
A checkbox to specify whether lines are to be drawn.

Changing the number of points causes the number of 
vertices that describe the geometric object to change. 
For a large number of points, the geometric object becomes
a circle. For only three points, it becomes a triangle. 
For four points, it becomes a square. For two points, it 
becomes a line, etc.

Changing the components of the displacement vector causes
the geometric object to be translated to a different 
location before being drawn in red.

Checking and unchecking the checkboxes causes the points 
and/or the lines to either be drawn or not drawn.

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

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

class GUI extends JFrame implements ActionListener{
  //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 
  Graphics2D g2D;//Off-screen graphics context.

  //The following two variables are used to establish the
  // location of the origin.
  double xAxisOffset;
  double yAxisOffset;

  int numberPoints = 6;//Can be modified by the user.
  JTextField numberPointsField; //User input field.
  //The commponents of the following displacement vector
  // can be modified by the user.
  GM2D04.Vector vector = 
           new GM2D04.Vector(new GM2D04.ColMatrix(50,50));
  JTextField vectorX;//User input field.
  JTextField vectorY;//User input field.

  //The following variables are used to determine whether
  // to draw the points and/or the lines.
  boolean drawPoints = true;
  boolean drawLines = true;
  Checkbox drawPointsBox;//User input field
  Checkbox drawLinesBox;//User input field.

  //The following variables are used to refer to array
  // objects containing the points that define the
  // vertices of the geometric object.
  GM2D04.Point[] points;
  GM2D04.Point[] newPoints;
  //----------------------------------------------------//

  GUI(){//constructor
    //Instantiate the array objects that will be used to
    // store the points that define the vertices of the
    // geometric object.
    points = new GM2D04.Point[numberPoints];
    newPoints = new GM2D04.Point[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");
    vectorX = new JTextField("50");
    vectorY = new JTextField("50");
    drawPointsBox = new Checkbox("Draw Points",true);
    drawLinesBox = new Checkbox("Draw Lines",true);
    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(3,3));

    //Add the user input component and appropriate labels
    // to the control panel.
    controlPanel.add(new JLabel(" Number Points"));    
    controlPanel.add(numberPointsField);
    controlPanel.add(drawPointsBox);
    controlPanel.add(new JLabel(" Vector X"));
    controlPanel.add(vectorX);
    controlPanel.add(drawLinesBox);
    controlPanel.add(new JLabel(" Vector Y"));
    controlPanel.add(vectorY);
    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();

    //Set the values that will be used to establish the
    // origin, thus defining a coordinate frame.
    xAxisOffset = osiWidth/2;
    yAxisOffset = osiHeight/2;

    //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 and draw the axes
    setCoordinateFrame(g2D,xAxisOffset,yAxisOffset,true);

    //Create the Point 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 Point
  // objects that define the vertices of the 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 change the coordinate frame.
    setCoordinateFrame(g2D,xAxisOffset,yAxisOffset,false);

    //Create a set of Point objects that specify
    // locations on the circumference of a circle and
    // save references to the Point objects in an array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM2D04.Point(
        new GM2D04.ColMatrix(
                        50*Math.cos((cnt*360/numberPoints)
                                          *Math.PI/180),
                        50*Math.sin((cnt*360/numberPoints)
                                          *Math.PI/180)));
      if(drawPoints){//Draw points if true
        points[cnt].draw(g2D);
      }//end if
    }//end for loop

    GM2D04.Line line;
    if(drawLines){//Instantiate and draw lines if true.
      for(int cnt = 0;cnt < numberPoints-1;cnt++){
        //Begin by drawing all of the lines but one.
        line = new GM2D04.Line(points[cnt],points[cnt+1]);
          line.draw(g2D);
      }//end for loop
      //Draw the remaining line required to close the
      // polygon.
      line = new GM2D04.Line(points[numberPoints-1],
                             points[0]);
      line.draw(g2D);
    }//end if

    g2D.setColor(Color.RED);//Change drawing color to RED.

    //Translate the geometic object and save the points
    // that define the translated object in another array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      newPoints[cnt] = 
                     points[cnt].addVectorToPoint(vector);
      if(drawPoints){//Draw points if true.
        newPoints[cnt].draw(g2D);
      }//end if
    }//end for loop

    if(drawLines){//Instantiate and draw lines if true.
      for(int cnt = 0;cnt < numberPoints-1;cnt++){
        line = new GM2D04.Line(newPoints[cnt],
                                        newPoints[cnt+1]);
        line.draw(g2D);
      }//end for loop
      //Draw the remaining line required to close the
      // polygon.
      line = new GM2D04.Line(newPoints[numberPoints-1],
                                            newPoints[0]);
      line.draw(g2D);
    }//end if

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

  //This method is used to set the coordinate frame of
  // the off-screen image by setting the origin to the
  // specified offset values relative to origin of the
  // world. The origin of the world is the upper-left
  // corner of the off-screen image.
  //The method draws black orthogonal axes on the
  // off-screen image.
  //There is no intention to perform mathematical
  // operations on the axes, so  they are drawn
  // independently of the classes and methods in the
  // game-math library using the simplest available method
  // for drawing lines.
  //The method assumes that the origin is at the
  // upper-left corner when the method is first called.
  //Each time the method is called, it paints the
  // background white erasing anything already there.
  //The fourth parameter is used to determine if the
  // origin should be translated by the values of the
  // second and third parameters.
  private void setCoordinateFrame(Graphics2D g2D,
                                  double xOffset,
                                  double yOffset,
                                  boolean translate){
    //Paint the background white
    g2D.setColor(Color.WHITE);
    g2D.fillRect(-(int)xOffset,-(int)yOffset,
                  (int)osiWidth,(int)osiHeight);

    //Translate the origin by the specified amount if the
    // fourth parameter is true.
    if(translate){
      g2D.translate((int)xOffset,(int)yOffset);
    }//end if

    //Draw new X and Y-axes in BLACK
    g2D.setColor(Color.BLACK);
    g2D.drawLine(-(int)(xOffset - 10),0,
                  (int)(xOffset - 10),0);
                  
    g2D.drawLine(0,-(int)(yOffset - 10),
                 0,(int)(yOffset - 10));

  }//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 translation and the
    // drawing.
    numberPoints = Integer.parseInt(
                             numberPointsField.getText());
    vector.setData(
                 0,Double.parseDouble(vectorX.getText()));
    vector.setData(
                 1,Double.parseDouble(vectorY.getText()));

    if(drawPointsBox.getState()){
      drawPoints = true;
    }else{
      drawPoints = false;
    }//end else

    if(drawLinesBox.getState()){
      drawLines = true;
    }else{
      drawLines = false;
    }//end else

    //Instantiate two new array objects with a length
    // that matches the new value for numberPoints.    
    points = new GM2D04.Point[numberPoints];
    newPoints = new GM2D04.Point[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 19. Source code for the program named VectorAdd05a.

/*VectorAdd05a.java
Copyright 2008, R.G.Baldwin
Revised 02/19/08

This program is a modified version of VectorAdd05 that
uses a different scheme for modifying the vertices of the 
geometric object.

Note that the comments in this program were not updated
to reflect these modifications. In particular, this 
version does not call the method named addVectorToPoint.
--------------------------------------------------------

The purpose of this program is 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.  Along the way, the program uses various methods
of the classes in the game math library named GM2D04 to 
accomplish its purpose.

The program initially constructs and draws a black hexagon
centered on the origin. The six points that define the
vertices of the hexagon lie on a circle with a radius of 
50 units. The points at the vertices and the lines that 
connect the points are drawn.

In addition, the program initially causes the hexagon to 
be translated by 50 units in the positive X direction and
50 units in the positive Y. The translated hexagon is
drawn in red. The original black hexagon is not erased 
when the translated version is drawn in red.

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
X-component of the displacement vector.
Y-component of the displacement vector.
A checkbox to specify whether points are to be drawn.
A checkbox to specify whether lines are to be drawn.

Changing the number of points causes the number of 
vertices that describe the geometric object to change. 
For a large number of points, the geometric object becomes
a circle. For only three points, it becomes a triangle. 
For four points, it becomes a square. For two points, it 
becomes a line, etc.

Changing the components of the displacement vector causes
the geometric object to be translated to a different 
location before being drawn in red.

Checking and unchecking the checkboxes causes the points 
and/or the lines to either be drawn or not drawn.

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

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

class GUI extends JFrame implements ActionListener{
  //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 
  Graphics2D g2D;//Off-screen graphics context.

  //The following two variables are used to establish the
  // location of the origin.
  double xAxisOffset;
  double yAxisOffset;

  int numberPoints = 6;//Can be modified by the user.
  JTextField numberPointsField; //User input field.
  //The commponents of the following displacement vector
  // can be modified by the user.
  GM2D04.Vector vector = 
           new GM2D04.Vector(new GM2D04.ColMatrix(50,50));
  JTextField vectorX;//User input field.
  JTextField vectorY;//User input field.

  //The following variables are used to determine whether
  // to draw the points and/or the lines.
  boolean drawPoints = true;
  boolean drawLines = true;
  Checkbox drawPointsBox;//User input field
  Checkbox drawLinesBox;//User input field.

  //The following variables are used to refer to array
  // objects containing the points that define the
  // vertices of the geometric object.
  GM2D04.Point[] points;
//  GM2D04.Point[] newPoints;
  //----------------------------------------------------//

  GUI(){//constructor
    //Instantiate the array objects that will be used to
    // store the points that define the vertices of the
    // geometric object.
    points = new GM2D04.Point[numberPoints];
//    newPoints = new GM2D04.Point[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");
    vectorX = new JTextField("50");
    vectorY = new JTextField("50");
    drawPointsBox = new Checkbox("Draw Points",true);
    drawLinesBox = new Checkbox("Draw Lines",true);
    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(3,3));

    //Add the user input component and appropriate labels
    // to the control panel.
    controlPanel.add(new JLabel(" Number Points"));
    controlPanel.add(numberPointsField);
    controlPanel.add(drawPointsBox);
    controlPanel.add(new JLabel(" Vector X"));
    controlPanel.add(vectorX);
    controlPanel.add(drawLinesBox);
    controlPanel.add(new JLabel(" Vector Y"));
    controlPanel.add(vectorY);
    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();

    //Set the values that will be used to establish the
    // origin, thus defining a coordinate frame.
    xAxisOffset = osiWidth/2;
    yAxisOffset = osiHeight/2;

    //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 and draw the axes
    setCoordinateFrame(g2D,xAxisOffset,yAxisOffset,true);

    //Create the Point 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 Point
  // objects that define the vertices of the 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 change the coordinate frame.
    setCoordinateFrame(g2D,xAxisOffset,yAxisOffset,false);

    //Create a set of Point objects that specify
    // locations on the circumference of a circle and
    // save references to the Point objects in an array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM2D04.Point(
        new GM2D04.ColMatrix(
                        50*Math.cos((cnt*360/numberPoints)
                                          *Math.PI/180),
                        50*Math.sin((cnt*360/numberPoints)
                                          *Math.PI/180)));
      if(drawPoints){//Draw points if true
        points[cnt].draw(g2D);
      }//end if
    }//end for loop

    GM2D04.Line line;
    if(drawLines){//Instantiate and draw lines if true.
      for(int cnt = 0;cnt < numberPoints-1;cnt++){
        //Begin by drawing all of the lines but one.
        line = new GM2D04.Line(points[cnt],points[cnt+1]);
          line.draw(g2D);
      }//end for loop
      //Draw the remaining line required to close the
      // polygon.
      line = new GM2D04.Line(points[numberPoints-1],
                             points[0]);
      line.draw(g2D);
    }//end if

    g2D.setColor(Color.RED);//Change drawing color to RED.

    //Translate the geometic object and save the points
    // that define the translated object in the same
    // array object.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt].setData(
            0,points[cnt].getData(0) + vector.getData(0));
      points[cnt].setData(
            1,points[cnt].getData(1) + vector.getData(1));

      if(drawPoints){//Draw points if true.
        points[cnt].draw(g2D);
      }//end if
    }//end for loop

    if(drawLines){//Instantiate and draw lines if true.
      for(int cnt = 0;cnt < numberPoints-1;cnt++){
        line = new GM2D04.Line(points[cnt],
                                        points[cnt+1]);
        line.draw(g2D);
      }//end for loop
      //Draw the remaining line required to close the
      // polygon.
      line = new GM2D04.Line(points[numberPoints-1],
                                            points[0]);
      line.draw(g2D);
    }//end if

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

  //This method is used to set the coordinate frame of
  // the off-screen image by setting the origin to the
  // specified offset values relative to origin of the
  // world. The origin of the world is the upper-left
  // corner of the off-screen image.
  //The method draws black orthogonal axes on the
  // off-screen image.
  //There is no intention to perform mathematical
  // operations on the axes, so  they are drawn
  // independently of the classes and methods in the
  // game-math library using the simplest available method
  // for drawing lines.
  //The method assumes that the origin is at the
  // upper-left corner when the method is first called.
  //Each time the method is called, it paints the
  // background white erasing anything already there.
  //The fourth parameter is used to determine if the
  // origin should be translated by the values of the
  // second and third parameters.
  private void setCoordinateFrame(Graphics2D g2D,
                                  double xOffset,
                                  double yOffset,
                                  boolean translate){
    //Paint the background white
    g2D.setColor(Color.WHITE);
    g2D.fillRect(-(int)xOffset,-(int)yOffset,
                  (int)osiWidth,(int)osiHeight);

    //Translate the origin by the specified amount if the
    // fourth parameter is true.
    if(translate){
      g2D.translate((int)xOffset,(int)yOffset);
    }//end if

    //Draw new X and Y-axes in BLACK
    g2D.setColor(Color.BLACK);
    g2D.drawLine(-(int)(xOffset - 10),0,
                  (int)(xOffset - 10),0);
                  
    g2D.drawLine(0,-(int)(yOffset - 10),
                 0,(int)(yOffset - 10));

  }//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 translation and the
    // drawing.
    numberPoints = Integer.parseInt(
                             numberPointsField.getText());
    vector.setData(
                 0,Double.parseDouble(vectorX.getText()));
    vector.setData(
                 1,Double.parseDouble(vectorY.getText()));

    if(drawPointsBox.getState()){
      drawPoints = true;
    }else{
      drawPoints = false;
    }//end else

    if(drawLinesBox.getState()){
      drawLines = true;
    }else{
      drawLines = false;
    }//end else

    //Instantiate two new array objects with a length
    // that matches the new value for numberPoints.
    points = new GM2D04.Point[numberPoints];
//    newPoints = new GM2D04.Point[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 20. Source code for the program named VectorAdd06.

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

This is an update to the program named VectorAdd05. The 
behavior of this program is similar to the earlier
program except that instead of displaying a static view of
the translated geometric object when the Replot button 
is clicked, this program animates the geometric object 
causing it to move from its original location to its new 
locationin 100 incremental steps along a straight line 
path.

The animation loop sleeps for ten milliseconds during each
of the 100 iterations. Therefore,approximately one second
(1000 milliseconds and possibly more) is required for the 
object to make the trip from its initial location to its 
new location.  Once it reaches the new location, the 
program is ready for the user to change input values and 
click the Replot button again.

As with the program named VectorAdd05, this program uses 
the addVectorToPoint method of the GM2D04.Point class to 
translate a geometric object from one location in space to
a different location in space. In this program, however, 
the translation is performed in 100 incremental steps 
to produce an animation. The animated geometric object is 
drawn in red to make it visually distinct from the 
original object. The original object is not erased from 
the display.

The program initially constructs and draws a black hexagon
in the upper-left corner of the canvas. The six points 
that define the vertices of the hexagon lie on a circle 
with a radius of 50 units. The points at the vertices and
the lines that connect the points are drawn initially.

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

Number points
X-component of the displacement vector.
Y-component of the displacement vector.
A checkbox to specify whether points are to be drawn.
A checkbox to specify whether lines are to be drawn.

Changing the number of points causes the number of 
vertices that describe the geometric object to change. 
For a large number of points, the geometric object becomes
a circle. For only three points, it becomes a triangle. 
For four points, it becomes a rectangle. For two points, 
it becomes a line, etc. On the computer that I am now 
using, the animation becomes jerky at about 700 points
when both the points and the lines are drawn.

Changing the components of the displacement vector causes
the geometric object to be translated to a different 
location.

Checking and unchecking the checkboxes causes the points 
and/or the lines to either be drawn or not drawn.

Tested using JDK 1.6 under WinXP.
*********************************************************/

import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import java.awt.event.*;

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

class GUI extends JFrame implements ActionListener{

  //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 
  Graphics2D g2D;//off-screen graphics context.

  //The following two variables are used to establish the
  // location of the origin.
  double xAxisOffset;
  double yAxisOffset;

  int numberPoints = 6;//Can be modified by the user.
  JTextField numberPointsField; //User input field.
  //The commponents of the following displacement vector
  // can be modified by the user.
  GM2D04.Vector vector = 
           new GM2D04.Vector(new GM2D04.ColMatrix(0,0));
  JTextField vectorX;//User input field.
  JTextField vectorY;//User input field.

  //The following variables are used to determine whether
  // to draw the points and/or the lines.
  boolean drawPoints = true;
  boolean drawLines = true;
  Checkbox drawPointsBox;//User input field
  Checkbox drawLinesBox;//User input field.

  //The following variables are used to refer to array
  // objects containing the points that define the
  // vertices of the geometric objects.
  GM2D04.Point[] points;
  GM2D04.Point[] newPoints;

  boolean animation = false;
  //----------------------------------------------------//

  GUI(){//constructor
    //Instantiate the array objects that will be used to
    // store the points that define the vertices of the
    // geometric object.
    points = new GM2D04.Point[numberPoints];
    newPoints = new GM2D04.Point[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");
    vectorX = new JTextField("300");
    vectorY = new JTextField("100");
    drawPointsBox = new Checkbox("Draw Points",true);
    drawLinesBox = new Checkbox("Draw Lines",true);
    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(3,3));

    //Add the user input component and appropriate labels
    // to the control panel.
    controlPanel.add(new JLabel(" Number Points"));    
    controlPanel.add(numberPointsField);
    controlPanel.add(drawPointsBox);
    controlPanel.add(new JLabel(" Vector X"));
    controlPanel.add(vectorX);
    controlPanel.add(drawLinesBox);
    controlPanel.add(new JLabel(" Vector Y"));
    controlPanel.add(vectorY);
    controlPanel.add(button);

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

    //Instantiate 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();

    //Set the values that will be used to establish the
    // origin, thus defining a coordinate frame.
    xAxisOffset = osiWidth/2;
    yAxisOffset = osiHeight/2;

    //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
    // coordinate frame by translating the origin to the
    // new location and draw the axes
    setCoordinateFrame(g2D,xAxisOffset,yAxisOffset,true);

    //Create the Point 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 Point
  // objects that define the vertices of the 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 change the coordinate frame.
    setCoordinateFrame(g2D,xAxisOffset,yAxisOffset,false);

    //Create a set of Point objects that specify
    // locations on the circumference of a circle and
    // save references to the Point objects in an array.
    // Position the circle at the upper-left corner of the
    // canvas.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM2D04.Point(
        new GM2D04.ColMatrix(
                        50*Math.cos((cnt*360/numberPoints)
                                      *Math.PI/180) - 150,
                        50*Math.sin((cnt*360/numberPoints)
                                    *Math.PI/180) - 100));
      if(drawPoints){//Draw points if true
        points[cnt].draw(g2D);
      }//end if
    }//end for loop

    GM2D04.Line line;
    if(drawLines){//Instantiate and draw lines if true.
      for(int cnt = 0;cnt < numberPoints-1;cnt++){
        //Begin by drawing all of the lines but one.
        line = new GM2D04.Line(points[cnt],points[cnt+1]);
          line.draw(g2D);
      }//end for loop
      //Draw the remaining line required to close the
      // polygon.
      line = new GM2D04.Line(points[numberPoints-1],
                             points[0]);
      line.draw(g2D);
    }//end if

    g2D.setColor(Color.RED);//Change drawing color to RED.


    if(animation){
      //This code is executed only after the Replot button
      // has been clicked once.
      //Translate the geometic object and save the points
      // that define the translated object in another
      // array.
      for(int cnt = 0;cnt < numberPoints;cnt++){
        newPoints[cnt] = 
                     points[cnt].addVectorToPoint(vector);
        if(drawPoints){//Draw points if true.
          newPoints[cnt].draw(g2D);
        }//end if
      }//end for loop

      if(drawLines){//Instantiate and draw lines if true.
        for(int cnt = 0;cnt < numberPoints-1;cnt++){
          line = new GM2D04.Line(newPoints[cnt],
                                        newPoints[cnt+1]);
          line.draw(g2D);
        }//end for loop
        //Draw the remaining line required to close the
        // polygon.
        line = new GM2D04.Line(newPoints[numberPoints-1],
                                            newPoints[0]);
        line.draw(g2D);
      }//end if
    }//end if(animation)

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

  //This method is used to set the coordinate frame of
  // the off-screen image by setting the origin to the
  // specified offset values relative to the origin of the
  // world. The origin of the world is the upper-left
  // corner of the off-screen image.
  //The method draws black orthogonal axes on the
  // off-screen image.
  //There is no intention to perform mathematical
  // operations on the axes, so  they are drawn
  // independently of the classes and methods in the
  // game-math library using the simplest available method
  // for drawing lines.
  //The method assumes that the origin is at the
  // upper-left corner when the method is first called.
  //Each time the method is called, it paints the
  // background white erasing anything already there.
  //The fourth parameter is used to determine if the
  // origin should be translated by the values of the
  // second and third parameters.
  private void setCoordinateFrame(Graphics2D g2D,
                                  double xOffset,
                                  double yOffset,
                                  boolean translate){
    //Paint the background white
    g2D.setColor(Color.WHITE);
    g2D.fillRect(-(int)xOffset,-(int)yOffset,
                  (int)osiWidth,(int)osiHeight);

    //Translate the origin by the specified amount if the
    // fourth parameter is true.
    if(translate){
      g2D.translate((int)xOffset,(int)yOffset);
    }//end if

    //Draw new X and Y-axes in BLACK
    g2D.setColor(Color.BLACK);
    g2D.drawLine(-(int)(xOffset - 10),0,
                  (int)(xOffset - 10),0);

    g2D.drawLine(0,-(int)(yOffset - 10),
                 0,(int)(yOffset - 10));

  }//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 translation and the
    // drawing.
    numberPoints = Integer.parseInt(
                             numberPointsField.getText());

    vector.setData(
                 0,Double.parseDouble(vectorX.getText()));
    vector.setData(
                 1,Double.parseDouble(vectorY.getText()));

    if(drawPointsBox.getState()){
      drawPoints = true;
    }else{
      drawPoints = false;
    }//end else

    if(drawLinesBox.getState()){
      drawLines = true;
    }else{
      drawLines = false;
    }//end else

    //Instantiate two new array objects with a length
    // that matches the new value for numberPoints.
    points = new GM2D04.Point[numberPoints];
    newPoints = new GM2D04.Point[numberPoints];

    //Set the animation flag to true to enable animation.
    animation = true;

    //Spawn an animation thread
    Animate animate = new Animate();
    animate.start();

  }//end actionPerformed
  //====================================================//
  
  //This is an inner class of the GUI class.
  class MyCanvas extends Canvas{
    //Override the update method to eliminate the default
    // clearing of the Canvas in order to reduce or
    // eliminate the flashing that that is often caused by
    // such default clearing.
    //In this case, it isn't necessary to clear the canvas
    // because the off-screen image is cleared each time
    // it is updated. 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.
    public void update(Graphics g){
      paint(g);//Call the overridden paint method.
    }//end overridden update()

    //Override the paint() method. The purpose of the
    // paint method in this program is simply to copy the
    // off-screen image onto the camvas. This method is
    // called by the update method above.
    public void paint(Graphics g){
      g.drawImage(osi,0,0,this);
    }//end overridden paint()

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

  //This is an animation thread.
  class Animate extends Thread{
    public void run(){
      //During each iteration of the following loop, an
      // incremental displacement vector is created with
      // X and Y components that are equal to 1/100 of the
      // user-specified displacement vector multiplied by
      // the loop counter value.
      //The incremental displacement vector is used by the
      // code in the drawOffScreen method to translate
      // the geometric object to a new location on the
      // off-screen image. Then the repaint method is
      // called on the canvas to cause the off-screen
      // image to be copied onto the canvas. After that,
      // this thread sleeps for ten milliseconds before
      // starting the next iteration.
      //The bottom line is that this code causes the
      // geometric object to move in incremental steps
      // from its initial location to a new location based
      // on a displacement vector specified by the user.
      // In other words, the geometric object reaches its
      // final location in a straight-line animated
      // manner, taking 100 steps to get there.

      //Compute the incremental distances that the
      // geometric object will move during each iteration.
      double xInc = vector.getData(0)/100;
      double yInc = vector.getData(1)/100;

      //Do the animated move.
      for(int cnt = 0;cnt < 100;cnt++){
        vector.setData(0,cnt*xInc);
        vector.setData(1,cnt*yInc);

        //Draw a new off-screen image based on the
        // incremental displacement vector and other user
        // inputs.
        drawOffScreen(g2D);
        //Copy off-screen image to canvas.
        myCanvas.repaint();

        //Sleep for ten milliseconds.
        try{
          Thread.currentThread().sleep(10);
        }catch(InterruptedException ex){
          ex.printStackTrace();
        }//end catch
      }//end for loop
    }//end run
  }//end inner class named Animate
  //====================================================//

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

 

Listing 21. Source code for the program named StringArt01.

/*StringArt01.java 
Copyright 2008, R.G.Baldwin
Revised 02/20/08

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

At startup, the program constructs and draws a multi-
colored hexagon centered on the origin. The six points 
that define the vertices of the hexagon lie on a circle 
with a radius of 100 units. The points at the vertices 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

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.

Making the number of points large and making the number
of loops large 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 StringArt01{
  public static void main(String[] args){
    GUI guiObj = new GUI();
  }//end main
}//end controlling class StringArt01
//======================================================//

class GUI extends JFrame implements ActionListener{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 300;
  int vSize = 320;
  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.

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

  //The following variable is used to refer to the array
  // object containing the points that define the
  // vertices of the geometric object.
  GM2D04.Point[] 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 GM2D04.Point[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");
    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(3,3));

    //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(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 and draw the axes
    setCoordinateFrame(g2D,true);

    //Create the Point 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 Point
  // objects that define the vertices of the 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 Point objects that specify
    // locations on the circumference of a circle and
    // save references to the Point objects in an array.
    for(int cnt = 0;cnt < numberPoints;cnt++){
      points[cnt] = new GM2D04.Point(
        new GM2D04.ColMatrix(
                       100*Math.cos((cnt*360/numberPoints)
                                            *Math.PI/180),
                       100*Math.sin((cnt*360/numberPoints)
                                          *Math.PI/180)));
    }//end for loop

    //Implement the algorithm that draws the lines.
    GM2D04.Line 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 GM2D04.Line 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 and draw it.
        line = new GM2D04.Line(points[start],points[end]);
        line.draw(g2D);
      }//end inner loop
    }//end outer loop
  }//end drawOffScreen
  //----------------------------------------------------//

  //This method is used to set the coordinate frame of
  // the off-screen image by setting the origin to the 
  // center of the off-screen image and drawing black
  // orthogonal axes 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 of the off-screen
    // image if the fourth parameter is true.
    if(translate){
      g2D.translate((int)osiWidth/2,(int)osiHeight/2);
    }//end if

    //Paint the background white
    g2D.setColor(Color.WHITE);
    g2D.fillRect(-(int)osiWidth/2,-(int)osiHeight/2,
                  (int)osiWidth,(int)osiHeight);

    //Draw new X and Y-axes in BLACK
    g2D.setColor(Color.BLACK);
    g2D.drawLine(-(int)(osiWidth/2),0,
                  (int)(osiWidth/2),0);

    g2D.drawLine(0,-(int)(osiHeight/2),
                 0,(int)(osiHeight/2));

  }//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()) + 1;

    //Instantiate a new array object with a length
    // that matches the new value for numberPoints.    
    points = new GM2D04.Point[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