Grow your own iris

by tsulej

Iris

(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

000032210713

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 love=\frac{1}{distance}

  1. If distance is larger than 2.0 attract with love amount
  2. 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();
  }
}

00004fab0781

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 (-1, 1). Where 1 means 100% love and -1 means 100% hate. Our factor can be calculated with such formula (or something like that):

(abs(mood_1 - mood_2) - 0.5 * mood_{max})*\frac{2.0}{mood_{max}}

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);
  }
 }

000054671590

What’s next?

You can mess with several factors in this small model, eg:

  • number of particles (beware, it’s O(n^2))
  • 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:

Left to explore more deeply: random growth guided by vector field.

Enjoy!

00008c5c01298

UPDATE: Reddit elpepe5 user worked on this topic a little bit and created really nice results. Found the on GITHUB