CS 261 Lab G - Fractal Drawing
Due Wed Mar 12 at 9:00am
Overview
A fractal image is an image in which every small subsection is the same as the larger image it is a part of--if you keep zooming in on the image, you just keep seeing the same thing. These images are defined recursively--each piece of the image contains a copy of itself, and so on.
For example, consider the simple sideways H (It's an H, not an I!) shape labeled Step 1. If you draw similar, but smaller, H shapes at the end of each leg, you get the second shape (Step 2). Continuing that process until the line segments are too small to draw gives you the final shape (Step N).
Although this is just a simple example (less than a dozen lines of code), more complicated images can be just as easy. Indeed, simple recursive rules like this can produce all kinds of amazing artwork.
In this lab, you will be writing methods to draw two simple factal images. You will draw one of either the above H-Tree or the Sierpinski Triangle, and one of either a fractal tree or the Koch Snowflake.
This lab will be completed in pairs. Partner assignments for this lab can be found on Moodle
- Remember to review the pair programming guidelines before you get started! NOTE: although this lab may lend itself to simply splitting the work in half, you should follow pair programming practices and together write the code for both fractals!
Objectives
- To practice with recursion
- To review working with abstract classes and inheritance
- To practice with drawing pretty pictures
Necessary Files
You will need a copy of the
FractalLab.zip file.
This file contains two classes you will need to import into Eclipse. DrawableFrame
is a JFrame that you can draw on (by passing an image to). FractalDrawing
is an abstract class that you should use as the basis for your own drawings. This class provides functionality to initialize the frame, and provides you with a Graphics2D
object that you can draw on. Do not modify either of these classes, simply use their methods! If you find that you're modifying them to make your code work, then you're doing it wrong!
The FractalDrawing
class also has a couple of helpful methods you may want to use. Calling showFractal()
will make your drawing so far appear in the frame, so you can use it as a kind of "refresh" method while you're drawing. Calling wait()
will make your program "pause" for a moment. You can use this to slow down the drawing so that you can see what's happening. On my computer, a pause time of 0 milliseconds is instantaneous, and pause time of 5-10 milliseconds looked good. Use whatever time is needed
- Note: if you include pause times, use a
CONSTANT
defined at the top of your class to specify how long your drawing pauses for. That way I can set the constant to be 0 to test your code without waiting. Do not make me alter your code or wait for a slow drawing.
Assignment Details
- You will two separate classes: one to draw each of your fractals. These classes should extend
FractalDrawing
. You should also need to make a Tester that can be used to start drawing both of your fractals. -
Each of your classes will need to override the
drawFractal()
method. This abstract method is effectively the "launcher" for your fractal drawing--that's why it's part of the public interface! -
None of the fractal methods require more than a dozen or so lines of code (depending on how fancy you get want to get with color, etc.). If you find your method is getting much longer than that, take a moment to convince yourself you are on the right track!
- Your helper methods should all be
private
; let the launcher be the only part of the public interface. - Think about what parameters you will need: what information is required for each "step" of the drawing (or for each "run" through the recursive loop?). I've included some hints in the descriptions
- Hint 1: Write the "base case" draw function first (e.g., draw a single iteration of your recursion). Test that, then add in the recursive loop. Doing the base first will let you confirm that your drawing is correct.
- Hint 2: Remember, it is an excellent debugging strategy to print out the arguments given at the start of each call to the recurive method (e.g., make the first line of the method a printout that says "I am X method called with args A B C"). This can help you follow what is going on.
- Your helper methods should all be
- Although we don't normally do this in a lab, you should include a full JavaDoc comment for each of your recursive methods. I highly recommend you write this comment first, so that you know what your parameters represent and what you should be returning (if anything).
The Fractals
This section details the fractals you will need to implement. Remember, you only need to implement two of these: pick one of either the H-Tree or the Sierpinksi Trangle, and one of the fractal tree or the Koch Snowflake.
1a. The H-Tree
The H-Tree is one of the simplest fractals to understand and program. You begin by drawing a vertical line with two (shorter) horizontal 'legs' like a sideways H (Figure H1). Then, at the ends of each 'leg', draw the same shape, rescaled (Figure H2). The process continues until the line segments are too small to draw (e.g., a single pixel or less). The result is a grid-like image (Figure H3).
IMPORTANT NOTE: Your H-Tree will draw one "leg" at a time; thus you will never see a figure that looks like H2. Instead, you'll see a single corner of the H-Tree get drawn, growing in a kind of spiral.
To draw the H-Tree, you will need the horizontal coordinate of the central segment and the y coordinates of the two endpoints (though you can even get away with just having the center of the H and the length of the different segments!). You will also need a ratio to specify the lengths of the 'legs' relative to the length of the central segment. Thus the signature for your recursive method might look like this:
private void drawH(int x1, int y1, int y2, double ratio)
where (x1, y1) and (x1, y2) are the coordinates of the endpoints of the central segment and ratio is the relative length of each leg.
- Hint: consider making multiple recursive calls each method! You want to draw 4 smaller H's... so think about sending out 4 helper cats!
The above images were generated with a ratio of 0.5; you should experiment with different ratios to see the effects.
1b. The Sierpinski Triangle
The Sierpinski Triangle Fractal is a classic simple fractal. You begin by drawing a triangle (Figure S1). Then, by connecting the midpoints of the triangle's sides, you construct three triangles (Figure S2). Note the triangles are at the corners. The 'upside down' one in the middle is not a triangle, but is formed as a result of the other three. The process continues: in the next iteration, you connect the midpoints of the three smaller triangles to create three triangles in each one (Figure S3). Continuing this process, the resulting fractal image looks like Figure S4.
IMPORTANT NOTE: Your Triangle will draw one "corner" at a time; thus you will never see a figure that looks like S3. Instead, you'll see a single corner of the triangle get drawn, growing down before filling in the next corner.
The only things you need to get started drawing a Sierpinski Triangle are the coordinates of the three vertices of the first triangle. So, the signature for your recursive method might look like this:
private void drawSierpinski(int x1, int y1, int x2, int y2, int x3, int y3)
where (x1, y1) (x2, y2) and (x3, y3) are the coordinates of the initial triangle vertices.
- Hint: consider making multiple recursive calls each method! You want to draw 3 smaller triangles... so think about sending out 3 helper cats!
Note that the vertices can be anywhere. That is, the triangle does not have to be equilateral or isosceles. Try drawing out Sierpinski Triangle that is neither!
2a. The Fractal Tree
You will be drawing a version of The Pythagoras Tree (or "tree fractal"), using lines instead of cubes. This fractal is a bit more complicated than the previous two. You begin by drawing a 'trunk' for the tree. In the next step, you draw 'branches'--each of which is another Pythagoras tree with a shorter trunk! That is, the 'branches' become the 'trunks' in the next recursion (see Figure P1).
You will need an initial spot (the base of the trunk) to draw each recursive tree. You should also use some passed in ratio (similar to with the H-Tree) to determine how much to shrink the next trunk; 0.8 is a good number.
In order to have your tree grow out, you will need to have each branch (recursive tree) spread out at an angle. This can be measured as the angle off the the horizontal axis (angle1 in Figure P2). In addition, your branches will be splitting by a particular angle each time (angle2 in Figure P2)--so you can figure out the new angle for a branch by adding the split angle to the current angle.
- Because Java math functions use them, your angles should be in radians measured counterclockwise from the x-axis. So a vertical trunk has an angle of PI/2 (that is: 90 degrees).
- If you use PI/2 as your "splitting angle", your branches will angle away at 90 degrees, forming a T. This is not a bad way to test that your recursion is working. Smaller angles will give the traditional Y-shape.
-
Math Hint! If you have some angle from the horizontal axis, you can figure out the location of a point some
length
distance away by using the equations from polar coordinates:newX = oldX + length * cos(angle) newY = oldY - length * sin(angle) //subtracted because of inverted y!
You will need to have some way of telling your tree when to stop recursing. You should pass in a value that indicates how many iterations to complete (e.g., how many times we want to send out helper cats). Thus your recursive method signature might look like:
public void drawTree(int x, int y, int length, double trunkAngle, double splitAngle, double ratio, int iterations)
2b. The Koch Snowflake
The Koch Snowflake is the most complicated fractal (but is still not too bad)--mostly because it doesn't use tail recursion (where you recurse at the end of the method). To draw the Kock Snowflake, think about drawing line segments, where a segment is the side of a triangle (thus you will want to draw 3 segments).
Each time you draw a segment, you are going to subdivide that segment up into three smaller pieces (that are themselves segments): you draw a segment over the "first" 1/3 of the edge and a segment over the "third" 1/3 of the edge. But instead of drawing the middle segment, you draw a pair of shorter segments that would form a kind of new triangle. See the below figures (from Wikipedia) for an example.
Drawing each segment is a recursive call--instead of drawing that segment, you actually break it up into the three pieces, and so forth. Keep going until you hit a maximum number of steps (an iteration count, similar to with the Pythagoras tree), in which case you can stop dividing the segment and just draw the straight line alreadt!
Note that the greens lines are for illustration purposes; you do not need to draw them!
The only things you'll need to draw the Koch snowflake are the starting x and y coordinates of the side of each triangle. Important! You'll need to keep your points in "order" (e.g., going clockwise) so that you build your fractal out instead of in. You will also want to keep track of how many iterations you have gone through, so that you can stop (4 or 5 ended up being a good number for me--though again, you can stop when your segments are too short to subdivide).
- Find the points that are 1/3 and 2/3 of the way through the segment easily; simply take the distance between the start and end coordinates (x2 - x1) and divide it by 3.
-
Math Hint! Finding the new, outside point to draw the growing triangle is a lot trickier; but some simple trigonometry can show that:
newX = x1 + 0.5 * (x2 - x1) + sqrt(3)*(y2 - y1)/6.0 newY = y1 + 0.5 * (y2 - y1) - sqrt(3)*(x2 - x1)/6.0
Again, make sure that you keep your points in order so you build out instead of in!
Thus overall your recursive method signature might look like:
public void drawSegment(int x1, int y1, int x2, int y2, int iterations)
Extensions
You are welcome to implement more than one of the fractals if you have time. This can be worth a few points of extra credit.
You can have lots of fun with using different colors for different parts of the recursive drawings, to make really neat images!
You are welcome to implement more than one of these fractals if you have time. There are lots of other amazing fractals out there, including the infamous Mandelbrot Set
You might also have fun creating a new FractalFrame
that has different buttons used to draw the different fractals. Note that you would need to adjust the FractalDrawing class to not create its own frame.
Submitting
Make sure both your names are on BOTH of your Fractal classes (you will be marked down if they are not). Once you're finished, upload the src code to the LabG submission folder on vhedwig. Only one partner needs to upload the code--we will only grade one partner's copy of the program. Make sure you upload your work to the correct folder! The lab is due at the start of class the day after lab.
After you have submitted your solution, log onto Moodle and submit the Lab G Partner Evaluation. Both partners need to submit evaluations.
Grading
This assignment will be graded out of 15 points:
- [1pt] You have implemented your fractal classes by extending the abstract FractalDrawer
- [4pt] You have implemented either the H-Tree or the Sierpinksi Triangle
- [6pt] You have implemented either the Fractal Tree or the Koch Snowflake
- [1pt] Your recursive method display good style; including private helper methods and appropriate paramaters
- [1pt] Your submitted program doesn't include excessive wait times
- [1pt] You have included JavaDoc comments on your recursive methods
- [1pt] You have completed your lab partner evaluation