Grow your own iris
by tsulej
(To be honest I have no idea how to call this organic object).
In this article I describe how to achieve such effect. Generally speaking it’s really simple particle system with also simple attract/repel model based on love/hate attributes. This is step-by-step tutorial.
Particles
Let’s build first our basis – particles. What we need are:
- current position
- current velocity
- age – we want them to die sometimes
- color
- id – to distinguish them
And of course something to init, update and draw. First we draw them on the distorted circle. Behaviour (interactions and movement) will be the next step and will be calculated externally.
// number of particles final static int N = 500; // colors (some of them taken from http://www.colourlovers.com/pattern/5526827/bleak final static color[] cols = { #FF8A00, #FFD200, #749D9D, #FCF5B3, #B39500, #272429 }; // collection of all particles ArrayList particles = new ArrayList(N); void setup() { size(800,800); noFill(); smooth(8); strokeWeight(2.0); // exaggerate a little bit for a while background(0,0,20); // initialize particles for(int i=0;i<N;i++) { particles.add( new Particle(i) ); } } void draw() { for(Particle p : particles) { p.draw(); } } class Particle { // position float x,y; // step float dx, dy; // id int id; // life length float age; // some random color color c = cols[(int)random(cols.length)]; void reset() { // distribute initial point on the ring, more near the outer edge, distorted float angle = random(TWO_PI); float r = 5.0*randomGaussian() + (width/2-100)*(1.0-pow(random(1.0),7.0)); x = cos(angle)*r; y = sin(angle)*r; // set random age age = (int)random(100,1000); } void draw() { stroke(c); point(x+width/2,y+height/2); } // update position with externally calculated speed // check also age void update() { x += dx; y += dy; if(--age < 0) reset(); } Particle(int i) { id = i; reset(); } }
Here is the initial result
Behaviour
The most important part, how we move them. Here is the only rule.
For every particle pair: if they love each other attract, reppel otherwise. That’s all.
Ok, detail is: what can be love/hate factor?
Basic love factor
The most obvious is distance. And this one is used here. Formula is
- If distance is larger than 2.0 attract with love amount
- Repel otherwise
Let’s see this.
// number of particles final static int N = 500; // colors (some of them taken from http://www.colourlovers.com/pattern/5526827/bleak final static color[] cols = { #FF8A00, #FFD200, #749D9D, #FCF5B3, #B39500, #272429 }; // collection of all particles ArrayList particles = new ArrayList(N); void setup() { size(800, 800); noFill(); smooth(8); strokeWeight(0.7); background(0, 0, 20); // initialize particles for (int i=0; i<N; i++) { particles.add( new Particle(i) ); } } void draw() { for (Particle p : particles) { // love/hate vector float lovex = 0.0; float lovey = 0.0; for (Particle o : particles) { // do not compare with yourself if (p.id != o.id) { // calculate vector to get distance and direction PVector v = new PVector(o.x-p.x, o.y-p.y); float distance = v.mag(); float angle = v.heading(); // love! float love = 1.0 / distance; // or hate... if (distance<2.0) love = -love; // not too fast! love *= 0.3; // update love vector lovex += love * cos(angle); lovey += love * sin(angle); } // calculated love vector will be our speed in resultant direction p.dx = lovex; p.dy = lovey; } } // update and draw for (Particle p : particles) { p.update(); p.draw(); } } class Particle { // position float x, y; // velocity float dx, dy; // id int id; // life length float age; // some random color color c = cols[(int)random(cols.length)]; void reset() { // distribute initial point on the ring, more near the outer edge, distorted float angle = random(TWO_PI); float r = 5.0*randomGaussian() + (width/2-100)*(1.0-pow(random(1.0), 7.0)); x = cos(angle)*r; y = sin(angle)*r; // set random age age = (int)random(100, 1000); } void draw() { stroke(c,50); point(x+width/2, y+height/2); } // update position with externally calculated speed // check also age void update() { x += dx; y += dy; if (--age < 0) reset(); } Particle(int i) { id = i; reset(); } }
Nice, we have branching and organic look already. Our particles attract mostly, repel only when distance is low.
Attribute love factor
Now let’s introduce another attribute of our particle. It will be nonnegative floating point value . Let’s call it mood. Rule:
Our particles love each other when they have similar mood. Hate otherwise.
That means that we have to calculate similarity factor between moods to get love factor. The similarity should be between . Where means 100% love and means 100% hate. Our factor can be calculated with such formula (or something like that):
One note: don’t be strict with similarity factor, some unbalance between love and hate gives different results.
What can be mood?
- inital angle
- noise value from current position and time (mood changes while particle moves and time goes on)
- some vector field
- etc…
The code for second case as follows
// number of particles final static int N = 500; // colors (some of them taken from http://www.colourlovers.com/pattern/5526827/bleak final static color[] cols = { #FF8A00, #FFD200, #749D9D, #FCF5B3, #B39500, #272429 }; // collection of all particles ArrayList particles = new ArrayList(N); void setup() { size(800, 800); noFill(); smooth(8); strokeWeight(0.7); background(0, 0, 20); // initialize particles for (int i=0; i<N; i++) { particles.add( new Particle(i) ); } } float time = 0.0; void draw() { for (Particle p : particles) { // love/hate vector float lovex = 0.0; float lovey = 0.0; for (Particle o : particles) { // do not compare with yourself if (p.id != o.id) { // calculate vector to get distance and direction PVector v = new PVector(o.x-p.x, o.y-p.y); float distance = v.mag(); float angle = v.heading(); // love! float love = 1.0 / distance; // or hate... if (distance<2.0) love = -love; // mood factor love *= p.moodSimilarity(o); // not too fast! love *= 0.5; // update love vector lovex += love * cos(angle); lovey += love * sin(angle); } // calculated love vector will be our speed in resultant direction p.dx = lovex; p.dy = lovey; } } // update and draw for (Particle p : particles) { p.update(); p.draw(); } time += 0.001; } class Particle { // position float x, y; // velocity float dx, dy; // id int id; // life length float age; // some random color color c = cols[(int)random(cols.length)]; // mood factor float mood; void reset() { // distribute initial point on the ring, more near the outer edge, distorted float angle = random(TWO_PI); float r = 5.0*randomGaussian() + (width/2-100)*(1.0-pow(random(1.0), 7.0)); x = cos(angle)*r; y = sin(angle)*r; // set random age age = (int)random(100, 2000); calcMood(); } void draw() { stroke(c,50); point(x+width/2, y+height/2); } // update position with externally calculated speed // check also age void update() { if(--age < 0) { reset(); } else { x += dx; y += dy; calcMood(); } } Particle(int i) { id = i; reset(); } // compare moods float moodSimilarity(Particle p) { return 1.0-abs(p.mood-this.mood); } // calculate current mood private void calcMood() { mood = sin(noise(x/10.0,y/10.0,time)*TWO_PI); } }
What’s next?
You can mess with several factors in this small model, eg:
- number of particles (beware, it’s )
- particle size, colors, alpha
- dinstance love/hate formulas (eg. hate distance)
- love amount (“not too fast!” comment)
- time speed
- initial position and age
- moodSimilarity and calcMood functions
Here are some examples:
Influences
Here are some links which guided me to this concept:
- http://www.complexification.net/gallery/machines/happyPlace/ – love/hate concept
- http://inconvergent.net/#writing – growth systems
- http://julienleonard.com/tutorials.html – growth systems
- https://ocw.mit.edu/courses/mathematics/18-177-universal-random-structures-in-2d-fall-2015/index.htm – random growth
Left to explore more deeply: random growth guided by vector field.
Enjoy!
UPDATE: Reddit elpepe5 user worked on this topic a little bit and created really nice results. Found the on GITHUB
thank you so much for sharing ! would you, please, go more in depth with the last one : random growth guided by vector field ? i mean…would you ? please ? thanks a lot anyway.
LikeLiked by 1 person
Sure. I have a concept to play with some random growth algorithms, like Browniam Motion, Bessel/Levy/Poisson processes, random tree growth etc.. but I want to make them not purely random. I want to use information from perlin noise field or some vector field to adjust movement. Like I do in force based on perlin noise in above examples. But first I have to digest all of this maths 🙂
LikeLike
I’m delighted to see what new aesthetics you will unveil. I also would like to thank you again for sharing your code and processes; you therefore allow me to delve into new fields that would otherwise stay far beyond my scope ! Add a donate me button at some point, you deserve that beer 🙂 !
all the best.
LikeLiked by 1 person
Thank you! All my works are free. Enjoy!
LikeLike
Hi! Thanks a lot for sharing! Great tutorial – very inspirational!
The only thing I just didn’t get how the age works.
LikeLike
Thanks! Age is number of frames the particle lives (100 to 1000, chosen randomly). Every step age is decremented by 1 and when reaches zero particle is recreated again.
The main reason is to have shorter paths which don’t allow to stick particle to any position/region too long.
I hope it helps.
LikeLike
Thanks a lot tsulej for this tutorial! I’m learning a lot thanks to you and I’m extremely grateful.
Just a quick note for those who are using the last version of Processing and encounter a bug, you should replace :
ArrayList particles = new ArrayList(N);
by
ArrayList particles = new ArrayList(N);
LikeLike
Woups I can’t edit my previous comment, but I meant replace :
ArrayList particles = new ArrayList(N);
by
ArrayList particles = new ArrayList(N);
LikeLike
Actually it is just WordPress which doesn’t handle Processing syntax… So the line you should use is:
ArrayList ‘left angle bracket’ Particle ‘rightanglebracket’ particles = new ArrayList ‘leftanglebracket’ Particle ‘rightanglebracket’ (N);
Where ‘left angle bracket’ and ‘rightanglebracket’ must be replaced by the corresponding keys.
I can’t preview this comment before posting it so I’m not sure it’s going the appear the way I want it to, but I hope it’s going to work this time. Please don’t hesitate to delete/edit my previous comments, I can’t do it myself…
LikeLike
Yes, there should be angle brackets with Particle class… I fight with formatting constantly. Thanks for spotting this.
LikeLike