tutorials, explanations, etc…

Tag: processing

More on vector fields – surfaces

Recently I’ve found one more nice method for vector fields visualization. What is a vector field and how to visualize it you can find in my previous posts:

Now let’s introduce new concept based on vectors’ angles. Angle can be calculated easily using $atan2()$ function.

So let’s define: $\alpha=atan2(\vec{v})$ $\beta=atan2(f(\vec{v}))$ or $\beta=atan2(f(\vec{v})-\vec{v})$

where $\vec{v}$ is input vector and $f()$ is vector field function ie. $\mathbf{R}^{2}$ to $\mathbf{R}^{2}$ function.

Having that $\alpha$ and $\beta$ are from the range $[-\pi,\pi]$ we can scan vector field, calculate angles and draw points. It will be kind of heat map of all possible pairs of angles given vector field can generate.

Let’s see how the code looks like:

void setup() {
size(800, 800);
smooth(8);
noStroke();
background(240);
fill(10, 10);
}

void draw() {
for (int i=0; i<10000; i++) { // draw 10000 points at once
PVector v = new PVector(random(-1, 1), random(-1, 1)); // random input vector
PVector fv = popcorn(v); // vector field
//fv.sub(v); // subtract trick

float x = map(alpha, -PI, PI, 50, width-50);
float y = map(beta, -PI, PI, 50, height-50);

rect(x, y, 1, 1);
}
}

void keyPressed() {
if (keyCode == 32) saveFrame("######.jpg");
}

float popcorn_c = random(-0.8, 0.8);
float popcorn_f = random(-0.8, 0.8);

PVector popcorn(PVector p) {
float x = p.x + popcorn_c * sin(tan(3.0 * p.y));
float y = p.y + popcorn_f * sin(tan(3.0 * p.x));
return new PVector(x, y);
}


Result of the code on vector field called popcorn. To get different variety let’s uncomment line 13 to use difference of vector field result and input. Lovely, what about another vector field – waves?

float waves_b = random(-0.8, 0.8);
float waves_e = random(-0.8, 0.8);
float waves_c = random(-0.8, 0.8);
float waves_f = random(-0.8, 0.8);

PVector waves(PVector p) {
float x = p.x + waves_b * sin(p.y * (1.0 / (waves_c * waves_c) ));
float y = p.y + waves_e * sin(p.x * (1.0 / (waves_f * waves_f) ));

return new PVector(x, y);
} I called result as surfaces since somtimes it looks like 3d surface folded in the space.

What can be done more here?

Let’s add some distortion to our angles based on noise function. This will break too much symmetry in the image.

Replace draw() with new one:

void draw() {
for (int i=0; i<50000; i++) {
PVector v = new PVector(random(-1, 1), random(-1, 1));
PVector fv = waves(popcorn(v));
fv.sub(v);

float nalpha = 0.5 * (noise(v.x,fv.y,alpha)-0.5);
float nbeta = 0.5 * (noise(fv.x,v.y,beta)-0.5);

float x = map(alpha+nalpha, -PI, PI, 50, width-50);
float y = map(beta+nbeta, -PI, PI, 50, height-50);

rect(x, y, 1, 1);
}
}


And results!

Yeah!

Two vector fields

You can of course use two vector fields and calculate $\alpha$ from a vector field not from input vector.

Color and log-density rendering

Using different rendering method (log-density) and color taken from vector magnitude we can achive following results.

More can be seen on folds2d site

Enjoy!

