Solution for
Programming Exercise 8.3
THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.
Exercise 8.3: A polygon is a geometric figure made up of a sequence of connected line segments. The points where the line segments meet are called the vertices of the polygon. The Graphics class includes commands for drawing and filling polygons. For these commands, the coordinates of the vertices of the polygon are stored in arrays. If g is a variable of type Graphics then
- g.drawPolygon(xCoords, yCoords, pointCt) will draw the outline of the polygon with vertices at (xCoords[0],yCoords[0]), (xCoords[1],yCoords[1]), ..., (xCoords[pointCt-1],yCoords[pointCt-1]). The third parameter, pointCt, is an int that specifies the number of vertices of the polygon. Its value should be 3 or greater. The first two parameters are arrays of type int[]. Note that the polygon automatically includes a line from the last point, (xCoords[pointCt-1],yCoords[pointCt-1]), back to the starting point (xCoords[0],yCoords[0]).
- g.fillPolygon(xCoords, yCoords, pointCt) fills the interior of the polygon with the current drawing color. The parameters have the same meaning as in the drawPolygon() method. Note that it is OK for the sides of the polygon to cross each other, but the interior of a polygon with self-intersections might not be exactly what you expect.
Write a little applet that lets the user draw polygons. As the user clicks a sequence of points, count them and store their x- and y-coordinates in two arrays. These points will be the vertices of the polygon. Also, draw a line between each consecutive pair of points to give the user some visual feedback. When the user clicks near the starting point, draw the complete polygon. Draw it with a red interior and a black border. The user should then be able to start drawing a new polygon. When the user shift-clicks on the applet, clear it.
There is no need to store information about the contents of the applet. The paintComponent() method can just draw a border around the applet. The lines and polygons can be drawn using a graphics context, g, obtained with the command "g = getGraphics();".
You can try my solution. Note that as the user is drawing the polygon, lines are drawn between the points that the user clicks. Click within two pixels of the starting point to see a filled polygon.
Discussion
I put all the programming for my solution into a nested class named Display. This class defines the drawing surface of the applet. The only method in the main class is the init() method, which creates the drawing surface and uses it as the content pane of the applet.
The program needs several instance variables to store information about the sequence of points that the user has clicked:
int[] xCoord, yCoord; // Arrays to hold he coordinates. int pointCt; // Number of points in the arrays.The arrays xCoord and yCoord are examples of "partially full arrays", as covered in Section 8.3. The variable pointCt keeps track of how many spaces in the array are used. The instance variables are initialized in the constructor of the Display with the commands:
xCoord = new int[500]; yCoord = new int[500]; pointCt = 0;The size of the arrays allows for polygons with up to 500 vertices, which should certainly be enough. When we start, there are not yet any points in the array, so pointCt is zero. (The command to set pointCt to zero is not really necessary, since pointCt, as an instance variable, is automatically initialized to zero in any case. However, including this command seemed to me to make my intentions clearer to the reader. That should always be a consideration.)
In my solution, I included a subroutine to draw a line and a subroutine to draw a polygon. These subroutines are called in the mousePressed() routine. Since the lines and polygons are drawn directly, rather than in the paintComponent() method, these subroutines are responsible for obtaining graphics contexts to do the drawing. The putLine method is simple:
private void putLine(int x1, int y1, int x2, int y2) { // Draw a line from (x1,y1) to (x2,y2) directly onto this // component, without going through the paintComponent() // method. Graphics g = getGraphics(); g.drawLine(x1,y1,x2,y2); g.dispose(); }The putPolygon() method uses the data for the polygon that is stored in the instance variables xCoord, yCoord, and pointCt. It is complicated a little by the fact that three points are required to draw a polygon. If there are only two points stored in the arrays, then I just draw a line between the two points. Note that the two points are stored in the first two locations in the xCoord and yCoord arrays. The coordinates of the two points are (xCoord[0],yCoord[0]) and (xCoord[1],yCoord[1]). If there are fewer than two points, I don't draw anything. If there are three or more points, then I first draw the interior of the polygon in red and then draw the outline of the polygon in black:
private void putPolygon() { // Draw the polygon described by the arrays xCoord and yCoord // and the integer pointCt. A filled polygon with a black // outline is drawn. If pointCt is 0 or 1, nothing is drawn. // If pointCt is 2, only a black line is drawn. if (pointCt < 2) return; Graphics g = getGraphics(); if (pointCt == 2) { g.drawLine(xCoord[0], yCoord[0], xCoord[1], yCoord[1]); } else { g.setColor(Color.red); g.fillPolygon(xCoord, yCoord, pointCt); g.setColor(Color.black); g.drawPolygon(xCoord, yCoord, pointCt); } g.dispose(); }The main logic of the applet is in the mousePressed() method. An algorithm for this method is:
if the user was holding down the Shift key: Clear the applet, and start a new polygon else if the user clicked near the first point: Draw the current polygon and start a new one else Add the point (evt.getX(), evt.getY()) to the coordinate arrays if this is not the first point in the arrays: Draw a line between the previous point and this oneActually, in my solution, I decided to add another case: If the user right-clicks the applet or if the number of points reaches 500, then I draw the current polygon and start a new one. Also, there is a flaw in the algorithm as stated, where it tests "if the user clicked near the first point". This test doesn't make sense unless there actually is a first point, that is unless pointCt is greater than zero. The test should really read "if pointCt > 0 and if the user clicked near (xCoord[0],yCoord[0])."
Where the algorithm says "start a new polygon", it is only necessary to say "pointCt = 0;" since that will indicate that the coordinate arrays contain no valid data. The command "draw the current polygon" can be translated as "putPolygon()". Adding a point to the coordinate arrays means putting the coordinates of the point in the next available location of the arrays and incrementing pointCt to record the fact that the number of valid data in the arrays has increased by one. This is done with the commands
xCoord[pointCt] = evt.getX(); yCoord[pointCt] = evt.getY(); pointCt++;Then, if pointCt is greater than 1, we have to draw a line between the last two points in the array. The coordinates of these points are (xCoord[pointCt-2],yCoord[pointCt-2]) and (xCoord[pointCt-1],yCoord[pointCt-1]), so this can be done with the command
putLine(xCoord[pointCt-2], yCoord[pointCt-2], xCoord[pointCt-1], yCoord[pointCt-1] );The only thing in the algorithm that still needs implementing is to test whether the user clicks "near the starting point". The starting point has coordinates (xCoord[0],yCoord[0]) and the point where the user clicked has coordinates (evt.getX(),evt.getY()). In my applet, I check whether the x-coordinates of these points are two pixels or less apart and the y-coordinates are also two pixels or less apart. This is done by checking whether "Math.abs(xCoord[0] - evt.getX()) <= 2 && Math.abs(yCoord[0] - evt.getY()) <= 2". If you are uncomfortable with absolute values, you can use the equivalent test "(evt.getX() - 2 <= xCoords[0]) && (xCoords[0] <= evt.getX() + 2) && (evt.getY() - 2 <= yCoords[0]) && (yCoords[0] <= evt.getY() + 2)".
The complete program for the applet is shown below.
(By the way, there are versions of the drawPolygon() and fillPolygon() methods that work with objects belonging to a class called Polygon, instead of with arrays. So, you don't absolutely need arrays to work with polygons.)
The Solution
/* This applet lets the user draw colored polygons. The user inputs a polygon by clicking a series of points. Clicking near the starting point (within 2 pixels) or right-clicking (or Command-clicking) will complete the polygon, so the user can begin a new one. Shift-clicking will clear the screen. Up to 500 points are allowed in a polygon. This applet does not keep a copy of the image on the screen, so it will not reappear if the applet is covered and then uncovered. */ import java.awt.*; import java.awt.event.*; import javax.swing.*; public class SimplePolygons extends JApplet { public void init() { // This simple init() method just creates a drawing surface // belonging to the nested class Display and uses it for // the content pane of the applet. (This is one case where // it is rather silly to use a JApplet rather than a plain // old Applet!) setContentPane( new Display() ); } class Display extends JPanel implements MouseListener { /* Variables for implementing polygon input. */ private int[] xCoord, yCoord; // Arrays containing the points of // the polygon. Up to 500 points // are allowed. private int pointCt; // The number of points that have been input. private Color polygonColor = Color.red; // Color that is used to draw the polygons. public Display() { // Initialize the applet. The applet listens for mouse events. // Arrays are created to hold the points. A drawing surface // belonging to the nested class Display is created and // used as the drawing surface for the applet. setBackground(Color.white); addMouseListener(this); xCoord = new int[500]; yCoord = new int[500]; pointCt = 0; } public void paintComponent(Graphics g) { // The paintComponent() routine just draws a 1-pixel black // border around the applet. Polygons drawn on the applet // are not permanent. super.paintComponent(g); // Fills with background color, white. g.setColor(Color.black); g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); g.drawRect(1, 1, getWidth() - 3, getHeight() - 3); } private void putLine(int x1, int y1, int x2, int y2) { // Draw a line from (x1,y1) to (x2,y2) directly onto this // component, without going through the paintComponent() // method. Graphics g = getGraphics(); g.drawLine(x1,y1,x2,y2); g.dispose(); } private void putPolygon() { // Draw the polygon described by the arrays xCoord and yCoord // and the integer pointCt. A filled polygon with a black // outline is drawn. If pointCt is 0 or 1, nothing is drawn. // If pointCt is 2, only a black line is drawn. if (pointCt < 2) return; Graphics g = getGraphics(); if (pointCt == 2) { g.drawLine(xCoord[0], yCoord[0], xCoord[1], yCoord[1]); } else { g.setColor(Color.red); g.fillPolygon(xCoord, yCoord, pointCt); g.setColor(Color.black); g.drawPolygon(xCoord, yCoord, pointCt); } g.dispose(); } public void mousePressed(MouseEvent evt) { // Process a user mouse-click. if (evt.isShiftDown()) { // Clear the applet. (This only requires a repaint.) // Also, set pointCt to zero to start a new polygon. pointCt = 0; repaint(); } else if ( pointCt > 0 && (Math.abs(xCoord[0] - evt.getX()) <= 2) && (Math.abs(yCoord[0] - evt.getY()) <= 2) ) { // User has clicked near the starting point. // Draw the polygon and reset pointCt so that the // user can start a new polygon. putPolygon(); pointCt = 0; } else if (evt.isMetaDown() || pointCt == 500) { // Draw the polygon and reset pointCt so that the // user can start a new polygon. Note that there // is a limit of 500 points for one polygon. putPolygon(); pointCt = 0; } else { // Add the point where the user clicked to the list of // points in the polygon, and draw a line between the // previous point and the current point. A line can // only be drawn if there are at least two points. xCoord[pointCt] = evt.getX(); yCoord[pointCt] = evt.getY(); pointCt++; if (pointCt >= 2) { putLine(xCoord[pointCt-2], yCoord[pointCt-2], xCoord[pointCt-1], yCoord[pointCt-1]); } } } // end mousePressed() public void mouseReleased(MouseEvent evt) { } public void mouseClicked(MouseEvent evt) { } public void mouseEntered(MouseEvent evt) { } public void mouseExited(MouseEvent evt) { } } // end nested class Display } // end class SimplePolygons
[ Exercises | Chapter Index | Main Index ]