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

Updating the Math Library for Graphics: Math for Java Game Programmers

  • April 8, 2008
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Java Programming Notes # 1702


 

Preface

General

Second in a series

This tutorial is the second 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 previous lesson was titled Math for Java Game Programmers, Getting Started (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. (I will assume that you either already have, or can gain the required skills in geometry and trigonometry on your own. There are many excellent tutorials available on the web to help you in that quest.)

A different approach

Insofar as vectors and matrices are concerned, I will take a different approach in this series from the approach that I normally take. In particular, 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, I introduced you to the Kjell tutorial and recommended that you study CHAPTER 0 -- Points and Lines (see Resources). I also recommended that you study CHAPTER 1 -- Vectors, Points, and Column Matrices down through the topic titled Variables as Elements. If you haven't finished studying that material, I recommend that you do so while studying this lesson.

Then I presented and explained two sample programs and a sample game-programming math library intended to represents concepts from Kjell's tutorial in Java code.

What you will learn

In this lesson, you will learn how to update the math library to provide a number of new capabilities including the addition of graphics and various set methods to the library. You will also learn that the addition of set methods exposes certain vulnerabilities, and you will learn how to protect against those vulnerabilities. Finally, you will learn how to draw on off-screen images.

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 both sample programs.

Listings

  • Listing 1. Beginning of the class named PointLine03 and the class named GUI.
  • Listing 2. Beginning of the constructor for the GUI class.
  • Listing 3. Create two off-screen images.
  • Listing 4. Remaining code in the constructor.
  • Listing 5. Beginning of the drawOffscreen method.
  • Listing 6. Define four lines that will be used to draw borders.
  • Listing 7. Draw a visual manifestation of each line.
  • Listing 8. The draw method of the GM2D02.Line class.
  • Listing 9. Draw a visual manifestation of a GM2D02.Point object.
  • Listing 10. The draw method of the GM2D02.Point class.
  • Listing 11. Draw the vertices of a hexagon.
  • Listing 12. Draw six lines connecting the vertices of the hexagon.
  • Listing 13. Instantiate three objects of type GM2D02.Vector.
  • Listing 14. Call the draw method to draw the vector.
  • Listing 15. The draw method of the GM2D02.Vector class.
  • Listing 16. Three visual manifestations of the same vector.
  • Listing 17. Draw the blue vector.
  • Listing 18. The MyCanvas class and the overridden paint method.
  • Listing 19. The setData method of the ColMatrix class.
  • Listing 20. Updated constructor for the Point class in GM2D02.
  • Listing 21. The drawOffscreen method of the program named PointLine04.
  • Listing 22. Instantiate a point at the upper-left corner.
  • Listing 23. Instantiate a moveable Point object.
  • Listing 24. Instantiate a moveable Line object.
  • Listing 25. Relocate the Line to three more locations.
  • Listing 26. Source code for the game-math library class named GM2D02.
  • Listing 27. Source code for the program named PointLine03.
  • Listing 28. Source code for the program named PointLine04.

Supplementary material

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

General background information

So far, we are dealing with column matrices, points, vectors, and lines. (We will get to other topics in future lessons.)

What is a point?

According to Kjell, a point is simply a location in space. It has no width, depth, or height. Therefore, it cannot be seen by the human eye, which means that we can't draw a point on the computer screen. However, it is possible to draw an object on the computer screen that indicates the location of the point.

What do I mean by this?

Suppose you go out onto your driveway and establish that one corner of the driveway represents the origin in a coordinate framework. Then you use measuring devices to identify a specific location on the driveway. You can point to that location with your finger, and others can see your finger, but they can't see the point. Once again, the point has no width, height, or depth, and therefore cannot be seen by the human eye.

Mark the point

What you can do, however, is to take a piece of chalk and draw a small circle around your finger. Then others can see the mark that you have drawn as an estimate of the location of the point. It is very important however to remember that the chalk mark is not the point. It is simply a visual object that indicates the location of the point to some degree of accuracy. The actual location of the point is a piece of underlying data. The chalk mark is a graphic object that you have invented to represent that underlying data to a human observer.

Program output

For example, both of the programs that I will explain in this lesson will produce the same graphic screen output, which is shown in Figure 1.

Figure 1. Screen output from both sample programs.

The left image in Figure 1 contains seven small circles. Each of those seven circles marks the location of a point in the 2D space of the image. However, those circles are not the points. The seven points consist solely of coordinate values and are not visible to the human eye. The seven circles are graphics objects that were placed there to mark the locations in space of the seven points. The points existed in the 2D space before the graphics objects were created, and would have existed even if I had not caused the circles to be drawn to mark the locations of the points.

What is a line segment?

This can be even more confusing. Kjell tells us that a line segment is the straight path between two points, and that it has no thickness. Since it has no thickness, a line cannot be seen by the human eye.

The left image in Figure 1 contains six graphics objects that we are likely to call lines or line segments. True enough, those graphics objects mark the path taken by the lines that represent straight paths between the pairs of points whose locations are indicated by the circles. However, those graphics objects are not lines in the context of this discussion. Rather, they are simply graphical objects that were used to mark the paths of the lines.

Why am I emphasizing this so heavily?

Shortly, you will see the changes that I made to the game-math library in preparation for this lesson. That library makes it possible for you easily create and manipulate underlying data objects for column matrices, points, vectors, and lines. Those changes include the ability for points, lines, and vectors to create graphical objects that represent themselves and to display those graphical objects on a specified graphics context upon request. The left image in Figure 1 shows the manner in which GM2D02.Point objects and GM2D02.Line objects represent themselves on a graphics context. The right image in Figure 1 shows the manner in which GM2D02.Vector objects represent themselves on a graphics context.

It is important to understand the difference between the objects that encapsulate the underlying data and the graphical objects that represent those objects for visible human consumption. Later on, we will be doing math using the underlying data objects. However, it is generally not possible to do math using the graphical objects shown in Figure 1. Additional modifications that I made to the library for this lesson makes it is very easy to modify the values encapsulated in one of the underlying data objects. However, once the graphical representation of one of those data objects is drawn on the graphics context, it is fairly difficult to modify, particularly if it overlaps another graphical object that you don't want to modify. The library does not provide that capability.

All Java parameters are passed to methods by value

Moving to a completely different topic, when you call out the name of a variable as a parameter to be passed to a method in Java, a copy of the contents of that variable is made and the copy is passed to the method. Code in the method can modify the copy but it cannot modify the contents of the original variable. This is typically referred to as passing parameters by value. (Despite what you may have read elsewhere, unlike C and C++, Java parameters are never passed by reference.)

Passing reference variables by value exposes some vulnerabilities in the original version of the library when set methods are added to the library. A complete explanation of this topic is beyond the scope of this lesson. However, I will show you how I modified the code in the library to protect against such vulnerabilities.

Preview

I will explain the changes to the library in this lesson in conjunction with two programs that illustrate the impact of those changes. Both programs produce the screen output shown in Figure 1, but they do so in significantly different ways.

The primary topics to be discussed have to do with:

  • Making it possible for the underlying data objects to represent themselves by drawing graphical objects on a specified 2D graphics context upon request.
  • Making it possible to call set methods to modify the data encapsulated in the underlying data objects.
  • Protecting the data from corruption due to certain vulnerabilities exposed by adding set methods to the library.

Discussion and sample code

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

Much of the code in the library remains unchanged. I explained that code in the previous lesson (see Resources) and I won't repeat that explanation in this lesson. Rather, in this lesson, I will concentrate on explaining the modifications that I made to the library.

The game-programming library named GM2D02

A complete listing of the modified game-programming library named GM2D02 is provided in Listing 26.

This library is an update of the earlier game-math library named GM2D01. This update added the following new capabilities:

  • Draw circles to represent GM2D02.Point objects.
  • Draw visible lines to represent GM2D02.Line objects.
  • Draw visible lines and circles to represent GM2D02.Vector objects.
  • Call a setData method to change the values in a GM2D02.ColMatrix object.
  • Call a setData method to change the values in a GM2D02.Point object.
  • Call a setData method to change the values in a GM2D02.Vector object.
  • Call a setHead method or a setTail method to change one of the points that defines a GM2D02.Line object.

Constructors were updated to ensure that existing points, vectors, and lines are not corrupted by using the new set methods to change the values in the ColMatrix and/or Point objects originally used to construct the points, vectors, and lines. The updated constructors create and save clones of the ColMatrix and/or Point objects originally used to define the Point, Vector, and/or Line objects.

I will explain the new code in the library in conjunction with the explanations of the programs named PointLine03 and PointLine04.

The program named PointLine03

A complete listing of the program named PointLine03 is provided in Listing 27.

This program illustrates the use of draw methods that were added to the GM2D02 game-math library to produce visual manifestations of Point, Line, and Vector objects instantiated from classes in the game-math library. The program also illustrates drawing on off-screen images and then copying those images to a Canvas object in an overridden paint method.

As mentioned earlier, this program produces the screen output shown in Figure 1.

Beginning of the class named PointLine03 and the class named GUI

Listing 1 shows the beginning of the class named PointLine03 (including the main method) and the beginning of the class named GUI.

Listing 1. Beginning of the class named PointLine03 and the class named GUI.

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

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

The code in the main method instantiates an object of the GUI class. The code showing in the GUI class declares several instance variables, initializing some of them.

Beginning of the constructor for the GUI class

Listing 2 shows the beginning of the constructor for the GUI class.

Listing 2. Beginning of the constructor for the GUI class.

  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()/2;
    osiHeight = myCanvas.getHeight();

The code in Listing 2 is straightforward and shouldn't require an explanation beyond the embedded comments.

Create two off-screen images

Here is where things begin to get interesting. Listing 3 creates two off-screen images and gets a graphics context on each of them.

Listing 3. Create two off-screen images.

    osiA = createImage(osiWidth,osiHeight);
    Graphics2D g2Da = (Graphics2D)(osiA.getGraphics());

    osiB = createImage(osiWidth,osiHeight);
    Graphics2D g2Db = (Graphics2D)(osiB.getGraphics());

Java programs are not constrained to simply drawing on the screen. It is possible to create an off-screen image, get a graphics context on it, and use methods of the Graphics and/or Graphics2D classes to draw on the off-screen image.

The off-screen image then can be used for a variety of purposes including:

  • Writing the image into a disk file.
  • Copying the image to a Canvas object for display on the screen.

Will copy off-screen images to the screen

In this program, we will copy each of the off-screen images to a Canvas object in a side-by-side format (see Figure 1) and display the Canvas object on the content pane in a JFrame object.

While animation is not the purpose of using off-screen images in this program, such images are often used to produce clean animation. Code is executed to produce an off-screen image and then the image is copied to the screen in a single blast. This prevents the user from seeing the development of the image that might otherwise be visible if the code was executed to produce the image directly onto the screen.

The createImage method

There are seven different versions of the createImage method in the JDK 1.6 standard library. The version of the method used in this program is defined in the Component class and inherited into the JFrame class. According to the Sun documentation, this method creates an off-screen drawable image to be used for double buffering.

The two parameters are width and height. If the image is later displayed on the screen, the units of width and height are measured in pixels. The values contained in the width and height parameters in Listing 3 were computed in Listing 2. The width of each off-screen image is set to half the width of the canvas. The height of each off-screen image is set to the height of the canvas. This facilitates the display of the two images in the side-by-side format shown in Figure 1.

Also, according to the Sun documentation, the method returns "an off-screen drawable image, which can be used for double buffering. The return value may be null if the component is not displayable." The returned value is type Image.

The getGraphics method

There are eleven different versions of the getGraphics method in the JDK 1.6 standard library. The version of the method used in this program is defined in the Image class. This method is called on the Image object returned by the createImage method in Listing 3.

According to the Sun documentation, this method creates a graphics context for drawing to an off-screen image. You will note that the reference to the graphics context was cast to type Graphics2D and saved in a reference variable of the type Graphics2D. I explained the relationship between the Graphics class and the Graphics2D class in the previous lesson (see Resources).

At this point, we have two off-screen image objects of type Image and we have a graphics context on each of them. This makes it possible to use the methods of the Graphics class and/or the methods of the Graphics2D class to draw on either of the off-screen images.

Remaining code in the constructor

Listing 4 shows the remainder of the constructor for the GUI class.

Listing 4. Remaining code in the constructor.

    drawOffscreen(g2Da,g2Db);

    myCanvas.repaint();

  }//end constructor

