April 18, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Vector Addition: Math for Java Game Programmers

  • June 3, 2008
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Java Programming Notes # 1706


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, Working with Column Matrices, Points, and Vectors (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 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 compare column matrices for equality
  • How to compare two points for equality
  • How to compare two vectors for equality
  • How to add one column matrix to another
  • How to subtract one column matrix from another
  • How to get a displacement vector from one point to another

What you will learn

In this lesson you will learn:

  • 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 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

I also recommend that you concurrently study the Kjell tutorial through Chapter 3 - Vector Addition (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. Screen output from the program named VectorAdd01.
  • Figure 2. Screen output from CoordinateFrame01 at startup.
  • Figure 3. Screen output from CoordinateFrame01 after changes to the coordinate frame.
  • Figure 4. Screen output from the program named VectorAdd02.
  • Figure 5. Graphic screen output from the program named VectorAdd03.
  • Figure 6. Command-line output from the program named VectorAdd03.
  • Figure 7. Screen output from the program named VectorAdd04

Listings

  • Listing 1. The add method of the GM2D04.Vector class.
  • Listing 2. The getLength method of the GM2D04.Vector class.
  • Listing 3. The addVectorToPoint method of the GM2D04 class.
  • Listing 4. Beginning of the program named VectorAdd01.
  • Listing 5. Beginning of the method named drawOffScreen.
  • Listing 6. The method named setCoordinateFrame.
  • Listing 7. Adding two vectors.
  • Listing 8. Draw vecA in RED with its tail at the origin.
  • Listing 9. Draw vecB in GREEN head-to-tail with vecA.
  • Listing 10. Draw sumOf2 in MAGENTA with its tail at the origin.
  • Listing 11. Extending the example to three vectors.
  • Listing 12. The actionPerformed method.
  • Listing 13. The setCoordinateFrame method.
  • Listing 14. Beginning of the drawOffScreen method.
  • Listing 15. Define a point to position the vectors.
  • Listing 16. Remaining code in the drawOffScreen method.
  • Listing 17. The method named drawOffScreen.
  • Listing 18. Do the same operations in a different order.
  • Listing 19. Source code for the game-math library named GM2D04.
  • Listing 20. Source code for the program named VectorAdd01.
  • Listing 21. Source code for the program named CoordinateFrame01.
  • Listing 22. Source code for the program named VectorAdd02.
  • Listing 23. Source code for the program named VectorAdd03.
  • Listing 24. Source code for the program named VectorAdd04.

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 several updates to the game-math library. In addition, I will present and explain five sample programs that illustrate the use of the new features of the library.

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 was updated and the name was changed to GM2D04 in preparation for this lesson. Much of the code in the updated library remains unchanged. I explained that code in the previous lessons (see Resources) and I won't repeat that explanation in this lesson. In this lesson, I will concentrate on explaining the modifications that I made to the library.

The game-math library named GM2D04

A complete listing of the library program is provided in Listing 19 near the end of the lesson.

This update added the following new capabilities to the library:

  • GM2D04.Vector.add - Adds this Vector object to a Vector object received as an incoming parameter and returns the sum as a new Vector object.
  • GM2D04.Vector.getLenth - Returns the length of a Vector as type double.
  • GM2D04.Point.addVectorToPoint - Adds a Vector object to a Point object returning a new Point object.

These three new methods are presented in Listing 1, Listing 2, and Listing 3 below. These methods are so simple that no explanation should be required for you to understand them.

Listing 1. The add method of the GM2D04.Vector class.

    //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

 

Listing 2. The getLength method of the GM2D04.Vector class.

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

 

Listing 3. The addVectorToPoint method of the GM2D04 class.

    //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

Simple but powerful

Although these new methods are individually very simple, when combined with the other methods in the library, they significantly increase the power of the library. For example, in the next lesson I will show you that the library in its current form supports translation and animation.

I will illustrate the use of these new methods in the sample programs that follow.

The program named VectorAdd01

This program illustrates the addition of two and then three vectors. It also illustrates the head-to-tail rule described by Kjell. A complete listing of the program is provided in Listing 20 near the end of the lesson.

Screen output from the program named VectorAdd01

The screen output from the program is shown in Figure 1.

Figure 1. Screen output from the program named VectorAdd01.

You will recall that the game-math library represents a Vector object graphically as a straight line with a small circle at the head. Thus, there are five vectors drawn in Figure 1. I will explain the meaning of the output in conjunction with the explanation of the program.

Beginning of the program named VectorAdd01

Listing 4 shows the entire class named VectorAdd01 along with the beginning of the class named GUI including the constructor for the GUI class.

Listing 4. Beginning of the program named VectorAdd01.

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

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

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

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

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

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

    //Draw some graphical objects on the off-screen
    // image that represent underlying data objects in
    // 2D space.
    drawOffScreen(g2D);

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

  }//end constructor

Very familiar code

The code in Listing 4 is very similar to code that I explained in the earlier lesson titled Math for Java Game Programmers, Updating the Math Library for Graphics (see Resources). Therefore, I won't explain that code again in this lesson.

Note that the code in Listing 4 makes a call to the method named drawOffScreen near the end of the listing.

Beginning of the method named drawOffScreen

The beginning of this method is shown in Listing 5.

The purpose of this method is to add some Vector objects and to cause visual manifestations of the raw Vector objects and the resultant Vector objects to be drawn onto an off-screen image.

Listing 5. Beginning of the method named drawOffScreen.

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

Listing 5 begins by calling the method named setCoordinateFrame, which is shown in its entirety in Listing 6.

The method named setCoordinateFrame

This method sets the origin to a point near the upper-left corner of the off-screen image (see Figure 1) and draws orthogonal axes on the off-screen image intersecting at the origin.

Listing 6. The method named setCoordinateFrame.

  private void setCoordinateFrame(Graphics2D g2D){
    //Translate the origin.
    g2D.translate(0.2*osiWidth,0.2*osiHeight);

    //Draw new X and Y-axes in default BLACK
    g2D.drawLine(-(int)(0.2*osiWidth),0,
                 (int)(0.8*osiWidth),0);
                 
    g2D.drawLine(0,-(int)(0.2*osiHeight),
                 0,(int)(0.8*osiHeight));

  }//end setCoordinateFrame method

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 method available for drawing a line.

The name of that simple method is drawLine, and it is a method of the Graphics class. The translate method is also a method of the Graphics class. Given that information, the code in Listing 6 is straightforward and should not required further explanation.

Adding two vectors

Returning to the method named drawOffScreen, Listing 7 begins by instantiating two objects of the GM2D04.Vector class.

Listing 7. Adding two vectors.

    GM2D04.Vector vecA = new GM2D04.Vector(
                            new GM2D04.ColMatrix(50,100));
    GM2D04.Vector vecB = new GM2D04.Vector(
                             new GM2D04.ColMatrix(75,25));

    GM2D04.Vector sumOf2 = vecA.add(vecB);
    

Then Listing 7 calls the new add method (see Listing 1) on one of the vectors, passing the other vector as a parameter to the method. The add method returns a third vector that is the sum of the other two vectors. The new vector is referred to as sumOf2 in Listing 7.

Draw vecA in RED with its tail at the origin

Recall that a vector has only two properties: length and direction. It does not have a position property. Therefore, if you decide to draw a vector, you can draw it anywhere in space, and one position is equally as valid as any other position.

Listing 8 sets the drawing color to RED and calls the draw method on vecA producing the red visual manifestation of the Vector object shown in Figure 1.

Listing 8. Draw vecA in RED with its tail at the origin.

    g2D.setColor(Color.RED);
    vecA.draw(g2D,new GM2D04.Point(
                              new GM2D04.ColMatrix(0,0)));

Note that I elected to draw the vector with its tail at the origin, but that was an arbitrary decision that I will discuss in more detail later.

Draw vecB in GREEN head-to-tail with vecA

While it's legal to draw a vector anywhere in space, certain positions may have advantages over other positions in some cases. You will see what I mean shortly. In the meantime, Listing 9 creates a visual manifestation of the vecB object with its tail at the head of the vecA object as shown by the green vector in Figure 1.

Listing 9. Draw vecB in GREEN head-to-tail with vecA.

    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       vecA.getData(0),vecA.getData(1))));

