Generative Design with Processing
"Generative art describes a strategy for artistic practice, not a style or genre of work. The artist describes a rule-based system external to him/herself that either produces works of art or is itself a work of art."
Parametric modeling can reveal the unexpected. It allows the artist to modify parameters in order to generate unique forms. The selection of imagery is the artist's choice.
Many established artists, architects and designers have integrated software into their process. According to Casey Reas "Learning to program and to engage the computer more directly with code opens the possibility to create not only tools, but systems, environments, and new modes of expression. It is here that the computer ceases to be a tool and becomes a medium."
3D Modeling with parameters
Parameters define a 3D model, and as they are set and modified, the model updates to reflect those changes. This type of modeling allows you to explore the effects of different feature sizes, without recreating model geometry.Software Needed: Processing, Netfabb Studio Basic or MeshLab
lerp
Linear Interpolationlerp(start, stop, amt)
Linear interpolation can be described this way:
delta*t+minVal //goal-start*t+start
Exercise
- Copy the following code into a new sketch:
float x1, x2, y1, y2; void setup() { size(400, 400); //every 200 frames create a new position newPosition(); } void draw() { background(0); if (frameCount%200==0)newPosition(); float t=(float)(frameCount%200)/200; float theX=(x2-x1)*t+x1; float theY=lerp(y1, y2, t); stroke(255, 0, 0); line(x1, y1, x2, y2); ellipse(theX, theY, 20, 20); } void newPosition() { x1=x2; y1=y2; x2=random(width); y2=random(height); }
-
Use lerp() to modify theX,theY, theS where theX refers to the horizontal position, theY to the vertical, and theS to the size of the ellipse.
Set the new size to a random number between 10 and 60.
map()
- In the example below there are 100 random points of data that are drawn to the sketch:
While this works, it would be better to scale the data to the size you need. That's where map() comes in:
float val[]; void setup() { size(400, 400); val=new float[100]; //create the data for (int i=0;i<val.length;i++) { val[i]=random(100); } } void draw() { background(0); noFill(); stroke(255); //draw the data beginShape(); for (int i=0;i<val.length-1;i++) { vertex(i, val[i]); } endShape(); }
float val[]; void setup() { size(400, 400); val=new float[100]; //create the data for (int i=0;i<val.length;i++) { val[i]=random(100); } } void draw() { background(0); noFill(); stroke(255); //draw the data drawValues(val,width,height); } void drawValues(float data[], float w, float h) { float maxVal=max(data); int n=data.length; beginShape(); for (int i=0;i<n;i++) { float x=map(i, 0, n-1, 0, w); float y=map(data[i], 0, maxVal, 0, h); vertex(x, y); } endShape(); }
- Now map the data so that the chart exists inside a shape that is 40 pixels from each edge.
- Add 2 horizontal lines. One at the top of the data and one at the bottom.
float val[]; void setup() { size(400, 400); val=new float[100]; //create the data for (int i=0;i<val.length;i++) { val[i]=random(100); } } void draw() { background(0); noFill(); stroke(255); //draw the data translate(40,40); drawValues(val,width-80,height-80); } void drawValues(float data[], float w, float h) { float maxVal=max(data); int n=data.length; line(0,0, w,0); line(0,h, w,h); beginShape(); for (int i=0;i<n;i++) { float x=map(i, 0, n-1, 0, w); float y=map(data[i], 0, maxVal, 0, h); vertex(x, y); } endShape(); }
- What if you want to see the data displayed radially?
float val[]; void setup() { size(400, 400); val=new float[100]; for (int i=0;i<val.length;i++) { val[i]=random(100); } } void draw() { background(0); noFill(); stroke(255, 255, 0); translate(width/2, height-200); drawValuesRadial(val, 80, 150); } void drawValuesRadial(float data[], float rInner, float rOuter) { float maxVal=max(data); int n=data.length; float x, y, angle, r; //x and y become angle and coordinates ellipse(0, 0, rInner*2, rInner*2); ellipse(0, 0, rOuter*2, rOuter*2); stroke(255, 255, 0); beginShape(); for (int i=0;i<n;i++) { angle=map(i, 0, n-1, 0, radians(360));//TWO_PI r=map(data[i], 0, maxVal, rInner, rOuter); x=cos(angle)*r; y=sin(angle)*r; vertex(x, y); line(x, y, x*0.5, y*0.5); } endShape(); }
Emulating organic behaviors
- Copy,paste and run the following sketch:
float x, y, xGoal, yGoal; void setup() { size(400, 400); xGoal=width/2; yGoal=height/2; x=xGoal; y=yGoal; } void draw() { background(0); ellipse(x, y, 50, 50); x=xGoal; y=yGoal; } void mousePressed() { xGoal=mouseX; yGoal=mouseY; }
- The code works, but it's not very interesting. Replace:
x=xGoal; y=yGoal;
float perc=0.1; //move towards the goal with "perc" as damping factor //if xGoal was 100, move 10, then 9, then 8, etch //when farthest away it will move faster, and will //slow down as you get closer x=x*(1-perc)+xGoal*perc; y=y*(1-perc)+yGoal*perc;
- Add a changing size attribute to the sketch. Save it.
- Now save as a copy and change the percentage. What happens?
- Abstract the function:
float dampen(float a, float b, float perc){ return a*(1-perc)+b*perc; }
- Save a copy and modify the changing size (r) of the ellipse by how close the mouse is to the center:
Then set the goal of the radius (rGoal) to be the remapping of d from 0 to the width to the two other values:
float d=dist(mouseX, mouseY, width/2, height/2);
rGoal=map(d, 0, width, val1, val2);
- Save
- Turn off the background and set the fill to have an alpha value of 20.
PVector
You can get a similar organic interactions using PVectors.PVector is a class that describes a two or three dimensional vector, specifically a Euclidean (also known as geometric) vector. A vector has both magnitude and direction. The PVector datatype stores the components of the vector (x,y for 2D, and x,y,z for 3D). The magnitude can be accessed with mag(). Direction can be accessed by heading().
//dynamic structure ArrayList <PVector> p1, p2; void setup() { size(400, 400); p1=new ArrayList<PVector>(); p2=new ArrayList<PVector>(); int n=1; //current randomizer(p1, n); //goal randomizer(p2, n); } void draw() { // background(0); fill(255, 255, 255, 60); float perc=0.1; //move towars the goal with "perc" as damping factor //if xGoal was 100, move 10, then 9, then 8, etch //when farthest away it will move faster, and will //slow down as you get closer for (int i=0;i<p1.size();i++) { PVector v=p1.get(i); v.x=dampen(v.x, p2.get(i).x, perc); v.y=dampen(v.y, p2.get(i).y, perc); v.z=dampen(v.z, p2.get(i).z, perc); ellipse(v.x, v.y, v.z, v.z); } } void randomizer(ArrayList <PVector> pt, int n) { //clear the arrayList pt.clear(); for (int i=0; i<n;i++) { PVector v=new PVector(random(width), random(height), random(2, 50)); pt.add(v); } } float dampen(float a, float b, float perc) { return a*(1-perc)+b*perc; } void mousePressed() { randomizer(p2, p1.size()); }
- Add more elements to your PVectors.
- Save a copy and change the percentage.
- Save a copy and turn off the background.
Code to play with
Copy the code into a Processing Sketch. Click to save a frame. You will find your image inside the data folder inside the sketch folder.// Dec 2008 // http://www.abandonedart.org // http://www.zenbullets.com // // This work is licensed under a Creative Commons 3.0 License. // (Attribution - NonCommerical - ShareAlike) // http://creativecommons.org/licenses/by-nc-sa/3.0/ // // This basically means, you are free to use it as long as you: // 1. give http://www.zenbullets.com a credit // 2. don't use it for commercial gain // 3. share anything you create with it in the same way I have // // These conditions can be waived if you want to do something groovy with it // though, so feel free to email me via http://www.zenbullets.com //================================= global vars float boxSize = 50; float margin = 65; //================================= init void setup() { size(500, 500, P3D); frameRate(12); noStroke(); } void clearBackground() { background(0); } //================================= frame loop void mouseReleased(){ saveFrame("image-#####.png"); } void draw() { clearBackground(); lights(); // Center and spin grid translate(width/2, height/2, 0); rotateY(frameCount * 0.01); rotateX(frameCount * 0.01); // Build grid using multiple translations for (float i =- height+margin; i <= height-margin; i += margin){ pushMatrix(); for (float j =- height+margin; j <= height-margin; j += margin){ pushMatrix(); for (float k =- width+margin; k <= width-margin; k += margin){ pushMatrix(); translate(k, j, i); fill(255, 20); box(boxSize, boxSize, boxSize); popMatrix(); } popMatrix(); } popMatrix(); } }
// Sept 2008 // http://www.abandonedart.org // http://www.zenbullets.com // // This work is licensed under a Creative Commons 3.0 License. // (Attribution - NonCommerical - ShareAlike) // http://creativecommons.org/licenses/by-nc-sa/3.0/ // // This basically means, you are free to use it as long as you: // 1. give http://www.zenbullets.com a credit // 2. don't use it for commercial gain // 3. share anything you create with it in the same way I have // // These conditions can be waived if you want to do something groovy with it // though, so feel free to email me via http://www.zenbullets.com int num = 2400; // colCycle - four cycles, black, colour1, white, colour2 String colCycle = "colour2"; Particle[] partArr; void setup() { size(500, 500); frameRate(12); background(0); partArr = new Particle[num]; for (int i=0;i<num;i++) { float r = PI*i/num; partArr[i] = new Particle(r); } } void draw() { for (int i=0;i<num;i++) { partArr[i].move(); } if (keyPressed) { if (key == 'b' || key == 'B') { saveFrame("image-#####.png"); } } } void mousePressed() { // manually switch colCycle periods if (colCycle == "colour1") { colCycle = "black"; } else if (colCycle == "black") { colCycle = "colour2"; } else if (colCycle == "colour2") { colCycle = "white"; } else if (colCycle == "white") { colCycle = "colour1"; } for (int i=0;i<num;i++) { partArr[i].origin(mouseX, mouseY); } } class Particle { float ox, oy; float x, y; float xx, yy; float vx; float vy; int age=int(random(200)); // colour int rc; int gc; int bc; int alph = 100; // line int maxline = 7; int linethick; Particle(float r) { ox = 250; oy = 20; x = ox; y = oy; xx = 0; yy = 0; vx = 2*cos(r); vy = 2*sin(r); rc = int(random(255)); gc = int(random(255)); bc = int(random(255)); linethick = int(random(maxline)); } void origin(float ex, float why) { // ox = ex; oy = why; } void move() { xx=x; yy=y; x+=vx; y+=vy; vx += (random(200)-random(200))*0.005; vy += (random(200)-random(200))*0.005; strokeWeight(1); if (colCycle == "colour1") { strokeWeight(linethick * 2); stroke(rc,gc,bc,60); } else if (colCycle == "colour2") { stroke(rc,gc,bc,60); } else if (colCycle == "black") { strokeWeight(linethick / 2); stroke(0,0,0,alph); } else if (colCycle == "white") { stroke(255,255,255,60); } line(ox+xx,oy+yy,ox+x,oy+y); line(ox-xx,oy+yy,ox-x,oy+y); age++; if (age>200) { x = ox; x = oy; xx=0; yy=0; vx=0; vy=0; age=0; } } }
// Oct 2008 // http://www.abandonedart.org // http://www.zenbullets.com // // This work is licensed under a Creative Commons 3.0 License. // (Attribution - NonCommerical - ShareAlike) // http://creativecommons.org/licenses/by-nc-sa/3.0/ // // This basically means, you are free to use it as long as you: // 1. give http://www.zenbullets.com a credit // 2. don't use it for commercial gain // 3. share anything you create with it in the same way I have // // These conditions can be waived if you want to do something groovy with it // though, so feel free to email me via http://www.zenbullets.com //================================= global vars int num = 4; int step = 10; float centx, centy; Particle[] pArr; //================================= init void setup() { size(500, 500); frameRate(320); clearBackground(); smooth(); centx = width/2; centy = height/2; pArr = new Particle[num]; for (int i=0;i<num;i++) { pArr[i] = new Particle(i); } } void clearBackground() { background(0); } //================================= frame loop void draw() { // clearBackground(); for (int i=0;i<num;i++) { pArr[i].update(); } if (keyPressed) { if (key == 'b' || key == 'B') { saveFrame("image-#####.png"); } } } //================================= interaction void mousePressed() { clearBackground(); centx = mouseX; centy = mouseY; for (int i=0;i<num;i++) { pArr[i].init(); } } //================================= objects class Particle { int id; int count, life, step; float origx, origy; float x1, y1, x2, y2; int rd, gr, bl, alph; float angle, radius; Particle (int num) { id = num; init(); } void trace(String str) { // println("Particle " + id + ": " + str); } void init() { trace("init"); count = 0; step = int(random(10)); life = 180 * int(random(5) + 1); radius = random(width) + 10; // radius = random(50); //origx = centx + (radius * cos(angle)); //origy = centy + (radius * sin(angle)); x1 = 999; //y1 = origy; alph = 255; rd = int(random(255)); //gr = int(random(255)); //bl = int(random(255)); bl = gr = rd; // grey } void update() { count += step; angle = count; // project x2,y2 from x1,y1, and draw a line to it x2 = centx + (radius * cos(angle)); y2 = centy + (radius * sin(angle)); // draw line to it if (x1 != 999) { strokeWeight(1); stroke(rd, gr, bl, alph); line(x1, y1, x2, y2); } // reset for next update x1 = x2; y1 = y2; alph -= step; // expiration if (count >= life) { init(); } } }
// Oct 2008 // http://www.abandonedart.org // http://www.zenbullets.com // // This work is licensed under a Creative Commons 3.0 License. // (Attribution - NonCommerical - ShareAlike) // http://creativecommons.org/licenses/by-nc-sa/3.0/ // // This basically means, you are free to use it as long as you: // 1. give http://www.zenbullets.com a credit // 2. don't use it for commercial gain // 3. share anything you create with it in the same way I have // // These conditions can be waived if you want to do something groovy with it // though, so feel free to email me via http://www.zenbullets.com //================================= global vars int num = 8; Particle[] pArr; //================================= init void setup() { size(500, 500); smooth(); frameRate(60); clearBackground(); pArr = new Particle[num]; for (int i=0;i<num;i++) { pArr[i] = new Particle(i); } } void clearBackground() { background(0); } //================================= frame loop void draw() { // clearBackground(); for (int i=0;i<num;i++) { pArr[i].update(); } if (keyPressed) { if (key == 'b' || key == 'B') { saveFrame("image-#####.png"); } } } //================================= interaction void mousePressed() { clearBackground(); for (int i=0;i<num;i++) { pArr[i].init(); } } //================================= objects class Particle { int id; float x, y; float pct = 0.0; float beginX, beginY; float endX, endY; float distX, distY; int grey, alph; float exponent = 4; float step = 0.01; Particle (int num) { id = num; init(); } void trace(String str) { // println("Particle " + id + ": " + str); } void init() { grey = int(random(255)); alph = int(random(100)); beginX = random(width * 2) - (width/2); beginY = random(height * 2) - (height/2); endX = random(width * 2) - (width/2); endY = random(height * 2) - (height/2); distX = endX - beginX; distY = endY - beginY; } void update() { pct += step; if (pct < 1.0) { x = beginX + (pct * distX); y = beginY + (pow(pct, exponent) * distY); } else { pct = 0.0; beginX = x; beginY = y; endX = random(width); endY = random(height); distX = endX - beginX; distY = endY - beginY; } // alter rad accrding to distance float rad = distX; if (distY > distX) { rad = distY; } noStroke(); fill(grey, alph); ellipse(x, y, rad, rad); // mirror it float mirrorx = width - x; ellipse(mirrorx, y, 6, 6); } }
/* @pjs preload="snowpalette.jpg"; */ // Oct 2008 // http://www.abandonedart.org // http://www.zenbullets.com // // This work is licensed under a Creative Commons 3.0 License. // (Attribution - NonCommerical - ShareAlike) // http://creativecommons.org/licenses/by-nc-sa/3.0/ // // This basically means, you are free to use it as long as you: // 1. give http://www.zenbullets.com a credit // 2. don't use it for commercial gain // 3. share anything you create with it in the same way I have // // These conditions can be waived if you want to do something groovy with it // though, so feel free to email me via http://www.zenbullets.com //================================= global vars int nextNum = 1; // keeps track fo current figure float xstart1, xstart2, xstart3; //================================= init void setup() { size(500, 500); frameRate(30); smooth(); sampleColour(); clearBackground(); xstart1 = 1 * width/4; xstart2 = 2 * width/4; xstart3 = 3 * width/4; create(); create(); create(); } //================================= colour sampling int numcols = 600; // 30x20 color[] colArr = new color[numcols]; void sampleColour() { PImage img; img = loadImage("snowpalette.jpg"); image(img,0,0); int count = 0; for (int x=0; x < img.width; x++){ for (int y=0; y < img.height; y++) { if (count < numcols) { color c = get(x,y); colArr[count] = c; } count++; } } } void create() { float x1, x2, y1, y2; stroke(40, 20, 60, random(100)+100); strokeWeight(1); float xpos = xstart1; if (nextNum == 2) { xpos = xstart2; } else if (nextNum == 3) { xpos = xstart3; } int ytop = 20; int ybottom = height - 20; float step = 5 + (random(4) - 4); // calc gap according to mouse position float xfactor = float(mouseX) / float(width); float yfactor = float(mouseY) / float(height); // println(xfactor + ": " + yfactor); float angle = 0; x1 = xpos; y1 = ytop; for (int ys = (ytop + nextNum -3); ys < ybottom; ys+= 3) { float sine = (sin(angle) * 100 * xfactor); x2 = xpos + sine; y2 = ys; strokeWeight(random(2)); // mm, blotches line(x1, y1, x2, y2); angle += PI/ (50 * yfactor); x1 = x2; y1 = y2; } x1 = xpos; y1 = ytop; // shortcut x1 = xpos + (random(20) - 10); if (x1 > width) { x1 = 0; } else if (x1 < 0) { x1 = width; } if (nextNum == 1) { xstart1 = x1; } else if (nextNum == 2) { xstart2 = x1; } else if (nextNum == 3) { xstart3 = x1; } // spiral noise spiralScratch(xpos, random(ybottom) + ytop); // move to next figure for next drawing nextNum++; if (nextNum > 3) { nextNum = 1; } } void spiralScratch(float xpos, float ypos) { float x1, x2, y1, y2; float step = 5 + (random(4) - 4); strokeWeight(random(3)); // mm, blotches color myCol = colArr[int(random(numcols))]; stroke(myCol, 40); float radius = 5; float rad = radians(90); x1 = xpos; y1 = ypos; for (int x = 0; x < (360 * 4); x+= step) { radius += (1 + (random(4) - 2)); rad = radians(x + step); x2 = xpos + (radius * cos(rad)) + (random(4) - 2); y2 = ypos + (radius * sin(rad)) + (random(4) - 2); // draw line to it line(x1, y1, x2, y2); x1 = x2; y1 = y2; } } void clearBackground() { background(255); // graph paper int gap = 10; strokeWeight(1); stroke(140, 40); for (int x=0;x<width;x+=gap) { line(x, 0, x, height); } for (int y=0;y<height;y+=gap) { line(0, y, width, y); } } //================================= frame loop void draw() { create(); if (keyPressed) { if (key == 'b' || key == 'B') { saveFrame("image-#####.png"); } } } //================================= interaction void mousePressed() { clearBackground(); xstart1 = 1 * width/4; xstart2 = 2 * width/4; xstart3 = 3 * width/4; } //================================= objects
// Oct 2008 // http://www.abandonedart.org // http://www.zenbullets.com // // This work is licensed under a Creative Commons 3.0 License. // (Attribution - NonCommerical - ShareAlike) // http://creativecommons.org/licenses/by-nc-sa/3.0/ // // This basically means, you are free to use it as long as you: // 1. give http://www.zenbullets.com a credit // 2. don't use it for commercial gain // 3. share anything you create with it in the same way I have // // These conditions can be waived if you want to do something groovy with it // though, so feel free to email me via http://www.zenbullets.com //================================= global vars int num = 5; int bgCol = 0; float rot; //================================= init void setup() { size(500, 500); smooth(); frameRate(60); rot = 0; clearBackground(); } void clearBackground() { background(bgCol); } //================================= frame loop int xdest = 0; int ydest = 0; void draw() { if (keyPressed) { if (key == 'b' || key == 'B') { saveFrame("image-#####.png"); } } rot += PI/36; translate(mouseX, mouseY); rotate(rot); int xstep; if (xdest == 0) { xstep = 0; xdest = int(random(width) - (width/2)); } else if (xdest > 0) { xstep = 1; xdest--; } else { xstep = -1; xdest++; } int ystep; if (ydest == 0) { ystep = 0; ydest = int(random(height) - (height/2)); } else if (ydest > 0) { ystep = 1; ydest--; } else { ystep = -1; ydest++; } drawStuff(); // pixelShift(-2,-2); pixelShift(xstep,ystep); } void drawStuff() { fill(255); stroke(0,0,0); rect(0,0,mouseX/4+6,mouseY/4+6); noStroke(); fill(0,0,255); rect(2,2,mouseX/4+2,mouseY/4+2); // stroke(255,0,0); fill(255,0,0); rect(4,4,mouseX/4,mouseY/4); } void pixelShift(int xshift, int yshift) { // copy screen into an array color transArr[] = new color[width * height]; loadPixels(); arrayCopy(pixels, transArr); for (int y=1; y < height; y++) { for (int x=1; x < width; x++){ if ((x+xshift < width) && (x+xshift > 0)) { if ((y+yshift < height) && (y+yshift > 0)) { pixels[x + (y*width)] = transArr[(x+xshift)+ ((y+yshift)*width)]; } } } } updatePixels(); } //================================= interaction void mousePressed() { clearBackground(); } //================================= objects
/* @pjs preload="tricolpalette.jpg"; */ // Dec 2008 // http://www.abandonedart.org // http://www.zenbullets.com // // with lots of thanks to Erik Natzke (obviously) // http://jot.eriknatzke.com/ // // and James Alliban, who saved me the job of doing the conversion // http://jamesalliban.wordpress.com/2008/12/04/2d-ribbons/ // // // This work is licensed under a Creative Commons 3.0 License. // (Attribution - NonCommerical - ShareAlike) // http://creativecommons.org/licenses/by-nc-sa/3.0/ // // This basically means, you are free to use it as long as you: // 1. give http://www.zenbullets.com a credit // 2. don't use it for commercial gain // 3. share anything you create with it in the same way I have // // These conditions can be waived if you want to do something groovy with it // though, so feel free to email me via http://www.zenbullets.com //================================= colour sampling int numcols = 600; // 30x20 color[] colArr = new color[numcols]; void sampleColour() { PImage img; img = loadImage("tricolpalette.jpg"); image(img,0,0); int count = 0; for (int x=0; x < img.width; x++){ for (int y=0; y < img.height; y++) { if (count < numcols) { color c = get(x,y); colArr[count] = c; } count++; } } } //================================= global vars int _numRibbons = 3; int _numParticles = 40; float _randomness = .2; RibbonManager ribbonManager; float _a, _b, _centx, _centy, _x, _y; float _noiseoff; int _angle; //================================= init void setup() { size(500, 500); smooth(); frameRate(30); background(0); sampleColour(); clearBackground(); _centx = (width / 2); _centy = (height / 2); restart(); } void restart() { _noiseoff = random(1); _angle = 1; _a = 3.5; _b = _a + (noise(_noiseoff) * 1) - 0.5; ribbonManager = new RibbonManager(_numRibbons, _numParticles, _randomness); ribbonManager.setRadiusMax(12); // default = 8 ribbonManager.setRadiusDivide(10); // default = 10 ribbonManager.setGravity(.0); // default = .03 ribbonManager.setFriction(1.1); // default = 1.1 ribbonManager.setMaxDistance(40); // default = 40 ribbonManager.setDrag(2.5); // default = 2 ribbonManager.setDragFlare(.015); // default = .008 } void clearBackground() { background(0); } //================================= frame loop void draw() { clearBackground(); if (keyPressed) { if (key == 'b' || key == 'B') { saveFrame("image-#####.png"); } } float newx = sin(_a + radians(_angle) + PI / 2) * _centx; float newy = sin(_b + radians(_angle)) * _centy; _angle += (random(180) - 90); if (_angle > 360) { _angle = 0; } if (_angle < 0) { _angle = 360; } translate(_centx, _centy); ribbonManager.update(newx* 0.5, newy*0.5); } //================================= interaction void mousePressed() { restart(); } //================================= objects // modified from original code by http://jamesalliban.wordpress.com/ //======== manager class RibbonManager { int _numRibbons; int _numParticles; float _randomness; Ribbon[] ribbons; // ribbon array RibbonManager(int _numRibbons, int _numParticles, float _randomness) { this._numRibbons = _numRibbons; this._numParticles = _numParticles; this._randomness = _randomness; init(); } void init() { addRibbon(); } void addRibbon() { ribbons = new Ribbon[_numRibbons]; for (int i = 0; i < _numRibbons; i++) { color ribbonColour = colArr[int(random(numcols))]; ribbons[i] = new Ribbon(_numParticles, ribbonColour, _randomness); } } void update(float currX, float currY) { for (int i = 0; i < _numRibbons; i++) { float randX = currX; float randY = currY; ribbons[i].update(randX, randY); } } void setRadiusMax(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].radiusMax = value; } } void setRadiusDivide(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].radiusDivide = value; } } void setGravity(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].gravity = value; } } void setFriction(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].friction = value; } } void setMaxDistance(int value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].maxDistance = value; } } void setDrag(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].drag = value; } } void setDragFlare(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].dragFlare = value; } } } //======== ribbon class Ribbon { int _numRibbons; float _randomness; int _numParticles; // length of the Particle Array (max number of points) int particlesAssigned = 0; // current amount of particles currently in the Particle array float radiusMax = 8; // maximum width of ribbon float radiusDivide = 10; // distance between current and next point / this = radius for first half of the ribbon float gravity = .03; // gravity applied to each particle float friction = 1.1; // friction applied to the gravity of each particle int maxDistance = 40; // if the distance between particles is larger than this the drag comes into effect float drag = 2; // if distance goes above maxDistance - the points begin to grag. high numbers = less drag float dragFlare = .008; // degree to which the drag makes the ribbon flare out RibbonParticle[] particles; // particle array color ribbonColor; Ribbon(int _numParticles, color ribbonColor, float _randomness) { this._numParticles = _numParticles; this.ribbonColor = ribbonColor; this._randomness = _randomness; init(); } void init() { particles = new RibbonParticle[_numParticles]; } void update(float randX, float randY){ addParticle(randX, randY); drawCurve(); } void addParticle(float randX, float randY) { if(particlesAssigned == _numParticles) { for (int i = 1; i < _numParticles; i++) { particles[i-1] = particles[i]; } particles[_numParticles - 1] = new RibbonParticle(_randomness, this); particles[_numParticles - 1].px = randX; particles[_numParticles - 1].py = randY; return; } else { particles[particlesAssigned] = new RibbonParticle(_randomness, this); particles[particlesAssigned].px = randX; particles[particlesAssigned].py = randY; ++particlesAssigned; } if (particlesAssigned > _numParticles) ++particlesAssigned; } void drawCurve() { smooth(); for (int i = 1; i < particlesAssigned - 1; i++) { RibbonParticle p = particles[i]; p.calculateParticles(particles[i-1], particles[i+1], _numParticles, i); } fill(30); for (int i = particlesAssigned - 3; i > 1 - 1; i--) { RibbonParticle p = particles[i]; RibbonParticle pm1 = particles[i-1]; fill(ribbonColor, 255); if (i < particlesAssigned-3) { noStroke(); beginShape(); vertex(p.lcx2, p.lcy2); bezierVertex(p.leftPX, p.leftPY, pm1.lcx2, pm1.lcy2, pm1.lcx2, pm1.lcy2); vertex(pm1.rcx2, pm1.rcy2); bezierVertex(p.rightPX, p.rightPY, p.rcx2, p.rcy2, p.rcx2, p.rcy2); vertex(p.lcx2, p.lcy2); endShape(); } } } } //======== particle class RibbonParticle { float px, py; // x and y position of particle (this is the bexier point) float xSpeed, ySpeed = 0; // speed of the x and y positions float cx1, cy1, cx2, cy2; // the avarage x and y positions between px and py and the points of the surrounding Particles float leftPX, leftPY, rightPX, rightPY; // the x and y points of that determine the thickness of this segment float lpx, lpy, rpx, rpy; // the x and y points of the outer bezier points float lcx1, lcy1, lcx2, lcy2; // the avarage x and y positions between leftPX and leftPX and the left points of the surrounding Particles float rcx1, rcy1, rcx2, rcy2; // the avarage x and y positions between rightPX and rightPX and the right points of the surrounding Particles float radius; // thickness of current particle float _randomness; Ribbon ribbon; RibbonParticle(float _randomness, Ribbon ribbon) { this._randomness = _randomness; this.ribbon = ribbon; } void calculateParticles(RibbonParticle pMinus1, RibbonParticle pPlus1, int particleMax, int i) { float div = 2; cx1 = (pMinus1.px + px) / div; cy1 = (pMinus1.py + py) / div; cx2 = (pPlus1.px + px) / div; cy2 = (pPlus1.py + py) / div; // calculate radians (direction of next point) float dx = cx2 - cx1; float dy = cy2 - cy1; float pRadians = atan2(dy, dx); float distance = sqrt(dx*dx + dy*dy); if (distance > ribbon.maxDistance) { float oldX = px; float oldY = py; px = px + ((ribbon.maxDistance/ribbon.drag) * cos(pRadians)); py = py + ((ribbon.maxDistance/ribbon.drag) * sin(pRadians)); xSpeed += (px - oldX) * ribbon.dragFlare; ySpeed += (py - oldY) * ribbon.dragFlare; } ySpeed += ribbon.gravity; xSpeed *= ribbon.friction; ySpeed *= ribbon.friction; px += xSpeed + random(.3); py += ySpeed + random(.3); float randX = ((_randomness / 2) - random(_randomness)) * distance; float randY = ((_randomness / 2) - random(_randomness)) * distance; px += randX; py += randY; //float radius = distance / 2; //if (radius > radiusMax) radius = ribbon.radiusMax; if (i > particleMax / 2) { radius = distance / ribbon.radiusDivide; } else { radius = pPlus1.radius * .9; } if (radius > ribbon.radiusMax) radius = ribbon.radiusMax; if (i == particleMax - 2 || i == 1) { if (radius > 1) radius = 1; } // calculate the positions of the particles relating to thickness leftPX = px + cos(pRadians + (HALF_PI * 3)) * radius; leftPY = py + sin(pRadians + (HALF_PI * 3)) * radius; rightPX = px + cos(pRadians + HALF_PI) * radius; rightPY = py + sin(pRadians + HALF_PI) * radius; // left and right points of current particle lpx = (pMinus1.lpx + lpx) / div; lpy = (pMinus1.lpy + lpy) / div; rpx = (pPlus1.rpx + rpx) / div; rpy = (pPlus1.rpy + rpy) / div; // left and right points of previous particle lcx1 = (pMinus1.leftPX + leftPX) / div; lcy1 = (pMinus1.leftPY + leftPY) / div; rcx1 = (pMinus1.rightPX + rightPX) / div; rcy1 = (pMinus1.rightPY + rightPY) / div; // left and right points of next particle lcx2 = (pPlus1.leftPX + leftPX) / div; lcy2 = (pPlus1.leftPY + leftPY) / div; rcx2 = (pPlus1.rightPX + rightPX) / div; rcy2 = (pPlus1.rightPY + rightPY) / div; } }
// Dec 2008 // http://www.abandonedart.org // http://www.zenbullets.com // // with lots of thanks to Erik Natzke (obviously) // http://jot.eriknatzke.com/ // // and James Alliban, who saved me the job of doing the conversion // http://jamesalliban.wordpress.com/2008/12/04/2d-ribbons/ // // // This work is licensed under a Creative Commons 3.0 License. // (Attribution - NonCommerical - ShareAlike) // http://creativecommons.org/licenses/by-nc-sa/3.0/ // // This basically means, you are free to use it as long as you: // 1. give http://www.zenbullets.com a credit // 2. don't use it for commercial gain // 3. share anything you create with it in the same way I have // // These conditions can be waived if you want to do something groovy with it // though, so feel free to email me via http://www.zenbullets.com //================================= global vars int _numRibbons = 6; int _numParticles = 40; float _randomness = .3; RibbonManager ribbonManager; float _a, _b, _centx, _centy, _x, _y; float _noiseoff; int _angle; //================================= init void setup() { size(500, 500); smooth(); frameRate(12); background(0); clearBackground(); _centx = (width / 2); _centy = (2* height / 3); restart(); } void restart() { _noiseoff = random(1); _angle = 1; _a = 3.5; _b = _a + (noise(_noiseoff) * 1) - 0.5; ribbonManager = new RibbonManager(_numRibbons, _numParticles, _randomness); ribbonManager.setRadiusMax(40); // default = 8 ribbonManager.setRadiusDivide(2); // default = 10 ribbonManager.setGravity(-0.09); // default = .03 ribbonManager.setFriction(1.1); // default = 1.1 ribbonManager.setMaxDistance(40); // default = 40 ribbonManager.setDrag(2.5); // default = 2 ribbonManager.setDragFlare(.015); // default = .008 } void clearBackground() { background(0); } //================================= frame loop void draw() { clearBackground(); if (keyPressed) { if (key == 'b' || key == 'B') { saveFrame("image-#####.png"); } } float newx = sin(_a + radians(_angle) + PI / 2) * _centx; float newy = sin(_b + radians(_angle)) * _centy; _angle = int(random(360)); _noiseoff += 0.05; _b = (noise(_noiseoff) * 8) +1; _a = _b; translate(_centx, _centy); ribbonManager.update(newx* 0.1, newy*0.1); } //================================= interaction void mousePressed() { _centx = mouseX; _centy = mouseY; restart(); } //================================= objects // modified from original code by http://jamesalliban.wordpress.com/ //======== manager class RibbonManager { // PImage img; int _numRibbons; int _numParticles; float _randomness; Ribbon[] ribbons; // ribbon array RibbonManager(int _numRibbons, int _numParticles, float _randomness) { this._numRibbons = _numRibbons; this._numParticles = _numParticles; this._randomness = _randomness; init(); } void init() { addRibbon(); } void addRibbon() { ribbons = new Ribbon[_numRibbons]; for (int i = 0; i < _numRibbons; i++) { color ribbonColour = color(random(255), ((40/_numRibbons) * i) ) ; ribbons[i] = new Ribbon(_numParticles, ribbonColour, _randomness); } } void update(float currX, float currY) { for (int i = 0; i < _numRibbons; i++) { float randX = currX; float randY = currY; ribbons[i].update(randX, randY); } } void setRadiusMax(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].radiusMax = value; } } void setRadiusDivide(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].radiusDivide = value; } } void setGravity(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].gravity = value; } } void setFriction(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].friction = value; } } void setMaxDistance(int value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].maxDistance = value; } } void setDrag(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].drag = value; } } void setDragFlare(float value) { for (int i = 0; i < _numRibbons; i++) { ribbons[i].dragFlare = value; } } } //======== ribbon class Ribbon { int _numRibbons; float _randomness; int _numParticles; // length of the Particle Array (max number of points) int particlesAssigned = 0; // current amount of particles currently in the Particle array float radiusMax = 8; // maximum width of ribbon float radiusDivide = 10; // distance between current and next point / this = radius for first half of the ribbon float gravity = .03; // gravity applied to each particle float friction = 1.1; // friction applied to the gravity of each particle int maxDistance = 40; // if the distance between particles is larger than this the drag comes into effect float drag = 2; // if distance goes above maxDistance - the points begin to grag. high numbers = less drag float dragFlare = .008; // degree to which the drag makes the ribbon flare out RibbonParticle[] particles; // particle array color ribbonColor; Ribbon(int _numParticles, color ribbonColor, float _randomness) { this._numParticles = _numParticles; this.ribbonColor = ribbonColor; this._randomness = _randomness; init(); } void init() { particles = new RibbonParticle[_numParticles]; } void update(float randX, float randY){ addParticle(randX, randY); drawCurve(); } void addParticle(float randX, float randY) { if(particlesAssigned == _numParticles) { for (int i = 1; i < _numParticles; i++) { particles[i-1] = particles[i]; } particles[_numParticles - 1] = new RibbonParticle(_randomness, this); particles[_numParticles - 1].px = randX; particles[_numParticles - 1].py = randY; return; } else { particles[particlesAssigned] = new RibbonParticle(_randomness, this); particles[particlesAssigned].px = randX; particles[particlesAssigned].py = randY; ++particlesAssigned; } if (particlesAssigned > _numParticles) ++particlesAssigned; } void drawCurve() { smooth(); for (int i = 1; i < particlesAssigned - 1; i++) { RibbonParticle p = particles[i]; p.calculateParticles(particles[i-1], particles[i+1], _numParticles, i); } // fill(30); for (int i = particlesAssigned - 3; i > 1 - 1; i--) { RibbonParticle p = particles[i]; RibbonParticle pm1 = particles[i-1]; fill(ribbonColor, 50); if (i < particlesAssigned-3) { noStroke(); beginShape(); vertex(p.lcx2, p.lcy2); bezierVertex(p.leftPX, p.leftPY, pm1.lcx2, pm1.lcy2, pm1.lcx2, pm1.lcy2); vertex(pm1.rcx2, pm1.rcy2); bezierVertex(p.rightPX, p.rightPY, p.rcx2, p.rcy2, p.rcx2, p.rcy2); vertex(p.lcx2, p.lcy2); endShape(); } } } } //======== particle class RibbonParticle { float px, py; // x and y position of particle (this is the bexier point) float xSpeed, ySpeed = 0; // speed of the x and y positions float cx1, cy1, cx2, cy2; // the avarage x and y positions between px and py and the points of the surrounding Particles float leftPX, leftPY, rightPX, rightPY; // the x and y points of that determine the thickness of this segment float lpx, lpy, rpx, rpy; // the x and y points of the outer bezier points float lcx1, lcy1, lcx2, lcy2; // the avarage x and y positions between leftPX and leftPX and the left points of the surrounding Particles float rcx1, rcy1, rcx2, rcy2; // the avarage x and y positions between rightPX and rightPX and the right points of the surrounding Particles float radius; // thickness of current particle float _randomness; Ribbon ribbon; RibbonParticle(float _randomness, Ribbon ribbon) { this._randomness = _randomness; this.ribbon = ribbon; } void calculateParticles(RibbonParticle pMinus1, RibbonParticle pPlus1, int particleMax, int i) { float div = 2; cx1 = (pMinus1.px + px) / div; cy1 = (pMinus1.py + py) / div; cx2 = (pPlus1.px + px) / div; cy2 = (pPlus1.py + py) / div; // calculate radians (direction of next point) float dx = cx2 - cx1; float dy = cy2 - cy1; float pRadians = atan2(dy, dx); float distance = sqrt(dx*dx + dy*dy); if (distance > ribbon.maxDistance) { float oldX = px; float oldY = py; px = px + ((ribbon.maxDistance/ribbon.drag) * cos(pRadians)); py = py + ((ribbon.maxDistance/ribbon.drag) * sin(pRadians)); xSpeed += (px - oldX) * ribbon.dragFlare; ySpeed += (py - oldY) * ribbon.dragFlare; } ySpeed += ribbon.gravity; xSpeed *= ribbon.friction; ySpeed *= ribbon.friction; px += xSpeed + random(.3); py += ySpeed + random(.3); float randX = ((_randomness / 2) - random(_randomness)) * distance; float randY = ((_randomness / 2) - random(_randomness)) * distance; px += randX; py += randY; //float radius = distance / 2; //if (radius > radiusMax) radius = ribbon.radiusMax; if (i > particleMax / 2) { radius = distance / ribbon.radiusDivide; } else { radius = pPlus1.radius * .9; } if (radius > ribbon.radiusMax) radius = ribbon.radiusMax; if (i == particleMax - 2 || i == 1) { if (radius > 1) radius = 1; } // calculate the positions of the particles relating to thickness leftPX = px + cos(pRadians + (HALF_PI * 3)) * radius; leftPY = py + sin(pRadians + (HALF_PI * 3)) * radius; rightPX = px + cos(pRadians + HALF_PI) * radius; rightPY = py + sin(pRadians + HALF_PI) * radius; // left and right points of current particle lpx = (pMinus1.lpx + lpx) / div; lpy = (pMinus1.lpy + lpy) / div; rpx = (pPlus1.rpx + rpx) / div; rpy = (pPlus1.rpy + rpy) / div; // left and right points of previous particle lcx1 = (pMinus1.leftPX + leftPX) / div; lcy1 = (pMinus1.leftPY + leftPY) / div; rcx1 = (pMinus1.rightPX + rightPX) / div; rcy1 = (pMinus1.rightPY + rightPY) / div; // left and right points of next particle lcx2 = (pPlus1.leftPX + leftPX) / div; lcy2 = (pPlus1.leftPY + leftPY) / div; rcx2 = (pPlus1.rightPX + rightPX) / div; rcy2 = (pPlus1.rightPY + rightPY) / div; } }
void setup() { size(600, 600); background(255) ; do_it(); } void mousePressed(){ background(255) ; do_it(); } void draw() { } void do_it() { float ry=random(120); float rx=random(120); for (int y=0;y<height;y+=ry) { for (int x=0;x<width;x+=rx) { if (random(1)>.5) { line (x, y, x+rx, y+ry); } else { line (x, y+ry, x+rx, y); } } } }