Java Fun and Games: Explore the geometry of nature

Journey into the realm of fractals

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.

The fractal animator component reveals a pale yellow background.
Figure 1. The fractal animator component reveals a pale yellow background.

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:

  1. For each of the equilateral triangle's line segments, divide the line segment into three equal-length line segments.
  2. For each middle line segment, generate a smaller equilateral triangle whose base line segment replaces the middle line segment.
  3. 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.

Each side of the original equilateral triangle approaches a limit known as the Koch curve.
Figure 2. Each side of the equilateral triangle approaches a limit known as the Koch curve. Click the thumbnail to view a full-sized image.

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

1 2 3 Page 1
Page 1 of 3