Draw sumOf2 in MAGENTA with its tail at the origin

Listing 10 creates a visual manifestation of the sumOf2 object with its tail at the origin as shown by the magenta vector in Figure 1. (In case you don't recognize the name of the color magenta, it looks similar to violet.) More correctly, Listing 10 draws the sumOf2 object with its tail coinciding with the tail of vecA. I elected to position the tails of the two vectors at the origin to keep the code a little simpler.

Listing 10. Draw sumOf2 in MAGENTA with its tail at the origin.

    g2D.setColor(Color.MAGENTA);
    sumOf2.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       0.0,0.0)));

The head-to-tail rule

We have just illustrated a very important rule that can make it much easier to visualize the results of vector addition. It is often called the head-to-tail rule.

If you add two or more vectors to produce a sum vector, and then draw the vectors that were included in the sum with the tail of one vector coinciding with the head of another vector, (as illustrated by the red and green vectors in Figure 1), the head of the last vector that you draw will be positioned at some particular point in space.

If you then draw the vector that represents the sum vector with its tail coinciding with the tail of the first vector that you drew, its head will coincide with the head of the last vector that you drew as shown by the magenta vector in Figure 1. The tail of the magenta vector coincides with the tail of the red vector, and the head of the magenta vector coincides with the head of the green vector. (It doesn't matter whether or not the coinciding tails are drawn at the origin.) Furthermore, this will be true regardless of the number of vectors included in the sum.

Extending the example to three vectors

Listing 11 extends this example to three vectors.

Listing 11. Extending the example to three vectors.

    //Now define another vector and add it to vecA and
    // vecB.
    GM2D04.Vector vecC = new GM2D04.Vector(
                           new GM2D04.ColMatrix(30,-150));

    //Draw vecD in BLUE with its tail positioned at the
    // sum of vecA and vecB
    g2D.setColor(Color.BLUE);
    vecC.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                   sumOf2.getData(0),sumOf2.getData(1))));

    //Define a vector as the sum of vecA, vecB, and vecC
    GM2D04.Vector sumOf3 = (vecA.add(vecB)).add(vecC);

    //Draw sumOf3 in BLACK with its tail at the origin.
    g2D.setColor(Color.BLACK);
    sumOf3.draw(g2D,new GM2D04.Point(
                          new GM2D04.ColMatrix(0.0,0.0)));

  }//end drawOffScreen

No overloaded operators
It is at times like this when I wish that Java supported overloaded operators in a manner similar to C++. Overloading the + operator would make the syntax much more intuitive than that shown by the boldface code in Listing 11.

You should understand this code

By now, you should have no difficulty understanding the code in Listing 11. The only tricky thing that I would call your attention to is the syntax of the code shown in boldface that adds the three vectors. You should make certain that you understand this syntax.