Listing 4 begins by calling the method named drawOffscreen to draw some points, lines, and vectors on the two off-screen images. Note that references to each of the two graphics contexts are passed as parameters to thedrawOffscreen method.

Then Listing 4 calls the repaint method belonging to the MyCanvas object to cause the overridden paint method belonging to that object to be executed. Later, we will see that the overridden paint method copies the two off-screen images to the canvas in the side-by-side format shown in Figure 1 causing the contents of those off-screen images to be displayed in a JFrame object.

Beginning of the drawOffscreen method

The beginning of the drawOffscreen method is shown in Listing 5. The purpose of this method is to define points, lines, and vectors as underlying data objects and then to cause a visual manifestation of some of the points, lines, and vectors to be drawn onto the two off-screen images.

Listing 5. Beginning of the drawOffscreen method.

  void drawOffscreen(Graphics2D g2Da,Graphics2D g2Db){

    //Draw a label on each off-screen image.
    g2Da.drawString("Off-screen image A",
                    osiWidth/8,
                    osiHeight/8);
    g2Db.drawString("Off-screen image B",
                    osiWidth/8,
                    osiHeight/8);

Before getting into the underlying data objects, however, Listing 5 calls the drawString method belonging to the Graphics class to draw the two text strings shown in Figure 1 on the two off-screen images.

Instantiate four Line objects that will be used to draw borders

Listing 6 instantiates four GM2D02.Line objects that will ultimately be used to draw the borders around the left and right images shown in Figure 1.

Listing 6. Define four lines that will be used to draw borders.

    //First define four points that will be used to define
    // the ends of the four lines.
    GM2D02.Point upperLeftPoint = new GM2D02.Point(
                           new GM2D02.ColMatrix(1.0,1.0));
    GM2D02.Point upperRightPoint = new GM2D02.Point(
                    new GM2D02.ColMatrix(osiWidth-1,1.0));
    GM2D02.Point lowerRightPoint = new GM2D02.Point(
            new GM2D02.ColMatrix(osiWidth-1,osiHeight-1));
    GM2D02.Point lowerLeftPoint = new GM2D02.Point(
                   new GM2D02.ColMatrix(1.0,osiHeight-1));

    //Now define the four lines based on the endpoints.
    GM2D02.Line top = new GM2D02.Line(upperLeftPoint,
                                      upperRightPoint);
    GM2D02.Line rightSide = new GM2D02.Line(
                                         upperRightPoint,
                                         lowerRightPoint);
    GM2D02.Line bottom = new GM2D02.Line(lowerLeftPoint,
                                         lowerRightPoint);
    GM2D02.Line leftSide = new GM2D02.Line(upperLeftPoint,
                                          lowerLeftPoint);

Nothing new here

There is nothing in Listing 6 that is new relative to what you learned in the previous lesson (see Resources). Listing 6 starts by defining four GM2D02.Point objects that will be used to define the ends of the line segments. References to these Point objects are passed to the constructor for the GM2D02.Line class to instantiate the four Line objects.

Note that the locations of the points, and hence the positions of the lines were one pixel away from the edges of the canvas towards the center of the canvas. These locations were chosen to ensure that the resulting visual manifestations of the lines would be visible on the canvas.

It is also extremely important to understand that the GM2D02.Point and GM2D02.Line objects instantiated in Listing 6 are not graphical objects. Rather, they are underlying data objects, which are suitable for use in mathematical operations.

Draw a visual manifestation of each line

Listing 7 calls the draw method of the GM2D02.Line class four times in succession for each off-screen image. Note that a reference to the off-screen image is passed as a parameter to the draw method each time the method is called on one of the GM2D02.Line objects.

Listing 7. Draw a visual manifestation of each line.

    //Now draw a visual manifestation of each line
    // on g2Da.
    top.draw(g2Da);
    rightSide.draw(g2Da);
    bottom.draw(g2Da);
    leftSide.draw(g2Da);

    //Now draw a visual manifestation of each of the same
    // four lines on g2Db
    top.draw(g2Db);
    rightSide.draw(g2Db);
    bottom.draw(g2Db);
    leftSide.draw(g2Db);

As you will see shortly, each of these calls to the draw method causes an object of the Line2D.Double class to be rendered onto the specified off-screen image. When the off-screen image is ultimately copied to the canvas object by the overridden paint method, this produces a visual manifestation of the GM2D02.Line object. Once again, however, it is very important to understand that the Line2D.Double object is a graphical object and the GM2D02.Line is an underlying data object. They are completely independent objects.

The draw method of the GM2D02.Line class

Now it's time to explain one of the methods that was added to the original game-math library named GM2D01 to produce the new library named GM2D02. Listing 8 shows the draw method that was called repeatedly on the GM2D02.Line objects in Listing 7.

Listing 8. The draw method of the GM2D02.Line class.

    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

Construct and render a Line2D.Double object on the graphics context

Listing 8 calls the getTail and getHead methods to get the x and y coordinate values (relative to the current coordinate frame) that define the ends of the line segment represented by the GM2D02.Line object on which the draw method was called. These four values are passed as parameters to the constructor for the Line2D.Double class to define the endpoints of a Line2D.Double object.

Then the draw method of the Graphics2D class is called on the reference to the incoming graphics context parameter to cause the Line2D.Double object to be rendered onto that graphics context. In this case, that graphics context is an off-screen graphics context. However, it could just as easily be an on-screen graphics context such as the graphics context that is received as a parameter to an overridden paint method.

Hallmarks of object-oriented programming

An object of the GM2D02.Line class knows how to produce a visual manifestation of itself onto a specified graphics context as illustrated by the borders surrounding the left and right images in Figure 1. This is one of the hallmarks of object-oriented programming. Objects know how to do useful things for themselves. Later on, we will see other hallmarks such as the ability of an object of the GM2D02.Vector class to add itself to another object of the same class and return a new vector that is the sum of the two vectors.

Draw a visual manifestation of a GM2D02.Point object

Listing 9 begins by calling the translate method of the Graphics class on the off-screen image shown in the left side of Figure 1 for the purpose of changing the origin from the upper-left corner of the graphics context to the center of the graphics context.

Listing 9. Draw a visual manifestation of a GM2D02.Point object.

    g2Da.translate(osiWidth/2.0,osiHeight/2.0);

    GM2D02.Point origin = new GM2D02.Point(
                           new GM2D02.ColMatrix(0.0,0.0));
    origin.draw(g2Da);

This is equivalent to changing the coordinate frame described by Kjell and discussed in the previous lesson (see Resources).

Then the code in Listing 9 defines a GM2D02.Point object located at the origin of the new coordinate frame and calls the draw method on that object to produce the visual manifestation of the object in the center of the left image in Figure 1. Once again, the GM2D02.Point object is an underlying data object. The visual manifestation will be produced by an object of the class Ellipse2D.Double, which will be a completely independent object.

The draw method of the GM2D02.Point class

Listing 10 shows the draw method that was added to the GM2D02.Point class to produce the updated game-math library. This method draws a small circle around the location of the point on the specified graphics context.

Listing 10. The draw method of the GM2D02.Point class.

    public void draw(Graphics2D g2D){
      Ellipse2D.Double circle = 
                        new Ellipse2D.Double(getData(0)-3,
                                             getData(1)-3,
                                             6,
                                             6);
      g2D.draw(circle);
    }//end draw

The logic behind this method is very similar to the logic that I explained relative to Listing 8. The constructor for the Ellipse2D.Double class requires four incoming parameters that specify the coordinates of the upper-left corner of a rectangle followed by the width and the height of the rectangle. The object then represents an ellipse that is bounded by the four sides of the rectangle. If the rectangle is square, the ellipse becomes a circle.

Listing 10 calls the draw method of the Graphics2D class to render the circle at the specified location on the graphics context specified by the incoming parameter. Thus the code in Listing 9 produces a visual manifestation of a point at the origin of the current coordinate frame. The visual manifestation consists of a small circle centered on the location of the point, resulting in the small circle at the center of the left image in Figure 1.

Draw the vertices of a hexagon

Listing 11 instantiates six GM2D02.Point objects that represent the vertices of a hexagon that is symmetrically located relative to the origin in the current coordinate frame

Listing 11. Draw the vertices of a hexagon.

    //First define three constants to make it easier to
    // write the code.
    final double aVal = osiWidth/4.0*0.5;
    final double bVal = osiWidth/4.0*0.866;
    final double cVal = osiWidth/4.0;
    //Now define the points.
    GM2D02.Point point0 = new GM2D02.Point(
                          new GM2D02.ColMatrix(cVal,0.0));
    GM2D02.Point point1 = new GM2D02.Point(
                         new GM2D02.ColMatrix(aVal,bVal));
    GM2D02.Point point2 = new GM2D02.Point(
                        new GM2D02.ColMatrix(-aVal,bVal));
    GM2D02.Point point3 = new GM2D02.Point(
                         new GM2D02.ColMatrix(-cVal,0.0));
    GM2D02.Point point4 = new GM2D02.Point(
                       new GM2D02.ColMatrix(-aVal,-bVal));
    GM2D02.Point point5 = new GM2D02.Point(
                        new GM2D02.ColMatrix(aVal,-bVal));

    //Now draw a visual manifestation of each of the six
    // points on g2Da.
    point0.draw(g2Da);
    point1.draw(g2Da);
    point2.draw(g2Da);
    point3.draw(g2Da);
    point4.draw(g2Da);
    point5.draw(g2Da);

Then Listing 11 calls the draw method of the GM2D02.Point class six times in succession to cause small circles that represent the six points to be rendered on the specified off-screen image. You can see those six circles in the left image in Figure 1.

Draw six lines connecting the vertices of the hexagon

Listing 12 instantiates six objects of the GM2D02.Line class whose endpoints are specified by the six GM2D02.Point objects from Listing 11, taken in pairs.

Listing 12. Draw six lines connecting the vertices of the hexagon.

    GM2D02.Line line01 = new GM2D02.Line(point0,point1);
    GM2D02.Line line12 = new GM2D02.Line(point1,point2);
    GM2D02.Line line23 = new GM2D02.Line(point2,point3);
    GM2D02.Line line34 = new GM2D02.Line(point3,point4);
    GM2D02.Line line45 = new GM2D02.Line(point4,point5);
    GM2D02.Line line50 = new GM2D02.Line(point5,point0);

    //Now draw a visual manifestation of each line
    // on g2Da.
    line01.draw(g2Da);
    line12.draw(g2Da);
    line23.draw(g2Da);
    line34.draw(g2Da);
    line45.draw(g2Da);
    line50.draw(g2Da);

Then Listing 12 calls the draw method belonging to the GM2D02.Line class six times in succession to cause visible lines to be rendered on the specified off-screen image. You can see those six lines in the left image in Figure 1.

That completes the drawing that is performed on the off-screen image shown in the left image in Figure 1. Next I will explain the drawing that is performed on the off-screen image that is shown as the right image in Figure 1.

Instantiate three objects of type GM2D02.Vector

According to Kjell, "A vector is a geometrical object that has two properties: length and direction." He also tells us, "A vector does not have a position." (See Resources)

The right image in Figure 1 shows visual manifestations three different objects of the GM2D02.Vector class. One vector is represented as a red line with a small red circle at its head. A second vector is represented as a blue line with a small blue circle at its head. The third vector is shown in three different positions represented by green lines with small green circles at their heads.

Listing 13 instantiates three objects of the GM2D02.Vector class, which are visually represented by the red, green, and blue lines in Figure 1. References to these three objects are saved in the variables named vecA, vecB, and vecC in Listing 13.

Listing 13. Instantiate three objects of type GM2D02.Vector.

    GM2D02.Vector vecA = new GM2D02.Vector(
                            new GM2D02.ColMatrix(50,100));
    GM2D02.Vector vecB = new GM2D02.Vector(
                             new GM2D02.ColMatrix(75,25));
    GM2D02.Vector vecC = new GM2D02.Vector(
                           new GM2D02.ColMatrix(125,125));

Call the draw method to draw the vector

Listing 14 begins by setting the drawing color to red for the off-screen image on which the visual manifestation of the first Vector object will be drawn. Then Listing 14 calls the draw method of the GM2D02.Vector class to cause the object referred to by vecA to be represented as a (red) line with its tail at the origin. (Unlike the image on the left side of Figure 1, the origin for the image on the right was not translated to the center.)

Listing 14. Call the draw method to draw the vector.

    g2Db.setColor(Color.RED);

    vecA.draw(g2Db,new GM2D02.Point(
                              new GM2D02.ColMatrix(0,0)));

Note that two parameter are passed to the draw method in Listing 14:

  • A reference to the off-screen graphics context on which the visual manifestation of the vector will be drawn.
  • A new object of the class GM2D02.Point that will be used to determine the position on the off-screen image in which the visual manifestation will appear.

Remember that according to Kjell, a vector doesn't have a position. Hence, there is nothing in the underlying data for a GM2D02.Vector object that specifies a position. In other words, the visual manifestation of a vector can be placed anywhere in space, and one placement is just as correct as the next. However, if you become heavily involved in the use of vectors, you will learn that certain placements may be preferable to others in some cases so as to better represent the problem being modeled by the use of vectors.

The draw method of the GM2D02.Vector class

Listing 15 shows the draw method that was added to the GM2D02.Vector class. This method is a little longer than the draw methods that I explained earlier for points and lines. This is mainly because:

  • It is necessary to deal with the issue of positioning the visual manifestation of the GM2D02.Vector object.
  • It is necessary to embellish the drawing to make it possible to visually determine which end is the tail and which end is the head.

This method renders a visual manifestation of a GM2D02.Vector on the specified graphics context, with the tail of the vector located at a point specified by the contents of a GM2D02.Point object. A small circle is drawn to visually identify the head of the vector.

Listing 15. The draw method of the GM2D02.Vector class.

    public void draw(Graphics2D g2D,GM2D02.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

Why a circle and not an arrowhead?

When we draw vectors, (particularly when drawing by hand), we often draw a small arrowhead at the head of the vector. Remember, a vector has only two properties: length and direction. The arrowhead normally points in the direction indicated by the direction property. However, there is nothing magic about an arrowhead. Circles are much easier to draw with a computer than arrowheads. As long as you can identify the head of the vector, you can always determine the direction. Although I didn't give a lot of thought to optimization when developing this library, this is one case where I took the approach that is likely to consume the minimum amount of computational resources.

In Figure 1, the length of each of the lines in the right image indicates the length property of the respective vector, and the small circle indicates the head. The orientation relative to the current coordinate frame represents the direction.

Beyond that, further explanation of the code in Listing 15 should not be required.

Three visual manifestations of the same vector

Listing 16 produces three visual manifestations at three different positions for the same Vector object referred to by the reference variable named vecB. All three of the visual manifestations are colored green in Figure 1.

Listing 16. Three visual manifestations of the same vector.

    g2Db.setColor(Color.GREEN);
    vecB.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                       vecA.getData(0),vecA.getData(1))));
    vecB.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                 vecA.getData(0)-10,vecA.getData(1)+15)));
    vecB.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                 vecA.getData(0)+10,vecA.getData(1)-60)));

