Package org.trebor.freesand

Source Code of org.trebor.freesand.Game$BrushSelectionAction

/*
*
* FreeSand is a pure java implementation of a cellular automata
* simulation inspired by falling sand like games.
*
* Copyright (C) 2007 Robert B. Harris (freesand@trebor.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*/

package org.trebor.freesand;

import static java.awt.Color.DARK_GRAY;
import static java.awt.event.InputEvent.CTRL_MASK;
import static java.awt.event.InputEvent.META_MASK;
import static java.awt.event.InputEvent.SHIFT_MASK;
import static java.awt.event.KeyEvent.*;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.round;
import static javax.swing.JFileChooser.APPROVE_OPTION;
import static javax.swing.KeyStroke.getKeyStroke;
import static org.trebor.freesand.World.Element.*;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.ImageProducer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Random;
import java.util.Stack;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;

import org.trebor.freesand.World.Element;

/**
* Game provides the freesand graphical user interface functionality.
* World simulation occurs in {@link World}, where element behavior is
* handled.
*/

@SuppressWarnings("serial")
public class Game extends JFrame
{
    // constants

    /** default world width */

    public static final int    WORLD_WIDTH    = 300;

    /** default world height */

    public static final int    WORLD_HEIGHT   = 300;

    /** frequency to paint graphics */

    public static final long   WORLD_PAINT_MS = 50;

    /** period of time to test each element */

    public static final long   TEST_PERIOD    = 1000;

    /** delay between when user stops resizing screen and when
     * resize computations occur */

    public static final int    RESIZE_DELAY   = 300;

    /** minimum brush size */

    public static final double MIN_BRUSH_SIZE = 0.01;

    /** maximum brush size */

    public static final double MAX_BRUSH_SIZE = 3 + MIN_BRUSH_SIZE;

    /** brush size change step */

    public static final double BRUSH_STEP     = 0.10;

    /** free sand image file extention */

    public static final String FILE_EXTENSION = "png";

    /** default period of time to show screen messages */

    public static final long   MESSAGE_DISPLAY_TIME = 500;

    /** color to paint on background to display element test results */

    public static final Color  TEST_BACKGROUND_COLOR = new Color(128, 128, 128);

    // globals


    /** simulation world image */

    protected World              world;

    /** buffer for manual double buffering */

    protected BufferedImage      frameBuffer;

    /** should the paint brush be antialiased */

    protected boolean            antiAliasBrush = false;

    /** statistics panel */

    protected JPanel             statsPanel;

    /** world simulation panel */

    protected JPanel             worldPanel;

    /** the menu bar */

    protected JMenuBar           menuBar;

    /** file chooser object */

    protected JFileChooser       fileChooser;

    /** toggle display of statistics panel */

    protected JCheckBoxMenuItem  statsToggleCbmi;

    /** toggle full screen mode */

    protected JCheckBoxMenuItem  fullScreenCbmi;

    /** graphics for frameBuffer */

    protected Graphics2D         bufferGr;

    /** graphics for world simulation */

    protected Graphics2D         worldGr;

    /** a handy dandy random number generater */

    protected Random             rnd = new Random();

    /** stack to store paused state on */

    protected Stack<Boolean>     pausedStack = new Stack<Boolean>();

    /** the cut and paste clipboard */

    protected Clipboard          clipboard;

    /** computed frame rate */

    protected double             frameRate;

    /** low pass filter rate for frame rate */

    protected double             rateFilter  = 0.95;

    /** percent of the time spent updateing the world simulation
     * @see #paintPercent
     */

    protected double             updatePercent;

    /** percent of the time spent painting the world image
     * @see #updatePercent
     */

    protected double             paintPercent;

    /** current brush color */

    protected Color              brushColor  = WATER_EL.getColor();

    /** current brush element */

    protected Element            brushElement = WATER_EL;

    /** current brush shape */

    protected Shape              brushShape  = circle;

    /** current brush name */

    protected String             brushName = "Circle";

    /** default scale to increase brush by */

    protected float              paintScale  = 60;

    /** current brush size */

    protected double             brushSize = 1 + MIN_BRUSH_SIZE;

    /** angle to rotate brush shape by */

    protected double             brushAngle = 0;

    /** world width */

    protected int                width;

    /** world height */

    protected int                height;

    /** this frame */

    protected JFrame             frame = null;

    /** background color of frame */

    protected Color              backGround = new Color(64, 64, 64);

    /** force window to be repainted at next oportunity */

    protected boolean            forcePaint = false;

    /** request that the animation thread pause @see paused */

    private   boolean            pauseRequest = false;

    /** paused state of animation thread @see #pauseRequest */

    private   boolean            paused = pauseRequest;

    /** request that simulation take 1 step */

    private   boolean            takeStep = false;

    /** display message @see #messageDisplayTime */

    private   String             message = "";

    /** time remaining to display message @see #message */

    private   long               messageDisplayTime = 0;

    /** triangle shape */

    public static Shape triangle = createRegularPoly(3);

    /** square shape */

    public static Shape square   = normalize(
      new Rectangle2D.Float(0, 0, 1, 1));

    /** rectangle shape */

    public static Shape rectangle = normalize(
      new Rectangle2D.Float(0f, 0f, 1f, .25f));

    /** diamond shape */

    public static Shape diamond = createRegularPoly(4);

    /** pyramid shape */

    public static Shape pyramid = createPyrmidShape();

    /** pentagon shape */

    public static Shape pentagon = createRegularPoly(5);

    /** hexagon shape */

    public static Shape hexagon  = createRegularPoly(6);

    /** cirlce shape */

    public static Shape circle   = normalize(
      new Ellipse2D.Float(0, 0, 1, 1));

    /** heart shape */

    public static Shape heart    = createHeartShape();

    /** star shape */

    public static Shape star     = createStar(5);

    /** cat shape */

    public static Shape cat      = createCatShape();

    /** dog shape */

    public static Shape dog      = createDogShape();

    /** fish shape */

    public static Shape fish     = createFishShape();

    // file fliter

    javax.swing.filechooser.FileFilter fileFilter =
      new javax.swing.filechooser.FileFilter()
      {
          // accept file?

          public boolean accept(File f)
          {
            // allow browse directories

            if (f.isDirectory())
              return true;

            // allow files with correct extention

            String extension = getExtension(f);
            return (extension != null && extension.equals(FILE_EXTENSION));
          }
          // get file extension

          public String getExtension(File f)
          {
            String ext = null;
            String s = f.getName();
            int i = s.lastIndexOf('.');

            if (i > 0 && i < s.length() - 1)
              ext = s.substring(i+1).toLowerCase();

            return ext;
          }
          // return descriton

          public String getDescription()
          {
            return "Image Files (.png)";
          }
      };

    // basic brush selection actions

    BrushSelectionAction[] basicBrushes =
    {
      new BrushSelectionAction("Square",    square,    getKeyStroke(VK_S, 0)),
      new BrushSelectionAction("Circle",    circle,    getKeyStroke(VK_C, 0)),
      new BrushSelectionAction("Pyramid",   pyramid,   getKeyStroke(VK_P, 0)),
      new BrushSelectionAction("Rectangle", rectangle, getKeyStroke(VK_R, 0)),
    };
    // more brush selection actions

    BrushSelectionAction[] moreBrushes =
    {
      new BrushSelectionAction("Triangle", triangle, null),
      new BrushSelectionAction("Diamond",  diamond,  null),
      new BrushSelectionAction("Pentagon", pentagon, null),
      new BrushSelectionAction("Hexagon",  hexagon,  null),
    };
    // fun brush selection actions