Back to the drawing

Now turn your attention back to the drawing in Figure 1. The red, green, and blue vectors are drawn in a head-to-tail manner as described earlier. The sumOf3 (black) vector is drawn with its tail coinciding with the tail of the first (red) vector. Note that the head of the black vector coincides with the head of the last (blue) vector in the sum. Although not a definitive proof, this is at least a strong indication that the head-to-tail rule works for adding any number of vectors.

An overridden paint method

The MyCanvas class also includes an overridden paint method. However, the code in that method is very similar to code that I explained in the earlier lesson titled Math for Java Game Programmers, Updating the Math Library for Graphics (see Resources). Therefore, I won't explain that code again in this lesson. You can view the overridden paint method in Listing 20.

That concludes the discussion of the program named VectorAdd01.

The program named CoordinateFrame01

A complete listing of this program is provided in Listing 21 near the end of the lesson. This program illustrates the relationship between the coordinate frame and a geometric object described in that coordinate frame.

A GUI allows the user to move the origin of the coordinate frame. Proper mathematical corrections are made such that a geometric object described in that coordinate frame maintains its position relative to world coordinates even when the coordinate frame is changed.  This causes its position relative to the coordinate frame to change.

Screen output at startup

Figure 2 shows the screen output of the program at startup.

Figure 2. Screen output from CoordinateFrame01 at startup.

The axes are difficult to see

At this point, the origin is at the upper-left corner of the canvas. Although orthogonal axes are drawn intersecting at the origin, they are at the very edge of the canvas and are only barely visible. I will later refer to this coordinate frame with the origin in the upper-left corner as world coordinates.

A point has been defined (but not drawn) at a location specified by X equal to 196 and Y equal to 165. Even though the point is not drawn, a red vector has been drawn with its tail located at that point. A green vector has been drawn with its tail at the head of the red vector. A blue vector has been computed as the sum of the red and green vectors and it has been drawn with its tail at the tail of the red vector. As you learned in the previous program, this results in a closed polygon that is drawn at the point defined above.

Modify the coordinate frame

Figure 3 shows the result of modifying the coordinate frame and then causing everything to be re-drawn. The coordinate frame is modified by entering values into the two user input fields and clicking the Replot button at the bottom of the GUI.

Figure 3. Screen output from CoordinateFrame01 after changes to the coordinate frame.

The new location of the origin

In Figure 3, the origin of the coordinate frame has been moved to the right by an amount that is equal to 40-percent of the width of the off-screen image, and has been moved down by an amount that is 60-percent of the height of the off-screen image. Then a pair of orthogonal axes was drawn, intersecting at the new location of the origin.

A point is simply a location in space

Although a vector doesn't have a location property and therefore is immune to changes in the coordinate frame, a point does have a location property. In fact that is the only property that a point does have, because a point is simply a location in space. Furthermore, that location is always specified relative to some coordinate frame. In order to cause the point to remain in the same location relative to world coordinates, (which was the objective here), its values relative to the current coordinate frame must be modified each time the coordinate frame is modified.

After the values representing the point were modified appropriately, the three vectors were drawn in Figure 3 with the tails of the red and blue vectors located at the point. This caused the geometric object described by the three vectors to remain in the same location relative to world coordinates. However, when the current coordinate frame no longer matched the world coordinate frame, the location of the geometric object changed relative to the current coordinate frame. The geometric object is closer to the origin of the current coordinate frame in Figure 3 than was the case in Figure 2.

More complicated code

The code in this program is somewhat more complicated than the code in the previous program, mainly due to the inclusion of an interactive GUI in the program. I have published many previous tutorials that explain how to write interactive programs in Java, and I won't repeat that explanation here. Instead, I will concentrate on the use of the game-math library in my explanation of this program. What this really means is that I am only going to explain the following three methods in this program:

  • actionPerformed
  • setCoordinateFrame
  • drawOffScreen

You can view the remaining program code in Listing 21 near the end of the lesson.

The actionPerformed method

This method must be defined in the class named GUI because the GUI class implements the ActionListener interface. Because an object of the GUI class is registered as a listener on the Replot button shown in Figure 2, the actionPerformed method is called each time the user clicks the button.

Listing 12. The actionPerformed method.

  public void actionPerformed(ActionEvent e){
    //Reset the coordinate frame to world coordinates by
    // reversing the most recent translation.
    setCoordinateFrame(g2D,-xOffset,-yOffset);

    //Compute new translation offsets based on user input.
    xOffsetFactor = 
               Double.parseDouble(xOffsetField.getText());
    yOffsetFactor = 
               Double.parseDouble(yOffsetField.getText());

    xOffset = osiWidth*xOffsetFactor;
    yOffset = osiHeight*yOffsetFactor;

    //Draw a new off-screen image based on user inputs
    // and copy it to the canvas. Note that the
    // drawOffScreen method will call the
    // setCoordinateFrame method again with the new
    // offset values for the origin of the coordinate
    // frame.
    drawOffScreen(g2D);
    myCanvas.repaint();
  }//end actionPerformed

What does the actionPerformed method do?

There is nothing complicated about the code in Listing 12. This code performs the following actions as indicated by the embedded comments:

  • Call the setCoordinateFrame method to reset the coordinate frame to world coordinates.
  • Get user input values and use them to compute and save new offset values that will be used to define the origin of the new coordinate frame.
  • Call the drawOffScreen method to erase the current image from the off-screen image and draw a new image on it using the new coordinate frame. Note that the drawOffScreen method will call the setCoordinateFrame method to establish the new coordinate frame before drawing the new image on the off-screen image.
  • Call the repaint method to cause the off-screen image to be copied to the canvas and displayed on the computer screen.