folds2d.tumblr.com A few years ago I started to explore fractal flames which resulted in Scratch implementation of flame explorer (https://scratch.mit.edu/projects/17114670/). Click HERE to read about what a fractal flame actually is.

Actually most of the ‘flavour’ in fractal flames is made of something authors call ‘variations’. Variations are nothing more than real number functions from $\mathbf{R}^{2}$ to $\mathbf{R}^{2}$ (it can be also a 3D function). Variations are often nonlinear, not continuous, not differentiable and sometimes semi-random.

I’ve started to draw them in a lot of different ways in order to explore their beauty. Here is how I do it:

Variations

A Variation is a multivariable real number function with domain and codomain R^n. I will focus on the 2D version. Sometimes it can have a name map or a morphism, but I call it just a function. So our function converts a point $(x,y)$ into new point $(x',y')$. $V:(x,y)\rightarrow(x',y')$

Usually a Variation is designed as a whole class of functions, which means there are a few external parameters that you need to set to certain values in order to get a specific function. In such a case a different set of parameters will result in a different function.

The other parameter used is the ‘amount’ of the function. ‘amount’ is a special parameter which in almost all cases scales the resulting value.

So a more formal definition of our function is: $V_{\alpha,P}=\alpha V_{P}(x,y)=(\alpha x',\alpha y')$, where $\alpha \in [0,1]$; which means:

Variation V is a function with some sequence of parameters Ρ which is scaled by a factor α. Sequence of parameters Ρ is just a sequence of values which are used in definition of the function.

Although the domain is the whole R² space I usually only operate on [-3,3]x[-3,3] range.

Examples of Variations

Here are some basic ones (without parameters and with α=1):

• Identity – $V(x,y)=(x,y)$
• $V(0,0)=(0,0)$
• $V(1,1)=(1,1)$
• Linear – $V(x,y)=(\alpha x,\alpha y)$
• Sinusoidal – $V(x,y)=(\alpha \sin{x},\alpha \sin{y})$
• $V(0,0)=(0,0)$
• $V(1,-1)=(0,84,-0,84)$

Things get more complicated when we add sequence of parameters (and α=1). These definitions are function class definitions and by using specific parameters you will obtain unique functions.

• PDJ – $V_{P}(x,y)=(\alpha (\sin{ay}-\cos{bx}),\alpha (\sin{cx}-\cos{dy}))$, where $P=\{a,b,c,d\}$ from $\mathbf{R}$.
• $V_{P}(1,-1) = (\sin{(-\pi)}-\cos{0}, \sin{2}-\cos{0}) = (-1,-0.09)$, where $P = \{\pi,0,2,0\}$
• Rectangles – $V_{P}(x,y) = (\alpha (a(2\lfloor \frac{x}{a}\rfloor +1)-x),\alpha (b(2\lfloor \frac{y}{b}\rfloor +1)-y))$, where $P=\{a,b\}$ from $\mathbf{R}$
• $V_{P}(1,-1) = (3\cdotp 1-1, -1\cdotp 1+1) = (2,0)$,  where $P = \{1,1\}$

How to draw it

Here is the template I use to draw. The general concept is to take every point from the [-3,3]x[-3,3] range, calculate the result of the function and draw it. This template draws a simple identity function:

void setup() {
size(600, 600);
background(250);
smooth(8);
noFill();
stroke(20, 15);
strokeWeight(0.9);

x1=y1=-3;
x2=y2=3;
y=y1;
step=(x2-x1)/(2.321*width);
}

float x1, y1, x2, y2; // function domain
float step; // step within domain
float y;

boolean go = true;
void draw() {
if (go) {
for (int i=0; (i<20)&go; i++) { // draw 20 lines at once
for (float x=x1; x<=x2; x+=step) {
drawVariation(x, y);
}
y+=step;
if (y>y2) {
go = false;
println("done");
}
}
}
}

void drawVariation(float x, float y) {
float xx = map(x, x1, x2, 20, width-20);
float yy = map(y, y1, y2, 20, height-20);
point(xx, yy);
} The result, which you can see here, is a grid that’s the result of rounding floating point values. By inserting the randomGaussian() function into the calculation I can distort the points a little and thus get a less rigid and more uniform look.

void drawVariation(float x, float y) {
float xx = map(x+0.003*randomGaussian(), x1, x2, 20, width-20);
float yy = map(y+0.003*randomGaussian(), y1, y2, 20, height-20);
point(xx, yy);
} Ok, now we are ready to plot our first functions!

Sinusoidal

V(x,y) = (αsin(x),αsin(y));

The code to draw this function looks like this:

void drawVariation(float x, float y) {
PVector v = new PVector(x,y);
float amount = 1.0;

v = sinusoidal(v,amount);

float xx = map(v.x+0.003*randomGaussian(), x1, x2, 20, width-20);
float yy = map(v.y+0.003*randomGaussian(), y1, y2, 20, height-20);
point(xx, yy);
}

PVector sinusoidal(PVector v, float amount) {
return new PVector(amount * sin(v.x), amount * sin(v.y));
}


The top image is a plot with amount=1.0. The bottom image is an example where I used amount=3.0 to scale the resulting value up to the [-3,3]x[-3,3] range.

Hyperbolic

Another example is the hyperbolic function, which uses polar coordinates to calculate x and y.

PVector hyperbolic(PVector v, float amount) {
float r = v.mag() + 1.0e-10;
float theta = atan2(v.x, v.y);
float x = amount * sin(theta) / r;
float y = amount * cos(theta) * r;
return new PVector(x, y);
} PDJ

Now let’s draw functions from the PDJ class. The sequence of parameters are provided as external variables and used in the function.

// parametrization P={pdj_a,pdj_b,pdj_c,pdj_d}
float pdj_a = 0.1;
float pdj_b = 1.9;
float pdj_c = -0.8;
float pdj_d = -1.2;
PVector pdj(PVector v, float amount) {
return new PVector( amount * (sin(pdj_a * v.y) - cos(pdj_b * v.x)),
amount * (sin(pdj_c * v.x) - cos(pdj_d * v.y)));
}


So, for example, when I set Ρ = {0.1, 1.9, -0.8, -1.2} I get: But with Ρ={1.0111, -1.011, 2.08, 10.2} I get something very different: Julia

The next function I want to show is Julia where random() function is used.

PVector julia(PVector v, float amount) {
float r = amount * sqrt(v.mag());
float theta = 0.5 * atan2(v.x, v.y) + (int)(2.0 * random(0, 1)) * PI;
float x = r * cos(theta);
float y = r * sin(theta);
return new PVector(x, y);
} Sech

And then there’s the last function, Sech, which is based on an hyperbolic functions:

float cosh(float x) { return 0.5 * (exp(x) + exp(-x));}
float sinh(float x) { return 0.5 * (exp(x) - exp(-x));}

PVector sech(PVector p, float weight) {
float d = cos(2.0*p.y) + cosh(2.0*p.x);
if (d != 0)
d = weight * 2.0 / d;
return new PVector(d * cos(p.y) * cosh(p.x), -d * sin(p.y) * sinh(p.x));
} More to see

Click HERE to see the animations of 129 basic variations that I have created in Scratch

Now let’s complicate things!

Being aware now of the base functions (which are widely available: flame.pdf paper defines 48 of them, JWildfire code defines 200+ usable functions or classes of functions), we can then create new ones ourselves. Math theories (like group/ring theories, homomorphism, analysis, etc) with associativity and distributivity of operations give us recipes to build new functions from the others.

Let’s suppose we have variations $V(x,y)=(x',y')$ and $W(x,y)=(x'',y'')$ and we want to produce a new function Z. In order to do this we can then do any of the following:

1. Sum of the function: $(V+W)(x,y) = V(x,y)+W(x,y) = (x'+x'', y'+y'')$
2. Subtraction of the functions: $(V-W)(x,y) = V(x,y)-W(x,y) = (x'-x'', y'-y'')$
3. Combination of the function: $(V\circ W)(x,y) = V(W(x,y)) = V(x'',y'')$
4. Kind of differencial/derivative (it’s not strict differential): $\partial(V(x,y)) = \frac{V(x+h,y+h)-V(x,y)}{\sqrt{h}} = (\frac{x^*-x'}{\sqrt{h}}, \frac{y^*-y'}{\sqrt{h}})$, where $V(x+h,y+h)=(x^*,y^*)$ and $h$ is some real number and $h\neq 0.0$
5. Multiplication: $(V*W)(x,y)=V(x,y)*W(x,y) = (x'*x'', y'*y'')$
6. Division: $(\frac{V}{W})(x,y)=\frac{V(x,y)}{W(x,y)} = (\frac{x'}{x''},\frac{y'}{y''})$, where when denominator is 0, value is also 0 (just to simplify things)
7. Power: $V^{n}(x,y) = V^{n-1}(x',y')$ or as recursive combinations: $V(V(V(V(\cdots V(x,y)\cdots ))))$

You can of course mix all of the above to create a bunch of new and unique functions. For example, you can create function which is a sum of combination of power of division of few base functions. Or whatever else you want.

The sum, subtraction, multiplication and division of functions can be coded like this:

PVector addF(PVector v1, PVector v2) { return new PVector(v1.x+v2.x, v1.y+v2.y); }
PVector subF(PVector v1, PVector v2) { return new PVector(v1.x-v2.x, v1.y-v2.y); }
PVector mulF(PVector v1, PVector v2) { return new PVector(v1.x*v2.x, v1.y*v2.y); }
PVector divF(PVector v1, PVector v2) { return new PVector(v2.x==0?0:v1.x/v2.x, v2.y==0?0:v1.y/v2.y); }


Combination can be obtained like this (julia o sech)

  v = julia(sech(v,amount),amount);


Power, which is multiple combination of the same function, can be coded like this (hyperbolic^5 = hyperbolic(hyperbolic(hyperbolic(hyperbolic(hyperbolic)))) )

  for(int i=0;i<5;i++) v = hyperbolic(v,amount);


And an example of a differential of pdj function:

PVector d_pdj(PVector v, float amount) {
float h = 0.1; // step
float sqrth = sqrt(h);
PVector v1 = pdj(v, amount);
PVector v2 = pdj(new PVector(v.x+h, v.y+h), amount);
return new PVector( (v2.x-v1.x)/sqrth, (v2.y-v1.y)/sqrth );
}


hyperbolic + pdj

  v = addF( hyperbolic(v,amount), pdj(v,amount) ); julia – hyperbolic

  v = subF( julia(v,amount), hyperbolic(v,amount) ); sech * pdj

  v = mulF( sech(v,amount), pdj(v,amount) ); hyperbolic / sech

  v = divF( hyperbolic(v,amount), sech(v,amount) ); d(pdj)

  float amount = 3.0; // scale up
v = d_pdj(v,amount); julia o hyperbolic = julia(hyperbolic)

  v = julia(hyperbolic(v,amount),amount); hyperbolic ^ 10

  for(int i=0;i<10;i++) v = hyperbolic(v,amount); [julia((hyperbolic + julia o sech) * pdj) – d(pdj)]^3

  for(int i=0;i<3;i++) Folding

In the above examples you can see that some of the points have escaped outside of the screen. The main reason is that the values exceeded -3 or 3. To keep them inside our area we have to force them to be in the required range of values. There are a couple of strategies for achieving this. I will describe two of them: modulo and sinusoidal.

I will use pdj + hyperbolic * sech function with amount = 2

  float amount = 2.0;


Modulo

The first strategy is to treat the screen as a torus (points that exceed the screen area will reappar on the opposite side: top <> down, left <> right). The formula for this wrapping is documented below. It is called after the function has been calculated.

  v.x = (v.x - x1) % (x2-x1);
if(v.x<0) v.x += (x2-x1);
v.y = (v.y - y1) % (y2-y1);
if(v.y<0) v.y += (y2-y1);
v.x += x1;
v.y += y1; Sinusoidal

The second strategy is to wrap the points using a sinus function, which moves between -1 to 1. With amount=3 you get the required range.

  v = sinusoidal(v,(x2-x1)/2); Towards fixed point

In maths a ‘fixed point’ is a special point a where f(a) = a. If the function has a special characteristic (is a contraction mapping) a fixed point can be calculated by using the power of the function as defined above. Just call f(f(f(f(f(f….))))). In our case we operate on a set of points (range [-3,3]x[-3,3]) and our “fixed point” can also be set (if exists). So let’s try to modify our code a little and draw each step for a certain power of our function.

Let’s create a function v = sinusoidal o julia o sech calculate v(v(v(v(…)))) and draw each step: first v, then v(v), v(v(v)), in order to finish at some chosen number.

Our algorithm will look like this:

1. set n = 3
2. choose a point p = (x,y)
3. calculate new point p = sinusoidal(julia(sech(p)))
4. draw point p
5. repeat n (=3) times steps 3-4 (using newly calculated point p)
6. go to 2

But first we need to adjust the step value and set the stroke color to be slightly lighter

  stroke(60, 15);
//...
// more points drawn in drawVariation, bigger step
step=sqrt(n)*(x2-x1)/(2.321*width);


int n=1;
void drawVariation(float x, float y) {
PVector v = new PVector(x, y);

for (int i=0; i<n; i++) {
v = julia(sech(v, 1), 1);
v = sinusoidal(v, (x2-x1)/2);
float xx = map(v.x+0.003*randomGaussian(), x1, x2, 20, width-20);
float yy = map(v.y+0.003*randomGaussian(), y1, y2, 20, height-20);
point(xx, yy);
}
}


The following images represent represent values of n = 1, 2, 4, 8, 16, 32, 64, 128.

For high n the sequence will finally result in this (it’s a fixed point of our function) Folds2d

To produce my works I prepared a few more things to have more variety of functions and semi-artistic look. These are:

• JWildfire wrapper for Processing (to get all Variations included there)
• Random formula generator with evaluation
• Randomization of parameters
• Noisy, gray canvas
• High resolution rendering
• Fixed point option
• Scaling / translating range
• Various folding options
• and probably few more

Final notes

I would like to THANK Jerome Herr for review of this article. Thank you very much man!

Don’t forget to visit his page: http://p5art.tumblr.com/

Questions/Errors? generateme.blog@gmail.com Story of one picture

I’ve got request to explain thought process behind following pictures (click to see two more):

I’ll try to recreate process from the scratch showing ready-to-use code progressively.

Inspiration

In addition to my own, a lot of ideas are taken from others’ works. It can be a specific palette, technique, composition, structure or texture. Then I recreate, rework, inspire myself by them to build my own dictionary and toolset. Still learning and searching.

This time it was picture by SuperColony:

Fortunately guys published source code and it was beginning for me.

Analysis

Code is quite simple and easy to understand but the result isn’t obvious. Let’s simplify code a little and draw one step. void setup() {
size(800,800);
smooth(8);
noFill();
stroke(233, 131, 45);
makeme();
}

float yoff = 0.0;
void makeme() {
background(23, 67, 88);
strokeWeight(2*noise(20,200));
beginShape();
float xoff2 = 0;
for (float x = 0; x <= width; x += 5) {
float y = map(noise(xoff2, yoff*20), 0, 1, 20, 800);
vertex(x, y);
xoff2 += random(0.01,0.05);
}
yoff += 0.01;
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
}

void draw() {}
void keyPressed() { saveFrame("f####.png"); }
void mousePressed() { makeme(); }


So, this is simply plot of noise function (line 16) using vertex(). If we draw 2000 steps we’ll see expected result. Originally authors chose 2 different colors (with alpha ranging from 5 to 20) and 3 positions of plot sets. void setup() {
size(800,800);
smooth(8);
noFill();
makeme();
}

float yoff = 0.0;
void makeme() {
background(23, 67, 88);
for(int iter=0;iter<2000;iter++) {
stroke(233, 131, 45, random(5,20));
strokeWeight(2*noise(20,200));
beginShape();
float xoff2 = 0;
for (float x = 0; x <= width; x += 5) {
float y = map(noise(xoff2, yoff*20.0), 0, 1, 20, 800);
vertex(x, y);
xoff2 += random(0.01,0.05);
}
yoff += 0.01;
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
}
}

void draw() {}
void keyPressed() { saveFrame("f####.png"); }
void mousePressed() { makeme(); }


Our picture represents traverse of noise field with almost the same x range (lines 15 and 19) and different y each line. We can see that we have some kind of periodicity. It leads to the conclusion that noise function is periodic. And this characteristic makes the effect. If you change line 15th to have xoff2 initialized randomly you can bypass this issue.

float xoff2 = random(1.0); Step 1 – Circle

Let’s make a circle from above wool-a-like structure, reset colors (will be decided later). To make circle just switch to polar coordinates. Random some attributes (xoff, yoff, ang step) to avoid regularity. Unfortunately nasty thing happens at angle=0.0. It’s due to fact that lines doesn’t join at the same point, traversing noise field I don’t return to the starting position. void setup() {
size(800,800);
smooth(8);
noFill();
makeme();
}

float yoff = random(1.0);
void makeme() {
background(20);
translate(400,400);
for(int iter=0;iter<3000;iter++) {
stroke(240, random(5.0,20.0));
strokeWeight(random(1.0));
float xoff = random(1.0);
beginShape();
for (float ang=0.0; ang<TWO_PI; ang+=random(TWO_PI/200.0)) {
float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
float x = r * cos(ang);
float y = r * sin(ang);
vertex(x, y);
xoff += random(0.01,0.05);
}
yoff += random(0.01,0.05);
endShape(CLOSE);
}
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }


Step 2 – Break the circle

To bypass this issue just make the gap wider. To make it change line 17. Better but sharp edges are not so pleasant. for (float ang=0.0; ang<0.8*TWO_PI; ang+=random(TWO_PI/200.0)) {


To fix it let’s random a little beginning and end of the arc. And provide variable shape_closed to make two options: one with closed circle and other with open.  void setup() {
size(800,800);
smooth(8);
noFill();
makeme();
}

void makeOptions() {
shape_closed = random(1)<0.5 ? true : false;
}

boolean shape_closed = true;
float yoff = random(1.0);
void makeme() {
background(20);
translate(400,400);
makeOptions(); // calculate some random parameters
for(int iter=0;iter<3000;iter++) {
stroke(240, random(5.0,20.0));
strokeWeight(random(1.0));
float xoff = random(1.0);
beginShape();
for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
float x = r * cos(ang);
float y = r * sin(ang);
vertex(x, y);
xoff += random(0.01,0.05);
}
yoff += random(0.01,0.05);
if(shape_closed) endShape(CLOSE); else endShape();
}
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }


Step 4 – Shift

This time let’s shift and make some distance of two halves. Make it optional (choosen randomly).  void setup() {
size(800,800);
smooth(8);
noFill();
makeme();
}

void makeOptions() {
shape_closed = random(1)<0.5 ? true : false;
shiftxy = (int)random(3);
}

int shiftxy = 0;
boolean shape_closed = true;
float yoff = random(1.0);
void makeme() {
background(20);
translate(400,400);
makeOptions(); // calculate some random parameters
for(int iter=0;iter<3000;iter++) {
stroke(240, random(5.0,20.0));
strokeWeight(random(1.0));
float xoff = random(1.0);
beginShape();
for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
float x = r * cos(ang);
float y = r * sin(ang);

if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
x += 20.0;
y += 20.0;
} else if(shiftxy > 0.0) {
x -= 20.0;
y -= 20.0;
}

vertex(x, y);
xoff += random(0.01,0.05);
}
yoff += random(0.01,0.05);
if(shape_closed) endShape(CLOSE); else endShape();
}
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }


Step 5 – Blocks

I like rectangular view or similar distortions so here I going to divide my picture to squares and inside each square exchange x and y axises. Of course let’s make it optional. Calculating abs() of x or y (line 46 and 47) stops exchanging x/y for negative values. One more issue: when doing blocks all points are moved by square size we need to adjust it (line 25)  void setup() {
size(800,800);
smooth(8);
noFill();
makeme();
}

void makeOptions() {
shape_closed = random(1)<0.5 ? true : false;
shiftxy = (int)random(3);
do_absx = random(1)<0.5 ? true : false;
do_absy = random(1)<0.5 ? true : false;
do_blocky = random(1)<0.75 ? true : false;
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
background(20);
if(do_blocky) translate(360,360); else translate(400,400);
makeOptions(); // calculate some random parameters
for(int iter=0;iter<3000;iter++) {
stroke(240, random(5.0,20.0));
strokeWeight(random(1.0));
float xoff = random(1.0);
beginShape();
for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {
float r = map(noise(xoff, yoff*5.0), 0, 1, 20, 400);
float x = r * cos(ang);
float y = r * sin(ang);

if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
x += 20.0;
y += 20.0;
} else if(shiftxy > 0.0) {
x -= 20.0;
y -= 20.0;
}

if(do_blocky) {
float sx = (do_absx?abs(x):x) % 40.0;
float sy = (do_absy?abs(y):y) % 40.0;
int xx = (int)(x/40.0);
int yy = (int)(y/40.0);
x = xx*40.0 + (40.0-sx);
y = yy*40.0 + (40.0-sy);
}

vertex(x, y);
xoff += random(0.01,0.05);
}
yoff += random(0.01,0.05);
if(shape_closed) endShape(CLOSE); else endShape();
}
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }


