Looks good @Kirupa! I took a stab at recreating this as well, wanted to see if one could try to get a good head start on the initial positions by treating it as a phyllotaxis without any randomness associated to it.
Then with some simple particle simulation, we could try to get the disturbance by having the particles move towards the origin, collide with an invisible circle in the center (and with each other).
I bet with some tweaking of parameters one could recreate this. I just sketched this out in Processing-python mode.
def setup():
global n,c,colorOptions,particles
size(600,600)
background(255)
noStroke()
particles = []
#Colorpicked hex values from image
colorOptions = ['#836871',
'#6d1a34',
'#af2944',
'#e94442',
'#e8af58',
'#ac7ead',
'#888f9f',
'#388c8c',
'#836871'][::-1]
#Setup initial phyllotaxis positions
c = 4
angle=137.5
for n in range(1000,3500):
a = n * radians(angle)
r = c * sqrt(n)
newPoint = PVector(r*cos(a),
r*sin(a))
newColor = lerpColors((a%TWO_PI)/TWO_PI,colorOptions)
particles.append(particle(newPoint,newColor))
def draw():
background(255)
#Move origin to center
translate(width/2,height/2)
for p in particles:
#Check for collisions (inefficent)
for other in particles:
if(p!=other):
d = PVector.dist(p.pos,other.pos)
if(d <= p.r + other.r + 4):
p.moveAwayFrom(other)
#Display and move particles
p.display()
p.move()
def lerpColors(amt, colorOptions):
"""Lerp function that takes an amount between 0 and 1,
and a list of colors and returns the appropriate
interpolation"""
if (len(colorOptions)==1): return colorOptions[0]
spacing = 1.0/(len(colorOptions)-1)
lhs = floor(amt/spacing)
rhs = ceil(amt/spacing)
try:
return lerpColor(colorOptions[lhs],
colorOptions[rhs],
amt%spacing/spacing)
except:
return lerpColor(colorOptions[constrain(lhs, 0, len(colorOptions)-2)],
colorOptions[constrain(rhs, 1, len(colorOptions)-1)],
amt);
class particle(object):
def __init__(self,pos,someColor):
self.pos = pos
self.someColor = someColor
self.r = 2.5
def move(self):
"""Move towards origin"""
d = PVector.dist(self.pos,PVector(0,0))
centerRadius = 100
if(d <= self.r + centerRadius):
direction = PVector.sub(PVector(0,0,0),self.pos).normalize().mult(-0.5)
else:
direction = PVector.sub(PVector(0,0,0),self.pos).normalize().mult(0.5)
self.pos.add(direction)
def moveAwayFrom(self,other):
direction = PVector.sub(other.pos,self.pos).normalize().mult(-1)
self.pos.add(direction)
def display(self):
noStroke()
fill(self.someColor)
ellipse(self.pos.x,self.pos.y,self.r*2,self.r*2)
Code has n**2 comparisons from collision detection, hence low fps. It would be better to test with a more optimized collision detection ds or library that implements it well.