Fractals entertain and are fun to explore, as evidenced by this article's fractal applets. Learn how to use math-based fractals that imitate nature's geometry to enhance your Java games.

Euclidean geometry emphasizes circles, cubes and other simple shapes. This geometry is seen in buildings and other man-made objects. In contrast, the geometry of nature is more complex, but it does exhibit the property of self-similarity: Natural objects (such as clouds and coastlines) look similar at different levels. This self-similarity is also evidenced in mathematics, especially when working with complex numbers.

In 1975, IBM researcher Benoît Mandelbrot invented the term *fractal*, "a rough or fragmented geometric shape that can be subdivided in parts, each of which is (at least approximately) a reduced-size copy of the whole," to describe natural and math-based self-similarity. Mandelbrot also discovered what is regarded to be the most famous math-based fractal, the Mandelbrot set.

This article takes you on a tour of math-based fractals, including the Mandelbrot set. You will examine algorithms for recursively generating fractals and play with applets that implement the algorithms. One of the fractals demonstrated imitates nature's geometry by generating a mountain ridgeline. Perhaps you will use this fractal to create background terrain for one of your Java games.

## Fractal generator and animator

I have created an infrastructure for generating and animating most of the fractals presented in this article. Before touring the fractals and their applets, you should have a basic understanding of the infrastructure. Let's begin with the fractal generator, which I've chosen to describe via the `FractalGenerator`

interface in Listing 1.

**Listing 1. The FractalGenerator interface**

```
interface FractalGenerator
{
public void generate (Graphics2D g, int depth, int width, int height);
public int maxDepth ();
}
```

The methods of the `FractalGenerator`

interface assume that a fractal is recursively generated: they are invoked by a fractal animator when it is time to generate the fractal at a new depth. The depth ranges from 0 to a maximum positive integer (0 ends the recursion). At this point, a `java.awt.Graphics2D`

object's methods render the fractal in a rectangular area bounded by the upper-left-corner (0, 0) and dimensions `width`

and `height`

.

After creating an object from a class that implements `FractalGenerator`

, a program connects this object to the fractal animator, which generates the fractal to a specific depth for each frame of the fractal's animation. As you see in Listing 2, I have chosen to describe the fractal animator via `FractalAnimator`

, a subclass of the `javax.swing.JPanel`

container class:

**Listing 2. FractalAnimator, a subclass of javax.swing.JPanel**

```
class FractalAnimator extends JPanel {
final static int DEFAULT_DELAY = 500;
final static int MAX_DELAY = 1000;
final static int MIN_DELAY = 100;
final static int STEP = 100;
private final static Color PANEL_COLOR = new Color (255, 255, 225);
private FractalGenerator fg;
private volatile int depth, ms = DEFAULT_DELAY;
private volatile Thread animThd;
FractalAnimator (FractalGenerator fg)
{
this.fg = fg;
}
public void paintComponent (Graphics g)
{
Graphics2D g2d = (Graphics2D) g.create ();
Insets insets = getInsets ();
g2d.translate (insets.left, insets.top);
int width = getWidth ()-insets.left-insets.right;
int height = getHeight ()-insets.top-insets.bottom;
g2d.setColor (PANEL_COLOR);
g2d.fillRect (0, 0, width, height);
g2d.setColor (Color.black);
fg.generate (g2d, depth, width, height);
g2d.dispose ();
}
void setDelay (int ms)
{
if (ms < MIN_DELAY)
throw new IllegalArgumentException (ms+" < MIN_DELAY");
if (ms > MAX_DELAY)
throw new IllegalArgumentException (ms+" > MAX_DELAY");
this.ms = ms;
}
void start ()
{
animThd = new Thread (new Runnable ()
{
public void run ()
{
depth = -1;
Thread currThd = Thread.currentThread ();
while (currThd == animThd)
{
if (++depth > fg.maxDepth ())
depth = 0;
repaint ();
try
{
Thread.sleep (ms);
}
catch (InterruptedException e)
{
}
}
}
});
animThd.start ();
}
void stop ()
{
animThd = null;
}
}
```

The `FractalAnimator`

class presents a simple API that includes a constructor and three additional methods. First, the constructor saves its fractal-generator argument. The `void setDelay(int ms)`

method then specifies a milliseconds delay that inversely determines the animation frame rate (higher delay, slower rate). Finally, the `void start()`

and `void stop()`

methods initiate and terminate the animation.

The API also includes constants `DEFAULT_DELAY`

, `MIN_DELAY`

, `MAX_DELAY`

, and `STEP`

, which can be conveniently used with a GUI component (such as a slider) to let the user determine animation speed. An `IllegalArgumentException`

is thrown for any value passed to `setDelay()`

that lies outside the `MIN_DELAY`

/`MAX_DELAY`

range.

Caution! |
---|

For convenience, I initialize `depth` to -1 at the beginning of `animThd` 's `run()` method. Although `depth` will not normally contain -1 when the component is repainted, it could happen. You should therefore treat any `depth` value less than or equal to 0 as the fractal recursion's stopping condition. |