This is a perfectly legitimate thing to do since the underlying data encapsulated in the Vector object does not contain any information relating to the position of the visual manifestation.

In one visual manifestation, Listing 16 causes the tail of vecB to coincide with the head of vecA. This is one of those cases where drawing the vector in one position is preferable to drawing it in a different position. Later on when we get into the topic of vector addition, I will probably explain that one way to perform graphical addition of vectors is to position the vectors in a tail-to-head relationship as indicated by the red and green vectors in Figure 1. The sum of the vectors can then be determined graphically by drawing a line from the tail of the first vector to the head of the last vector as indicated by the blue line in Figure 1. The length of the sum (or resultant) vector is indicated by the length of that line, and the direction of the resultant vector is indicated by the orientation of that line.

A practical example

This is the solution to a classical problem in a freshman engineering class. Assume that a boat is traveling diagonally across a river with a strong current. Assume that the green vector in Figure 1 represents the speed and direction of the current in the river. Assume that the red vector represents the speed and direction that the boat would travel if there were no current. Because of the effect of the current on the boat, the actual speed and direction of the boat will be given by the blue vector, which is different from the speed and direction indicated by the red vector. If the blue vector is pointing at the desired landing point on the other side of the river, everything is okay. Otherwise, the captain needs to change the speed and/or the direction of the boat to compensate for the effect of the current on the boat.

