2015-02-22 20:26:03 +00:00
|
|
|
/**
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2016-02-07 08:48:36 +00:00
|
|
|
StippleGen_2_40
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2016-02-07 08:48:36 +00:00
|
|
|
SVG Stipple Generator, v. 2.40
|
|
|
|
Copyright (C) 2016 by Windell H. Oskay, www.evilmadscientist.com
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2016-11-22 17:23:30 +00:00
|
|
|
Full Documentation: http://wiki.evilmadscientist.com/StippleGen
|
2015-02-22 20:26:03 +00:00
|
|
|
Blog post about the release: http://www.evilmadscientist.com/go/stipple2
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
An implementation of Weighted Voronoi Stippling:
|
|
|
|
http://mrl.nyu.edu/~ajsecord/stipples.html
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
*******************************************************************************
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
Change Log:
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2016-02-07 08:48:36 +00:00
|
|
|
v 2.4
|
|
|
|
* Compiling in Processing 3.0.1
|
2016-02-07 22:43:00 +00:00
|
|
|
* Add GUI option to fill circles with a spiral
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
v 2.3
|
|
|
|
* Forked from 2.1.1
|
|
|
|
* Fixed saving bug
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
v 2.20
|
|
|
|
* [Cancelled development branch.]
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
v 2.1.1
|
|
|
|
* Faster now, with number of stipples calculated at a time.
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
v 2.1.0
|
|
|
|
* Now compiling in Processing 2.0b6
|
|
|
|
* selectInput() and selectOutput() calls modified for Processing 2.
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
v 2.02
|
|
|
|
* Force files to end in .svg
|
|
|
|
* Fix bug that gave wrong size to stipple files saved white stipples on black background
|
2016-03-25 13:55:13 +00:00
|
|
|
|
|
|
|
v 2.01:
|
2015-02-22 20:26:03 +00:00
|
|
|
* Improved handling of Save process, to prevent accidental "not saving" by users.
|
2016-03-25 13:55:13 +00:00
|
|
|
|
|
|
|
v 2.0:
|
2015-02-22 20:26:03 +00:00
|
|
|
* Add tone reversal option (white on black / black on white)
|
|
|
|
* Reduce vertical extent of GUI, to reduce likelihood of cropping on small screens
|
2016-03-25 13:55:13 +00:00
|
|
|
* Speling corections
|
2015-02-22 20:26:03 +00:00
|
|
|
* Fixed a bug that caused unintended cropping of long, wide images
|
|
|
|
* Reorganized GUI controls
|
|
|
|
* Fail less disgracefully when a bad image type is selected.
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
*******************************************************************************
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
Program is based on the Toxic Libs Library ( http://toxiclibs.org/ )
|
|
|
|
& example code:
|
|
|
|
http://forum.processing.org/topic/toxiclib-voronoi-example-sketch
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
Additional inspiration:
|
|
|
|
Stipple Cam from Jim Bumgardner
|
|
|
|
http://joyofprocessing.com/blog/2011/11/stipple-cam/
|
2016-03-25 13:55:13 +00:00
|
|
|
|
|
|
|
and
|
|
|
|
|
|
|
|
MeshLibDemo.pde - Demo of Lee Byron's Mesh library, by
|
2015-02-22 20:26:03 +00:00
|
|
|
Marius Watz - http://workshop.evolutionzone.com/
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
Requires ControlP5 library and Toxic Libs library:
|
|
|
|
http://www.sojamo.de/libraries/controlP5/
|
2016-11-22 17:23:30 +00:00
|
|
|
http://bitbucket.org/postspectacular/toxiclibs/downloads/
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
*/
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
/*
|
2015-02-22 20:26:03 +00:00
|
|
|
* This is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2016-03-25 13:55:13 +00:00
|
|
|
*
|
2015-02-22 20:26:03 +00:00
|
|
|
* http://creativecommons.org/licenses/LGPL/2.1/
|
2016-03-25 13:55:13 +00:00
|
|
|
*
|
2015-02-22 20:26:03 +00:00
|
|
|
* This library 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
|
|
|
|
* Lesser General Public License for more details.
|
2016-03-25 13:55:13 +00:00
|
|
|
*
|
2015-02-22 20:26:03 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
// You need the controlP5 library from http://www.sojamo.de/libraries/controlP5/
|
2016-03-25 13:55:13 +00:00
|
|
|
import controlP5.*;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
//You need the Toxic Libs library: http://hg.postspectacular.com/toxiclibs/downloads
|
|
|
|
import toxi.geom.*;
|
|
|
|
import toxi.geom.mesh2d.*;
|
|
|
|
import toxi.util.datatypes.*;
|
|
|
|
import toxi.processing.*;
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
import javax.swing.UIManager;
|
|
|
|
import javax.swing.JFileChooser;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
// helper class for rendering
|
|
|
|
ToxiclibsSupport gfx;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
// Feel free to play with these three default settings
|
|
|
|
float cutoff = 0;
|
|
|
|
float minDotSize = 1.75;
|
|
|
|
float dotSizeFactor = 4;
|
|
|
|
// Max value is normally 10000. Press 'x' key to allow 50000 stipples. (SLOW)
|
|
|
|
int maxParticles = 2000;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
//Scale each cell to fit in a cellBuffer-sized square window for computing the centroid.
|
|
|
|
int cellBuffer = 100;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
// Display window and GUI area sizes:
|
2016-03-25 13:55:13 +00:00
|
|
|
int mainwidth;
|
2015-02-22 20:26:03 +00:00
|
|
|
int mainheight;
|
|
|
|
int borderWidth;
|
|
|
|
int ctrlheight;
|
2016-03-25 13:00:49 +00:00
|
|
|
int textColumnStart;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
float lowBorderX;
|
|
|
|
float hiBorderX;
|
|
|
|
float lowBorderY;
|
|
|
|
float hiBorderY;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
float maxDotSize;
|
2016-03-25 13:55:13 +00:00
|
|
|
boolean reInitiallizeArray;
|
2015-02-22 20:26:03 +00:00
|
|
|
boolean pausemode;
|
|
|
|
boolean fileLoaded;
|
2016-03-25 13:00:49 +00:00
|
|
|
boolean saveNow;
|
2015-02-22 20:26:03 +00:00
|
|
|
String savePath;
|
2016-03-25 13:55:13 +00:00
|
|
|
String[] fileOutput;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
boolean fillingCircles;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
String statusDisplay = "Initializing, please wait. :)";
|
2015-02-22 20:26:03 +00:00
|
|
|
float millisLastFrame = 0;
|
|
|
|
float frameTime = 0;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
float errorTime;
|
|
|
|
String errorDisplay = "";
|
|
|
|
boolean errorDisp = false;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
int generation;
|
2015-02-22 20:26:03 +00:00
|
|
|
int particleRouteLength;
|
2016-03-25 13:55:13 +00:00
|
|
|
int routeStep;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
boolean invertImg;
|
2016-03-25 13:00:49 +00:00
|
|
|
boolean fileModeTSP;
|
|
|
|
boolean tempShowCells;
|
2016-03-25 13:55:13 +00:00
|
|
|
boolean showBG, showPath, showCells;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
int vorPointsAdded;
|
2016-03-25 13:00:49 +00:00
|
|
|
boolean voronoiCalculated;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
int cellsTotal, cellsCalculated, cellsCalculatedLast;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
int[] particleRoute;
|
|
|
|
Vec2D[] particles;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
ControlP5 cp5;
|
|
|
|
Voronoi voronoi;
|
2016-03-25 13:00:49 +00:00
|
|
|
Polygon2D regionList[];
|
|
|
|
PolygonClipper2D clip;
|
2016-03-25 13:55:13 +00:00
|
|
|
PImage img, imgload, imgblur;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
void LoadImageAndScale() {
|
|
|
|
int tempx = 0;
|
|
|
|
int tempy = 0;
|
|
|
|
|
|
|
|
img = createImage(mainwidth, mainheight, RGB);
|
|
|
|
imgblur = createImage(mainwidth, mainheight, RGB);
|
|
|
|
|
|
|
|
img.loadPixels();
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
for (int i = 0; i < img.pixels.length; i++) {
|
|
|
|
img.pixels[i] = color(invertImg ? 0 : 255);
|
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
img.updatePixels();
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (!fileLoaded) {
|
2015-02-22 20:26:03 +00:00
|
|
|
// Load a demo image, at least until we have a "real" image to work with.
|
2016-03-25 13:00:49 +00:00
|
|
|
// Image from: http://commons.wikimedia.org/wiki/File:Kelly,_Grace_(Rear_Window).jpg
|
2015-02-22 20:26:03 +00:00
|
|
|
imgload = loadImage("grace.jpg"); // Load demo image
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((imgload.width > mainwidth) || (imgload.height > mainheight)) {
|
2016-03-25 13:00:49 +00:00
|
|
|
if (((float)imgload.width / (float)imgload.height) > ((float)mainwidth / (float)mainheight))
|
2016-03-25 13:55:13 +00:00
|
|
|
{
|
2015-02-22 20:26:03 +00:00
|
|
|
imgload.resize(mainwidth, 0);
|
2016-03-25 13:55:13 +00:00
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
imgload.resize(0, mainheight);
|
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
if (imgload.height < (mainheight - 2)) {
|
2016-03-25 13:00:49 +00:00
|
|
|
tempy = (int)((mainheight - imgload.height) / 2) ;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
if (imgload.width < (mainwidth - 2)) {
|
2016-03-25 13:00:49 +00:00
|
|
|
tempx = (int)((mainwidth - imgload.width) / 2) ;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
img.copy(imgload, 0, 0, imgload.width, imgload.height, tempx, tempy, imgload.width, imgload.height);
|
|
|
|
// For background image!
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
/*
|
|
|
|
// Optional gamma correction for background image.
|
2015-02-22 20:26:03 +00:00
|
|
|
img.loadPixels();
|
2016-03-25 13:55:13 +00:00
|
|
|
|
|
|
|
float tempFloat;
|
2015-02-22 20:26:03 +00:00
|
|
|
float GammaValue = 1.0; // Normally in the range 0.25 - 4.0
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
for (int i = 0; i < img.pixels.length; i++) {
|
2016-03-25 13:55:13 +00:00
|
|
|
tempFloat = brightness(img.pixels[i])/255;
|
|
|
|
img.pixels[i] = color(floor(255 * pow(tempFloat,GammaValue)));
|
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
img.updatePixels();
|
2016-03-25 13:00:49 +00:00
|
|
|
*/
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
imgblur.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
|
|
|
|
// This is a duplicate of the background image, that we will apply a blur to,
|
|
|
|
// to reduce "high frequency" noise artifacts.
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
// Low-level blur filter to elminate pixel-to-pixel noise artifacts.
|
|
|
|
imgblur.filter(BLUR, 1);
|
2015-02-22 20:26:03 +00:00
|
|
|
imgblur.loadPixels();
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void MainArraySetup() {
|
2015-02-22 20:26:03 +00:00
|
|
|
// Main particle array initialization (to be called whenever necessary):
|
|
|
|
LoadImageAndScale();
|
|
|
|
|
|
|
|
// image(img, 0, 0); // SHOW BG IMG
|
|
|
|
|
|
|
|
particles = new Vec2D[maxParticles];
|
|
|
|
|
|
|
|
// Fill array by "rejection sampling"
|
|
|
|
int i = 0;
|
2016-03-25 13:00:49 +00:00
|
|
|
while (i < maxParticles) {
|
|
|
|
float fx = lowBorderX + random(hiBorderX - lowBorderX);
|
|
|
|
float fy = lowBorderY + random(hiBorderY - lowBorderY);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
float p = brightness(imgblur.pixels[ floor(fy)*imgblur.width + floor(fx) ])/255;
|
|
|
|
// OK to use simple floor_ rounding here, because this is a one-time operation,
|
|
|
|
// creating the initial distribution that will be iterated.
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (invertImg) {
|
2015-02-22 20:26:03 +00:00
|
|
|
p = 1 - p;
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
if (random(1) >= p ) {
|
2015-02-22 20:26:03 +00:00
|
|
|
Vec2D p1 = new Vec2D(fx, fy);
|
2016-03-25 13:55:13 +00:00
|
|
|
particles[i] = p1;
|
2015-02-22 20:26:03 +00:00
|
|
|
i++;
|
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
particleRouteLength = 0;
|
2016-03-25 13:55:13 +00:00
|
|
|
generation = 0;
|
2015-02-22 20:26:03 +00:00
|
|
|
millisLastFrame = millis();
|
2016-03-25 13:55:13 +00:00
|
|
|
routeStep = 0;
|
2016-03-25 13:00:49 +00:00
|
|
|
voronoiCalculated = false;
|
2015-02-22 20:26:03 +00:00
|
|
|
cellsCalculated = 0;
|
|
|
|
vorPointsAdded = 0;
|
|
|
|
voronoi = new Voronoi(); // Erase mesh
|
2016-03-25 13:00:49 +00:00
|
|
|
tempShowCells = true;
|
|
|
|
fileModeTSP = false;
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
void setup() {
|
2015-02-22 20:26:03 +00:00
|
|
|
borderWidth = 6;
|
|
|
|
mainwidth = 800;
|
|
|
|
mainheight = 600;
|
|
|
|
ctrlheight = 110;
|
2016-03-25 13:00:49 +00:00
|
|
|
fillingCircles = true;
|
2016-02-07 08:48:36 +00:00
|
|
|
|
|
|
|
size(800, 710);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
gfx = new ToxiclibsSupport(this);
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
lowBorderX = borderWidth; //mainwidth*0.01;
|
2015-02-22 20:26:03 +00:00
|
|
|
hiBorderX = mainwidth - borderWidth; //mainwidth*0.98;
|
|
|
|
lowBorderY = borderWidth; // mainheight*0.01;
|
2016-03-25 13:00:49 +00:00
|
|
|
hiBorderY = mainheight - borderWidth; //mainheight*0.98;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
int innerWidth = mainwidth - 2 * borderWidth;
|
|
|
|
int innerHeight = mainheight - 2 * borderWidth;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
Rect rect = new Rect(lowBorderX, lowBorderY, innerWidth, innerHeight);
|
|
|
|
clip = new SutherlandHodgemanClipper(rect);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
MainArraySetup(); // Main particle array setup
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-02-07 22:43:00 +00:00
|
|
|
frameRate(24);
|
2015-02-22 20:26:03 +00:00
|
|
|
smooth();
|
|
|
|
noStroke();
|
|
|
|
fill(153); // Background fill color, for control section
|
|
|
|
|
|
|
|
textFont(createFont("SansSerif", 10));
|
|
|
|
|
|
|
|
cp5 = new ControlP5(this);
|
|
|
|
|
|
|
|
int leftcolumwidth = 225;
|
2016-03-25 13:00:49 +00:00
|
|
|
int guiTop = mainheight + 15;
|
|
|
|
int gui2ndRow = 4; // Spacing for firt row after group heading
|
|
|
|
int guiRowSpacing = 14; // Spacing for subsequent rows
|
|
|
|
int buttonHeight = mainheight + 19 + int(round(2.25 * guiRowSpacing));
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
ControlGroup l3 = cp5.addGroup("Primary controls (Changing will restart)", 10, guiTop, 225);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addSlider("sliderStipples", 10, 10000, maxParticles, 10, gui2ndRow, 150, 10)
|
2016-03-25 13:55:13 +00:00
|
|
|
.setGroup(l3);
|
2016-02-07 22:43:00 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonInvertImg", 10, 10, gui2ndRow + guiRowSpacing, 190, 10)
|
|
|
|
.setCaptionLabel("Black stipples, White Background")
|
2016-03-25 13:55:13 +00:00
|
|
|
.setGroup(l3);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonLoadFile", 10, 10, buttonHeight, 175, 10)
|
|
|
|
.setCaptionLabel("LOAD IMAGE FILE (.PNG, .JPG, or .GIF)");
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonQuit", 10, 205, buttonHeight, 30, 10)
|
|
|
|
.setCaptionLabel("Quit");
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonSaveStipples", 10, 25, buttonHeight + guiRowSpacing, 160, 10)
|
|
|
|
.setCaptionLabel("Save Stipple File (.SVG format)");
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonSavePath", 10, 25, buttonHeight + 2 * guiRowSpacing, 160, 10)
|
|
|
|
.setCaptionLabel("Save \"TSP\" Path (.SVG format)");
|
2016-02-07 22:43:00 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonFillCircles", 10, 10, buttonHeight + 3 * guiRowSpacing, 190, 10)
|
|
|
|
.setCaptionLabel("Generate Filled circles in output");
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
ControlGroup l5 = cp5.addGroup("Display Options - Updated on next generation", leftcolumwidth+50, guiTop, 225);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addSlider("sliderMinDotSize", .5, 8, 2, 10, 4, 140, 10)
|
|
|
|
.setCaptionLabel("Min. Dot Size")
|
|
|
|
.setValue(minDotSize)
|
|
|
|
.setGroup(l5);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addSlider("sliderDotSizeRange", 0, 20, 5, 10, 18, 140, 10)
|
|
|
|
.setCaptionLabel("Dot Size Range")
|
|
|
|
.setValue(dotSizeFactor)
|
|
|
|
.setGroup(l5);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addSlider("sliderWhiteCutoff", 0, 1, 0, 10, 32, 140, 10)
|
|
|
|
.setCaptionLabel("White Cutoff")
|
|
|
|
.setValue(cutoff)
|
|
|
|
.setGroup(l5);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonImgOnOff", 10, 10, 46, 90, 10)
|
|
|
|
.setCaptionLabel("Image BG >> Hide")
|
|
|
|
.setGroup(l5);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonCellsOnOff", 10, 110, 46, 90, 10)
|
|
|
|
.setCaptionLabel("Cells >> Hide")
|
|
|
|
.setGroup(l5);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonPause", 10, 10, 60, 190, 10)
|
|
|
|
.setCaptionLabel("Pause (to calculate TSP path)")
|
|
|
|
.setGroup(l5);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.addButton("buttonOrderOnOff", 10, 10, 74, 190, 10)
|
|
|
|
.setCaptionLabel("Plotting path >> shown while paused")
|
|
|
|
.setGroup(l5);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
textColumnStart = 2 * leftcolumwidth + 100;
|
|
|
|
maxDotSize = getMaxDotSize(minDotSize);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
saveNow = false;
|
|
|
|
showBG = false;
|
2015-02-22 20:26:03 +00:00
|
|
|
showPath = true;
|
|
|
|
showCells = false;
|
2016-03-25 13:00:49 +00:00
|
|
|
pausemode = false;
|
|
|
|
invertImg = false;
|
2015-02-22 20:26:03 +00:00
|
|
|
fileLoaded = false;
|
2016-03-25 13:00:49 +00:00
|
|
|
reInitiallizeArray = false;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void fileSelected(File selection) {
|
|
|
|
if (selection == null) {
|
|
|
|
println("Window was closed or the user hit cancel.");
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
//println("User selected " + selection.getAbsolutePath());
|
|
|
|
|
|
|
|
String loadPath = selection.getAbsolutePath();
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
// If a file was selected, print path to file
|
|
|
|
println("Loaded file: " + loadPath);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
String[] p = splitTokens(loadPath, ".");
|
2016-03-25 13:00:49 +00:00
|
|
|
String ext = p[p.length - 1].toLowerCase();
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
boolean fileOK = false;
|
|
|
|
fileOK = fileOK || ext.equals("gif");
|
|
|
|
fileOK = fileOK || ext.equals("jpg");
|
|
|
|
fileOK = fileOK || ext.equals("tga");
|
|
|
|
fileOK = fileOK || ext.equals("png");
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
println("File OK: " + fileOK);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
if (fileOK) {
|
2016-03-25 13:55:13 +00:00
|
|
|
imgload = loadImage(loadPath);
|
2015-02-22 20:26:03 +00:00
|
|
|
fileLoaded = true;
|
2016-03-25 13:00:49 +00:00
|
|
|
reInitiallizeArray = true;
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
// Can't load file
|
2016-03-25 13:00:49 +00:00
|
|
|
errorDisplay = "ERROR: BAD FILE TYPE";
|
|
|
|
errorTime = millis();
|
|
|
|
errorDisp = true;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonLoadFile(float theValue) {
|
2015-02-22 20:26:03 +00:00
|
|
|
println(":::LOAD JPG, GIF or PNG FILE:::");
|
|
|
|
selectInput("Select a file to process:", "fileSelected"); // Opens file chooser
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonSavePath(float theValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
fileModeTSP = true;
|
|
|
|
saveSvg(0);
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonSaveStipples(float theValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
fileModeTSP = false;
|
|
|
|
saveSvg(0);
|
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
void SavefileSelected(File selection) {
|
|
|
|
if (selection == null) {
|
|
|
|
// If a file was not selected
|
|
|
|
println("No output file was selected...");
|
2016-03-25 13:00:49 +00:00
|
|
|
errorDisplay = "ERROR: NO FILE NAME CHOSEN.";
|
|
|
|
errorTime = millis();
|
|
|
|
errorDisp = true;
|
2016-03-25 13:55:13 +00:00
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
savePath = selection.getAbsolutePath();
|
2016-02-07 22:43:00 +00:00
|
|
|
String[] p = splitTokens(savePath, ".");
|
2016-03-25 13:00:49 +00:00
|
|
|
boolean fileOK = p[p.length - 1].toLowerCase().equals("svg");
|
|
|
|
if (!fileOK) savePath = savePath + ".svg";
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
// If a file was selected, print path to folder
|
2016-02-07 22:43:00 +00:00
|
|
|
println("Save file: " + savePath);
|
2016-03-25 13:55:13 +00:00
|
|
|
saveNow = true;
|
2016-03-25 13:00:49 +00:00
|
|
|
showPath = true;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
errorDisplay = "SAVING FILE...";
|
|
|
|
errorTime = millis();
|
|
|
|
errorDisp = true;
|
2016-02-07 22:43:00 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void saveSvg(float theValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
if (!pausemode) {
|
|
|
|
buttonPause(0.0);
|
|
|
|
errorDisplay = "Error: PAUSE before saving.";
|
|
|
|
errorTime = millis();
|
|
|
|
errorDisp = true;
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
selectOutput("Output .svg file name:", "SavefileSelected");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonQuit(float theValue) {
|
2015-02-22 20:26:03 +00:00
|
|
|
exit();
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
void buttonOrderOnOff(float theValue) {
|
|
|
|
Button orderOnOff = (Button)cp5.getController("buttonOrderOnOff");
|
2015-02-22 20:26:03 +00:00
|
|
|
if (showPath) {
|
2016-03-25 13:00:49 +00:00
|
|
|
showPath = false;
|
|
|
|
orderOnOff.setCaptionLabel("Plotting path >> Hide");
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2016-03-25 13:00:49 +00:00
|
|
|
showPath = true;
|
|
|
|
orderOnOff.setCaptionLabel("Plotting path >> Shown while paused");
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonCellsOnOff(float theValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
Button cellsOnOff = (Button)cp5.getController("buttonCellsOnOff");
|
2015-02-22 20:26:03 +00:00
|
|
|
if (showCells) {
|
2016-03-25 13:00:49 +00:00
|
|
|
showCells = false;
|
|
|
|
cellsOnOff.setCaptionLabel("Cells >> Hide");
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2016-03-25 13:00:49 +00:00
|
|
|
showCells = true;
|
|
|
|
cellsOnOff.setCaptionLabel("Cells >> Show");
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonImgOnOff(float theValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
Button imgOnOffButton = (Button)cp5.getController("buttonImgOnOff");
|
2015-02-22 20:26:03 +00:00
|
|
|
if (showBG) {
|
2016-03-25 13:00:49 +00:00
|
|
|
showBG = false;
|
|
|
|
imgOnOffButton.setCaptionLabel("Image BG >> Hide");
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2016-03-25 13:00:49 +00:00
|
|
|
showBG = true;
|
|
|
|
imgOnOffButton.setCaptionLabel("Image BG >> Show");
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonInvertImg(float theValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
Slider cutoffSlider = (Slider)cp5.getController("sliderWhiteCutoff");
|
|
|
|
Button invertImgButton = (Button)cp5.getController("buttonInvertImg");
|
2015-02-22 20:26:03 +00:00
|
|
|
if (invertImg) {
|
2016-03-25 13:00:49 +00:00
|
|
|
invertImg = false;
|
|
|
|
invertImgButton.setCaptionLabel("Black stipples, White background");
|
|
|
|
cutoffSlider.setCaptionLabel("White Cutoff");
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2016-03-25 13:00:49 +00:00
|
|
|
invertImg = true;
|
|
|
|
invertImgButton.setCaptionLabel("White stipples, Black background");
|
|
|
|
cutoffSlider.setCaptionLabel("Black Cutoff");
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
reInitiallizeArray = true;
|
|
|
|
pausemode = false;
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonFillCircles(float theValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
Button fillCircleButton = (Button)cp5.getController("buttonFillCircles");
|
|
|
|
if (fillingCircles) {
|
|
|
|
fillingCircles = false;
|
|
|
|
fillCircleButton.setCaptionLabel("Generate Open circles in output");
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2016-03-25 13:00:49 +00:00
|
|
|
fillingCircles = true;
|
|
|
|
fillCircleButton.setCaptionLabel("Generate Filled circles in output");
|
2016-02-07 22:43:00 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2016-02-07 22:43:00 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void buttonPause(float theValue) {
|
2015-02-22 20:26:03 +00:00
|
|
|
// Main particle array setup (to be repeated if necessary):
|
2016-03-25 13:00:49 +00:00
|
|
|
Button pauseButton = (Button)cp5.getController("buttonPause");
|
|
|
|
if (pausemode) {
|
2015-02-22 20:26:03 +00:00
|
|
|
pausemode = false;
|
|
|
|
println("Resuming.");
|
2016-03-25 13:00:49 +00:00
|
|
|
pauseButton.setCaptionLabel("Pause (to calculate TSP path)");
|
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
pausemode = true;
|
|
|
|
println("Paused. Press PAUSE again to resume.");
|
2016-03-25 13:00:49 +00:00
|
|
|
pauseButton.setCaptionLabel("Paused (calculating TSP path)");
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
routeStep = 0;
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
boolean overRect(int x, int y, int width, int height) {
|
|
|
|
return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void sliderStipples(int inValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
if (maxParticles != inValue) {
|
2016-03-25 13:55:13 +00:00
|
|
|
println("Update: Stipple Count -> " + inValue);
|
2016-03-25 13:00:49 +00:00
|
|
|
reInitiallizeArray = true;
|
|
|
|
pausemode = false;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
void sliderMinDotSize(float inValue) {
|
|
|
|
if (minDotSize != inValue) {
|
2016-03-25 13:55:13 +00:00
|
|
|
println("Update: sliderMinDotSize -> " + inValue);
|
|
|
|
minDotSize = inValue;
|
2016-03-25 13:00:49 +00:00
|
|
|
maxDotSize = getMaxDotSize(minDotSize);
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void sliderDotSizeRange(float inValue) {
|
2016-03-25 13:00:49 +00:00
|
|
|
if (dotSizeFactor != inValue) {
|
2016-03-25 13:55:13 +00:00
|
|
|
println("Update: Dot Size Range -> " + inValue);
|
2016-03-25 13:00:49 +00:00
|
|
|
dotSizeFactor = inValue;
|
|
|
|
maxDotSize = getMaxDotSize(minDotSize);
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
void sliderWhiteCutoff(float inValue) {
|
2015-02-22 20:26:03 +00:00
|
|
|
if (cutoff != inValue) {
|
2016-03-25 13:55:13 +00:00
|
|
|
println("Update: White_Cutoff -> " + inValue);
|
|
|
|
cutoff = inValue;
|
2016-03-25 13:00:49 +00:00
|
|
|
routeStep = 0; // Reset TSP path
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
float getMaxDotSize(float minDotSize) {
|
|
|
|
return minDotSize * (1 + dotSizeFactor);
|
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
void doBackgrounds() {
|
|
|
|
if (showBG) {
|
|
|
|
image(img, 0, 0); // Show original (cropped and scaled, but not blurred!) image in background
|
2016-03-25 13:55:13 +00:00
|
|
|
} else {
|
2016-03-25 13:00:49 +00:00
|
|
|
fill(invertImg ? 0 : 255);
|
2015-02-22 20:26:03 +00:00
|
|
|
rect(0, 0, mainwidth, mainheight);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
void optimizePlotPath() {
|
2015-02-22 20:26:03 +00:00
|
|
|
int temp;
|
|
|
|
// Calculate and show "optimized" plotting path, beneath points.
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
statusDisplay = "Optimizing plotting path";
|
2015-02-22 20:26:03 +00:00
|
|
|
/*
|
2016-03-25 13:00:49 +00:00
|
|
|
if (routeStep % 100 == 0) {
|
|
|
|
println("RouteStep:" + routeStep);
|
|
|
|
println("fps = " + frameRate );
|
|
|
|
}
|
|
|
|
*/
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
Vec2D p1;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (routeStep == 0) {
|
2015-02-22 20:26:03 +00:00
|
|
|
float cutoffScaled = 1 - cutoff;
|
|
|
|
// Begin process of optimizing plotting route, by flagging particles that will be shown.
|
|
|
|
|
|
|
|
particleRouteLength = 0;
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
boolean particleRouteTemp[] = new boolean[maxParticles];
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
for (int i = 0; i < maxParticles; ++i) {
|
|
|
|
particleRouteTemp[i] = false;
|
|
|
|
|
|
|
|
int px = (int) particles[i].x;
|
|
|
|
int py = (int) particles[i].y;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0)) {
|
2015-02-22 20:26:03 +00:00
|
|
|
continue;
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
float v = (brightness(imgblur.pixels[py * imgblur.width + px])) / 255;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (invertImg) {
|
2015-02-22 20:26:03 +00:00
|
|
|
v = 1 - v;
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
if (v < cutoffScaled) {
|
2016-03-25 13:55:13 +00:00
|
|
|
particleRouteTemp[i] = true;
|
2015-02-22 20:26:03 +00:00
|
|
|
particleRouteLength++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
particleRoute = new int[particleRouteLength];
|
|
|
|
int tempCounter = 0;
|
|
|
|
for (int i = 0; i < maxParticles; ++i) {
|
2016-03-25 13:00:49 +00:00
|
|
|
if (particleRouteTemp[i]) {
|
2015-02-22 20:26:03 +00:00
|
|
|
particleRoute[tempCounter] = i;
|
|
|
|
tempCounter++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// These are the ONLY points to be drawn in the tour.
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
if (routeStep < (particleRouteLength - 2)) {
|
2015-02-22 20:26:03 +00:00
|
|
|
// Nearest neighbor ("Simple, Greedy") algorithm path optimization:
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
int StopPoint = routeStep + 1000; // 1000 steps per frame displayed; you can edit this number!
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (StopPoint > (particleRouteLength - 1)) {
|
2015-02-22 20:26:03 +00:00
|
|
|
StopPoint = particleRouteLength - 1;
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (int i = routeStep; i < StopPoint; ++i) {
|
2016-03-25 13:00:49 +00:00
|
|
|
p1 = particles[particleRoute[routeStep]];
|
2016-03-25 13:55:13 +00:00
|
|
|
int ClosestParticle = 0;
|
2015-02-22 20:26:03 +00:00
|
|
|
float distMin = Float.MAX_VALUE;
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (int j = routeStep + 1; j < (particleRouteLength - 1); ++j) {
|
2015-02-22 20:26:03 +00:00
|
|
|
Vec2D p2 = particles[particleRoute[j]];
|
|
|
|
|
|
|
|
float dx = p1.x - p2.x;
|
|
|
|
float dy = p1.y - p2.y;
|
|
|
|
float distance = (float) (dx*dx+dy*dy); // Only looking for closest; do not need sqrt factor!
|
|
|
|
|
|
|
|
if (distance < distMin) {
|
2016-03-25 13:55:13 +00:00
|
|
|
ClosestParticle = j;
|
2015-02-22 20:26:03 +00:00
|
|
|
distMin = distance;
|
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
temp = particleRoute[routeStep + 1];
|
|
|
|
// p1 = particles[particleRoute[routeStep + 1]];
|
|
|
|
particleRoute[routeStep + 1] = particleRoute[ClosestParticle];
|
2015-02-22 20:26:03 +00:00
|
|
|
particleRoute[ClosestParticle] = temp;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (routeStep < (particleRouteLength - 1)) {
|
|
|
|
routeStep++;
|
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
println("Now optimizing plot path" );
|
|
|
|
}
|
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
} else { // Initial routing is complete
|
2015-02-22 20:26:03 +00:00
|
|
|
// 2-opt heuristic optimization:
|
|
|
|
// Identify a pair of edges that would become shorter by reversing part of the tour.
|
|
|
|
|
|
|
|
for (int i = 0; i < 90000; ++i) { // 1000 tests per frame; you can edit this number.
|
|
|
|
int indexA = floor(random(particleRouteLength - 1));
|
|
|
|
int indexB = floor(random(particleRouteLength - 1));
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (Math.abs(indexA - indexB) < 2) {
|
2015-02-22 20:26:03 +00:00
|
|
|
continue;
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (indexB < indexA) { // swap A, B.
|
2015-02-22 20:26:03 +00:00
|
|
|
temp = indexB;
|
|
|
|
indexB = indexA;
|
|
|
|
indexA = temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vec2D a0 = particles[particleRoute[indexA]];
|
|
|
|
Vec2D a1 = particles[particleRoute[indexA + 1]];
|
|
|
|
Vec2D b0 = particles[particleRoute[indexB]];
|
|
|
|
Vec2D b1 = particles[particleRoute[indexB + 1]];
|
|
|
|
|
|
|
|
// Original distance:
|
|
|
|
float dx = a0.x - a1.x;
|
|
|
|
float dy = a0.y - a1.y;
|
2016-03-25 13:55:13 +00:00
|
|
|
float distance = (float)(dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
|
2015-02-22 20:26:03 +00:00
|
|
|
dx = b0.x - b1.x;
|
|
|
|
dy = b0.y - b1.y;
|
2016-03-25 13:55:13 +00:00
|
|
|
distance += (float)(dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
// Possible shorter distance?
|
|
|
|
dx = a0.x - b0.x;
|
|
|
|
dy = a0.y - b0.y;
|
2016-03-25 13:00:49 +00:00
|
|
|
float distance2 = (float)(dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
|
2015-02-22 20:26:03 +00:00
|
|
|
dx = a1.x - b1.x;
|
|
|
|
dy = a1.y - b1.y;
|
2016-03-25 13:00:49 +00:00
|
|
|
distance2 += (float)(dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (distance2 < distance) {
|
2016-03-25 13:55:13 +00:00
|
|
|
// Reverse tour between a1 and b0.
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
int indexhigh = indexB;
|
|
|
|
int indexlow = indexA + 1;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
// println("Shorten!" + frameRate );
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
while (indexhigh > indexlow) {
|
2015-02-22 20:26:03 +00:00
|
|
|
temp = particleRoute[indexlow];
|
|
|
|
particleRoute[indexlow] = particleRoute[indexhigh];
|
|
|
|
particleRoute[indexhigh] = temp;
|
|
|
|
|
|
|
|
indexhigh--;
|
|
|
|
indexlow++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
frameTime = (millis() - millisLastFrame) / 1000;
|
2015-02-22 20:26:03 +00:00
|
|
|
millisLastFrame = millis();
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
void doPhysics() { // Iterative relaxation via weighted Lloyd's algorithm.
|
2015-02-22 20:26:03 +00:00
|
|
|
int temp;
|
|
|
|
int CountTemp;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (!voronoiCalculated) {
|
|
|
|
// Part I: Calculate voronoi cell diagram of the points.
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
statusDisplay = "Calculating Voronoi diagram ";
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
// float millisBaseline = millis(); // Baseline for timing studies
|
|
|
|
// println("Baseline. Time = " + (millis() - millisBaseline) );
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (vorPointsAdded == 0) {
|
2015-02-22 20:26:03 +00:00
|
|
|
voronoi = new Voronoi(); // Erase mesh
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
temp = vorPointsAdded + 500; // This line: VoronoiPointsPerPass (Feel free to edit this number.)
|
2016-03-25 13:00:49 +00:00
|
|
|
if (temp > maxParticles) {
|
2016-03-25 13:55:13 +00:00
|
|
|
temp = maxParticles;
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (int i = vorPointsAdded; i < temp; i++) {
|
2015-02-22 20:26:03 +00:00
|
|
|
// Optional, for diagnostics:::
|
|
|
|
// println("particles[i].x, particles[i].y " + particles[i].x + ", " + particles[i].y );
|
|
|
|
|
|
|
|
voronoi.addPoint(new Vec2D(particles[i].x, particles[i].y ));
|
|
|
|
vorPointsAdded++;
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (vorPointsAdded >= maxParticles) {
|
|
|
|
// println("Points added. Time = " + (millis() - millisBaseline) );
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
cellsTotal = voronoi.getRegions().size();
|
2015-02-22 20:26:03 +00:00
|
|
|
vorPointsAdded = 0;
|
|
|
|
cellsCalculated = 0;
|
|
|
|
cellsCalculatedLast = 0;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
regionList = new Polygon2D[cellsTotal];
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
for (Polygon2D poly : voronoi.getRegions()) {
|
2016-03-25 13:00:49 +00:00
|
|
|
regionList[i++] = poly; // Build array of polygons
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
voronoiCalculated = true;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
} else { // Part II: Calculate weighted centroids of cells.
|
2015-02-22 20:26:03 +00:00
|
|
|
// float millisBaseline = millis();
|
|
|
|
// println("fps = " + frameRate );
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
statusDisplay = "Calculating weighted centroids";
|
2016-02-07 22:43:00 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
temp = cellsCalculated + 500; // This line: CentroidsPerPass (Feel free to edit this number.)
|
|
|
|
// Higher values give slightly faster computation, but a less responsive GUI.
|
|
|
|
// Default value: 500
|
2016-02-07 22:43:00 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
// Time/frame @ 100: 2.07 @ 50 frames in
|
|
|
|
// Time/frame @ 200: 1.575 @ 50
|
|
|
|
// Time/frame @ 500: 1.44 @ 50
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (temp > cellsTotal) {
|
2015-02-22 20:26:03 +00:00
|
|
|
temp = cellsTotal;
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (int i=cellsCalculated; i< temp; i++) {
|
2015-02-22 20:26:03 +00:00
|
|
|
float xMax = 0;
|
|
|
|
float xMin = mainwidth;
|
|
|
|
float yMax = 0;
|
|
|
|
float yMin = mainheight;
|
|
|
|
float xt, yt;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
Polygon2D region = clip.clipPolygon(regionList[i]);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (Vec2D v : region.vertices) {
|
2015-02-22 20:26:03 +00:00
|
|
|
xt = v.x;
|
|
|
|
yt = v.y;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (xt < xMin) xMin = xt;
|
|
|
|
if (xt > xMax) xMax = xt;
|
|
|
|
if (yt < yMin) yMin = yt;
|
|
|
|
if (yt > yMax) yMax = yt;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-02-07 22:43:00 +00:00
|
|
|
|
2015-02-22 20:26:03 +00:00
|
|
|
float xDiff = xMax - xMin;
|
|
|
|
float yDiff = yMax - yMin;
|
|
|
|
float maxSize = max(xDiff, yDiff);
|
|
|
|
float minSize = min(xDiff, yDiff);
|
|
|
|
|
|
|
|
float scaleFactor = 1.0;
|
|
|
|
|
|
|
|
// Maximum voronoi cell extent should be between
|
|
|
|
// cellBuffer/2 and cellBuffer in size.
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
while (maxSize > cellBuffer) {
|
2015-02-22 20:26:03 +00:00
|
|
|
scaleFactor *= 0.5;
|
|
|
|
maxSize *= 0.5;
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
while (maxSize < (cellBuffer / 2)) {
|
2015-02-22 20:26:03 +00:00
|
|
|
scaleFactor *= 2;
|
|
|
|
maxSize *= 2;
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if ((minSize * scaleFactor) > (cellBuffer/2)) {
|
2016-03-25 13:55:13 +00:00
|
|
|
// Special correction for objects of near-unity (square-like) aspect ratio,
|
2015-02-22 20:26:03 +00:00
|
|
|
// which have larger area *and* where it is less essential to find the exact centroid:
|
|
|
|
scaleFactor *= 0.5;
|
|
|
|
}
|
|
|
|
|
|
|
|
float StepSize = (1/scaleFactor);
|
|
|
|
|
|
|
|
float xSum = 0;
|
|
|
|
float ySum = 0;
|
2016-03-25 13:55:13 +00:00
|
|
|
float dSum = 0;
|
|
|
|
float PicDensity = 1.0;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (invertImg) {
|
2015-02-22 20:26:03 +00:00
|
|
|
for (float x=xMin; x<=xMax; x += StepSize) {
|
|
|
|
for (float y=yMin; y<=yMax; y += StepSize) {
|
|
|
|
Vec2D p0 = new Vec2D(x, y);
|
2016-03-25 13:55:13 +00:00
|
|
|
if (region.containsPoint(p0)) {
|
|
|
|
// Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
|
|
|
|
PicDensity = 0.001 + (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
xSum += PicDensity * x;
|
2016-03-25 13:55:13 +00:00
|
|
|
ySum += PicDensity * y;
|
2015-02-22 20:26:03 +00:00
|
|
|
dSum += PicDensity;
|
|
|
|
}
|
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
for (float x=xMin; x<=xMax; x += StepSize) {
|
|
|
|
for (float y=yMin; y<=yMax; y += StepSize) {
|
|
|
|
Vec2D p0 = new Vec2D(x, y);
|
|
|
|
if (region.containsPoint(p0)) {
|
2016-03-25 13:55:13 +00:00
|
|
|
// Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
|
|
|
|
PicDensity = 255.001 - (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
xSum += PicDensity * x;
|
2016-03-25 13:55:13 +00:00
|
|
|
ySum += PicDensity * y;
|
2015-02-22 20:26:03 +00:00
|
|
|
dSum += PicDensity;
|
|
|
|
}
|
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (dSum > 0) {
|
2015-02-22 20:26:03 +00:00
|
|
|
xSum /= dSum;
|
|
|
|
ySum /= dSum;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vec2D centr;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
float xTemp = xSum;
|
|
|
|
float yTemp = ySum;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
if ((xTemp <= lowBorderX) || (xTemp >= hiBorderX) || (yTemp <= lowBorderY) || (yTemp >= hiBorderY)) {
|
|
|
|
// If new centroid is computed to be outside the visible region, use the geometric centroid instead.
|
2016-03-25 13:55:13 +00:00
|
|
|
// This will help to prevent runaway points due to numerical artifacts.
|
|
|
|
centr = region.getCentroid();
|
2015-02-22 20:26:03 +00:00
|
|
|
xTemp = centr.x;
|
|
|
|
yTemp = centr.y;
|
|
|
|
|
|
|
|
// Enforce sides, if absolutely necessary: (Failure to do so *will* cause a crash, eventually.)
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
if (xTemp <= lowBorderX) xTemp = lowBorderX + 1;
|
|
|
|
if (xTemp >= hiBorderX) xTemp = hiBorderX - 1;
|
|
|
|
if (yTemp <= lowBorderY) yTemp = lowBorderY + 1;
|
2016-03-25 13:00:49 +00:00
|
|
|
if (yTemp >= hiBorderY) yTemp = hiBorderY - 1;
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
particles[i].x = xTemp;
|
|
|
|
particles[i].y = yTemp;
|
|
|
|
|
|
|
|
cellsCalculated++;
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
// println("cellsCalculated = " + cellsCalculated );
|
|
|
|
// println("cellsTotal = " + cellsTotal );
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (cellsCalculated >= cellsTotal) {
|
2016-03-25 13:55:13 +00:00
|
|
|
voronoiCalculated = false;
|
2016-03-25 13:00:49 +00:00
|
|
|
generation++;
|
|
|
|
println("Generation = " + generation );
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
frameTime = (millis() - millisLastFrame)/1000;
|
|
|
|
millisLastFrame = millis();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-07 22:43:00 +00:00
|
|
|
String makeSpiral ( float xOrigin, float yOrigin, float turns, float radius)
|
|
|
|
{
|
|
|
|
float resolution = 20.0;
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
float AngleStep = TAU / resolution;
|
2016-02-07 22:43:00 +00:00
|
|
|
float ScaledRadiusPerTurn = radius / (TAU * turns);
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
String spiralSVG = "<path d=\"M " + xOrigin + "," + yOrigin + " "; // Mark center point of spiral
|
2016-02-07 22:43:00 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
float x, y;
|
2016-02-07 22:43:00 +00:00
|
|
|
float angle = 0;
|
2016-03-25 13:55:13 +00:00
|
|
|
|
2016-02-07 22:43:00 +00:00
|
|
|
int stopPoint = ceil (resolution * turns);
|
|
|
|
int startPoint = floor(resolution / 4); // Skip the first quarter turn in the spiral, since we have a center point already.
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (turns > 1.0) { // For small enough circles, skip the fill, and just draw the circle.
|
2016-02-07 22:43:00 +00:00
|
|
|
for (int i = startPoint; i <= stopPoint; i = i+1) {
|
|
|
|
angle = i * AngleStep;
|
|
|
|
x = xOrigin + ScaledRadiusPerTurn * angle * cos(angle);
|
|
|
|
y = yOrigin + ScaledRadiusPerTurn * angle * sin(angle);
|
|
|
|
spiralSVG += x + "," + y + " ";
|
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2016-02-07 22:43:00 +00:00
|
|
|
|
|
|
|
// Last turn is a circle:
|
|
|
|
float CircleRad = ScaledRadiusPerTurn * angle;
|
|
|
|
|
|
|
|
for (int i = 0; i <= resolution; i = i+1) {
|
|
|
|
angle += AngleStep;
|
|
|
|
x = xOrigin + radius * cos(angle);
|
|
|
|
y = yOrigin + radius * sin(angle);
|
|
|
|
|
|
|
|
spiralSVG += x + "," + y + " ";
|
|
|
|
}
|
|
|
|
|
|
|
|
spiralSVG += "\" />" ;
|
|
|
|
return spiralSVG;
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
void draw() {
|
2015-02-22 20:26:03 +00:00
|
|
|
int i = 0;
|
|
|
|
int temp;
|
2016-03-25 13:00:49 +00:00
|
|
|
float dotScale = (maxDotSize - minDotSize);
|
2015-02-22 20:26:03 +00:00
|
|
|
float cutoffScaled = 1 - cutoff;
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (reInitiallizeArray) {
|
2016-03-25 13:55:13 +00:00
|
|
|
// Only change maxParticles here!
|
|
|
|
maxParticles = (int)cp5.getController("sliderStipples").getValue();
|
2016-03-25 13:00:49 +00:00
|
|
|
MainArraySetup();
|
|
|
|
reInitiallizeArray = false;
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (pausemode && !voronoiCalculated) {
|
|
|
|
optimizePlotPath();
|
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
doPhysics();
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (pausemode) {
|
|
|
|
doBackgrounds();
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
// Draw paths:
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
if (showPath) {
|
|
|
|
stroke(128, 128, 255); // Stroke color (blue)
|
2015-02-22 20:26:03 +00:00
|
|
|
strokeWeight (1);
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (i = 0; i < particleRouteLength - 1; ++i) {
|
2015-02-22 20:26:03 +00:00
|
|
|
Vec2D p1 = particles[particleRoute[i]];
|
|
|
|
Vec2D p2 = particles[particleRoute[i + 1]];
|
|
|
|
line(p1.x, p1.y, p2.x, p2.y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
stroke(invertImg ? 255 : 0);
|
|
|
|
fill (invertImg ? 0 : 255);
|
2016-03-25 13:55:13 +00:00
|
|
|
strokeWeight(1);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
for ( i = 0; i < particleRouteLength; ++i) {
|
|
|
|
// Only show "routed" particles-- those above the white cutoff.
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
Vec2D p1 = particles[particleRoute[i]];
|
2016-03-25 13:00:49 +00:00
|
|
|
int px = (int)p1.x;
|
|
|
|
int py = (int)p1.y;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
float v = (brightness(imgblur.pixels[py * imgblur.width + px])) / 255;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (invertImg) v = 1 - v;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (fillingCircles) {
|
2016-03-25 13:55:13 +00:00
|
|
|
strokeWeight(maxDotSize - v * dotScale);
|
2016-02-07 22:43:00 +00:00
|
|
|
point(px, py);
|
2016-03-25 13:00:49 +00:00
|
|
|
} else {
|
2016-03-25 13:55:13 +00:00
|
|
|
float dotSize = maxDotSize - v * dotScale;
|
|
|
|
ellipse(px, py, dotSize, dotSize);
|
2016-02-07 22:43:00 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
} else { // NOT in pause mode. i.e., just displaying stipples.
|
2015-02-22 20:26:03 +00:00
|
|
|
if (cellsCalculated == 0) {
|
2016-03-25 13:00:49 +00:00
|
|
|
doBackgrounds();
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
tempShowCells = generation == 0;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (showCells || tempShowCells) { // Draw voronoi cells, over background.
|
2015-02-22 20:26:03 +00:00
|
|
|
strokeWeight(1);
|
|
|
|
noFill();
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
stroke(invertImg && !showBG ? 100 : 200);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
i = 0;
|
|
|
|
for (Polygon2D poly : voronoi.getRegions()) {
|
2016-03-25 13:55:13 +00:00
|
|
|
//regionList[i++] = poly;
|
2015-02-22 20:26:03 +00:00
|
|
|
gfx.polygon2D(clip.clipPolygon(poly));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (showCells) {
|
|
|
|
// Show "before and after" centroids, when polygons are shown.
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
strokeWeight(minDotSize); // Normal w/ Min & Max dot size
|
2015-02-22 20:26:03 +00:00
|
|
|
for ( i = 0; i < maxParticles; ++i) {
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
int px = (int)particles[i].x;
|
|
|
|
int py = (int)particles[i].y;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
|
|
|
|
continue;
|
2016-03-25 13:55:13 +00:00
|
|
|
{
|
2015-02-22 20:26:03 +00:00
|
|
|
//Uncomment the following four lines, if you wish to display the "before" dots at weighted sizes.
|
2016-03-25 13:55:13 +00:00
|
|
|
//float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
|
2015-02-22 20:26:03 +00:00
|
|
|
//if (invertImg)
|
|
|
|
//v = 1 - v;
|
2016-03-25 13:55:13 +00:00
|
|
|
//strokeWeight (maxDotSize - v * dotScale);
|
2015-02-22 20:26:03 +00:00
|
|
|
point(px, py);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
// Stipple calculation is still underway
|
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (tempShowCells) {
|
2016-03-25 13:55:13 +00:00
|
|
|
doBackgrounds();
|
2016-03-25 13:00:49 +00:00
|
|
|
tempShowCells = false;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
stroke(invertImg ? 255 : 0);
|
|
|
|
fill(invertImg ? 0 : 255);
|
2016-02-07 22:43:00 +00:00
|
|
|
strokeWeight(1);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (i = cellsCalculatedLast; i < cellsCalculated; ++i) {
|
|
|
|
int px = (int)particles[i].x;
|
|
|
|
int py = (int)particles[i].y;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
|
|
|
|
continue;
|
2016-03-25 13:55:13 +00:00
|
|
|
{
|
|
|
|
float v = (brightness(imgblur.pixels[py * imgblur.width + px])) / 255;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (invertImg) v = 1 - v;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
if (v < cutoffScaled) {
|
|
|
|
if (fillingCircles) {
|
|
|
|
strokeWeight(maxDotSize - v * dotScale);
|
2016-02-07 22:43:00 +00:00
|
|
|
point(px, py);
|
2016-03-25 13:00:49 +00:00
|
|
|
} else {
|
2016-03-25 13:55:13 +00:00
|
|
|
float dotSize = maxDotSize - v * dotScale;
|
|
|
|
ellipse(px, py, dotSize, dotSize);
|
2016-02-07 22:43:00 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cellsCalculatedLast = cellsCalculated;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
noStroke();
|
2016-03-25 13:55:13 +00:00
|
|
|
fill(100); // Background fill color
|
2015-02-22 20:26:03 +00:00
|
|
|
rect(0, mainheight, mainwidth, height); // Control area fill
|
|
|
|
|
|
|
|
// Underlay for hyperlink:
|
2016-03-25 13:00:49 +00:00
|
|
|
if (overRect(textColumnStart - 10, mainheight + 35, 205, 20) ) {
|
2016-03-25 13:55:13 +00:00
|
|
|
fill(150);
|
2016-03-25 13:00:49 +00:00
|
|
|
rect(textColumnStart - 10, mainheight + 35, 205, 20);
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
fill(255); // Text color
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
text("StippleGen 2 (v. 2.4.0)", textColumnStart, mainheight + 15);
|
|
|
|
text("by Evil Mad Scientist Laboratories", textColumnStart, mainheight + 30);
|
|
|
|
text("www.evilmadscientist.com/go/stipple2", textColumnStart, mainheight + 50);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
text("Generations completed: " + generation, textColumnStart, mainheight + 85);
|
2016-03-25 13:00:49 +00:00
|
|
|
text("Time/Frame: " + frameTime + " s", textColumnStart, mainheight + 100);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (errorDisp) {
|
2016-03-25 13:55:13 +00:00
|
|
|
fill(255, 0, 0); // Text color
|
2016-03-25 13:00:49 +00:00
|
|
|
text(errorDisplay, textColumnStart, mainheight + 70);
|
|
|
|
errorDisp = !(millis() - errorTime > 8000);
|
|
|
|
} else {
|
|
|
|
text("Status: " + statusDisplay, textColumnStart, mainheight + 70);
|
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (saveNow) {
|
|
|
|
statusDisplay = "Saving SVG File";
|
|
|
|
saveNow = false;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
fileOutput = loadStrings("header.txt");
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
String rowTemp;
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
float SVGscale = (800.0 / (float) mainheight);
|
|
|
|
int xOffset = (int)(1600 - (SVGscale * mainwidth / 2));
|
|
|
|
int yOffset = (int)(400 - (SVGscale * mainheight / 2));
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (fileModeTSP) { // Plot the PATH between the points only.
|
2015-02-22 20:26:03 +00:00
|
|
|
println("Save TSP File (SVG)");
|
|
|
|
|
|
|
|
// Path header::
|
2016-03-25 13:55:13 +00:00
|
|
|
rowTemp = "<path style=\"fill:none;stroke:black;stroke-width:2px;stroke-linejoin:round;stroke-linecap:round;\" d=\"M ";
|
2016-03-25 13:00:49 +00:00
|
|
|
fileOutput = append(fileOutput, rowTemp);
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (i = 0; i < particleRouteLength; ++i) {
|
|
|
|
Vec2D p1 = particles[particleRoute[i]];
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
float xTemp = SVGscale * p1.x + xOffset;
|
|
|
|
float yTemp = SVGscale * p1.y + yOffset;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
rowTemp = xTemp + " " + yTemp + "\r";
|
2016-03-25 13:00:49 +00:00
|
|
|
fileOutput = append(fileOutput, rowTemp);
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
fileOutput = append(fileOutput, "\" />"); // End path description
|
2016-02-07 22:43:00 +00:00
|
|
|
} else {
|
2015-02-22 20:26:03 +00:00
|
|
|
println("Save Stipple File (SVG)");
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
for (i = 0; i < particleRouteLength; ++i) {
|
|
|
|
Vec2D p1 = particles[particleRoute[i]];
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
int px = floor(p1.x);
|
|
|
|
int py = floor(p1.y);
|
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
float v = (brightness(imgblur.pixels[py * imgblur.width + px])) / 255;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (invertImg) v = 1 - v;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
float dotrad = (maxDotSize - v * dotScale) / 2;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:55:13 +00:00
|
|
|
float xTemp = SVGscale * p1.x + xOffset;
|
|
|
|
float yTemp = SVGscale * p1.y + yOffset;
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (fillingCircles) {
|
2016-03-25 13:55:13 +00:00
|
|
|
rowTemp = makeSpiral(xTemp, yTemp, dotrad / 2.0, dotrad);
|
2016-03-25 13:00:49 +00:00
|
|
|
} else {
|
2016-02-07 22:43:00 +00:00
|
|
|
rowTemp = "<circle cx=\"" + xTemp + "\" cy=\"" + yTemp + "\" r=\"" + dotrad + "\"/> ";
|
|
|
|
}
|
|
|
|
//Typ: <circle cx="1600" cy="450" r="3" />
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
fileOutput = append(fileOutput, rowTemp);
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SVG footer:
|
2016-03-25 13:00:49 +00:00
|
|
|
fileOutput = append(fileOutput, "</g></g></svg>");
|
|
|
|
saveStrings(savePath, fileOutput);
|
|
|
|
fileModeTSP = false; // reset for next time
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
if (fileModeTSP) {
|
|
|
|
errorDisplay = "TSP Path .SVG file Saved";
|
|
|
|
} else {
|
|
|
|
errorDisplay = "Stipple .SVG file saved ";
|
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
2016-03-25 13:00:49 +00:00
|
|
|
errorTime = millis();
|
|
|
|
errorDisp = true;
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
void mousePressed() {
|
2016-03-25 13:00:49 +00:00
|
|
|
// rect(textColumnStart, mainheight, 200, 75);
|
|
|
|
if (overRect(textColumnStart - 15, mainheight + 35, 205, 20) ) {
|
2015-02-22 20:26:03 +00:00
|
|
|
link("http://www.evilmadscientist.com/go/stipple2");
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|
2016-03-25 13:55:13 +00:00
|
|
|
}
|
2015-02-22 20:26:03 +00:00
|
|
|
|
|
|
|
void keyPressed() {
|
2016-03-25 13:00:49 +00:00
|
|
|
if (key == 'x') { // If this program doesn't run slowly enough for you,
|
2015-02-22 20:26:03 +00:00
|
|
|
// simply press the 'x' key on your keyboard. :)
|
2016-03-25 13:00:49 +00:00
|
|
|
cp5.getController("sliderStipples").setMax(50000.0);
|
2015-02-22 20:26:03 +00:00
|
|
|
}
|
2016-03-25 13:00:49 +00:00
|
|
|
}
|