import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JApplet;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.border.Border;
/**
* An applet to demonstrate Turing's reaction-diffusion model.
* For more information, see:
* [1] Rafael Collantes. Algorithm Alley. Dr. Dobb's Journal, December 1996.
* [2] Alan M. Turing. The chemical basis of morphogenesis. Philosophical
* Transactions of the Royal Society of London. B 327, 37–72 (1952)
*
* @author Christopher G. Jennings (cjennings [at] acm.org)
*/
public class TuringMorph extends JApplet {
/**
* Labels that describe the sizes of the Turing systems that the user
* can generate. These labels are added to the size drop down and
* when a new size is selected, it is read directly from the label.
* You may add the size n x n by adding a new string that ends with
* " n)" (the textures are always assumed to be square). If you add
* an entry larger than the current maximum size, you must also update
* MAXIMUM_SYSTEM_SIZE.
*/
private static final String[] SYSTEM_SIZES = new String[] {
"Tiny (64 x 64)", "Small (128 x 128)", "Medium (192 x 192)",
"Large (256 x 256)", "Extra Large (350 x 350)"
};
/**
* The size of the largest possible data set. This must be the largest of all
* the size settings in imageSizeDropDown.
*/
private static final int MAXIMUM_SYSTEM_SIZE = 350;
/** Names for the built-in examples. */
private static final String[] PRESET_NAMES = new String[] {
"Cheetah", "Colony", "Fine", "Fingerprint", "Maze", "Pocked",
};
/** Constant pairs for the built-in examples. */
private static final double[] PRESET_CONSTANTS = new double[] {
3.5d, 16d, 1.6d, 6d, 0.1d, 1d, 1d, 16d, 2.6d, 24d, 1d, 3d,
};
/**
* Initializes the applet when the web browser first loads it in.
*/
@Override
public void init() {
try {
java.awt.EventQueue.invokeAndWait( new Runnable() {
public void run() {
try {
String lafClass = UIManager.getSystemLookAndFeelClassName();
for( LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels() ) {
if( laf.getName().equals( "Nimbus" ) ) {
lafClass = laf.getClassName();
break;
}
}
UIManager.setLookAndFeel( lafClass );
} catch( ClassNotFoundException e ) {
// shouldn't be possible, as not even a system LaF is
// available, but it doesn't matter
} catch( Exception e ) {
e.printStackTrace();
}
initComponents();
}
} );
} catch( Exception ex ) {
ex.printStackTrace();
}
}
/**
* Start solving for the default constants when the applet starts.
*/
@Override
public void start() {
try {
java.awt.EventQueue.invokeLater( new Runnable() {
public void run() {
okButtonActionPerformed( null );
}
} );
} catch( Exception ex ) {
ex.printStackTrace();
}
}
/**
* Stop solver thread, if running, when browser leaves the page the applet is on.
*/
@Override
public void stop() {
if( solver != null ) {
// does not have to be called from EDT
try {
solver.stop();
} catch( NullPointerException e ) {
}
}
}
/**
* If run in Java 6 update 10 or newer, this allows the applet to be dragged
* out of the web page as an independent window by dragging the title bar.
*
* @param e the MouseEvent to test the component with
* @return true if Alt+left click is down or the title bar is clicked
*/
public boolean isAppletDragStart( MouseEvent e ) {
Object source = e.getSource();
// since this method was called, the applet is draggable
// tell the user how to drag it out of the web page
if( source == this ) {
if( titleLabel.getToolTipText().equals( ABOUT_TIP ) ) {
titleLabel.setToolTipText( ABOUT_TIP + DRAG_TIP );
titlePanel.setToolTipText( ABOUT_TIP + DRAG_TIP );
}
}
if( (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0 ) {
// check if dragging title bar
// e.getSource() is the applet when it is embedded in the web page
// and a parent of the applet when it is in an independent window
if( source == this || ((Container) source).isAncestorOf( this ) || source == titlePanel || source == titleLabel ) {
if( e.getY() <= titlePanel.getHeight() ) {
return true;
}
}
// always drag if ALT is held down
return (e.getModifiersEx() & MouseEvent.ALT_DOWN_MASK) != 0;
}
return false;
}
/** This method is called from within the init() method to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// //GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
javax.swing.JPanel controlPanel = new javax.swing.JPanel();
javax.swing.JLabel imageSizeLabel = new javax.swing.JLabel();
imageSizeDropDown = new javax.swing.JComboBox();
iterationsLabel = new javax.swing.JLabel();
iterationsSlider = new javax.swing.JSlider();
javax.swing.JLabel constantsLabel = new javax.swing.JLabel();
constAField = new javax.swing.JTextField();
constBField = new javax.swing.JTextField();
okButton = new javax.swing.JButton();
javax.swing.JLabel colourLabel = new javax.swing.JLabel();
minColourButton = new javax.swing.JButton();
maxColourButton = new javax.swing.JButton();
progressBar = new javax.swing.JProgressBar();
randomColourButton = new javax.swing.JButton();
tileCheckBox = new javax.swing.JCheckBox();
presetsLabel = new javax.swing.JLabel();
presetDropDown = new javax.swing.JComboBox();
randomizeCheck = new javax.swing.JCheckBox();
randomizeNowBtn = new javax.swing.JButton();
javax.swing.JLabel appearanceLabel = new javax.swing.JLabel();
javax.swing.JSeparator appearanceSeparator = new javax.swing.JSeparator();
javax.swing.JLabel advancedLabel = new javax.swing.JLabel();
javax.swing.JSeparator advancedSeparator = new javax.swing.JSeparator();
javax.swing.JLabel basicLabel = new javax.swing.JLabel();
javax.swing.JSeparator basicSeparator = new javax.swing.JSeparator();
javax.swing.JPanel visualizerPanel = new javax.swing.JPanel();
titlePanel = new javax.swing.JPanel();
titleLabel = new javax.swing.JLabel();
controlPanel.setBackground(java.awt.Color.white);
controlPanel.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
controlPanel.setLayout(new java.awt.GridBagLayout());
imageSizeLabel.setDisplayedMnemonic('S');
imageSizeLabel.setLabelFor(imageSizeDropDown);
imageSizeLabel.setText("Size (in Pixels)");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 13;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(8, 8, 4, 8);
controlPanel.add(imageSizeLabel, gridBagConstraints);
imageSizeDropDown.setModel( new javax.swing.DefaultComboBoxModel( SYSTEM_SIZES ) );
imageSizeDropDown.setToolTipText("Select a Size for the Texture to be Created");
imageSizeDropDown.setOpaque(false);
imageSizeDropDown.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
imageSizeDropDownActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 13;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(8, 0, 0, 8);
controlPanel.add(imageSizeDropDown, gridBagConstraints);
iterationsLabel.setDisplayedMnemonic('N');
iterationsLabel.setLabelFor(iterationsSlider);
iterationsLabel.setText( "Number of Iterations: " + formatter.format( 2000 ) );
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 7;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(8, 8, 4, 8);
controlPanel.add(iterationsLabel, gridBagConstraints);
iterationsSlider.setBackground(java.awt.Color.white);
iterationsSlider.setMajorTickSpacing(4999);
iterationsSlider.setMaximum(5000);
iterationsSlider.setMinimum(1);
iterationsSlider.setMinorTickSpacing(500);
iterationsSlider.setPaintLabels(true);
iterationsSlider.setPaintTicks(true);
iterationsSlider.setValue(2000);
iterationsSlider.setOpaque(false);
iterationsSlider.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
iterationsSliderStateChanged(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 8;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(0, 16, 0, 8);
controlPanel.add(iterationsSlider, gridBagConstraints);
constantsLabel.setDisplayedMnemonic('C');
constantsLabel.setLabelFor(constAField);
constantsLabel.setText("Diffusion Constants");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(8, 8, 2, 8);
controlPanel.add(constantsLabel, gridBagConstraints);
constAField.setColumns(4);
constAField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
constAField.setText( formatter.format( PRESET_CONSTANTS[0] ) );
constAField.setMinimumSize(new java.awt.Dimension(32, 20));
constAField.setOpaque(false);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(0, 16, 0, 0);
controlPanel.add(constAField, gridBagConstraints);
constBField.setColumns(4);
constBField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
constBField.setText( formatter.format( PRESET_CONSTANTS[1] ) );
constBField.setMinimumSize(new java.awt.Dimension(32, 20));
constBField.setOpaque(false);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(0, 1, 0, 4);
controlPanel.add(constBField, gridBagConstraints);
okButton.setMnemonic('D');
okButton.setText(" Do It! ");
okButton.setToolTipText("Create a Texture Using These Settings");
okButton.setOpaque(false);
okButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
okButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 4;
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(10, 16, 4, 8);
controlPanel.add(okButton, gridBagConstraints);
colourLabel.setText("Colours (click on a colour to change it)");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 14;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(14, 8, 2, 8);
controlPanel.add(colourLabel, gridBagConstraints);
minColourButton.setBackground( TuringSystemVisualizer.DEFAULT_CMIN );
minColourButton.setText(" ");
minColourButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
colourButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 15;
gridBagConstraints.insets = new java.awt.Insets(0, 16, 0, 0);
controlPanel.add(minColourButton, gridBagConstraints);
maxColourButton.setBackground( TuringSystemVisualizer.DEFAULT_CMAX );
maxColourButton.setText(" ");
maxColourButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
colourButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 15;
gridBagConstraints.insets = new java.awt.Insets(0, 8, 0, 0);
controlPanel.add(maxColourButton, gridBagConstraints);
progressBar.setMaximum(2000);
progressBar.setString("0");
progressBar.setStringPainted(true);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 4;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(10, 8, 4, 8);
controlPanel.add(progressBar, gridBagConstraints);
randomColourButton.setFont(randomColourButton.getFont().deriveFont(randomColourButton.getFont().getSize()-2f));
randomColourButton.setText("Pick Randomly");
randomColourButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
randomColourButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 15;
gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
gridBagConstraints.insets = new java.awt.Insets(0, 8, 0, 8);
controlPanel.add(randomColourButton, gridBagConstraints);
tileCheckBox.setSelected(true);
tileCheckBox.setText("Tile the Texture Over the Entire Canvas");
tileCheckBox.setOpaque(false);
tileCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
tileCheckBoxActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 16;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(8, 8, 0, 8);
controlPanel.add(tileCheckBox, gridBagConstraints);
presetsLabel.setText("Presets");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 2;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(8, 0, 2, 8);
controlPanel.add(presetsLabel, gridBagConstraints);
presetDropDown.setFont(presetDropDown.getFont().deriveFont(presetDropDown.getFont().getSize()-1f));
presetDropDown.setModel( new DefaultComboBoxModel( PRESET_NAMES ) );
presetDropDown.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
presetDropDownActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(0, 4, 0, 8);
controlPanel.add(presetDropDown, gridBagConstraints);
randomizeCheck.setSelected(true);
randomizeCheck.setText("Randomize Cells at the Start of Each Run");
randomizeCheck.setOpaque(false);
randomizeCheck.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
randomizeCheckActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 9;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(8, 8, 0, 8);
controlPanel.add(randomizeCheck, gridBagConstraints);
randomizeNowBtn.setFont(randomizeNowBtn.getFont().deriveFont(randomizeNowBtn.getFont().getSize()-2f));
randomizeNowBtn.setText("Randomize Cells Now");
randomizeNowBtn.setEnabled(false);
randomizeNowBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
randomizeNowBtnActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 10;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(4, 24, 0, 8);
controlPanel.add(randomizeNowBtn, gridBagConstraints);
appearanceLabel.setFont(appearanceLabel.getFont().deriveFont(appearanceLabel.getFont().getStyle() | java.awt.Font.BOLD, appearanceLabel.getFont().getSize()+2));
appearanceLabel.setText("Texture Appearance");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 11;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(16, 8, 0, 8);
controlPanel.add(appearanceLabel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 12;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 8, 0, 8);
controlPanel.add(appearanceSeparator, gridBagConstraints);
advancedLabel.setFont(advancedLabel.getFont().deriveFont(advancedLabel.getFont().getStyle() | java.awt.Font.BOLD, advancedLabel.getFont().getSize()+2));
advancedLabel.setText("More Options");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 5;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(16, 8, 0, 8);
controlPanel.add(advancedLabel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 6;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 8, 0, 8);
controlPanel.add(advancedSeparator, gridBagConstraints);
basicLabel.setFont(basicLabel.getFont().deriveFont(basicLabel.getFont().getStyle() | java.awt.Font.BOLD, basicLabel.getFont().getSize()+2));
basicLabel.setText("The Basics");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(8, 8, 0, 8);
controlPanel.add(basicLabel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.gridwidth = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 8, 0, 8);
controlPanel.add(basicSeparator, gridBagConstraints);
getContentPane().add(controlPanel, java.awt.BorderLayout.WEST);
visualizer = new TuringSystemVisualizer( MAXIMUM_SYSTEM_SIZE, MAXIMUM_SYSTEM_SIZE );
visualizerPanel.add( visualizer );
visualizer.setTiled( tileCheckBox.isSelected() );
imageSizeDropDown.setSelectedIndex( 2 );
// copy the standard so we can swap between it and an "error" border
textFieldBorder = constAField.getBorder();
visualizerPanel.setBackground(java.awt.Color.white);
visualizerPanel.setBorder(javax.swing.BorderFactory.createMatteBorder(1, 0, 1, 1, new java.awt.Color(0, 0, 0)));
visualizerPanel.setLayout(new javax.swing.BoxLayout(visualizerPanel, javax.swing.BoxLayout.LINE_AXIS));
getContentPane().add(visualizerPanel, java.awt.BorderLayout.CENTER);
titlePanel.setBackground(java.awt.Color.black);
titlePanel.setToolTipText( ABOUT_TIP );
titlePanel.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent evt) {
titlePanelMouseEntered(evt);
}
public void mouseExited(java.awt.event.MouseEvent evt) {
titlePanelMouseExited(evt);
}
});
titleLabel.setFont(titleLabel.getFont().deriveFont(titleLabel.getFont().getStyle() | java.awt.Font.BOLD, titleLabel.getFont().getSize()+1));
titleLabel.setForeground(java.awt.Color.white);
titleLabel.setText( TITLE );
titleLabel.setToolTipText( ABOUT_TIP );
titlePanel.add(titleLabel);
getContentPane().add(titlePanel, java.awt.BorderLayout.PAGE_START);
}// //GEN-END:initComponents
private void iterationsSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_iterationsSliderStateChanged
iterationsLabel.setText( "Number of Iterations: " + formatter.format( iterationsSlider.getValue() ) );
}//GEN-LAST:event_iterationsSliderStateChanged
private void imageSizeDropDownActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_imageSizeDropDownActionPerformed
int newBaseSize = baseSize;
// parse the new size directly from the drop down item text
Object o = imageSizeDropDown.getSelectedItem();
String s = "";
if( o != null ) {
try {
s = ((String) o);
s = s.substring( s.lastIndexOf( ' ' ) + 1, s.length() - 1 );
newBaseSize = Integer.parseInt( s );
} catch( NumberFormatException e ) {
throw new AssertionError( "Size drop down item has illegal " +
"format---must end in \" n)\"" );
}
if( newBaseSize != baseSize ) {
baseSize = newBaseSize;
if( solver != null ) {
solver.stop();
}
solver = new TuringSystemSolver( new TuringSystemSolver.ResultListener() {
public void solverResultEvent( int iteration, double[][] result ) {
visualizer.updateImage( result, baseSize, baseSize );
final int i = iteration;
SwingUtilities.invokeLater( new Runnable() {
public void run() {
progressBar.setValue( i );
progressBar.setString( formatter.format( i ) );
}
} );
}
}, baseSize, baseSize );
solver.randomize();
}
}
}//GEN-LAST:event_imageSizeDropDownActionPerformed
/** Generate a new texture. */
private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
iterations = iterationsSlider.getValue();
progressBar.setMaximum( iterations );
solver.stop();
if( randomizeCheck.isSelected() ) {
solver.randomize();
}
CA = parseConstant( constAField, CA );
CB = parseConstant( constBField, CB );
solver.solve( iterations, CA, CB );
}//GEN-LAST:event_okButtonActionPerformed
private void colourButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_colourButtonActionPerformed
boolean isMin = evt.getSource() == minColourButton;
Color min = minColourButton.getBackground();
Color max = maxColourButton.getBackground();
Component parent = isMin ? minColourButton : maxColourButton;
Color pick = JColorChooser.showDialog( parent, "Choose Colour", isMin ? min : max );
if( pick != null ) {
if( isMin ) {
min = pick;
} else {
max = pick;
}
visualizer.setColors( min, max );
minColourButton.setBackground( min );
maxColourButton.setBackground( max );
}
}//GEN-LAST:event_colourButtonActionPerformed
private void randomColourButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_randomColourButtonActionPerformed
float hue = (float) Math.random();
float sat = (float) Math.random();
float bri = (float) Math.random() * 0.667f;
Color min = new Color( Color.HSBtoRGB( hue, sat, bri ) );
hue += (float) Math.random() * 26f / 180f;
bri = 0.75f + (float) Math.random() / 4f;
Color max = new Color( Color.HSBtoRGB( hue, sat, bri ) );
if( Math.random() >= 0.5f ) {
Color temp = min;
min = max;
max = temp;
}
minColourButton.setBackground( min );
maxColourButton.setBackground( max );
visualizer.setColors( min, max );
}//GEN-LAST:event_randomColourButtonActionPerformed
private void tileCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tileCheckBoxActionPerformed
visualizer.setTiled( tileCheckBox.isSelected() );
}//GEN-LAST:event_tileCheckBoxActionPerformed
private void presetDropDownActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_presetDropDownActionPerformed
int i = presetDropDown.getSelectedIndex();
if( i >= 0 ) {
constAField.setText( formatter.format( PRESET_CONSTANTS[i * 2] ) );
constBField.setText( formatter.format( PRESET_CONSTANTS[i * 2 + 1] ) );
}
}//GEN-LAST:event_presetDropDownActionPerformed
private void randomizeCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_randomizeCheckActionPerformed
randomizeNowBtn.setEnabled( !randomizeCheck.isSelected() );
}//GEN-LAST:event_randomizeCheckActionPerformed
private void randomizeNowBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_randomizeNowBtnActionPerformed
solver.stop();
solver.randomize();
}//GEN-LAST:event_randomizeNowBtnActionPerformed
private void titlePanelMouseEntered(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_titlePanelMouseEntered
ToolTipManager.sharedInstance().setInitialDelay( 50 );
}//GEN-LAST:event_titlePanelMouseEntered
private void titlePanelMouseExited(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_titlePanelMouseExited
ToolTipManager.sharedInstance().setInitialDelay( 750 );
}//GEN-LAST:event_titlePanelMouseExited
/**
* Parse the content of field for a double value.
* If the content is a valid double, its value is returned.
* Otherwise, defaultValue is returned and the field
* is highlighted with a red border.
*
* @return the value in field, or defaultValue
*/
private double parseConstant( JTextField field, double defaultValue ) {
double value;
try {
value = formatter.parse( field.getText() ).doubleValue();
field.setBorder( textFieldBorder );
} catch( ParseException e ) {
field.setText( formatter.format( defaultValue ) );
value = defaultValue;
field.setBorder( errorBorder );
}
return value;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField constAField;
private javax.swing.JTextField constBField;
private javax.swing.JComboBox imageSizeDropDown;
private javax.swing.JLabel iterationsLabel;
private javax.swing.JSlider iterationsSlider;
private javax.swing.JButton maxColourButton;
private javax.swing.JButton minColourButton;
private javax.swing.JButton okButton;
private javax.swing.JComboBox presetDropDown;
private javax.swing.JLabel presetsLabel;
private javax.swing.JProgressBar progressBar;
private javax.swing.JButton randomColourButton;
private javax.swing.JCheckBox randomizeCheck;
private javax.swing.JButton randomizeNowBtn;
private javax.swing.JCheckBox tileCheckBox;
private javax.swing.JLabel titleLabel;
private javax.swing.JPanel titlePanel;
// End of variables declaration//GEN-END:variables
private TuringSystemSolver solver;
private int baseSize = 64;
private int iterations = 2000;
private double CA = 3.5d, CB = 16d;
private TuringSystemVisualizer visualizer;
private Border textFieldBorder;
private Border errorBorder = BorderFactory.createLineBorder( Color.RED, 2 );
private NumberFormat formatter = NumberFormat.getNumberInstance();
private static final String TITLE = "Turing Reaction-Diffusion Morphogenesis";
private static final String ABOUT_TIP = "Christopher G. Jennings" +
" (Version 5)
http://cgjennings.ca";
private static final String DRAG_TIP = "
Drag This Title Bar" +
" to Separate From Web Page";
}
/**
* A component that displays a visualization of a {@link TuringSystemSolver}
* result.
*/
class TuringSystemVisualizer extends JComponent {
public static final Color DEFAULT_CMIN = new Color( 0x3c0009 );
public static final Color DEFAULT_CMAX = new Color( 0xffff3a );
/**
* Create a visualizer for a {@link TuringSystemSolver} with a maximum
* size of width x height.
*
* @param width the maximum width of the systems to be visualized
* @param height the maximum height of the systems to be visualized
*/
TuringSystemVisualizer( int width, int height ) {
super();
setSize( width, height );
colours = new int[256];
setColors( DEFAULT_CMIN, DEFAULT_CMAX );
}
/**
* Set the colours to use for visualizing results. The image colours will
* vary smoothly from cmin (representing the minimum value in
* the system) to cmax (representing the maximum value).
*
* @param cmin colour to use for the minimum value
* @param cmax colour to use for the maximum value
*/
public synchronized void setColors( Color cmin, Color cmax ) {
int Rlo = cmin.getRed();
int Glo = cmin.getGreen();
int Blo = cmin.getBlue();
int Rhi = cmax.getRed();
int Ghi = cmax.getGreen();
int Bhi = cmax.getBlue();
for( int i=0; i<256; ++i ) {
colours[i] = scale( i, Rlo, Rhi ) << 16 |
scale( i, Glo, Ghi ) << 8 |
scale( i, Blo, Bhi );
}
updateImage();
}
/**
* Calculate an interpolated colour component that is i/255 of the way between lo and hi.
*/
private static int scale( int i, int lo, int hi ) {
return lo + (hi-lo) * i / 255;
}
/**
* Return the colours currently used to visualize results. The colours are
* returned in an array in cmin, cmax order
* (see {@link #setColors}).
*
* @return an array of the colours used for the extreme values in the system
*/
public Color[] getColors() {
return new Color[] { new Color( colours[0] ), new Color( colours[255] ) };
}
/**
* Set the tiling mode of the component. If tiling is enabled, the component
* will create a tiled image by repeating the visualization until the
* entire component is filled. Otherwise, the tile is centered in the
* middle of the component.
* @param tile whether or not to enable tiling
*/
public void setTiled( boolean tile ) {
this.tile = tile;
repaint();
}
/**
* Return true if the component will tile the image.
* @return true if tiling is enabled
*/
public boolean isTiled() {
return tile;
}
/**
* Update the visualiztion with a new data set, causing the display to be redrawn.
*/
public void updateImage( double data[][], int width, int height ) {
if( image==null || image.getWidth() != width || image.getHeight() != height ) {
image = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB );
scaledData = new int[width][height];
}
int i, j;
// find range of concentrations
double high = Double.NEGATIVE_INFINITY;
double low = Double.POSITIVE_INFINITY;
for( i=0; i high ) high = val;
else if( val < low ) low = val;
}
}
// scale data to lie within 0--255
for( i=0; iiteration may be used to determine if
* it is an initial, final, or interim result. The current state of the
* system is passed in result, which is an array of the
* same size as the requested width and height of the system.
* This array should be considered read-only.
*
* @param iteration the iteration number represented by this result,
* from 0 to the total iterations requested
* @param result an array representing the current system state
*/
void solverResultEvent( int iteration, double[][] result );
}
private int iterations;
private double CA, CB;
private volatile Thread solveThread;
public TuringSystemSolver( ResultListener listener, int width, int height ) {
this.listener = listener;
Ao = new double[width][height];
An = new double[width][height];
Bo = new double[width][height];
Bn = new double[width][height];
this.width = width;
this.height = height;
}
public void solve( int iterations, double CA, double CB ) {
if( Thread.currentThread() == solveThread )
throw new AssertionError( "called solve() from solveThread" );
stop();
this.iterations = iterations;
this.CA = CA;
this.CB = CB;
solveThread = new Thread( new Runnable() {
public void run() {
solveImpl();
}
} );
solveThread.start();
}
private void solveImpl() {
int n, i, j, iplus1, iminus1, jplus1, jminus1;
double DiA, ReA, DiB, ReB;
long lastUpdateTime = System.nanoTime(), currentTime;
int lastUpdateFrame = 0;
// uses Euler's method to solve the diff eqns
for( n=0; n= UPDATE_RATE_TIME)
|| ((n - lastUpdateFrame) >= UPDATE_RATE_FRAMES) ) {
lastUpdateTime = currentTime;
lastUpdateFrame = n;
sendFrameToListener( n, An );
}
// Swap Ao for An, Bo for Bn
swapBuffers();
}
// send the final data set to be displayed
sendFrameToListener( n, An );
solveThread = null;
}
/**
* Stop an in-progress solution, if any.
*/
public synchronized void stop() {
if( Thread.currentThread() == solveThread )
throw new AssertionError( "called stop() from solveThread" );
Thread thread = solveThread;
if( thread != null ) {
solveThread = null;
thread.interrupt();
boolean killed = false;
while( !killed ) {
try { thread.join(); killed = true; }
catch( InterruptedException e ) {}
}
}
}
/**
* Set the system to an initial noise state.
* This should normally called before solving.
*/
public void randomize() {
stop();
for( int i=0; i