    BrushSelectionAction[] funBrushes =
    {
      new BrushSelectionAction("Heart",   heart, null),
      new BrushSelectionAction("Star",    star,  null),
      new BrushSelectionAction("Cat",     cat,   null),
      new BrushSelectionAction("Dog",     dog,   null),
      new BrushSelectionAction("Fish",    fish,  null),
    };
    // element selection actions

    ElementSelectionAction[] elementActions =
    {
      new ElementSelectionAction(AIR_EL,          getKeyStroke(VK_1, 0)),
      new ElementSelectionAction(WATER_EL,        getKeyStroke(VK_2, 0)),
      new ElementSelectionAction(FIRE1_EL,        getKeyStroke(VK_3, 0)),
      new ElementSelectionAction(EARTH_EL,        getKeyStroke(VK_4, 0)),
      new ElementSelectionAction(SAND_EL,         getKeyStroke(VK_5, 0)),
      new ElementSelectionAction(PLANT_EL,        getKeyStroke(VK_6, 0)),
      new ElementSelectionAction(OIL_EL,          getKeyStroke(VK_7, 0)),
      new ElementSelectionAction(ROCK_EL,         getKeyStroke(VK_8, 0)),
      new ElementSelectionAction(AIR_SOURCE_EL,   getKeyStroke(VK_1, SHIFT_MASK)),
      new ElementSelectionAction(WATER_SOURCE_EL, getKeyStroke(VK_2, SHIFT_MASK)),
      new ElementSelectionAction(FIRE_SOURCE_EL,  getKeyStroke(VK_3, SHIFT_MASK)),
      new ElementSelectionAction(SAND_SOURCE_EL,  getKeyStroke(VK_5, SHIFT_MASK)),
      new ElementSelectionAction(OIL_SOURCE_EL,   getKeyStroke(VK_7, SHIFT_MASK)),
    };
    // load world from disk

    SandAction actionOpen = new SandAction(
      "Open",
      getKeyStroke(VK_O, META_MASK),
      "load from file")
      {
          public void actionPerformed(ActionEvent e)
          {
            readWorld();
          }
      };

    // save world to disk

    SandAction actionSave = new SandAction(
      "Save",
      getKeyStroke(VK_S, META_MASK),
      "save to file")
      {
          public void actionPerformed(ActionEvent e)
          {
            writeWorld();
          }
      };

    // pause sim

    SandAction actionPause = new SandAction(
      "Pause",
      getKeyStroke(VK_SPACE, 0),
      "togglel pause of the simulation")
      {
          public void actionPerformed(ActionEvent e)
          {
            togglePause();
          }
      };

    // step sim

    SandAction actionStep = new SandAction(
      "Step",
      getKeyStroke(VK_SPACE, SHIFT_MASK),
      "cause simulation to take single step")
      {
          public void actionPerformed(ActionEvent e)
          {
            if (!isPaused())
              pause();
            takeStep = true;
          }
      };

    // test performance of various elements

    SandAction actionPerformanceTest = new SandAction(
      "Performance Tests",
      getKeyStroke(VK_T, CTRL_MASK),
      "test performance of elements")
      {
          public void actionPerformed(ActionEvent e)
          {
            new Thread()
            {
                public void run()
                {
                  String OverwriteOption = "Run Tests";
                  String CancelOption = "Cancel";
                  Object[] possibleValues =
                    {OverwriteOption, CancelOption};
                  int n = JOptionPane.showOptionDialog(
                    frame,
                    "Element performance tests will overwrite your current work.",
                    "Run Element Tests?",
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE, null,
                    possibleValues, OverwriteOption);

                  // if overwrite authorized, run tests

                  if (n == 0)
                    testElements();
                }
            }
              .start();
          }
      };

    // exit program

    SandAction actionExit = new SandAction(
      "Exit Program",
      getKeyStroke(VK_Q, META_MASK),
      "quit this program")
      {
          public void actionPerformed(ActionEvent e)
          {
            System.exit(0);
          }
      };

    // cut action

    SandAction actionCut = new SandAction(
      "Cut",
      getKeyStroke(VK_X, META_MASK),
      "cut screen contents")
      {
          public void actionPerformed(ActionEvent e)
          {
            cut();
          }
      };

    // copy action

    SandAction actionCopy = new SandAction(
      "Copy",
      getKeyStroke(VK_C, META_MASK),
      "copy screen contents")
      {
          public void actionPerformed(ActionEvent e)
          {
            copy();
          }
      };

    // paste action

    SandAction actionPaste = new SandAction(
      "Paste",
      getKeyStroke(VK_V, META_MASK),
      "copy screen contents")
      {
          public void actionPerformed(ActionEvent e)
          {
            paste();
          }
      };

    // fill screen with current element

    SandAction actionFill = new SandAction(
      "Fill Screen",
      getKeyStroke(VK_BACK_SPACE, SHIFT_MASK),
      "fill entire screen with currently selected element")
      {
          public void actionPerformed(ActionEvent e)
          {
            fillWorld(brushElement);
          }
      };

    // toggle statistics panel

    SandAction actionToggleStatsPanel = new SandAction(
      "Statistics",
      getKeyStroke(VK_A, META_MASK),
      "toggle visibilty of statistics menu")
      {
          public void actionPerformed(ActionEvent e)
          {
            toggleStatsPanel();
          }
      };

    // go to full screen mode

    SandAction actionFullScreen = new SandAction(
      "Full Screen",
      getKeyStroke(VK_F, META_MASK),
      "toggle full screen mode")
      {
          public void actionPerformed(ActionEvent e)
          {
            toggleFullScreen();
          }
      };
    // go to full screen mode

    SandAction actionEscapeFullScreen = new SandAction(
      "Escape Full Screen",
      getKeyStroke(VK_ESCAPE, 0),
      "return to windowed mode")
      {
          public void actionPerformed(ActionEvent e)
          {
            if (fullScreenCbmi.isSelected())
              fullScreenCbmi.doClick();
          }
      };

    // rotate brush left

    SandAction actionRotateLeft = new SandAction(
      "Rotate Left",
      getKeyStroke(VK_PERIOD, SHIFT_MASK),
      "rotate paint cursor left")
      {
          public void actionPerformed(ActionEvent e)
          {
            brushAngle = (brushAngle + 45) % 360;
            setPaintCursor();
          }
      };

    // rotate brush right

    SandAction actionRotateRight = new SandAction(
      "Rotate Right",
      getKeyStroke(VK_COMMA, SHIFT_MASK),
      "rotate paint cursor right")
      {
          public void actionPerformed(ActionEvent e)
          {
            brushAngle = (brushAngle - 45) % 360;
            setPaintCursor();
          }
      };

    // increase brush size

    SandAction actionGrowBrush = new SandAction(
      "Grow Brush",
      getKeyStroke(VK_PERIOD, 0),
      "increase brush size")
      {
          public void actionPerformed(ActionEvent e)
          {
            brushSize = min(brushSize + BRUSH_STEP, MAX_BRUSH_SIZE);
            setPaintCursor();
          }
      };

    // decrease brush size

    SandAction actionShrinkBrush = new SandAction(
      "Shrink Brush",
      getKeyStroke(VK_COMMA, 0),
      "decrease brush size")
      {
          public void actionPerformed(ActionEvent e)
          {
            brushSize = max(brushSize - BRUSH_STEP, MIN_BRUSH_SIZE);
            setPaintCursor();
          }
      };

