gt: 						moveTo(out, newX, newY, false);
context: package com.marginallyclever.makelangeloRobot.converters;
  
  import java.awt.GridLayout;
  import java.awt.Point;
  import java.io.IOException;
  import java.io.Writer;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.List;
  import java.util.concurrent.locks.ReentrantLock;
  
  import javax.swing.JLabel;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JTextField;
  
  import com.jogamp.opengl.GL2;
  import com.marginallyclever.makelangelo.Log;
  import com.marginallyclever.makelangeloRobot.TransformedImage;
  import com.marginallyclever.makelangelo.Translator;
  import com.marginallyclever.makelangeloRobot.MakelangeloRobotDecorator;
  import com.marginallyclever.makelangeloRobot.imageFilters.Filter_BlackAndWhite;
  import com.marginallyclever.makelangeloRobot.settings.MakelangeloRobotSettings;
  import com.marginallyclever.voronoi.VoronoiCell;
  import com.marginallyclever.voronoi.VoronoiCellEdge;
  import com.marginallyclever.voronoi.VoronoiGraphEdge;
  import com.marginallyclever.voronoi.VoronoiTesselator;
  
  
  /**
   * Dithering using a particle system
   *
   * @author Dan
   *         http://en.wikipedia.org/wiki/Fortune%27s_algorithm
   *         http://skynet.ie/~sos/mapviewer/voronoi.php
   * @since 7.0.0?
   */
  public class Converter_VoronoiStippling extends ImageConverter implements MakelangeloRobotDecorator {
  	private ReentrantLock lock = new ReentrantLock();
  
  	private VoronoiTesselator voronoiTesselator = new VoronoiTesselator();
  	private VoronoiCell[] cells = new VoronoiCell[1];
  	private TransformedImage sourceImage;
  	private List<VoronoiGraphEdge> graphEdges = null;
  	private static int MAX_GENERATIONS = 400;
  	private static int MAX_CELLS = 1000;
  	private static float MAX_DOT_SIZE = 5.0f;
  	private static float MIN_DOT_SIZE = 1.0f;
  	private Point bound_min = new Point();
  	private Point bound_max = new Point();
  	private int numEdgesInCell;
  	private List<VoronoiCellEdge> cellBorder = null;
  	private double[] xValuesIn = null;
  	private double[] yValuesIn = null;
  
  	private float yBottom, yTop, xLeft, xRight;
  	
  
  	@Override
  	public String getName() {
  		return Translator.get("voronoiStipplingName");
  	}
  
  	@Override
  	public boolean convert(TransformedImage img,Writer out) throws IOException {
  		JTextField text_gens = new JTextField(Integer.toString(MAX_GENERATIONS), 8);
  		JTextField text_cells = new JTextField(Integer.toString(MAX_CELLS), 8);
  		JTextField text_dot_max = new JTextField(Float.toString(MAX_DOT_SIZE), 8);
  		JTextField text_dot_min = new JTextField(Float.toString(MIN_DOT_SIZE), 8);
  
  		JPanel panel = new JPanel(new GridLayout(0, 1));
  		panel.add(new JLabel(Translator.get("voronoiStipplingCellCount")));
  		panel.add(text_cells);
  		panel.add(new JLabel(Translator.get("voronoiStipplingGenCount")));
  		panel.add(text_gens);
  		panel.add(new JLabel(Translator.get("voronoiStipplingDotMax")));
  		panel.add(text_dot_max);
  		panel.add(new JLabel(Translator.get("voronoiStipplingDotMin")));
  		panel.add(text_dot_min);
  
  
  		int result = JOptionPane.showConfirmDialog(null, panel, getName(), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
  		if (result == JOptionPane.OK_OPTION) {
  			MAX_GENERATIONS = Integer.parseInt(text_gens.getText());
  			MAX_CELLS = Integer.parseInt(text_cells.getText());
  			MAX_DOT_SIZE = Float.parseFloat(text_dot_max.getText());
  			MIN_DOT_SIZE = Float.parseFloat(text_dot_min.getText());
  
  			// make black & white
  			Filter_BlackAndWhite bw = new Filter_BlackAndWhite(255);
  			img = bw.filter(img);
  
  			sourceImage = img;
  			
  			yBottom = (float)machine.getPaperBottom() * (float)machine.getPaperMargin() * 10;
  			yTop    = (float)machine.getPaperTop()    * (float)machine.getPaperMargin() * 10;
  			xLeft   = (float)machine.getPaperLeft()   * (float)machine.getPaperMargin() * 10;
  			xRight  = (float)machine.getPaperRight()  * (float)machine.getPaperMargin() * 10;
  			
  			tool = machine.getCurrentTool();
  
  			cellBorder = new ArrayList<>();
  
  			initializeCells(0.001);
  			evolveCells();
  			writeOutCells(out);
  
  			return true;
  		}
  		return false;
  	}
  
  
  	@Override
  	public void render(GL2 gl2, MakelangeloRobotSettings machine) {
  		if (graphEdges == null) return;
  
  		while(lock.isLocked());
  		lock.lock();
  
  		gl2.glScalef(0.1f, 0.1f, 1);
  
  		// draw cell edges
  		gl2.glColor3f(0.9f, 0.9f, 0.9f);
  		gl2.glBegin(GL2.GL_LINES);
  		for (VoronoiGraphEdge e : graphEdges) {
  			gl2.glVertex2d( e.x1, e.y1 );
  			gl2.glVertex2d( e.x2, e.y2 );
  		}
  		gl2.glEnd();
  
  		// draw cell centers
  		gl2.glPointSize(3);
  		gl2.glColor3f(0, 0, 0);
  		for (VoronoiCell c : cells) {
  			float x = (float)c.centroid.getX();
  			float y = (float)c.centroid.getY();
  			if( sourceImage.canSampleAt(x,y) ) {
  				float val = 1.0f - (sourceImage.sample1x1( x, y) / 255.0f);
  				float r = (val * MAX_DOT_SIZE);
  				gl2.glBegin(GL2.GL_TRIANGLE_FAN);
  				for (float j = 0; j < Math.PI * 2; j += (Math.PI / 4)) {
  					gl2.glVertex2d(x + Math.cos(j) * r,
  								   y + Math.sin(j) * r);
  				}
  				gl2.glEnd();
  			}
  		}
  
  		lock.unlock();
  	}
  
  	// set some starting points in a grid
  	protected void initializeCells(double minDistanceBetweenSites) {
  		Log.write("green","Initializing cells");
  
  		cells = new VoronoiCell[MAX_CELLS];
  
  		// from top to bottom of the margin area...
  		float yBottom = (float)machine.getPaperBottom() * (float)machine.getPaperMargin() * 10;
  		float yTop    = (float)machine.getPaperTop()    * (float)machine.getPaperMargin() * 10;
  		float xLeft   = (float)machine.getPaperLeft()   * (float)machine.getPaperMargin() * 10;
  		float xRight  = (float)machine.getPaperRight()  * (float)machine.getPaperMargin() * 10;
  		
  		int used;
  		for (used=0;used<MAX_CELLS;++used) {
  			cells[used] = new VoronoiCell();
  			cells[used].centroid.setLocation(xLeft   + ((float)Math.random()*(xRight-xLeft)),
  											 yBottom + ((float)Math.random()*(yTop-yBottom))
  											 );
  		}
  
  		// convert the cells to sites used in the Voronoi class.
  		xValuesIn = new double[MAX_CELLS];
  		yValuesIn = new double[MAX_CELLS];
  
  		voronoiTesselator.Init(minDistanceBetweenSites);
  	}
  
  
  	/**
  	 * Jiggle the dots until they make a nice picture
  	 */
  	protected void evolveCells() {
  		try {
  			Log.write("green","Mutating");
  
  			int generation = 0;
  			do {
  				++generation;
  				Log.write("green","Generation " + generation);
  
  				assert !lock.isHeldByCurrentThread();
  				lock.lock();
  				tessellateVoronoiDiagram();
  				lock.unlock();
  				adjustCentroids();
  
  				// Do again if things are still moving a lot.  Cap the # of times so we don't have an infinite loop.
  			} while (generation < MAX_GENERATIONS);
  		} catch (Exception e) {
  			e.printStackTrace();
  			if(lock.isHeldByCurrentThread() && lock.isLocked()) {
  				lock.unlock();
  			}
  		}
  	}
  
  
  	// write cell centroids to gcode.
  	protected void writeOutCells(Writer out) throws IOException {
  		if (graphEdges == null) return;
  
  		Log.message("Writing gcode.");
  
  		imageStart(out);
  		tool = machine.getCurrentTool();
  		liftPen(out);
  		tool.writeChangeTo(out);
  
  		float toolDiameter = tool.getDiameter();
  
  		Arrays.sort(cells);
  		
  		int i;
  		for (i = 0; i < cells.length; ++i) {
  			float x = cells[i].centroid.x;
  			float y = cells[i].centroid.y;
  			float val = 1.0f - (sourceImage.sample1x1(x,y) / 255.0f);
  			float r = val * MAX_DOT_SIZE;
  			if (r < MIN_DOT_SIZE) continue;
  
  			float newX=0,newY=0;
  			boolean first=true;
  			// filled circles
  			while (r > 0) {
  				float detail = (float)Math.ceil(Math.PI * r*2 / (toolDiameter*4));
  				if (detail < 4) detail = 4;
  				if (detail > 20) detail = 20;
  				for (float j = 0; j <= detail; ++j) {
  					double v = Math.PI * 2.0f * j / detail;
  					newX = x + r * (float) Math.cos(v);
  					newY = y + r * (float) Math.sin(v);
  					if(first) {
  						if(isInsidePaperMargins(newX,newY)) {
  							moveTo(out, newX, newY, true);
  							lowerPen(out);
  							first=false;
  						}
  					} else {
  