Step 6 – Colors

Usually I have a huge problem with color choice. So I use Colour Lovers or Paletton (or random generated) to create/choose palletes. Here Infinite Skies from Colour Lovers is used. Let’s add one more option with 1% probablity: make filament orange/gold and put it in the middle of the ring. Some variety of main color is added also. void setup() {
size(800,800);
smooth(8);
noFill();
makeme();
}

void makeOptions() {
shape_closed = random(1)<0.5 ? true : false;
shiftxy = (int)random(3);
do_absx = random(1)<0.5 ? true : false;
do_absy = random(1)<0.5 ? true : false;
do_blocky = random(1)<0.75 ? true : false;
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
background(37,49,63);
if(do_blocky) translate(360,360); else translate(400,400);
makeOptions(); // calculate some random parameters

for(int iter=0;iter<3000;iter++) {

boolean gold_line = false;
if(random(1)>0.01) {
stroke(lerpColor(color(255,241,170),color(255,255,250),random(1)), random(5.0,20.0));
strokeWeight(random(1.0));
} else {
gold_line = true;
stroke(253,155,87,random(20,60));
strokeWeight(random(1.0,1.5));
}

float xoff = random(1.0);
beginShape();
for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {

float ll,rr;
if(gold_line) { ll=120; rr=320; } else { ll=20; rr=400; }

float r = map(noise(xoff, yoff*5.0), 0, 1, ll, rr);
float x = r * cos(ang);
float y = r * sin(ang);

if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
x += 20.0;
y += 20.0;
} else if(shiftxy > 0.0) {
x -= 20.0;
y -= 20.0;
}

if(do_blocky) {
float sx = (do_absx?abs(x):x) % 40.0;
float sy = (do_absy?abs(y):y) % 40.0;
int xx = (int)(x/40.0);
int yy = (int)(y/40.0);
x = xx*40.0 + (40.0-sx);
y = yy*40.0 + (40.0-sy);
}

vertex(x, y);
xoff += random(0.01,0.05);
}
yoff += random(0.01,0.05);
if(shape_closed) endShape(CLOSE); else endShape();
}
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }


Step 9 – Background

The last thing remains: background. It’s grid made of lines with variable intensity. Grid is rotated 45 degrees. void setup() {
size(800,800);
smooth(8);
noFill();
makeme();
}

void makeOptions() {
shape_closed = random(1)<0.5 ? true : false;
shiftxy = (int)random(3);
do_absx = random(1)<0.5 ? true : false;
do_absy = random(1)<0.5 ? true : false;
do_blocky = random(1)<0.75 ? true : false;
}

// background grid
void makeBackground() {
background(37,49,63);
strokeWeight(1);
pushMatrix();
translate(400,400);
rotate(HALF_PI/2.0);
for(float x=-600;x<600;x+=random(5)) {
stroke(90,104,115,random(5,15));
line(-600,x,600,x);
line(x,-600,x,600);
}
popMatrix();
}

boolean do_absx=false;
boolean do_absy=false;
boolean do_blocky=true;
int shiftxy = 0;
boolean shape_closed = true;

float yoff = random(1.0);
void makeme() {
makeBackground();
pushMatrix();
if(do_blocky) translate(360,360); else translate(400,400);
makeOptions(); // calculate some random parameters

for(int iter=0;iter<3000;iter++) {

// gold line
boolean gold_line = false;
if(random(1)>0.01) {
stroke(lerpColor(color(255,241,170),color(255,255,250),random(1)), random(5.0,20.0));
strokeWeight(random(1.0));
} else {
gold_line = true;
stroke(253,155,87,random(20,60));
strokeWeight(random(1.0,1.5));
}

float xoff = random(1.0);
beginShape();
for (float ang=random(0.2); ang<random(0.8,0.9)*TWO_PI; ang+=random(TWO_PI/200.0)) {

float ll,rr;
if(gold_line) { ll=120; rr=320; } else { ll=20; rr=400; }

float r = map(noise(xoff, yoff*5.0), 0, 1, ll, rr);
float x = r * cos(ang);
float y = r * sin(ang);

// shift
if( (shiftxy == 1 && x>0.0) || (shiftxy == 2 && y>0.0)) {
x += 20.0;
y += 20.0;
} else if(shiftxy > 0.0) {
x -= 20.0;
y -= 20.0;
}

// rectangles
if(do_blocky) {
float sx = (do_absx?abs(x):x) % 40.0;
float sy = (do_absy?abs(y):y) % 40.0;
int xx = (int)(x/40.0);
int yy = (int)(y/40.0);
x = xx*40.0 + (40.0-sx);
y = yy*40.0 + (40.0-sy);
}

vertex(x, y);
xoff += random(0.01,0.05);
}
yoff += random(0.01,0.05);
if(shape_closed) endShape(CLOSE); else endShape();
}
popMatrix();
}

void draw() {}
void keyPressed() { saveFrame("f####.jpg"); }
void mousePressed() { makeme(); }


Finished

That’s all. There is difference in colors of picture on my Tumblr and here. Reason is different algorithm of choosing color used (line 49). For Tumblr it’s exactly this:

if(random(1)<0.2)
stroke(255,241,170,random(5,20));
else
stroke(255,255,250,random(5,20));


If you want more explanation or clarification or another tutorial, let me know writing an e-mail: generateme.blog@gmail.com