## A standardized GUI for fractal applets

Although it is possible to place the `FractalGenerator`

interface and `FractalAnimator`

class into their own package (they would need to be marked *public*), I have not done so -- consider this an exercise. Instead, I've embedded them into fractal applets. Each applet's `public void createGUI()`

method creates the fractal generator and fractal animator, and integrates the fractal animator into the applet's GUI, as Listing 3 shows.

**Listing 3. public void createGUI() creates the fractal infrastructure**

```
private void createGUI ()
{
getContentPane ().setLayout (new GridBagLayout ());
final FractalAnimator fa;
fa = new FractalAnimator (new
```*FractalGenerator* ());
fa.setBorder (BorderFactory.createEtchedBorder ());
int size = Math.min (getWidth ()/2, getHeight ()/2);
fa.setPreferredSize (new Dimension ((int) (size*1.15), size));
GridBagConstraints gbc = new GridBagConstraints ();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridheight = 1;
getContentPane ().add (fa, gbc);
JLabel lbl = new JLabel ("Delay (milliseconds)");
gbc = new GridBagConstraints ();
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridheight = 1;
gbc.insets = new Insets (10, 10, 10, 10);
getContentPane ().add (lbl, gbc);
sliderMS = new JSlider (JSlider.HORIZONTAL,
FractalAnimator.MIN_DELAY,
FractalAnimator.MAX_DELAY,
FractalAnimator.DEFAULT_DELAY);
sliderMS.setMinorTickSpacing (FractalAnimator.STEP/2);
sliderMS.setMajorTickSpacing (FractalAnimator.STEP);
sliderMS.setPaintTicks (true);
sliderMS.setPaintLabels (true);
sliderMS.setLabelTable (sliderMS.createStandardLabels (FractalAnimator.STEP));
Dimension prefSize = sliderMS.getPreferredSize ();
prefSize.width += prefSize.width/2;
sliderMS.setPreferredSize (prefSize);
ChangeListener cl;
cl = new ChangeListener ()
{
public void stateChanged (ChangeEvent e)
{
fa.setDelay (sliderMS.getValue ());
}
};
sliderMS.addChangeListener (cl);
gbc = new GridBagConstraints ();
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridheight = 1;
gbc.insets = new Insets (10, 10, 10, 10);
getContentPane ().add (sliderMS, gbc);
JPanel pnl = new JPanel ();
btnAnimate = new JButton ("Animate");
ActionListener al;
al = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
fa.start ();
btnAnimate.setEnabled (false);
btnStop.setEnabled (true);
sliderMS.setEnabled (false);
}
};
btnAnimate.addActionListener (al);
pnl.add (btnAnimate);
btnStop = new JButton ("Stop");
btnStop.setEnabled (false);
al = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
fa.stop ();
btnAnimate.setEnabled (true);
btnStop.setEnabled (false);
sliderMS.setEnabled (true);
}
};
btnStop.addActionListener (al);
pnl.add (btnStop);
gbc = new GridBagConstraints ();
gbc.gridx = 0;
gbc.gridy = 3;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridheight = 1;
getContentPane ().add (pnl, gbc);
}

The `createGUI()`

method uses the `java.awt.GridBagLayout`

class and its `java.awt.GridBagConstraints`

support class to lay out the GUI. After installing this layout manager, `createGUI()`

creates a `FractalAnimator`

, passing an object whose class implements `FractalGenerator`

to the constructor. Figure 1 presents the GUI.

You might have noticed an oddity in the `createGUI()`

source code. Specifically, I have included an `(int) (size*1.15)`

expression to calculate the width of the fractal animator component. I did this to compensate for an equilateral triangle not looking equilateral on my LCD screen (at a resolution of 1024 by 768 pixels). You might need to adjust this expression for your display.

## Koch snowflake

In 1904, Swedish mathematician Helge von Koch presented a paper that included a fractal curve known as the *Koch snowflake* (also known as the *Koch star*). The following algorithm recursively generates this fractal, which is based on an equilateral triangle:

- For each of the equilateral triangle's line segments, divide the line segment into three equal-length line segments.
- For each middle line segment, generate a smaller equilateral triangle whose base line segment replaces the middle line segment.
- Remove the base line segment from the previous step's equilateral triangle. Repeat steps 1 through 3 with this new equilateral triangle.

As you recursively repeat the above steps, the equilateral triangle morphs into a snowflake. Figure 2 reveals the original triangle on the left, the fractal after one iteration in the middle, and the fractal after two iterations on the right.

The Koch snowflake generator algorithm is described by a `KSFractalGenerator`

class. Listing 4 is an excerpt of the `KS`

applet. (See the Resources section for the Koch Snowflake applet, `KS.java`

and `KS.html`

.)

**Listing 4. An excerpt of the Koch Snowflake applet**