The setCoordinateFrame method

This method, which is shown in Listing 13, 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 coordinate frame.

Listing 13. The setCoordinateFrame method.

  private void setCoordinateFrame(
                  Graphics2D g2D,double xOff,double yOff){

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

    //Translate the origin by the specified amount
    g2D.translate((int)xOff,(int)yOff);

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

  }//end setCoordinateFrame method

What does the setCoordinateFrame method do?

As mentioned earlier, the origin of the world coordinate frame is the upper-left corner of the off-screen image. The setCoordinateFrame method assumes that the current coordinate frame coincides with the world coordinate frame when the method is called. This is because the method changes the coordinate frame relative to the current coordinate frame.

The method begins by painting the background white erasing anything already there. (Note that this step erases the entire image only when the coordinate frame coincides with the world coordinate frame when the method is called.)

Then the method establishes the new coordinate frame by translating the origin of the off-screen image using the X and Y offset values received as incoming parameters.

Finally, he method draws black orthogonal axes intersecting at the origin of the new coordinate frame on the off-screen image.

Beginning of the drawOffScreen method

The code for the method named drawOffScreen begins in Listing 14. This method is called once in the constructor for the GUI class (see Listing 21), and again each time the user clicks the Replot button shown in Figure 2.

The purpose of this method is to create some Vector objects and to cause visual manifestations of the Vector objects to be drawn onto an off-screen image.

Listing 14. Beginning of the drawOffScreen method.

  void drawOffScreen(Graphics2D g2D){
    setCoordinateFrame(g2D,xOffset,yOffset);

The drawOffScreen method begins by calling the setCoordinateFrame method to establish a new coordinate frame using offset values entered by the user, (or default offset values of 0.0 at startup).

Define a point to position the vectors

Listing 15 instantiates a new GM2D04.Point object that will be used to locate the three vectors that form the closed polygon shown in Figure 2. The location of the polygon relative to the world coordinate frame will remain the same regardless of how the current coordinate frame is changed.

Listing 15. Define a point to position the vectors.

    double startPointX = (196 - xOffset);
    double startPointY = (165 - yOffset);
    GM2D04.Point startPoint = new GM2D04.Point(
           new GM2D04.ColMatrix(startPointX,startPointY));

The boldface code in Listing 15 is probably the most important code in the entire program relative to the objective of the program. As I mentioned earlier, if you want the location of a point to remain the same relative to the world coordinate frame when you change the coordinate frame, you must modify the values that represent that point whenever you cause the current coordinate frame to be different from the world coordinate frame. The boldface code in Listing 15 makes that modification.

Remaining code in the drawOffScreen method

The remaining code in the drawOffScreen method is shown in Listing 16.

Listing 16. Remaining code in the drawOffScreen method.

    //Instantiate three Vector objects that form a closed
    // polygon when drawn head-to-tail.
    double vecAx = 25;
    double vecAy = 50;
    GM2D04.Vector vecA = new GM2D04.Vector(
                       new GM2D04.ColMatrix(vecAx,vecAy));

    double vecBx = 37;
    double vecBy = -12;
    GM2D04.Vector vecB = new GM2D04.Vector(
                             new GM2D04.ColMatrix(37,12));

    //Define vecC as the sum of vecA and vecB. It will be
    // of the correct length and direction to close the
    // polygon when drawn such that its tail coincides
    // with the tail of vecA.
    GM2D04.Vector vecC = vecA.add(vecB);

    //Draw vecA in red with its tail at startPoint.
    g2D.setColor(Color.RED);
    vecA.draw(g2D,startPoint);

    //Compute the location of the head of vecA relative to
    // the current coordinate frame.
    double headX =
                  vecA.getData(0) + startPoint.getData(0);
    double headY =
                  vecA.getData(1) + startPoint.getData(1);

    //Draw vecB in GREEN with its tail at the head of
    // vecA.
    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(
                      new GM2D04.ColMatrix(headX,headY)));

    //Draw vecC in BLUE with its tail at startPoint,
    // coinciding with the tail of vecA. This forms a
    // closed polygon.
    g2D.setColor(Color.BLUE);
    vecC.draw(g2D,startPoint);

  }//end drawOffScreen

With one exception, there is nothing in Listing 16 that I haven't already explained in earlier programs in this or previous lessons. Therefore, I won't repeat those explanations here.

The one exception

The one exception is shown by the boldface code in Listing 16. Recall from Figure 2 that we need to draw the green vector with its tail located at the head of the red vector. In order to do that, we must be able to determine the location of the head of the red vector relative to the current coordinate frame.

The getData method of the Vector class knows nothing about position. That method simply returns the X and Y coordinate values that describe the length of the vector relative to its tail. (For the special case where the tail is located at the origin, the getData method returns the X and Y coordinates of the head of the vector.) However, because the tail of the red vector in this case is not necessarily located at the origin, we must calculate the position of the head of the red vector taking the position of its tail (startPoint) into account. The boldface code in Listing 16 does just that.

That concludes the discussion of the program named CoordinateFrame01.

The program named VectorAdd02

Hopefully, you are studying the Kjell tutorial (see Resources) concurrently with your study of this lesson. This program illustrates the addition of two vectors using two different approaches that result in the parallelogram described by Kjell. That parallelogram is shown in the screen output in Figure 4.

Figure 4. Screen output from the program named VectorAdd02.

The method named drawOffScreen