    // show about box

    SandAction actionAbout = new SandAction(
      "About",
      null,
      "information about the FreeSand program")
      {
          public void actionPerformed(ActionEvent e)
          {
          }
      };

    // the animation thread

    Thread animation = new Thread()
      {
          public void run()
          {
            // stats values

            long start = 0;
            long update = 0;
            long end = 0;
            double total = 0;
            long statsSum = 0;
            long worldSum = 0;

            // main loop

            while (true)
            {
              // note it if paused requested

              paused = pauseRequest;

              // record start time

              start = System.currentTimeMillis();

              // if we're not paused update the world

              if (!paused || takeStep)
              {
                world.update();
                if (takeStep)
                {
                  takeStep = false;
                  forcePaint = true;
                }
              }
              // otherwise have litte nap to save the cpu

              else
              {
                try
                {
                  sleep(10);
                }
                catch (Exception e)
                {
                  e.printStackTrace();
                }
              }
              // record time to update

              update = System.currentTimeMillis();

              // update time since last paint

              worldSum += total;

              // draw frame every once in a while or if forced to

              if (forcePaint ||
              ((message != null || !paused)
              && worldSum >= WORLD_PAINT_MS))
              {
                // paint world to buffer

                world.paint(bufferGr);

                // if expected to, paint message to buffer

                if (messageDisplayTime > 0 && message != null)
                  paintMessage(bufferGr, message);
                else
                  message = null;

                // now actually draw buffer to frame

                worldPanel.repaint();

                // update time sum and mark that we did
                // force paint

                worldSum = 0;
                forcePaint = false;
              }
              // compute total time for update and draw

              end = System.currentTimeMillis();
              total = (float)(end - start);

              // if the message has not timed out,
              // update message time

              if (messageDisplayTime > 0)
                messageDisplayTime -= total;

              // if not paused update stats

              if (total > 0 && !paused)
              {
                // compute filtered frame rate

                frameRate = rateFilter * frameRate +
                  (1 - rateFilter) * 1000 / total;

                // compute filtered percents

                updatePercent = rateFilter * updatePercent +
                  (1 - rateFilter) * ((update - start) / total);
                paintPercent = rateFilter * paintPercent +
                  (1 - rateFilter) * ((end - update) / total);

                // if we've been going for a second, print stats

                if ((statsSum += total) >= 1000)
                {
                  statsSum = 0;
                  statsPanel.repaint();
                }
                // total = tmp;
              }
            }
          }
      };

    /**
     * Main entry point into program.  It creates and starts a
     * freesand game. It also sets the mac style menus.
     *
     * @param  args currently ignored
     */

    static public void main(String[] args)
    {
      // if we're on a mac, use mac style menus

      System.setProperty("apple.laf.useScreenMenuBar", "true");

      // create a new free sand game

      new Game(true);
    }
    /**
     * Construct a freesand game with option to build gui
     * components at construction time.
     *
     * @param build build and start game at construction time
     * @see #buildGame()
     */

    public Game(boolean build)
    {
      if (build)
        buildGame();
    }
    /**
     * Construct gui elements, display and start game.
     */

    protected void buildGame()
    {
      // create the frame

      constructFrame();

      // display the frame

      displayFrame();

      // create the world

      constructWorld();

      // start the animation thread

      animation.start();

      // set the cursor

      setPaintCursor();
    }
    /**
     * Put together elements of the gui frame.
     */

