I love how mathematics can be used to represent phenomenons that we can observe in nature. And when you combined this with programming you can create beautiful patterns. For this project, I was inspired by succulents and the arrangements that the leaves form when they grow, this is referred as phyllotaxis. This pattern can be represented using a spiral and the golden angle. I made a Processing script (p5js) based on this. Multiples succulents are generated by the script. Their layout and size are computed using an algorithm used to randomly fill space (based on the work by Paul Bourke and John Shier).
Here is one example of the images generated.
And here is a sketch with which you can interact: move your mouse over it and the succulents will grow!
var sketch = function (p) {
// parameters for the function used to locate all succulents
var circles = [];
var maxRadius = 125;
var minRadius = 35.0;
var gap = 0;
var c = 1.15; // must be larger than 1
var n = 12;
var a0;
var numIter;
spirals = [];
// parameters for the spirals (succulents)
var petals = 0; // 0: convexe, 1: concave
var z = 1.015;
// stroke weight: initial and stroke factor
var s0 = 0.1;
var sf = 0.02;
// stroke color the radius is a ratio of final radiux
var rcol1 = 0.2;
var rcol2 = 0.8;
var init = 0;
p.setup = function () {
p.createCanvas(880, 660);
p.background('#ffffff');
// parameters for the function used to locate all succulents
// initial area to have a1 have maxRadius, a1= a0*pow(n+1,-c)
a0 = p.PI*p.sq(maxRadius)/p.pow(n+1,-c);
// compute the location of all succulents
// num of iterations to have last circles radius minRadius
// reference: Paul Bourke: http://paulbourke.net/texture_colour/randomtile/
numIter = parseInt(p.pow(a0/(p.PI*p.sq(minRadius)), 1/c))-n;
for (var i = 1; i <= numIter; i++) {
var an = a0*p.pow(n+i,-c);
var prad = p.sqrt(an/p.PI);
var px = 0;
var py = 0;
var tries = 0;
var found = false;
while (!found & tries < 1000 ) {
px = p.random(p.width);
py = p.random(p.height);
// check that it is not overlapping previous circle
if (!Overlapping(px, py, prad)) {
found = true;
}
tries++;
}
if (found) {
circles.push(new HCircle(px, py, prad));
}
}
// for all the locations, calculate the parameters of the spiral for the succulents
for (var i=0; i<circles.length; i++) {
spirals.push(new HSpiral(circles[i]));
}
}
p.draw = function () {
for (var i=0; i<circles.length; i++) {
var s = spirals[i];
s.draw();
}
init = 1;
}
p.mouseMoved = function () {
if (init==1) {
// find if mouse on a succulent, if so make it grow
for (var i=0; i<circles.length; i++) {
var c = circles[i];
if (Overlap(c.x, c.y, c.rad, p.mouseX, p.mouseY, 0.0)) {
spirals[i].grow();
// can only be overlapping one circle
break;
}
}
}
}
// Separated(), from Paul Bourke: http://paulbourke.net/texture_colour/randomtile/
function Overlapping(px, py, prad)
{
for (var i=0; i<circles.length; i++) {
var c = circles[i];
if (Overlap(c.x, c.y, c.rad, px, py, prad)) {
return true;
}
}
return false;
}
function Overlap(c1x, c1y, c1rad, c2x, c2y, c2rad) {
var dx,dy;
dx = c1x - c2x;
dy = c1y - c2y;
// adding a gap so they don't touch each other
var radiusSum = c1rad + c2rad + gap;
var centreDistanceSquared = dx*dx + dy*dy;
// circles overlapping if distance between center less than sum of the two radii
if (centreDistanceSquared < radiusSum*radiusSum) {
return true;
}
return false;
}
function HCircle(x, y, rad) {
this.x = x;
this.y = y;
this.rad = rad;
}
function HSpiral(circle) {
this.r0 = p.random(5, 10);
this.cx = circle.x;
this.cy = circle.y;
this.radius = circle.rad;
this.nvertex = parseInt(p.log(this.radius/this.r0)/p.log(z));
var min_nvertex = parseInt(p.log(minRadius/this.r0)/p.log(z));
this.ncurrent = 0;
this.nstep = parseInt(p.random(20,50));
if (this.nstep < min_nvertex) {
this.nstep = min_nvertex;
} else if (this.nstep > this.nvertex) {
this.nstep = this.nvertex;
}
var golden_angle = p.PI*(3- p.sqrt(5)); // golden angle
var theta = p.radians(p.random(0, 45)); //random rotation of the succulent
this.xn = [];
this.yn = [];
this.rn = [];
for (var i = 0; i < this.nvertex; i++) {
var angle = (i*golden_angle + theta)%p.TWO_PI;
this.rn[i] = this.r0*p.pow(z,i); // spiral lattice
this.xn[i] = this.cx + p.cos(angle) * this.rn[i];
this.yn[i] = this.cy + p.sin(angle) * this.rn[i];
}
}
HSpiral.prototype.draw = function() {
for (var i = this.ncurrent; i < this.nstep; i++) {
p.noFill();
p.strokeWeight(sf*this.rn[i] + s0);
var s_col;
p.colorMode(p.RGB);
var col1 = p.color(182,219,25); //#B6DB19
var col2 = p.color(58,9,24); //#3A0918
var rad_ratio = this.rn[i]/this.radius;
if (rad_ratio < rcol1) {
s_col = col1;
} else if (rad_ratio > rcol2) {
s_col = col2;
} else {
var amt =(rad_ratio - rcol1)/(rcol2-rcol1);
s_col = p.lerpColor(col1, col2, amt);
}
p.stroke(s_col);
if (petals == 0) {
p.curveTightness(-0.0);
p.beginShape();
var f = 0.25;
if (i>=8) {
p.curveVertex(this.xn[i-8]+f*(this.cx-this.xn[i-8]), this.yn[i-8]+f*(this.cy-this.yn[i-8]));
p.curveVertex(this.xn[i-8], this.yn[i-8]);
} else {
p.curveVertex(this.cx, this.cy);
p.curveVertex(this.cx, this.cy);
}
p.curveVertex(this.xn[i], this.yn[i]);
if (i>=5) {
p.curveVertex(this.xn[i-5], this.yn[i-5]);
p.curveVertex(this.xn[i-5]+f*(this.cx-this.xn[i-5]), this.yn[i-5]+f*(this.cy-this.yn[i-5]));
} else {
p.curveVertex(this.cx, this.cy);
p.curveVertex(this.cx, this.cy);
}
p.endShape();
} else {
p.curveTightness(0.5);
p.beginShape();
p.curveVertex(this.cx, this.cy);
if (i>=8) {
p.curveVertex(this.xn[i-8], this.yn[i-8]);
} else {
p.curveVertex(this.cx, this.cy);
}
p.curveVertex(this.xn[i], this.yn[i]);
p.curveVertex(this.xn[i]+0.5*(this.xn[i]-this.cx), this.yn[i]+0.5*(this.yn[i]-this.cy));
p.endShape();
p.beginShape();
p.curveVertex(this.xn[i]+0.5*(this.xn[i]-this.cx), this.yn[i]+0.5*(this.yn[i]-this.cy));
p.curveVertex(this.xn[i], this.yn[i]);
if (i>=5) {
p.curveVertex(this.xn[i-5], this.yn[i-5]);
} else {
p.curveVertex(this.cx, this.cy);
}
p.curveVertex(this.cx, this.cy);
p.endShape();
}
}
this.ncurrent = this.nstep;
}
HSpiral.prototype.grow = function() {
if (this.ncurrent < this.nvertex) {
this.nstep = this.ncurrent + 1;
this.draw();
}
}
};
new p5(sketch);
If your interested by patterns in nature, here is a fun series of videos by Vi Hart: Doodling in Math: Spirals, Fibonacci, and Being a Plant.