A complete listing of this program is provided in Listing 22 near the end of the lesson. Most of the new material is contained in the method named drawOffScreen. Therefore, I will limit my explanation to the method named drawOffScreen, which begins in Listing 17.

Listing 17. The method named drawOffScreen.

  void drawOffScreen(Graphics2D g2D){

    //Translate the origin to a position near the bottom-
    // left corner of the off-screen image and draw a pair
    // of orthogonal axes that intersect at the origin.
    setCoordinateFrame(g2D);

    //Define two vectors.
    GM2D04.Vector vecA = new GM2D04.Vector(
                           new GM2D04.ColMatrix(50,-100));
    GM2D04.Vector vecB = new GM2D04.Vector(
                            new GM2D04.ColMatrix(75,-25));

    //Draw vecA in RED with its tail at the origin
    g2D.setColor(Color.RED);
    vecA.draw(g2D,new GM2D04.Point(
                              new GM2D04.ColMatrix(0,0)));

    //Draw vecB in GREEN with its tail at the head of vecA
    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       vecA.getData(0),vecA.getData(1))));

    //Define a third vector as the sum of the first
    // two vectors defined above by adding vecB to vecA.
    GM2D04.Vector sumA = vecA.add(vecB);

    //Draw sumA in BLACK with its tail at the origin.
    // The head will coincide with the head of the
    // green vecB.
    g2D.setColor(Color.BLACK);
    sumA.draw(g2D,new GM2D04.Point(
                          new GM2D04.ColMatrix(0.0,0.0)));

There is nothing new in Listing 17. The code in Listing 17 produces the black vector in Figure 4 plus the red and green vectors that appear above the black vector.

Do the same operations in a different order

Listing 18 begins by drawing the red and green vectors again. However, this time the green vector is drawn with its tail at the origin, and the red vector is drawn with its tail at the head of the green vector as shown by the green and red vectors below the black vector in Figure 4. This is perfectly legal because a vector has no location property. We can draw a vector anywhere we please provided we draw it with the correct length and direction.

Listing 18. Do the same operations in a different order.

    //Draw vecB in GREEN with its tail at the origin.
    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(
                              new GM2D04.ColMatrix(0,0)));

    //Draw vecA in RED with its tail at the head of vecB.
    g2D.setColor(Color.RED);
    vecA.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       vecB.getData(0),vecB.getData(1))));

    //Define a fourth vector as the sum of the first
    // two vectors defined above by adding vecA to vecB.
    GM2D04.Vector sumB = vecB.add(vecA);

    //Draw sumB in BLACK with its tail at the origin.
    // The head will coincide with the head of the
    // red vecA, and the visual manifestation of sumB will
    // overlay the visual manifestation of sumA
    g2D.setColor(Color.BLACK);
    sumB.draw(g2D,new GM2D04.Point(
                          new GM2D04.ColMatrix(0.0,0.0)));

  }//end drawOffScreen

Add the red and green vectors again

Then Listing 18 creates another vector by adding the red and green vectors and refers to the new vector as sumB. In the case of Listing 17, vecB is added to vecA to produce sumA. In Listing 18, vecA is added to vecB to produce sumB. In other words, the two addition operations are performed in reverse order in the two listings. As I have mentioned before, the order in which you add vectors doesn't matter. The result will be the same no matter the order in which you add them.

This is demonstrated in Listing 18 by drawing the vector referred to by sumB in black. As you can see in Figure 4, the drawing of sumB overlays the earlier drawing of sumA and you can't tell one from the other. (You know that the length of the two vectors is the same because the little circles at the heads of the two vectors overlay one another. You know that the directions are the same because sumB completely covers sumA.)

That concludes the discussion of the program named VectorAdd02.

The program named VectorAdd03

This program illustrates the fact that the length of the sum of two vectors is not necessarily equal to the sum of the lengths of the two vectors.

Two vectors are added by the program. The length of the sum is much smaller than the length of either of the original vectors. This is shown in Figure 5 where the red vector is added to the green vector producing the shorter black vector.

Figure 5. Graphic screen output from the program named VectorAdd03.

Command-line output

In addition to the graphic output shown in Figure 5, this program also produces three lines of text on the command-line screen as shown in Figure 6.

Figure 6. Command-line output from the program named VectorAdd03.

Red length = 111.80339887498948
Green length = 115.43396380615195
Black length = 18.027756377319946

The text output on the command-line screen shows the length of each vector. As you can see in Figure 6, the length of the black vector is much less than the length of either the red vector or the blue vector.

Not much that is new

The only thing that is new in this program is the use of the new getLength method of the GM2D04.Vector class to compute and display the length of each vector in Figure 6.

The source code for the getLength method is shown in Listing 2. This code computes the length of the vector as the square root of the sum of the squares of the X and Y components of the vector. Other than that code, there is no code in this program that warrants further explanation.

You can view a complete listing of the program in Listing 23 near the end of the lesson.

The program named VectorAdd04

This program illustrates the addition of a Vector object to a Point object producing a new Point object. Both points and the vector are drawn on the screen as shown in Figure 7.

Figure 7. Screen output from the program named VectorAdd04.

The only thing that is new in this program is the use of the new addVectorToPoint method of the GM2D04.Point class to add the vector to the point producing a new point.

The source code for the addVectorToPoint method is shown in Listing 3. You can view a complete listing of the program in Listing 24 near the end of the lesson.

A very powerful capability

Don't be fooled by the apparent simplicity of the addVectorToPoint method. The ability to add a vector to a point provides a powerful new capability to the game-math library. As you will see in the next lesson, this capability makes it possible not only to translate geometrical objects from one location in space to another, but also makes it possible to animate geometrical objects.