    protected void constructFrame()
    {
      // indentify the frame

      frame = this;

      // get system clipboard

      try
      {
        clipboard =  Toolkit.getDefaultToolkit().getSystemClipboard();
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
      // app exits on frame close

      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

      fileChooser = new JFileChooser();
      fileChooser.addChoosableFileFilter(fileFilter);

      // create the menu bar

      menuBar = new JMenuBar();

      // add the file menu

      JMenu menu = new JMenu("File");
      menu.add(actionOpen);
      menu.add(actionSave);
      menu.addSeparator();
      menu.add(actionPause);
      menu.add(actionStep);
      menu.add(actionPerformanceTest);
      menu.addSeparator();
      menu.add(actionExit);
      menuBar.add(menu);

      // add edit menu

      menu = new JMenu("Edit");
      menu.add(actionCut);
      menu.add(actionCopy);
      menu.add(actionPaste);
      menu.addSeparator();
      menu.add(actionFill);
      menuBar.add(menu);

      // add view menu

      menu = new JMenu("View");
      menu.add(statsToggleCbmi =
      new JCheckBoxMenuItem(actionToggleStatsPanel));
      menu.add(fullScreenCbmi =
      new JCheckBoxMenuItem(actionFullScreen));
      menu.add(actionEscapeFullScreen);
      menuBar.add(menu);

      // full screen only enabled if it's supported

      GraphicsDevice gv = GraphicsEnvironment.
        getLocalGraphicsEnvironment().getScreenDevices()[0];
      fullScreenCbmi.setEnabled(gv.isFullScreenSupported());

      // add brush menu

      menu = new JMenu("Brush");
      for (BrushSelectionAction bb: basicBrushes)
        menu.add(bb);
      menu.addSeparator();

      // add more brushes sub menu

      JMenu mBrushes = new JMenu("More Brushes");
      for (BrushSelectionAction ab: moreBrushes)
        mBrushes.add(ab);
      menu.add(mBrushes);

      // add fun brushes sub menu

      mBrushes = new JMenu("Fun Brushes");
      for (BrushSelectionAction fb: funBrushes)
        mBrushes.add(fb);
      menu.add(mBrushes);

      // add brush manipulation options

      menu.addSeparator();
      menu.add(actionRotateLeft);
      menu.add(actionRotateRight);
      menu.addSeparator();
      menu.add(actionGrowBrush);
      menu.add(actionShrinkBrush);
      menuBar.add(menu);
      setJMenuBar(menuBar);

      // add element menu

      menu = new JMenu("Elements");
      for (ElementSelectionAction ea: elementActions)
        menu.add(ea);
      menuBar.add(menu);

      // add toolbar

      JToolBar toolBar = new JToolBar("Elements");
      for (ElementSelectionAction ea: elementActions)
        toolBar.add(ea);
      //add(toolBar);

      // set background

      setBackground(backGround);

      // create graphics panel

      worldPanel = new JPanel()
        {
            public void paint(Graphics graphics)
            {
              Graphics2D g = (Graphics2D)graphics;
              g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
              g.drawImage(frameBuffer, 0, 0, null);
            }
        };
      worldPanel.setPreferredSize(
        new Dimension(WORLD_WIDTH, WORLD_HEIGHT));
      worldPanel.setLayout(null);
      add(worldPanel);

      // add component listener to handle frame resize

      addComponentListener(new ComponentAdapter()
        {
            // timing thread that waits until
            // the user has stopped dragging to resize
            // the window

            Thread resizeThread = null;

            // handle resize event

            public void componentResized(ComponentEvent e)
            {
              // assign the timing thread

              (resizeThread = new Thread()
                {
                    public void run()
                    {
                      try
                      {
                        // sleep for a bit

                        sleep(RESIZE_DELAY);

                        // if, when woken up, some other
                        // delay thread has not been
                        // created, then we'll it handle
                        // the resize event

                        if (resizeThread == this)
                          resize();
                      }
                      catch (Exception ex)
                      {
                        ex.printStackTrace();
                      }
                    }
                }
                ).start();
            }
        }
        );

      // add mouse listener

      MouseInputAdapter mia = new MouseInputAdapter()
        {
            // mouse clicked event

            public void mouseClicked(MouseEvent e)
            {
              paint(e);
            }
            // mouse clicked event

            public void mouseDragged(MouseEvent e)
            {
              paint(e);
            }
            // paint

            public void paint(MouseEvent e)
            {
              Element source = null;
              if (e.isShiftDown() && (source = Element.lookup(brushColor)
              .lookupSourceOrOutput()) != null)
                worldGr.setColor(source.getColor());
              else
                worldGr.setColor(brushColor);

              paintBrushShape(brushShape, worldGr, e.getX(), e.getY());
              forcePaint = true;
            }
        };
      worldPanel.addMouseListener(mia);
      worldPanel.addMouseMotionListener(mia);

      // create the stats panel

      statsPanel = new JPanel()
        {
            public void paint(Graphics graphics)
            {
              Graphics2D gr = (Graphics2D)graphics;
              gr.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
              gr.setColor(new Color(205, 205, 205));
              gr.fill(gr.getClipBounds());
              gr.setColor(Color.GRAY);
              gr.setFont(Font.decode("Courier"));
              gr.drawString("fps:   " +
              round(frameRate * 100) / 100.0, 5, 15);
              gr.drawString("sim:   " +
              round(updatePercent * 100) + "%", 5, 30);
              gr.drawString("paint: " +
              round(paintPercent  * 100) + "%", 5, 45);
              gr.drawString("width:  " + width,  100, 15);
              gr.drawString("height: " + height, 100, 30);
              gr.drawString("pixels: " + (width * height), 100, 45);
              gr.drawString("brush: " + brushName,    195, 15);
              gr.drawString("elmnt: " + brushElement, 195, 30);
            }
        };

      // add the statistcs panel

      statsPanel.setPreferredSize(new Dimension(300, 55));
      statsPanel.setMinimumSize(new Dimension(150, 55));
      statsPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 55));
    }
    /**
     * Toggle display of statistics panel.
     */

    public void toggleStatsPanel()
    {
      if (statsToggleCbmi.isSelected())
        add(statsPanel);
      else
        remove(statsPanel);

      worldPanel.setPreferredSize(
        new Dimension(worldPanel.getWidth(), worldPanel.getHeight()));

      invalidate();
      statsPanel.invalidate();
      pack();
    }

    /**
     * Toggle full screen mode.
     */

    public void toggleFullScreen()
    {
      // get the graphics device from the local graphic environment

      GraphicsDevice gv = GraphicsEnvironment.
        getLocalGraphicsEnvironment().getScreenDevices()[0];

      // if full screen selected

      if (fullScreenCbmi.isSelected())
      {
        // if full screen is supported setup frame accordingly

        if (gv.isFullScreenSupported())
        {
          setVisible(false);
          dispose();
          setUndecorated(true);
          pack();
          gv.setFullScreenWindow(this);
          setVisible(true);
        }
        else
        {
          showMessage("Not Supported");
          fullScreenCbmi.setSelected(false);
        }
      }
      // otherwise just pack and show the thing

      else
      {
        setVisible(false);
        dispose();
        setUndecorated(false);
        pack();
        gv.setFullScreenWindow(null);
        setVisible(true);
      }
    }
    /**
     * Pack and display frame. Also record size of world.
     */

    protected void displayFrame()
    {
      pack();
      setVisible(true);

      // anti alias graphics

      ((Graphics2D)worldPanel.getGraphics()).setRenderingHint(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);

      // record the size of the world

      width = worldPanel.getWidth();
      height = worldPanel.getHeight();
    }
    /**
     * Construct simulaiton world.
     */

    protected void constructWorld()
    {
      // create the world

      world = (world == null)
        ? new World(width, height)
        : new World(width, height, world);

      // initialize the world

      world.initialize();

      // frame buffer

      frameBuffer = new BufferedImage(
        world.getWidth(),
        world.getHeight(),
        BufferedImage.TYPE_INT_ARGB);
      bufferGr = (Graphics2D)frameBuffer.getGraphics();

      // get graphics for world image

      worldGr = (Graphics2D)world.getGraphics();
    }
    /**
     * Resize the world to match the current world panel dimentions.
     */

    public void resize()
    {
      pushPaused(true);

      // get the world panel dimensions

      width = worldPanel.getWidth();
      height = worldPanel.getHeight();

      // construct the world

      constructWorld();
      popPaused();
      forcePaint = true;
    }
    /**
     * Copy world frame image to copy/paste buffer.
     */

    public void copy()
    {
      showMessage("Copying");
      World copy = new World(world);
      clipboard.setContents(copy, null);
      forcePaint = true;
    }
    /**
     * Cut world frame image to copy/paste buffer.
     */

    public void cut()
    {
      showMessage("Cutting");
      World copy = new World(world);
      clipboard.setContents(copy, null);
      fillWorld(AIR_EL);
      forcePaint = true;
    }
    /**
     * Paste image in copy/paste buffer to world frame.
     */

    public void paste()
    {
      pushPaused(true);
      showMessage("Pasting");
      try
      {
        Transferable content = clipboard.getContents(null);
        DataFlavor flavor = content.getTransferDataFlavors()[0];

        if (content.isDataFlavorSupported(DataFlavor.imageFlavor))
        {
          setWorldImage((BufferedImage)content
          .getTransferData(DataFlavor.imageFlavor));
        }
        else if (flavor.isMimeTypeEqual("image/x-pict"))
        {
          InputStream is = (InputStream)content.getTransferData(flavor);
          Image image = (Image)getImageFromPictStream(is);

          if (image != null)
            setWorldImage(image);
        }
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
      popPaused();
      forcePaint = true;
    }
    /**
     * Convert pixels in provided image to nearest {@link Element} color.
     *
     * @param  image image to convert
     * @return The modified image passed to this function. No new image is created.
     */

    public static BufferedImage convertToElements(BufferedImage image)
    {
      int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
      for (int i = 0; i < pixels.length; ++i)
        pixels[i] = nearest(new Color(pixels[i])).getValue();
      return image;
    }
    /**
     * Read pict type image from input stream.  This is used to
     * read images out of the copy/paste buffer.
     *
     * @param  is inputstream from wich the image will be read.
     * @return The image read from the input stream.
     */


    @SuppressWarnings("unchecked")

    protected Image getImageFromPictStream(InputStream is)
    {
      try
      {
        // cast nulls to eliminate compiler warnings

        java.lang.Object[] nullObjects = null;
        java.lang.Class<?>[] nullClasses = null;

        // a place to put the data

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] header = new byte[512];
        byte[] buf = new byte[4096];
        int retval = 0;
        int size = 0;
        Method m;

        // write data into byte array

        baos.write(header, 0, 512);
        while ((retval = is.read(buf, 0, 4096)) > 0)
          baos.write(buf, 0, retval);
        baos.close();

        // if we got no data, return null

        size = baos.size();
        if (size<=0)
          return null;

        // now get the data into a nice big array

        byte[] imgBytes = baos.toByteArray();

        // create a quick time session

        Class c = Class.forName("quicktime.QTSession");
          m = c.getMethod("isInitialized", nullClasses);
       
        // verify session initialized

        Boolean b = (Boolean)m.invoke(nullObjects, nullObjects);
        if (b.booleanValue() == false)
        {
          m = (Method)c.getMethod("open", nullClasses)
            .invoke(null, nullObjects);
        }

        // get the handle

        c = Class.forName("quicktime.util.QTHandle");
        Constructor con = c.getConstructor(
          new Class[] {imgBytes.getClass()});
        Object handle = con.newInstance(
          new Object[] {imgBytes});

        String s = new String("PICT");
        c = Class.forName("quicktime.util.QTUtils");
        m = c.getMethod("toOSType", new Class[] {s.getClass()});


        Integer type = (Integer)m.invoke(nullObjects, new Object[] { s });
        c = Class.forName("quicktime.std.image.GraphicsImporter");
        con = c.getConstructor(new Class[] { Integer.TYPE });
        Object importer= con.newInstance(new Object[] { type });
        m = c.getMethod("setDataHandle", new Class[]
          {Class.forName("quicktime.util." + "QTHandleRef") });
        m.invoke(importer, new Object[] { handle });
        m = c.getMethod("getNaturalBounds", nullClasses);
        Object rect= m.invoke(importer, nullObjects);
        c = Class.forName("quicktime.app.view.GraphicsImporterDrawer");
        con = c.getConstructor(new Class[] { importer.getClass() });
        Object iDrawer = con.newInstance(new Object[] { importer });
        m = rect.getClass().getMethod("getWidth", nullClasses);
        Integer width= (Integer)m.invoke(rect, nullObjects);
        m = rect.getClass().getMethod("getHeight", nullClasses);
        Integer height= (Integer)m.invoke(rect, nullObjects);
        Dimension d= new Dimension(width.intValue(), height.intValue());
        c = Class.forName("quicktime.app.view.QTImageProducer");
        con = c.getConstructor(new Class[]
          {iDrawer.getClass(), d.getClass()});
        Object producer= con.newInstance(new Object[] {iDrawer, d});
        if (producer instanceof ImageProducer)
          return(Toolkit.getDefaultToolkit()
          .createImage((ImageProducer)producer));
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
      return null;
    }
    /**
     * Write current world image to disk.
     */

    public void writeWorld()
    {
      try
      {
        pushPaused(true);
        showMessage("Saving");
        if (fileChooser.showSaveDialog(this) == APPROVE_OPTION)
        {
          // select file

          File file = fileChooser.getSelectedFile();

          // if file name does not end if correct file extension
          // add file extension

          if (!file.toString().toLowerCase()
          .endsWith("." + FILE_EXTENSION))
          {
            file = new File(file + "." + FILE_EXTENSION);
          }
          // if file exists be sure we should overwrite it

          if (file.exists())
          {
            String OverwriteOption = "Overwrite";
            String CancelOption = "Cancel";
            Object[] possibleValues = {OverwriteOption, CancelOption};
            int n = JOptionPane.showOptionDialog(
              this,
              file.getName() +
              " already exists in this directory.  Should it be overwritten?",
              "Overwrite?",
              JOptionPane.YES_NO_OPTION,
              JOptionPane.QUESTION_MESSAGE, null,
              possibleValues, OverwriteOption);

            // if overwrite authorized

            if (n == 0)
              ImageIO.write(world, "png", file);
          }
          else
            ImageIO.write(world, "png", file);
        }
        popPaused();
        forcePaint = true;
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }
    /**
     * Read an image from the disk.
     */

    public void readWorld()
    {
      try
      {
        pushPaused(true);
        showMessage("Loading");
        if (fileChooser.showOpenDialog(this) == APPROVE_OPTION)
          setWorldImage(ImageIO.read(fileChooser.getSelectedFile()));

        popPaused();
        forcePaint = true;
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }
    /**
     * Set the world image to the provided image.
     *
     * @param  rawImage image to set as the new world image
     */

    protected void setWorldImage(Image rawImage)
    {
      pushPaused(true);

      // convert image to buffered image with corrected pixel colors

      BufferedImage image = new BufferedImage(
        rawImage.getWidth(null),
        rawImage.getHeight(null),
        BufferedImage.TYPE_INT_ARGB);
      image.getGraphics().drawImage(rawImage, 0, 0, null);
      image = convertToElements(image);

      // create world from image

      World newWorld = new World(image);

      // if new world differntly sized than current, maybe
      // the window should be resized

      if (newWorld.width != worldPanel.getWidth() || newWorld.height != worldPanel.getHeight())
      {
        String ResizeOption = "Resize";
        String CancelOption = "Cancel";
        Object[] possibleValues = {ResizeOption, CancelOption};
        int n = JOptionPane.showOptionDialog(
          this, "Resize to " + newWorld.width + "x" + newWorld.height +
          " to fit new image?", "Resize?",
          JOptionPane.YES_NO_OPTION,
          JOptionPane.QUESTION_MESSAGE, null,
          possibleValues, ResizeOption);

        // if resize requested

        if (n == 0)
        {
          worldPanel.setPreferredSize(
            new Dimension(newWorld.width, newWorld.height));
          invalidate();
          worldPanel.invalidate();
          pack();

          // get the world panel dimensions

          // construct the world

          width = newWorld.width;
          height = newWorld.height;
        }
      }

      world = newWorld;
      worldGr = (Graphics2D)world.getGraphics();
      constructWorld();

      popPaused();
    }
    /**
     * Execute performance tests on all {@link Element}s.  Destroys
     * current world image.
     */

    public void testElements()
    {
      try
      {
        showMessage("Testing");

        // set rate filter to zero for accurate readings

        double tmpRateFilter = rateFilter;
        rateFilter = 0;

        // a class to store results so, so they sort

        class Result implements Comparable<Object>
        {
          Element element;
          double fps;

          public Result(Element element, double fps)
          {
            this.element = element;
            this.fps = fps;
          }
          public int compareTo(Object o)
          {
            Result other = (Result)o;
            if (other.fps < fps)
              return -1;
            return other.fps > fps ? 1 : 0;
          }
        };
        // a place to put the results

        Vector<Result> results = new Vector<Result>();

        // ensure that the system is running

        unpause();

        // test elements in element action window

        for (int i = 0; i < elementActions.length; ++i)
        {
          // paint the world with the subject element

          fillWorld(elementActions[i].element);

          // record data for the test period


          double maxFps = 0;
          long start = System.currentTimeMillis();
          while (System.currentTimeMillis() - start
          < TEST_PERIOD)
          {
            Thread.sleep(7);
            if (frameRate > maxFps)
              maxFps = frameRate;
          }
          // now add results to results data structure

          results.add(new Result(elementActions[i].element, maxFps));
        }
        // pause the system

        pause();
        Thread.sleep(2 * WORLD_PAINT_MS);

        // set a reasonable background color

        fillWorld(TEST_BACKGROUND_COLOR);

        // sort the results

        //Vector<Result> checkedResult = (Vector<Result>)Collections.checkedCollection(results, Result.class);
        Collections.sort(results);

        // establish the fastest element

        double fastest = 0;
        for (Result result: results)
          if (result.fps > fastest)
            fastest = result.fps;

        // compute display scale based on fastest material

        double scale = (width - 40) / fastest;

        // set results font

        worldGr.setFont(worldGr.getFont().deriveFont(10f));

        // display the results

        int i = 0;
        showMessage("Results");
        for (Result result: results)
        {
          Element el = result.element;
          double fps = result.fps;
          worldGr.setColor(el.getColor());
          worldGr.fillRect(20, 20 + 20 * i, (int)(scale * fps), 10);
          worldGr.setColor(computeMatchingColor(el.getColor()));
          worldGr.drawString(el + ": " + round(100 * fps) / 100d + " fps",
          25, 29 + 20 * i++);
          forcePaint = true;
        }
        // restore rate filter

        rateFilter = tmpRateFilter;

        // force results to paint

        forcePaint = true;
      }
      catch (Exception ex)
      {
        ex.printStackTrace();
      }
    }
    /**
     * Push current paused state onto a stack and request new
     * paused state.  This function does not return until pause
     * state is synchronizePause.
     *
     * @param  pauseRequest new requested pause state
     * @see    #popPaused()
     * @see    #synchronizePause()
     */

    public void pushPaused(boolean pauseRequest)
    {
      pausedStack.push(this.pauseRequest);
      this.pauseRequest = pauseRequest;
      synchronizePause();
    }
    /**
     * Pop paused state from stack.  This function does not return
     * until pause state is synchronized.
     *
     * @return Returns the resulting paused state.
     * @see    #pushPaused(boolean pauseRequest)
     * @see    #synchronizePause()
     */

    public boolean popPaused()
    {
      this.pauseRequest = pausedStack.pop();
      synchronizePause();
      return isPaused();
    }
    /**
     * Pause simulation.  This function does not return until pause
     * state is synchronized.
     *
     * @see    #unpause()
     * @see    #synchronizePause()
     */

    public void pause()
    {
      pauseRequest = true;
      synchronizePause();
      showMessage("Paused");
    }
    /**
     * Resume paused simulation.  This function does not return
     * until pause state is synchronized.
     *
     * @see    #pause()
     * @see    #synchronizePause()
     */

    public void unpause()
    {
      pauseRequest = false;
      synchronizePause();
    }
    /**
     * Test paused state of simulation.  This function does not
     * return until pause state is synchronized.
     *
     * @return The paused state of the simulation.
     * @see    #pause()
     * @see    #unpause()
     * @see    #synchronizePause()
     */

    public boolean isPaused()
    {
      synchronizePause();
      return paused;
    }
    /**
     * Toggle paused state of simulation.  This function does not
     * return until pause state is synchronized.
     *
     * @return The paused state of the simulation.
     * @see    #synchronizePause()
     */

    public boolean togglePause()
    {
      pauseRequest = !pauseRequest;
      synchronizePause();
      if (paused)
        showMessage("Paused");
      return paused;
    }
    /**
     * Wait until the animation thread has recognized the requested
     * paused state.  This is done so that the the world is not
     * treated as though it is paused even if it's in the middle
     * updating the world.
     *
     * @see    #pause()
     * @see    #unpause()
     * @see    #isPaused()
     * @see    #pushPaused(boolean pauseRequest)
     * @see    #popPaused()
     */

    protected void synchronizePause()
    {
      try
      {
        while (paused != pauseRequest)
          Thread.sleep(10);
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }
    /**
     * Fill entire world image with a provided element.  This
     * function must pause or the animation thread may cause one
     * pixel to be missed.
     *
     * @param  element element to fill image with
     */

    protected void fillWorld(Element element)
    {
      fillWorld(element.getColor());
    }
    /**
     * Fill entire world image with a provided color.  This
     * function must pause or the animation thread may cause one
     * pixel to be missed.
     *
     * @param  color color to fill image with
     */

    protected void fillWorld(Color color)
    {
      // must pause or there might be a left over pixel

      pushPaused(true);
      world.fill(color);
      popPaused();
    }
    /**
     * Set current paint cursor from currently selected brush.
     */

    protected void setPaintCursor()
    {
      // create cursor image

      BufferedImage cursorImage =
        createShapeImage(transformBrush(brushShape, 0, 0),
        computeMatchingColor(brushColor),
        antiAliasBrush);

      // create the custom cursor

      Toolkit tk = Toolkit.getDefaultToolkit();
      Cursor cursor = tk.createCustomCursor(
        cursorImage,
        new Point(cursorImage.getWidth() / 2, cursorImage.getHeight() / 2),
        "");

      // set the cursor for the world panel

      worldPanel.setCursor(cursor);
    }
    // construct an image of a given shape

    /**
     * Create a BufferedImage from a given Shape.  The returned
     * image has the shape painted into the image filled with the
     * provided color, and optionally is antialiased.
     *
     * @param  shape shape to create image with
     * @param  color color to fill shape when it's drawn into image
     * @param  antialias whether or not to antialias the painted shape
     * @return A BufferedImage the size of the provided shape.
     */

    public static BufferedImage createShapeImage(Shape shape, Color color,
    boolean antialias)
    {
      // get the bounds and create the image

      Rectangle2D bounds = shape.getBounds();
      int size = max((int)(bounds.getWidth()), (int)(bounds.getHeight()));

      BufferedImage image = new BufferedImage(
        size, size,
        BufferedImage.TYPE_4BYTE_ABGR);

      // paint the shape onto the image

      Graphics2D g = (Graphics2D)image.getGraphics();
      if (antialias)
        g.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON);
      g.translate((size - bounds.getWidth ()) / 2 - bounds.getX(),
      (size - bounds.getHeight()) / 2 - bounds.getY());
      g.setColor(color);
      g.fill(shape);

      // return the image

      return image;
    }
    // set cursor of a container and all its descendents

    /**
     * Set the cursor for a given container and all its descendents.
     * @deprecated This function should no loger be needed as
     * cursors are being handled in a better way.
     *
     * @param  container container to change cursor of
     * @param  cursor new cursor for the container
     */

    public static void setCursor(Container container, Cursor cursor)
    {
      for (Component c: container.getComponents())
      {
        c.setCursor(cursor);
        if (c instanceof Container)
          setCursor((Container)c, cursor);
      }
    }

    /**
     * SandAction is derived from AbstractAction and provides a
     * standard class from which to subclass Game actions.
     */

    public abstract class SandAction extends AbstractAction
    {
        /**
         * Create a SandAction with a given name, shortcut key
         * and description.
         *
         * @param  name name of action
         * @param  key shortcut key to trigger action
         */

        public SandAction(String name, KeyStroke key,
        String description)
        {
          putValue(NAME, name);
          putValue(SHORT_DESCRIPTION, description);
          putValue(ACCELERATOR_KEY, key);
        }
        /**
         * Called when the given action is to be executed.  This
         * function must be implemented by the subclass.
         *
         * @param  e action event
         */

        abstract public void actionPerformed(ActionEvent e);

        /**
         * Update the enabled state of the given action.
         * Currently this functionality is not used.
         *
         * @return The enabled state of this action.
         */

        // update enabled state of action

        public boolean updateEnabledState()
        {
          return isEnabled();
        }
    }
    /**
     * BrushAction is derived from SandActionAction and is used
     * to select different brushes.
     */

    protected class BrushSelectionAction extends SandAction
    {
        Shape brush;

        // create a brush selection action

        public BrushSelectionAction(String name, Shape brush, KeyStroke key)
        {
          super(name, key, "Select " + name.toLowerCase() + " brush");
          this.brush = brush;

          // construct image of brush

          putValue(
            SMALL_ICON,
            new ImageIcon(createShapeImage(
              transformBrush(brush, 0, 0),
              DARK_GRAY, true)));
        }
        // execute action

        public void actionPerformed(ActionEvent e)
        {
          showMessage(getValue(NAME) + " Brush");
          brushShape = brush;
          brushName = getValue(NAME).toString();
          setPaintCursor();
          forcePaint = true;
        }
    }
    // element selection action

    class ElementSelectionAction extends SandAction
    {
        Element element;

        // create element selection action

        public ElementSelectionAction(Element element, KeyStroke key)
        {
          super(null, key, "Select " + element + " element");
          this.element = element;

          // construct image of element

          BufferedImage elementImage = new BufferedImage(
            70, 25,
            BufferedImage.TYPE_4BYTE_ABGR);
          Graphics g = elementImage.getGraphics();
          g.setClip(0, 0, elementImage.getWidth(), elementImage.getHeight());
          paint(g);
          putValue(SMALL_ICON, new ImageIcon(elementImage));
        }
        // paint element icon onto provided graphics

        public void paint(Graphics graphics)
        {
          Graphics2D g = (Graphics2D)graphics;
          g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
          g.setColor(new Color(0,0,0,0));
          Rectangle2D bounds = g.getClipBounds();
          g.fill(bounds);
          g.setColor(element.getColor());
          g.fillRoundRect(0, 0,
          (int)bounds.getWidth() - 1,
          (int)bounds.getHeight() - 1,
          20, 20);
          g.setFont(g.getFont().deriveFont(10f));
          g.setColor(computeMatchingColor(element.getColor()));
          FontMetrics fm = g.getFontMetrics();
          Rectangle2D sBounds = fm.getStringBounds(element.toString(), g);
          g.drawString(
            element.toString(),
            (int)((bounds.getWidth()  - sBounds.getWidth()     ) / 2),
            (int)((bounds.getHeight() + sBounds.getHeight() / 2) / 2));
        }
        // execute action

        public void actionPerformed(ActionEvent e)
        {
          showMessage(element.toString());
          brushColor = element.getColor();
          brushElement = element;
          setPaintCursor();
          forcePaint = true;
        }
    }
    /**
     * Show a message on the screen for the default amount of time.
     *
     * @param  message message to show on the screen
     * @see    #MESSAGE_DISPLAY_TIME
     */

    public void showMessage(String message)
    {
      showMessage(message, MESSAGE_DISPLAY_TIME);
    }
    /**
     * Show a message on the screen for the specifed number of miliseconds.
     *
     * @param  message message to show on the screen
     * @param  messageDisplayTime time in miliseconds to display message
     */

    public void showMessage(String message, long messageDisplayTime)
    {
      this.message = message;
      this.messageDisplayTime = messageDisplayTime;
      forcePaint = true;
    }
    /**
     * Paint provided message onto the provided graphics object as
     * big as possible.  The function does not know how to handle
     * carriage returns.
     *
     * @param  g graphic on which to draw message
     * @param  message message to draw
     */

    public void paintMessage(Graphics2D g, String message)
    {
      g.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);

      // figure out how big the string will be

      Rectangle2D sBounds = g.getFontMetrics().getStringBounds(message, g);

      // compute how much to increase font by to make it fit
      // nicely on the screen

      double increase = (worldPanel.getWidth() / sBounds.getWidth()) * 0.9d;

      // set font size

      Font font = g.getFont();
      g.setFont(font.deriveFont(font.getSize() * (float)increase));

      // draw message into frame

      g.setColor(new Color(255, 255, 255, 128));
      sBounds = g.getFontMetrics().getStringBounds(message, g);
      g.drawString(
        message,
        (int)((worldPanel.getWidth()  - sBounds.getWidth()     ) / 2),
        (int)((worldPanel.getHeight() + sBounds.getHeight() / 2) / 2));

      // restore font

      g.setFont(font);
    }
    /**
     * Given a color, compute a close color which will be visible
     * if overlayed on the original color.
     *
     * @param  color color to find match to
     * @return The new matched color.
     */

    Color computeMatchingColor(Color color)
    {
      float[] hsb = Color.RGBtoHSB(
        color.getRed(),
        color.getGreen(),
        color.getBlue(),
        new float[3]);

      return hsb[2] < 0.2
        ? Color.getHSBColor(hsb[0], hsb[1], hsb[2] < .1
        ? 0.5f
        : hsb[2] * 2f)
        : Color.getHSBColor(hsb[0], hsb[1], hsb[2] / 2);
    }
    /**
     * Create normalized pyramid shape.
     *
     * @return The normalized shape.
     * @see #normalize(Shape shape)
     */

    public static Shape createPyrmidShape()
    {
      Area gp = new Area();
      gp.add(new Area(createRegularPoly(4)));
      gp.subtract(new Area(translate(square, 0, 0.5)));
      return normalize(gp);
    }
    /**
     * Create normalized heart shape.
     *
     * @return The normalized shape.
     * @see #normalize(Shape shape)
     */

    public static Shape createHeartShape()
    {
      GeneralPath gp = new GeneralPath();
      gp.append(translate(circle, 0.5, 0), false);
      gp.append(translate(circle, 0, 0.5), false);
      gp.append(square, false);
      return normalize(rotate(gp, 225));
    }
    /**
     * Create normalized cat shape.
     *
     * @return The normalized shape.
     * @see #normalize(Shape shape)
     */

    public static Shape createCatShape()
    {
      Area cat = new Area(circle);
      Area wisker = new Area(new Rectangle2D.Double(0, -.01, .3, .02));

      // create left wiskers

      Area leftWiskers = new Area();
      leftWiskers.add(rotate(wisker, -20));
      leftWiskers.add(rotate(wisker,  20));
      leftWiskers.add(rotate(wisker,  20));

      // create right wiskers

      Area rightWiskers = new Area();
      rightWiskers.add(rotate(wisker, 180));
      rightWiskers.add(rotate(wisker, -20));
      rightWiskers.add(rotate(wisker, -20));

      // add the ears

      Area ear = new Area(translate(scale(triangle, .5, .5), 0.0, -0.6));
      translate(ear, .07, 0);
      cat.add(ear);
      rotate(cat, 60);
      translate(ear, -.14, 0);
      cat.add(ear);
      rotate(cat, -30);

      // add the eyes

      Area eye = new Area(scale(circle, 0.18, 0.18));
      eye.subtract(new Area(scale(circle, .06, .12)));
      translate(eye, -.15, -.1);
      cat.subtract(eye);
      translate(eye, .3, 0);
      cat.subtract(eye);

      // add the wiskers

      cat.subtract(translate(leftWiskers,   .08, .14));
      cat.subtract(translate(rightWiskers, -.08, .14));

      // add nose

      Area nose = new Area(createRegularPoly(3));
      rotate(nose, 180);
      scale(nose, .15, .15);
      translate(nose, 0, .1);
      cat.subtract(nose);

      // flatten the cat

      scale(cat, 1.0, 0.85);

      // return normalized shape

      return normalize(cat);
    }
    /**
     * Create normalized dog shape.
     *
     * @return The normalized shape.
     * @see #normalize(Shape shape)
     */

    public static Shape createDogShape()
    {
      Area dog = new Area(circle);

      // add the ears

      Area ear = new Area(scale(circle, .4, .7));
      rotate(ear, 20);
      translate(ear, -.5, -.2);
      dog.subtract(ear);
      scale(ear, -1, 1);
      dog.subtract(ear);
      scale(ear, -1, 1);
      translate(ear, -.05, 0);
      dog.add(ear);
      scale(ear, -1, 1);
      dog.add(ear);
      scale(ear, -1, 1);

      // add the eyes

      Area eye = new Area(scale(circle, 0.18, 0.18));
      eye.subtract(new Area(scale(circle, .12, .12)));
      translate(eye, -.15, -.1);
      dog.subtract(eye);
      translate(eye, .3, 0);
      dog.subtract(eye);

      // add snout

      Area snout = new Area(circle);
      scale(snout, .30, .30);
      translate(snout, 0, .2);
      dog.subtract(snout);

      // add nose

      Area nose = new Area(createRegularPoly(3));
      rotate(nose, 180);
      scale(nose, .20, .20);
      translate(nose, 0, .2);
      dog.add(nose);

      // stretch the dog

      scale(dog, 0.90, 1.0);

      // return normalized shape

      return normalize(dog);
    }
    /**
     * Create normalized fish shape.
     *
     * @return The normalized shape.
     * @see #normalize(Shape shape)
     */

    public static Shape createFishShape()
    {
      Area fish = new Area();
      Area body = new Area(new Arc2D.Double(0.0, 0, 1.0, 1.0, 30, 120, Arc2D.CHORD));
      Rectangle2D bounds = body.getBounds2D();
      translate(body,
      -(bounds.getX() + bounds.getWidth()  / 2),
      -bounds.getHeight());
      fish.add(body);
      scale(body, 1, -1);
      fish.add(body);

      // add the eye

      Area eye = new Area(scale(circle, .13, .13));
      eye.subtract(new Area(scale(circle, .08, .08)));
      translate(eye, -.15, -.08);
      fish.subtract(eye);

      // add tail

      Area tail = new Area(normalize(rotate(triangle, 30)));
      scale(tail, .50, .50);
      translate(tail, .4, 0);
      fish.add(tail);

      // return normalized shape

      return normalize(fish);
    }
    /**
     * Create normalized regular polygon shape.
     *
     * @return The normalized shape.
     * @see #normalize(Shape shape)
     */

    public static Shape createRegularPoly(int edges)
    {
      double radius = 1000;
      double theta = 0.75 * (2 * Math.PI);
      double dTheta = (2 * Math.PI) / edges;
      Polygon p = new Polygon();

      // add a point for each edge

      for (int edge = 0; edge < edges; ++edge)
      {
        p.addPoint(
          (int)(Math.cos(theta) * radius),
          (int)(Math.sin(theta) * radius));
        theta += dTheta;
      }
      // return the normalized poly

      return normalize(p);
    }
    /**
     * Create normalized star shape.
     *
     * @return The normalized shape.
     * @see #normalize(Shape shape)
     */

    public static Shape createStar(int points)
    {
      double radius = 1000;
      double theta = 0.75 * (2 * Math.PI);
      double dTheta = (4 * Math.PI) / points;
      Polygon p = new Polygon();

      // add a point for each edge

      for (int point = 0; point < points; ++point)
      {
        p.addPoint(
          (int)(Math.cos(theta) * radius),
          (int)(Math.sin(theta) * radius));
        theta += dTheta;
      }
      // convert to a general path to fill the shape

      GeneralPath gp = new GeneralPath(GeneralPath.WIND_NON_ZERO);
      gp.append(p, true);

      // return the normalized star

      return normalize(gp);
    }
    /**
     * Create a transformed version of the provided shape with a
     * new width & length <= 1 and centered at the origin (0, 0).
     *
     * @param  shape source shape to tranform, it is not changed
     * @return The newly normalized shape.
     */

    public static Shape normalize(Shape shape)
    {
      // center the shape on the origin

      Rectangle2D bounds = shape.getBounds2D();
      shape = translate(shape,
      -(bounds.getX() + bounds.getWidth() / 2),
      -(bounds.getY() + bounds.getHeight() / 2));

      // normalize size

      bounds = shape.getBounds2D();
      double scale = bounds.getWidth() > bounds.getHeight()
        ? 1.0 / bounds.getWidth()
        : 1.0 / bounds.getHeight();
      return scale(shape, scale, scale);
    }
    /**
     * Rotate a provided shape the number of degrees specified.
     *
     * @param  shape source shape to rotate, remains unchanged
     * @param  degrees to rotate shape
     * @return The new instance of the rotated shape.
     */

    public static Shape rotate(Shape shape, double degrees)
    {
      return AffineTransform.getRotateInstance(degrees / 180 * Math.PI)
        .createTransformedShape(shape);
    }
    /**
     * Rotate a provided shape around the point specifed, the
     * number of degrees specified.
     *
     * @param  shape source shape to rotate, remains unchanged
     * @param  degrees to rotate shape
     * @param  x x location of point of rotation
     * @param  y y location of point of rotation
     * @return The new instance of the rotated shape.
     */

    public static Shape rotate(Shape shape, double degrees,
    double x, double y)
    {
      return AffineTransform.
        getRotateInstance(degrees / 180 * Math.PI, x, y)
        .createTransformedShape(shape);
    }
    /**
     * Translate a provided shape by the amounts specifed.
     *
     * @param  shape source shape to translate, remains unchanged
     * @param  x x location to translate shape to
     * @param  y y location to translate shape to
     * @return The new instance of the translated shape.
     */

    public static Shape translate(Shape shape, double x, double y)
    {
      return AffineTransform.getTranslateInstance(x, y).createTransformedShape(shape);
    }
    /**
     * Scale a provided shape by the amounts specifed.
     *
     * @param  shape source shape to translate, remains unchanged
     * @param  x scale shape in x dimention by this amount
     * @param  y scale shape in y dimention by this amount
     * @return The new instance of the scaled shape.
     */

    public static Shape scale(Shape shape, double x, double y)
    {
      return AffineTransform.getScaleInstance(x, y).createTransformedShape(shape);
    }
    /**
     * Rotate a provided area the number of degrees specified.
     *
     * @param  area source area to rotate, remains unchanged
     * @param  degrees to rotate area
     * @return The new instance of the rotated area.
     */

    public static Area rotate(Area area, double degrees)
    {
      area.transform(AffineTransform.getRotateInstance(degrees / 180 * Math.PI));
      return area;
    }
    /**
     * Translate a provided area by the amounts specifed.
     *
     * @param  area source area to translate, remains unchanged
     * @param  x x location to translate area to
     * @param  y y location to translate area to
     * @return The new instance of the translated area.
     */

    public static Area translate(Area area, double x, double y)
    {
      area.transform(AffineTransform.getTranslateInstance(x, y));
      return area;
    }
    /**
     * Scale a provided area by the amounts specifed.
     *
     * @param  area source area to translate, remains unchanged
     * @param  x scale area in x dimention by this amount
     * @param  y scale area in y dimention by this amount
     * @return The new instance of the scaled area.
     */

    public static Area scale(Area area, double x, double y)
    {
      area.transform(AffineTransform.getScaleInstance(x, y));
      return area;
    }
    /**
     * Perform all standard transforms to brush before it is
     * painted to the screen.
     *
     * @param  brush original brush shape
     * @param  x x location translate brush to
     * @param  y y location translate brush to
     * @return The new instance of the tranfromed brush shape.
     */

    public Shape transformBrush(Shape brush, double x, double y)
    {
      brush = rotate(brush, brushAngle);
      brush = scale(brush,
      paintScale * brushSize,
      paintScale * brushSize);
      brush = translate(brush, x, y);
      return brush;
    }
    /**
     * Paint brush onto provided graphics.
     *
     * @param  brush brush shape
     * @param  x x location translate brush to
     * @param  y y location translate brush to
     * @return The new instance of the tranfromed brush shape.
     */

    public Shape paintBrushShape(Shape brush, Graphics2D g, double x, double y)
    {
      brush = transformBrush(brush, x, y);
      g.fill(brush);
      return brush;
    }
}
TOP

Related Classes of org.trebor.freesand.Game$BrushSelectionAction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.