Draw the blue vector

Listing 17 produces the blue visual manifestation of the Vector object referred to by vecC as shown in Figure 1.

Listing 17. Draw the blue vector.

    g2Db.setColor(Color.BLUE);
    vecC.draw(g2Db,new GM2D02.Point(
                              new GM2D02.ColMatrix(0,0)));

  }//end drawOffscreen

Listing 17 also signals the end of the method named drawOffscreen.

The MyCanvas class and the overridden paint method

Listing 18 shows the entire inner class named MyClass including the overridden paint method belonging to that class.

Listing 18. The MyCanvas class and the overridden paint method.

  //This is an inner class of the GUI class.
  class MyCanvas extends Canvas{
    public void paint(Graphics g){
      g.drawImage(osiA,0,0,this);
      g.drawImage(osiB,this.getWidth()/2,0,this);
    }//end overridden paint()
    
  }//end inner class MyCanvas

}//end class GUI

The overridden paint 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 call the drawImage method twice in succession to draw the two off-screen images on the screen in the side-by-side format shown in Figure 1.

The first parameter to the drawImage method is the off-screen image that is to be drawn on the canvas. The second and third parameters specify the location where the upper-left corner of the image will be drawn.

The fourth parameter is a reference to an ImageObserver object, which is essentially a dummy image observer in this case, because an actual image observer isn't needed. (To learn more about image observers, see The AWT Package, Graphics- Introduction to Images in Resources.)

