I ran through your code, and agree that the output is pretty. I like the idea, and it is a good use-case for a number of exercises. Your code suffers though from a few issues that are awkward.
First up, you have just one real method. Why? You should isolate discrete logic elements in to their own methods. That's the most apparent issue, but we'll get there, but other things get in the way first.
Constants
While we are isolating things, why do you redeclare constant values each time you call paint? Let's take the first chunk of your code:
public class ChasePolygon extends Applet { public void paint(Graphics g) { final int radius = 200; final float velocity = 0.001f; final int sides = 6; final float theta = (float) (2 * Math.PI / sides); final int centerX = 200, centerY = 200;
That should be:
public class ChasePolygon extends Applet { private final int radius = 200; private final float velocity = 0.001f; private final int sides = 6; private final float theta = (float) (2 * Math.PI / sides); private final int centerX = 200; private final int centerY = 200; public void paint(Graphics g) {
That's making a bunch of your constants... constants.
Floats
Floats are an awkward value type in Java. You have to keep casting them as floats because the 'natural' floating-point number is a double. I recommend tossing floats out unless you are space-constrained. You are not space constrained, so all your floats should just be doubles. This makes your code a bunch easier to read... for example, lines like:
final float pX = (float) (centerX + radius * Math.cos(i * theta));
become just:
final double pX = centerX + radius * Math.cos(i * theta);
vertices_loc
This ArrayList is a decent concept, but, your code would become a lot easier, and all the 1's and 0's that are scattered though your code would be redundant if you just created two double[] arrays. Consider:
double[] verticesX = new double[sides]; double[] verticesY = new double[sides];
Now you lose your [0] and [1] subscripts for X and Y instead. It is shorter, and even has more meaning. Double-win.
Function Extraction
Right, now it is time for function extraction. First up, extract the polygon draw. We have a trick we can do there too...
Your current polygon draw code is:
GeneralPath polyline = new GeneralPath(GeneralPath.WIND_EVEN_ODD, sides); polyline.moveTo(vertices_loc.get(0)[0], vertices_loc.get(0)[1]); for (int i = 0; i < sides; i++) { polyline.lineTo(vertices_loc.get(i)[0], vertices_loc.get(i)[1]); } polyline.lineTo(vertices_loc.get(0)[0], vertices_loc.get(0)[1]); g2.draw(polyline);
Note how you start at (moveTo) vertix 0, and your first line draws to (lineTo) vertix 0? You essentially start with a 0-length line, and then you have to fix that with a post-loop line-to vertix 0.
A neater solution would be to just start at the last vertex, and then go from there to the first.... But, put that in a function too. Using my suggestion for the verticesX and verticesY, it becomes (note the sides - 1 moveTo):
private void paintPolygon(Graphics2D g2, double[] verticesX, double[] verticesY) { GeneralPath polyline = new GeneralPath(GeneralPath.WIND_EVEN_ODD, sides); polyline.moveTo(verticesX[sides - 1], verticesY[sides - 1]); for (int i = 0; i < sides; i++) { polyline.lineTo(verticesX[i], verticesY[i]); } g2.draw(polyline); }
Spirals
The final functional extraction comes from the spiral paths. These should be extracted. Again, using an actual array makes it easier than using an ArrayList. Additionally, the final print loop would be better as a for-each loop. That final method becomes:
private void paintCurves(Graphics2D g2, double[] verticesX, double[] verticesY) { GeneralPath[] g_p = new GeneralPath[sides]; for (int i = 0; i < sides; i++) { g_p[i] = new GeneralPath(GeneralPath.WIND_EVEN_ODD, delta_T); g_p[i].moveTo(verticesX[i], verticesY[i]); } for (int i = 0; i < delta_T; i++) { for (int j = 0; j < sides; j++) { double dX = verticesX[(j + 1) % sides] - verticesX[j]; double dY = verticesY[(j + 1) % sides] - verticesY[j]; verticesX[j] += dX * velocity; verticesY[j] += dY * velocity; g_p[j].lineTo(verticesX[j], verticesY[j]); } } for (GeneralPath gp : g_p) { g2.draw(gp); } }
Constant vertices.
Finally, after simplifying things, it became apparent that the initial vertices are constant, and you then overwrite them as you spiral in. This makes it simpler to calculate the vertices once, use those for the polygon, and then use a copy for the spirals. The final paint method is just:
public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; paintPolygon(g2); paintCurves(g2, Arrays.copyOf(verticesX, sides), Arrays.copyOf(verticesY, sides)); }
Clean version
All told, I would go with the following:
import java.applet.Applet; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.GeneralPath; import java.util.ArrayList; import java.util.Arrays; public class ChasePolygon extends Applet { private final int radius = 200; private final double velocity = 0.001; private final int sides = 7; private final double theta = 2.0 * Math.PI / sides; private final int centerX = 200; private final int centerY = 200; private final int delta_T; private final double[] verticesX = new double[sides]; private final double[] verticesY = new double[sides]; public ChasePolygon() { delta_T = (int) Math.floor(radius * Math.sin(theta / 2) / velocity); for (int i = 0; i < sides; i++) { verticesX[i] = centerX + radius * Math.cos(i * theta); verticesY[i] = centerY + radius * Math.sin(i * theta); } } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; paintPolygon(g2); paintCurves(g2, Arrays.copyOf(verticesX, sides), Arrays.copyOf(verticesY, sides)); } private void paintCurves(Graphics2D g2, double[] vertX, double[] vertY) { GeneralPath[] g_p = new GeneralPath[sides]; for (int i = 0; i < sides; i++) { g_p[i] = new GeneralPath(GeneralPath.WIND_EVEN_ODD, delta_T); g_p[i].moveTo(vertX[i], vertY[i]); } for (int i = 0; i < delta_T; i++) { for (int j = 0; j < sides; j++) { double dX = vertX[(j + 1) % sides] - vertX[j]; double dY = vertY[(j + 1) % sides] - vertY[j]; vertX[j] += dX * velocity; vertY[j] += dY * velocity; g_p[j].lineTo(vertX[j], vertY[j]); } } for (GeneralPath gp : g_p) { g2.draw(gp); } } private void paintPolygon(Graphics2D g2) { GeneralPath polyline = new GeneralPath(GeneralPath.WIND_EVEN_ODD, sides); polyline.moveTo(verticesX[sides - 1], verticesY[sides - 1]); for (int i = 0; i < sides; i++) { polyline.lineTo(verticesX[i], verticesY[i]); } g2.draw(polyline); } }