Run the programs

I encourage you to copy the code from Listing 20 through Listing 24, compile the code, and execute it in conjunction with the game-math library provided in Listing 19. 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 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 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's next?

In the next lesson you will learn how to use the game-math library for translation and animation in two dimensions.

Resources

Complete program listings

Complete listings of the programs discussed in this lesson are shown in Listing 19 through Listing 24 below.

Listing 19. 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 20. Source code for the program named VectorAdd01.

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

This program illustrates the addition of two or more
vectors. It also illustrates the Head-to-Tail rule
described by Kjell.

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

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

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

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

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

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

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

    //Draw some graphical objects on the off-screen
    // image that represent underlying data objects in
    // 2D space.
    drawOffScreen(g2D);

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

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

  //The purpose of this method is to add some Vector
  // objects and to cause visual manifestations of the raw
  // Vector objects and the resultant Vector objects to be
  // drawn onto an off-screen image.
  void drawOffScreen(Graphics2D g2D){

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

    //Define two vectors.
    GM2D04.Vector vecA = new GM2D04.Vector(
                            new GM2D04.ColMatrix(50,100));
    GM2D04.Vector vecB = new GM2D04.Vector(
                             new GM2D04.ColMatrix(75,25));

    //Define a third vector as the sum of the first
    // two vectors defined above.
    GM2D04.Vector sumOf2 = vecA.add(vecB);

    //Draw vecA in RED with its tail at the origin
    g2D.setColor(Color.RED);
    vecA.draw(g2D,new GM2D04.Point(
                              new GM2D04.ColMatrix(0,0)));
                              
    //Draw vecB in GREEN with its tail at the head of vecA
    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       vecA.getData(0),vecA.getData(1))));

    //Draw sumOf2 in MAGENTA with its tail at the origin.
    // The head will coincide with the head of vecB.
    g2D.setColor(Color.MAGENTA);
    sumOf2.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       0.0,0.0)));

    //Now define another vector and add it to vecA and
    // vecB.
    GM2D04.Vector vecC = new GM2D04.Vector(
                           new GM2D04.ColMatrix(30,-150));

    //Draw vecD in BLUE with its tail positioned at the
    // sum of vecA and vecB
    g2D.setColor(Color.BLUE);
    vecC.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                   sumOf2.getData(0),sumOf2.getData(1))));

    //Define a vector as the sum of vecA, vecB, and vecC
    GM2D04.Vector sumOf3 = (vecA.add(vecB)).add(vecC);

    //Draw sumOf3 in BLACK with its tail at the origin.
    g2D.setColor(Color.BLACK);
    sumOf3.draw(g2D,new GM2D04.Point(
                          new GM2D04.ColMatrix(0.0,0.0)));

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

  //This method is used to set the origin to a point near
  // the upper-left corner of the off-screen image  and
  // draw 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 method available for drawing a line.
  private void setCoordinateFrame(Graphics2D g2D){
    //Translate the origin.
    g2D.translate(0.2*osiWidth,0.2*osiHeight);

    //Draw new X and Y-axes in default BLACK
    g2D.drawLine(-(int)(0.2*osiWidth),0,
                 (int)(0.8*osiWidth),0);

    g2D.drawLine(0,-(int)(0.2*osiHeight),
                 0,(int)(0.8*osiHeight));

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


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

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

 

Listing 21. Source code for the program named CoordinateFrame01.

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

This program illustrates the relationship between the 
coordinate frame and a geometric object described in that 
coordinate frame. 

A GUI allows the user to move the origin of the coordinate 
frame. Proper mathematical corrections are made such that 
a geometric object described in that coordinate frame 
maintains its position relative to world coordinates even 
when the coordinate frame is changed. This causes its 
position relative to the coordinate frame to change.

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

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

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

  //The following offset values are applied to the width
  // and the height of the off-screen image to modify the 
  // coordinate frame. They are used to store user input
  // values.
  double xOffsetFactor = 0.0;
  double yOffsetFactor = 0.0;

  //The following offset values are computed using the
  // user-specified offset factor values.
  double xOffset;
  double yOffset;

  Graphics2D g2D;//Off-screen graphics context.

  //User input fields.
  JTextField xOffsetField;
  JTextField yOffsetField;
  //----------------------------------------------------//

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

    //Create a new drawing canvas and add it to the
    // center of the content pane.
    myCanvas = new MyCanvas();
    this.getContentPane().add(
                            BorderLayout.CENTER,myCanvas);

    //Create and populate a JPanel to be used as a user-
    // input panel and add it to the SOUTH position on
    // the content pane.
    JPanel controlPanel = new JPanel();
    controlPanel.add(new JLabel("X-offset"));
    xOffsetField = new JTextField("0.0",3);
    controlPanel.add(xOffsetField);

    controlPanel.add(new JLabel("Y-offset"));
    yOffsetField = new JTextField("0.0",3);
    controlPanel.add(yOffsetField);

    JButton button = new JButton("Replot");
    controlPanel.add(button);
    this.getContentPane().add(
                         BorderLayout.SOUTH,controlPanel);

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

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

    //Create some underlying data objects in
    // 2D space and draw visual manifestations of them.
    drawOffScreen(g2D);

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

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

  //The purpose of this method is to create some Vector
  // objects and to cause visual manifestations of them to
  // be drawn onto an off-screen image.
  void drawOffScreen(Graphics2D g2D){

    //Establish the current coordinate frame and prepare
    // the off-screen image with axes, etc.
    setCoordinateFrame(g2D,xOffset,yOffset);

    //Define a Point that will be used to locate three
    // vectors that form a closed polygon. The physical
    // location of the polygon on the canvas (world)
    // remains the same regardless of how the coordinate
    // frame is changed.
    double startPointX = (196 - xOffset);
    double startPointY = (165 - yOffset);
    GM2D04.Point startPoint = new GM2D04.Point(
           new GM2D04.ColMatrix(startPointX,startPointY));

    //Instantiate three Vector objects that form a closed
    // polygon when drawn head-to-tail.
    double vecAx = 25;
    double vecAy = 50;
    GM2D04.Vector vecA = new GM2D04.Vector(
                       new GM2D04.ColMatrix(vecAx,vecAy));

    double vecBx = 37;
    double vecBy = -12;
    GM2D04.Vector vecB = new GM2D04.Vector(
                             new GM2D04.ColMatrix(37,12));

    //Define vecC as the sum of vecA and vecB. It will be
    // of the correct length and direction to close the
    // polygon when drawn such that its tail coincides
    // with the tail of vecA.
    GM2D04.Vector vecC = vecA.add(vecB);

    //Draw vecA in red with its tail at startPoint.
    g2D.setColor(Color.RED);
    vecA.draw(g2D,startPoint);

    //Compute the location of the head of vecA relative to
    // the current coordinate frame.
    double headX = 
                  vecA.getData(0) + startPoint.getData(0);
    double headY = 
                  vecA.getData(1) + startPoint.getData(1);

    //Draw vecB in GREEN with its tail at the head of
    // vecA.
    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(
                      new GM2D04.ColMatrix(headX,headY)));

    //Draw vecC in BLUE with its tail at startPoint,
    // coinciding with the tail of vecA. This forms a
    // closed polygon.
    g2D.setColor(Color.BLUE);
    vecC.draw(g2D,startPoint);

  }//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.
  //The method paints the background white erasing
  // anything already there.
  private void setCoordinateFrame(
                  Graphics2D g2D,double xOff,double yOff){

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

    //Translate the origin by the specified amount
    g2D.translate((int)xOff,(int)yOff);

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

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

  //This method must be defined because the class 
  // implements the ActionListener interface. Because an
  // object of this class is registered as a listener on
  // the button, this method is called each time the user
  // presses the button.
  public void actionPerformed(ActionEvent e){
    //Reset the coordinate frame to world coordinates by
    // reversing the most recent translation.
    setCoordinateFrame(g2D,-xOffset,-yOffset);

    //Compute new translation offsets based on user input.
    xOffsetFactor = 
               Double.parseDouble(xOffsetField.getText());
    yOffsetFactor = 
               Double.parseDouble(yOffsetField.getText());

    xOffset = osiWidth*xOffsetFactor;
    yOffset = osiHeight*yOffsetFactor;

    //Draw a new off-screen image based on user inputs
    // and copy it to the canvas. Note that the
    // drawOffScreen method will call the
    // setCoordinateFrame method again with the new
    // offset values for the origin of the coordinate
    // frame.
    drawOffScreen(g2D);
    myCanvas.repaint();
  }//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 22. Source code for the program named VectorAdd02.

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

This program illustrates the addition of two vectors using
two different approaches that result in the parallelogram 
described by Kjell. It also illustrates the Head-to-Tail 
rule described by Kjell.

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

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

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

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

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

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

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

    //Draw some graphical objects on the off-screen
    // image that represent underlying data objects in
    // 2D space.
    drawOffScreen(g2D);

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

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

  //The purpose of this method is to add some Vector
  // objects and to cause visual manifestations of the raw
  // Vector objects and the resultant Vector objects to be
  // drawn onto an off-screen image.
  void drawOffScreen(Graphics2D g2D){

    //Translate the origin to a position near the bottom-
    // left corner of the off-screen image and draw a pair
    // of orthogonal axes that intersect at the origin.
    setCoordinateFrame(g2D);

    //Define two vectors.
    GM2D04.Vector vecA = new GM2D04.Vector(
                           new GM2D04.ColMatrix(50,-100));
    GM2D04.Vector vecB = new GM2D04.Vector(
                            new GM2D04.ColMatrix(75,-25));

    //Draw vecA in RED with its tail at the origin
    g2D.setColor(Color.RED);
    vecA.draw(g2D,new GM2D04.Point(
                              new GM2D04.ColMatrix(0,0)));

    //Draw vecB in GREEN with its tail at the head of vecA
    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       vecA.getData(0),vecA.getData(1))));

    //Define a third vector as the sum of the first
    // two vectors defined above by adding vecB to vecA.
    GM2D04.Vector sumA = vecA.add(vecB);

    //Draw sumA in BLACK with its tail at the origin.
    // The head will coincide with the head of the
    // green vecB.
    g2D.setColor(Color.BLACK);
    sumA.draw(g2D,new GM2D04.Point(
                          new GM2D04.ColMatrix(0.0,0.0)));

    //Do the same operations but in a different order.

    //Draw vecB in GREEN with its tail at the origin.
    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(
                              new GM2D04.ColMatrix(0,0)));

    //Draw vecA in RED with its tail at the head of vecB.
    g2D.setColor(Color.RED);
    vecA.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       vecB.getData(0),vecB.getData(1))));

    //Define a fourth vector as the sum of the first
    // two vectors defined above by adding vecA to vecB.
    GM2D04.Vector sumB = vecB.add(vecA);

    //Draw sumB in BLACK with its tail at the origin.
    // The head will coincide with the head of the
    // red vecA, and the visual manifestation of sumB will
    // overlay the visual manifestation of sumA
    g2D.setColor(Color.BLACK);
    sumB.draw(g2D,new GM2D04.Point(
                          new GM2D04.ColMatrix(0.0,0.0)));

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

  //This method is used to set the origin of the
  // off-screen image to a location near the lower-left
  // corner and to draw orthogonal axes that intersect
  // at the origin.
  private void setCoordinateFrame(Graphics2D g2D){

    //Translate the origin to a point near the lower-left
    // corner of the off-screen image.
    g2D.translate(0.2*osiWidth,0.8*osiHeight);

    //Draw new X and Y-axes in default BLACK
    g2D.drawLine(-(int)(0.2*osiWidth),0,
                 (int)(0.8*osiWidth),0);

    g2D.drawLine(0,-(int)(0.8*osiHeight),
                 0,(int)(0.2*osiHeight));   

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


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

  }//end inner class MyCanvas

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

 