End of the program

Listing 18 also signals the end of the GUI class and the end of the program named PointLine03.

The program named PointLine04

A complete listing of this program is provided in Listing 28. Just like the previous program named PointLine03, this program produces the screen output shown in Figure 1. However, it produces that output in a significantly different way.

This program emphasizes the differences between graphics objects and underlying data objects. It also illustrates the use of the new setData methods of the Point class and the Vector class, along with the new setTail and setHead methods of the Line class. These methods were added to the game-math library when it was updated to the GM2D02 version.

Dealing with some vulnerabilities

The addition of the new set methods exposed some vulnerabilities in the original version of the game-math library. I will explain how the code in the library was modified to deal with that issue. In particular, constructors were updated to ensure that existing points, vectors, and lines are not corrupted by using the new set methods to change the values in the ColMatrix and/or Point objects originally used to construct the points, vectors, and lines. The updated constructors create and save clones of the ColMatrix and/or Point objects originally used to define the Point, Vector, and/or Line objects.

Will explain the code in fragments

As before, I will explain the code in fragments.  Some of the code in this program is identical to or very similar to code that I have already explained in the program named PointLine03. I won't repeat that explanation. Instead, I will concentrate on the differences between the two programs.

The setData method of the ColMatrix class

The new setData method of the ColMatrix class is shown in Listing 19.

Listing 19. The setData method of the ColMatrix class.

    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 is a very simple method, which should not require further explanation.  I am presenting it here mainly for the purpose of leading into a discussion of the vulnerabilities that this method could expose on Point and Vector objects instantiated from the original math class named GM2D01.

You may recall from the lesson titled Math for Java Game Programmers, Getting Started (see Resources) that the constructor for the Point class in the original library received and saved a reference to an object of the ColMatrix class that contained the two values required to define the Point. Now that it is possible to change the values stored in an object of the ColMatrix class, if such an object is used to instantiate an object of the Point class and then the values stored in the ColMatrix object are changed, the values that define the Point object will change accordingly. This is not good.

Updated constructor for the Point class in GM2D02

Listing 20 shows the updated constructor in the new version of the library.