Listing 23. Source code for the program named VectorAdd03.

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

This program illustrates the fact that the length of the 
sum of two vectors is not necessarily equal to the sum of 
the lengths of the two vectors. Two vectors are added such
and the length of the sum is much smaller than the length 
of either of the original vectors. The red and green 
vectors shown in the screen output are added producing the
black vector. The lengths of all three vectors are 
displayed on the command-line screen, demonstrating that 
the length of the black vector is much less than the 
length of either the red vector or the blue vector.

The only thing that is new in this program is the
getLength method of the GM2D04.Vector class.

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

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

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

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

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

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

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

    //Draw some graphical objects on the off-screen
    // image that represent underlying data objects in
    // 2D space.
    drawOffScreen(g2D);

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

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

  //The purpose of this method is to add some Vector
  // objects and to cause visual manifestations of the raw
  // Vector objects and the resultant Vector objects to be
  // drawn onto an off-screen image.
  void drawOffScreen(Graphics2D g2D){

    //Translate the origin and draw a pair of orthogonal
    // axes that intersect at the origin.
    setCoordinateFrame(g2D);

    //Define two vectors.
    GM2D04.Vector vecA = new GM2D04.Vector(
                           new GM2D04.ColMatrix(50,-100));
    GM2D04.Vector vecB = new GM2D04.Vector(
                           new GM2D04.ColMatrix(-35,110));

    //Define a third vector as the sum of the first
    // two vectors defined above.
    GM2D04.Vector sumOf2 = vecA.add(vecB);

    //Draw vecA in RED with its tail at the origin
    g2D.setColor(Color.RED);
    vecA.draw(g2D,new GM2D04.Point(
                              new GM2D04.ColMatrix(0,0)));

    //Draw vecB in GREEN with its tail at the head of vecA
    g2D.setColor(Color.GREEN);
    vecB.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                       vecA.getData(0),vecA.getData(1))));

    //Draw sumOf2 in BLACK with its tail at the origin.
    // The head will coincide with the head of vecB.
    g2D.setColor(Color.BLACK);
    sumOf2.draw(g2D,new GM2D04.Point(new GM2D04.ColMatrix(
                                               0.0,0.0)));

    System.out.println(
                      "Red length = " + vecA.getLength());
    System.out.println(
                    "Green length = " + vecB.getLength());
    System.out.println(
                  "Black length = " + sumOf2.getLength());
  }//end drawOffScreen
  //----------------------------------------------------//

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

    //Translate the origin to a point near the lower-left
    // corner of the off-screen image.
    g2D.translate(0.2*osiWidth,0.8*osiHeight);

    //Draw new X and Y-axes in default BLACK
    g2D.drawLine(-(int)(0.2*osiWidth),0,
                 (int)(0.8*osiWidth),0);

    g2D.drawLine(0,-(int)(0.8*osiHeight),
                 0,(int)(0.2*osiHeight));   

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


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

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

 