Listing 20. Updated constructor for the Point class in GM2D02.

  public static class Point{
    GM2D02.ColMatrix point;

    Point(GM2D02.ColMatrix point){//constructor
      this.point = 
         new ColMatrix(point.getData(0),point.getData(1));
    }//end constructor

The new version of the constructor for the Point class creates and saves 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 the use of its new setData method.

Updated constructor for the Vector class in GM2D02

Because the constructor for the Vector class in the original library was essentially the same as the constructor for the Point class, the addition of the setData method to the ColMatrix class created the same vulnerability for objects instantiated from the Vector class. Essentially the same correction was made for the constructor for the Vector class. You can view the new code in Listing 26.

Additional changes to the library

In addition to the changes described above, the following changes were made to the game-math library to update it from version GM2D01 to GM2D02. All of the changes were straightforward and shouldn't require an explanation beyond the embedded comments. You can view the new code for all of these changes in Listing 26.

  • Added a setData method for the Point class.
  • Updated to the constructor for the Line class to deal with the same kind of vulnerability described above.
  • Added a setTail method for the Line class.
  • Added a setHead method for the Line class.

The drawOffscreen method of the program named PointLine04

Most of the differences between the programs named PointLine03 and PointLine04 occur in the method named drawOffscreen. I will concentrate my discussion there.

The drawOffscreen method begins in Listing 21.

Listing 21. The drawOffscreen method of the program named PointLine04.

  void drawOffscreen(Graphics2D g2Da,Graphics2D g2Db){

    //Draw a label on each off-screen image.
    g2Da.drawString("Off-screen image A",
                    osiWidth/8,
                    osiHeight/8);
    g2Db.drawString("Off-screen image B",
                    osiWidth/8,
                    osiHeight/8);

As before, the purpose of this method is to define points, lines, and vectors and then to cause a visual manifestation of some of the points, lines, and vectors to be drawn onto two separate off-screen images. Unlike before, however, this version of the method uses a minimum number of underlying data objects to produce the output, thereby emphasizing the differences between graphics objects and underlying data objects.

The code in Listing 21 is essentially the same as before and doesn't require any explanation.

As before, and as shown in Figure 1, this method draws borders on each of the off-screen images by drawing lines parallel to the edges of the off-screen images. Each line is offset by one pixel toward the center of the off-screen image.

Instantiate a point at the upper-left corner

Listing 22 instantiates and saves an underlying data object of type Point containing coordinate values for the upper left corner. This object will be used as one endpoint for a moveable Line object from which the top and left borders will be drawn.

Listing 22. Instantiate a point at the upper-left corner.

    GM2D02.Point upperLeftPoint = new GM2D02.Point(
                           new GM2D02.ColMatrix(1.0,1.0));

Instantiate a moveable Point object

Listing 23 instantiates a Point object that will be used as an endpoint for a moveable line in all four locations.

Listing 23. Instantiate a moveable Point object.

    GM2D02.Point aPoint = new GM2D02.Point(
         new GM2D02.ColMatrix(osiWidth-1,
                              upperLeftPoint.getData(1)));

The location of this point will be modified several times during the drawing of the border by calling thesetData method on the point. The initial location of this point is the upper-right corner. (Recall that the origin is at the upper-left corner of both off-screen images at this point in the program.) The call to the Point constructor causes the y-coordinate to be the same as the y-coordinate for the Point object instantiated in Listing 22.

Instantiate a moveable Line object

Listing 24 instantiates a Line object that will be modified several times in succession by calling its setHead and setTail methods to place it in four different locations.

Listing 24. Instantiate a moveable Line object.

    GM2D02.Line theLine = new GM2D02.Line(
                                   upperLeftPoint,aPoint);
    theLine.draw(g2Da);
    theLine.draw(g2Db);

This Line object will be used to draw the top, right, bottom, and left lines that make up the border. It is used in Listing 24 to cause a graphical Line2D.Double object to be instantiated and rendered as the line at the top of both of the off-screen images.

Relocate the Line to three more locations

Listing 25 calls various methods of the Point and Line classes (including setData, setTail, and setHead) to relocate the GM2D02.Line object to three more locations, each parallel to an edge of an off-screen image.

Listing 25. Relocate the Line to three more locations.

    //Draw right border.
    //Save the previous head as the new tail.
    theLine.setTail(theLine.getHead());
    //Modify the location of aPoint. There's no need to
    // change the x-coordinate. Just change the
    // y-coordinate to move the point down the screen in
    // the positive y direction to the lower-right corner.
    aPoint.setData(1,osiHeight-1);
    //Use the new location of aPoint as the new head.
    theLine.setHead(aPoint);
    theLine.draw(g2Da);
    theLine.draw(g2Db);

    //Draw bottom border. There's no need to change the
    // y-coordinate in the new point, which is located
    // at the lower-left corner.
    theLine.setTail(theLine.getHead());
    aPoint.setData(0,1.0);
    theLine.setHead(aPoint);
    theLine.draw(g2Da);
    theLine.draw(g2Db);

    //Draw left border to close the rectangular border.
    theLine.setTail(theLine.getHead());
    theLine.setHead(upperLeftPoint);
    theLine.draw(g2Da);
    theLine.draw(g2Db);

Draw the border

While the single GM2D02.Line object is at each location, draw methods are called to cause a Line2D.Double object to be instantiated and rendered on each of the off-screen images. This results in four different visual manifestations of the single GM2D02.Line object, producing visible lines at the edges of each off-screen image. Although this code is not particularly straightforward, the embedded comments should suffice to explain it.

Draw the hexagon

Essentially the same process is used to draw the hexagon shown in the left image in Figure 1. You can view the code that does that in Listing 28. Once you understand the code that I explained above, you should have no difficulty understanding the code that draws the hexagon.

Draw the vectors

A very similar process is used to draw the vectors shown in the right image in Figure 1. You can view the code in Listing 28. Once again, if you understand the code that I explained above, you should have no difficulty understanding the code that draws the vectors.

That ends the explanation of the program named PointLine04.

Run the program

I encourage you to copy the code from Listing 26, Listing 27, and Listing 28, compile it, and execute it. Experiment with it, 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 update the game-math library to provide a number of new capabilities including the addition of graphics to the library and the addition of set methods for column matrices, points, vectors, and lines.

You also saw sample program that illustrate the use of those new capabilities and learned how to draw on off-screen images.

What's next?

In the next lesson, you will learn 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, and how to get a displacement vector from one point to another.

Resources

Complete program listings

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

Listing 26. Source code for the game-math library class named GM2D02.

/*GM2D02.java 
Copyright 2008, R.G.Baldwin
Revised 02/03/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 
GM2D01.

This update added the following new capabilities:

Draw circles around points
Draw lines
Draw vectors
Call a set method to change the values in a
 ColMatrix object.
Call a set method to change the values in a
 Point object.
Call a set method to change the values in a
 Vector object.
Call a set method to change one of the points
 that defines a line.

Constructors were updated to ensure that existing points,
vectors, and lines are not corrupted by using the new 
set methods to change the values in the ColMatrix and/or
Point objects originally used to construct the points, 
vectors, and lines.

The updated constructors create and save clones of the 
ColMatrix and/or Point objects originally used to define 
the Point, Vector, and/or Line objects.

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

public class GM2D02{

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

    ColMatrix(double data0,double data1){//constructor
      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
    //--------------------------------------------------//
  }//end class ColMatrix
  //====================================================//
  
  public static class Point{
    GM2D02.ColMatrix point;

    Point(GM2D02.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
    //--------------------------------------------------//
  }//end class Point
  //====================================================//

  public static class Vector{
    GM2D02.ColMatrix vector;

    Vector(GM2D02.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 ColMatrix 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,GM2D02.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
    //--------------------------------------------------//
  }//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{
    GM2D02.Point[] line = new GM2D02.Point[2];

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

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

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

    public void setHead(GM2D02.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 GM2D02.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 GM2D02
//======================================================//

 

Listing 27. Source code for the program named PointLine03.

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

This program illustrates the use of draw methods that
were added to the GM2D02 game-math library to produce 
visual manifestations of point, line, and vector objects 
instantiated from classes in the game-math library
named GM2D02.

The program also illustrates drawing on off-screen images
and then copying those images to a Canvas object in an
overridden paint method.

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

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

class GUI extends JFrame{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 400;
  int vSize = 200;
  Image osiA;//one off-screen image
  Image osiB;//another 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()/2;
    osiHeight = myCanvas.getHeight();

    //Create two off-screen images and get a graphics
    // context on each.
    osiA = createImage(osiWidth,osiHeight);
    Graphics2D g2Da = (Graphics2D)(osiA.getGraphics());

    osiB = createImage(osiWidth,osiHeight);
    Graphics2D g2Db = (Graphics2D)(osiB.getGraphics());

    //Draw some points, lines, and vectors on the two
    // off-screen images.
    drawOffscreen(g2Da,g2Db);

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

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

  //The purpose of this method is to define points, lines,
  // and vectors and then to cause a visual manifestation
  // of some of the points, lines, and vectors to be
  // drawn onto two separate off-screen images.
  void drawOffscreen(Graphics2D g2Da,Graphics2D g2Db){

    //Draw a label on each off-screen image.
    g2Da.drawString("Off-screen image A",
                    osiWidth/8,
                    osiHeight/8);
    g2Db.drawString("Off-screen image B",
                    osiWidth/8,
                    osiHeight/8);

    //Define four lines that can be used to draw borders
    // on each of the off-screen images.
    //First define four points that will be used to define
    // the ends of the four lines.
    GM2D02.Point upperLeftPoint = new GM2D02.Point(
                           new GM2D02.ColMatrix(1.0,1.0));
    GM2D02.Point upperRightPoint = new GM2D02.Point(
                    new GM2D02.ColMatrix(osiWidth-1,1.0));
    GM2D02.Point lowerRightPoint = new GM2D02.Point(
            new GM2D02.ColMatrix(osiWidth-1,osiHeight-1));
    GM2D02.Point lowerLeftPoint = new GM2D02.Point(
                   new GM2D02.ColMatrix(1.0,osiHeight-1));

    //Now define the four lines based on the endpoints..
    GM2D02.Line top = new GM2D02.Line(upperLeftPoint,
                                      upperRightPoint);
    GM2D02.Line rightSide = new GM2D02.Line(
                                         upperRightPoint,
                                         lowerRightPoint);
    GM2D02.Line bottom = new GM2D02.Line(lowerLeftPoint,
                                         lowerRightPoint);
    GM2D02.Line leftSide = new GM2D02.Line(upperLeftPoint,
                                          lowerLeftPoint);

    //Now draw a visual manifestation of each line
    // on g2Da.
    top.draw(g2Da);
    rightSide.draw(g2Da);
    bottom.draw(g2Da);
    leftSide.draw(g2Da);

    //Now draw a visual manifestation of each of the same
    // four lines on g2Db
    top.draw(g2Db);
    rightSide.draw(g2Db);
    bottom.draw(g2Db);
    leftSide.draw(g2Db);

    //Translate the origin of g2Da to the center of the
    // off-screen image.
    g2Da.translate(osiWidth/2.0,osiHeight/2.0);

    //Define a point at the new origin and draw a visual
    // manifestation of the point.
    GM2D02.Point origin = new GM2D02.Point(
                           new GM2D02.ColMatrix(0.0,0.0));
    origin.draw(g2Da);

    //Define six points that define the vertices of a
    // hexagon that is symmetrically located relative to
    // the origin.  Begin at the right and move clockwise
    // around the origin.
    //First define three constants to make it easier to
    // write the code.
    final double aVal = osiWidth/4.0*0.5;
    final double bVal = osiWidth/4.0*0.866;
    final double cVal = osiWidth/4.0;
    //Now define the points.
    GM2D02.Point point0 = new GM2D02.Point(
                          new GM2D02.ColMatrix(cVal,0.0));
    GM2D02.Point point1 = new GM2D02.Point(
                         new GM2D02.ColMatrix(aVal,bVal));
    GM2D02.Point point2 = new GM2D02.Point(
                        new GM2D02.ColMatrix(-aVal,bVal));
    GM2D02.Point point3 = new GM2D02.Point(
                         new GM2D02.ColMatrix(-cVal,0.0));
    GM2D02.Point point4 = new GM2D02.Point(
                       new GM2D02.ColMatrix(-aVal,-bVal));
    GM2D02.Point point5 = new GM2D02.Point(
                        new GM2D02.ColMatrix(aVal,-bVal));

    //Now draw a visual manifestation of each of the six
    // points on g2Da.
    point0.draw(g2Da);
    point1.draw(g2Da);
    point2.draw(g2Da);
    point3.draw(g2Da);
    point4.draw(g2Da);
    point5.draw(g2Da);

    //Now define six lines using the six points taken in
    // pairs to define the endpoints of the lines.
    GM2D02.Line line01 = new GM2D02.Line(point0,point1);
    GM2D02.Line line12 = new GM2D02.Line(point1,point2);
    GM2D02.Line line23 = new GM2D02.Line(point2,point3);
    GM2D02.Line line34 = new GM2D02.Line(point3,point4);
    GM2D02.Line line45 = new GM2D02.Line(point4,point5);
    GM2D02.Line line50 = new GM2D02.Line(point5,point0);

    //Now draw a visual manifestation of each line
    // on g2Da.
    line01.draw(g2Da);
    line12.draw(g2Da);
    line23.draw(g2Da);
    line34.draw(g2Da);
    line45.draw(g2Da);
    line50.draw(g2Da);


    //Now define three vectors and draw visual
    // manifestations of the vectors on g2Db.
    GM2D02.Vector vecA = new GM2D02.Vector(
                            new GM2D02.ColMatrix(50,100));
    GM2D02.Vector vecB = new GM2D02.Vector(
                             new GM2D02.ColMatrix(75,25));
    GM2D02.Vector vecC = new GM2D02.Vector(
                           new GM2D02.ColMatrix(125,125));

    //Draw vecA in red with its tail at the origin, which
    // is still at the upper-left corner of the g2Db
    // off-screen image
    g2Db.setColor(Color.RED);
    vecA.draw(g2Db,new GM2D02.Point(
                              new GM2D02.ColMatrix(0,0)));

    //Draw three visual manifestations of vecB.  Cause
    // the tail of vecB to coincide with the head of vecA
    // in one of the three visual manifestations. Make all
    // three of them green.
    g2Db.setColor(Color.GREEN);
    vecB.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                       vecA.getData(0),vecA.getData(1))));
    vecB.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                 vecA.getData(0)-10,vecA.getData(1)+15)));
    vecB.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                 vecA.getData(0)+10,vecA.getData(1)-60)));

    //Draw vecC in blue with its tail at the origin.
    g2Db.setColor(Color.BLUE);
    vecC.draw(g2Db,new GM2D02.Point(
                              new GM2D02.ColMatrix(0,0)));

  }//end drawOffscreen
  //====================================================//


  //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 two
    // off-screen images on the screen in a side-by-side
    // format.
    public void paint(Graphics g){
      g.drawImage(osiA,0,0,this);
      g.drawImage(osiB,this.getWidth()/2,0,this);
    }//end overridden paint()

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

 

Listing 28. Source code for the program named PointLine04.

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

This program emphasizes the differences between graphics 
objects and underlying data objects.  It also illustrates 
the use of the new setData methods of the Point class and 
the Vector class, along with the new setTail and setHead 
methods of the Line class. The methods were added to the
game-math library when it was updated to the GM2D02
version.

This program produces the same graphic output as that 
produced by PointLine03, but it produces that output in
a significantly different way.

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

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

class GUI extends JFrame{
  //Specify the horizontal and vertical size of a JFrame
  // object.
  int hSize = 400;
  int vSize = 200;
  Image osiA;//one off-screen image
  Image osiB;//another 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()/2;
    osiHeight = myCanvas.getHeight();

    //Create two off-screen images and get a graphics
    // context on each.
    osiA = createImage(osiWidth,osiHeight);
    Graphics2D g2Da = (Graphics2D)(osiA.getGraphics());

    osiB = createImage(osiWidth,osiHeight);
    Graphics2D g2Db = (Graphics2D)(osiB.getGraphics());

    //Draw some points, lines, and vectors on the two
    // off-screen images.
    drawOffscreen(g2Da,g2Db);

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

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

  //The purpose of this method is to define points, lines,
  // and vectors and then to cause a visual manifestation
  // of some of the points, lines, and vectors to be
  // drawn onto two separate off-screen images. The method
  // uses a minimum number of underlying data objects to 
  // produce the output, thereby emphasizing the
  // differences between graphics objects and underlying
  // data objects.
  void drawOffscreen(Graphics2D g2Da,Graphics2D g2Db){

    //Draw a label on each off-screen image.
    g2Da.drawString("Off-screen image A",
                    osiWidth/8,
                    osiHeight/8);
    g2Db.drawString("Off-screen image B",
                    osiWidth/8,
                    osiHeight/8);

    //Draw borders on each of the off-screen images by
    // drawing lines parallel to the edges of the
    // off-screen. Each line is offset by one pixel toward
    // the center of the off-screen image.
    //First define and save a point at the upper left
    // corner that will be used as an endpoint of the
    // top and left borders.
    GM2D02.Point upperLeftPoint = new GM2D02.Point(
                           new GM2D02.ColMatrix(1.0,1.0));

    //Define a point that will be used as an endpoint for
    // all four lines. The location of this point will be
    // modified several times during the drawing of the
    // border by calling the set method on the point. The
    // initial location of this point is the upper-right
    // corner. Remember, the origin is at the upper-left
    // corner of the off-screen images at this point in
    // the program. Make the y-coordinate the same as the
    // y-coordinate for the upperLeftPoint.
    GM2D02.Point aPoint = new GM2D02.Point(
         new GM2D02.ColMatrix(osiWidth-1,
                              upperLeftPoint.getData(1)));

    //Define a line that will be modified several times in
    // succession by calling its set methods to draw the
    // top, right, bottom, and left lines that make up the
    // border. Use it here to draw the line at the top
    // of both of the off-screen images.
    GM2D02.Line theLine = new GM2D02.Line(
                                   upperLeftPoint,aPoint);
    theLine.draw(g2Da);
    theLine.draw(g2Db);

    //Draw right border.
    //Save the previous head as the new tail.
    theLine.setTail(theLine.getHead());
    //Modify the location of aPoint. There's no need to
    // change the x-coordinate. Just change the
    // y-coordinate to move the point down the screen in
    // the positive y direction to the lower-right corner.
    aPoint.setData(1,osiHeight-1);
    //Use the new location of aPoint as the new head.
    theLine.setHead(aPoint);
    theLine.draw(g2Da);
    theLine.draw(g2Db);

    //Draw bottom border. There's no need to change the
    // y-coordinate in the new point, which is located
    // at the lower-left corner.
    theLine.setTail(theLine.getHead());
    aPoint.setData(0,1.0);
    theLine.setHead(aPoint);
    theLine.draw(g2Da);
    theLine.draw(g2Db);

    //Draw left border to close the rectangular border.
    theLine.setTail(theLine.getHead());
    theLine.setHead(upperLeftPoint);
    theLine.draw(g2Da);
    theLine.draw(g2Db);


    //Translate the origin of g2Da to the center of the
    // off-screen image.
    g2Da.translate(osiWidth/2.0,osiHeight/2.0);

    //Define a point at the new origin and draw a visual
    // manifestation of the point.
    GM2D02.Point origin = new GM2D02.Point(
                           new GM2D02.ColMatrix(0.0,0.0));
    origin.draw(g2Da);

    //Define two points and one line and use them to
    // define and draw a hexagon that is symmetrically
    // located relative to the origin.  Begin at the right
    // and move clockwise around the origin.

    //First establish three constants to make it easier
    // to write the code.
    final double aVal = osiWidth/4.0*0.5;
    final double bVal = osiWidth/4.0*0.866;
    final double cVal = osiWidth/4.0;

    //The coordinates of the following point are modified
    // several times in succession to define the vertices
    // of the hexagon.  Instantiate the point and draw it.
    GM2D02.Point thePoint = new GM2D02.Point(
                          new GM2D02.ColMatrix(cVal,0.0));
    thePoint.draw(g2Da);

    //Save a clone of thePoint to be used to draw the
    // first line and the last line that closes the
    // hexagon.
    GM2D02.Point startAndEndPoint = new GM2D02.Point(
              new GM2D02.ColMatrix(
                thePoint.getData(0),thePoint.getData(1)));

    //Now call the set method twice to modify the location
    // of thePoint, Draw thePoint, and use it to
    // instantiate and draw a line from the starting
    // location to the new location.
    thePoint.setData(0,aVal);
    thePoint.setData(1,bVal);
    thePoint.draw(g2Da);
    GM2D02.Line aLine = new GM2D02.Line(
                               startAndEndPoint,thePoint);
    aLine.draw(g2Da);

    //Repeat the process four more times using
    // startAndEndPoint, thePoint, and aLine to draw the
    // remaining vertices and lines.
    //Modify aLine, saving the previous head as the new
    // tail.
    aLine.setTail(aLine.getHead());
    //Modify the location of aPoint and draw it. There's
    // no need to change the y-coordinate of the point.
    thePoint.setData(0,-aVal);
    thePoint.draw(g2Da);
    //Modify the endpoint of aLine and draw it.
    aLine.setHead(thePoint);  
    aLine.draw(g2Da);

    aLine.setTail(aLine.getHead());
    thePoint.setData(0,-cVal);
    thePoint.setData(1,0.0);
    thePoint.draw(g2Da);
    aLine.setHead(thePoint);
    aLine.draw(g2Da);

    aLine.setTail(aLine.getHead());
    thePoint.setData(0,-aVal);
    thePoint.setData(1,-bVal);
    thePoint.draw(g2Da);
    aLine.setHead(thePoint);
    aLine.draw(g2Da);

    aLine.setTail(aLine.getHead());
    thePoint.setData(0,aVal);
    //No need to change the y-coordinate.
    thePoint.draw(g2Da);
    aLine.setHead(thePoint);
    aLine.draw(g2Da);

    //Now modify and draw aLine to close the hexagon.
    aLine.setTail(aLine.getHead());
    aLine.setHead(startAndEndPoint);
    aLine.draw(g2Da);


    //Now define a vector, call the set method to modify
    // it several times, and draw different visual
    // manifestations of the vector on g2Db.
    //First define two constants to make it easier to
    // write the code.
    final double firstTail = 50;
    final double firstHead = 100;

    GM2D02.Vector vec = new GM2D02.Vector(
               new GM2D02.ColMatrix(firstTail,firstHead));
    //Draw vec in red with its tail at the origin, which
    // is still at the upper-left corner of the g2Db
    // off-screen image
    g2Db.setColor(Color.RED);
    vec.draw(g2Db,new GM2D02.Point(
                              new GM2D02.ColMatrix(0,0)));

    //Call the set method to modify vec.
    vec.setData(0,75);
    vec.setData(1,25);
    //Draw three visual manifestations of vec.  Cause
    // the tail to coincide with the head of the previous
    // visual manifestation of vec in one of the three new
    // visual manifestations. Make all three of them
    // green.
    g2Db.setColor(Color.GREEN);
    vec.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                                   firstTail,firstHead)));
    vec.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                             firstTail-10,firstHead+15)));
    vec.draw(g2Db,new GM2D02.Point(new GM2D02.ColMatrix(
                             firstTail+10,firstHead-60)));

    //Call the set method to modify vec again.
    vec.setData(0,125);
    vec.setData(1,125);

    //Draw the modified vec in blue with its tail at the
    // origin.
    g2Db.setColor(Color.BLUE);
    vec.draw(g2Db,new GM2D02.Point(
                              new GM2D02.ColMatrix(0,0)));

  }//end drawOffScreen
  //====================================================//


  //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 two
    // off-screen images on the screen in a side-by-side
    // format.
    public void paint(Graphics g){
      g.drawImage(osiA,0,0,this);
      g.drawImage(osiB,this.getWidth()/2,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