Listing 24. Source code for the program named VectorAdd04.

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

This program illustrates the addition of a Vector to a 
Point producing a new Point. Both points and the vector
are drawn on the screen.

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

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

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

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

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

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

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

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

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

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

  //The purpose of this method is to add a Vector object
  // to a Point object and to draw the result onto an
  // off-screen image..
  void drawOffScreen(Graphics2D g2D){

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

    //Define one point
    GM2D04.Point point = new GM2D04.Point(
                            new GM2D04.ColMatrix(50,-25));

    //Define one vector.
    GM2D04.Vector vecA = new GM2D04.Vector(
                            new GM2D04.ColMatrix(25,-50));

    //Add the Vector object to the Point object producing
    // a new Point object
    GM2D04.Point newPoint = point.addVectorToPoint(vecA);

    //Draw vecA in RED with its tail at the original
    // point.
    g2D.setColor(Color.RED);
    vecA.draw(g2D,point);

    //Draw the original point in GREEN.
    g2D.setColor(Color.GREEN);
    point.draw(g2D);

    //Draw the new point in BLUE.
    g2D.setColor(Color.BLUE);
    newPoint.draw(g2D);

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

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

    //Translate the origin to a point near the lower-left
    // corner of the off-screen image.
    g2D.translate(0.2*osiWidth,0.8*osiHeight);

    //Draw new X and Y-axes in default BLACK
    g2D.drawLine(-(int)(0.2*osiWidth),0,
                 (int)(0.8*osiWidth),0);

    g2D.drawLine(0,-(int)(0.8*osiHeight),
                 0,(int)(0.2*osiHeight));   

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


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

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

 


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