nylki_EllipticalLife/EllipticalLife.pde

/*
AUTHOR:
Tom Brewe

LICENCE:
GPL-3.0
http://www.gnu.org/licenses/gpl-3.0

Description:

This program is an automation inspired by Cornway's Game of Life from 1970.
Groups of colored circles will try to assimilate the other circles around,
by 'eating' them, destryoing them, simply bouncing off, or creating new colors
when colliding.
It also featuresf child<->parent interaction and collision detection.
If childs move too far away from their parents, they will be dragged by their
parents into their direction.

You can also add own balls for experimentation
by simply clicking and holding the left mouse button.
Your new circle-species will now try to conquer the other ones :)

This is a Processing/Java program. compile and run with the processing IDE.
Or download the built app from my github repository at:
https://github.com/nylki





*/
import java.util.LinkedList;

int steps = 300;
int maxCircles = 300;

//the distance to which children can move autonom.
//if more far away than this var, parent drags children
int maxChildParentDistance = 250;

LinkedList<Kreis> allCircles;
Kreis k1, k2, kreisCreatedAfterMousePressed;
int groupsCount=0;



void setup() {
  size(1300, 800, P2D);
  smooth(8);
  colorMode(HSB, 1000, 1000, 1000, 100);
  frameRate(50);
  noStroke();
  allCircles = new LinkedList();
  float randomHue = random(0,1000);
  k1 = new Kreis(random(width), random(height), 1, 50, null);
  k2 = new Kreis(random(width), random(height), 1, 50, null);
  k1.c = color(randomHue, random(500,1000), random(600,1000));
  k1.c = color(1000 - randomHue, random(400,1000), random(600,1000)); //making sure, counterpart colors
  allCircles.add(k1);
  allCircles.add(k2);


  k1.direction.set(k2.location);
  k1.generateChildren(4);

  k2.direction.set(k1.location);
  k2.generateChildren(4);
}

//noch bedingungen für verkleinerung eines kreises später
public class Kreis {
  int groupID;
  float speed, durchmesser;
  color c;

  //ToDo: using PVector instead of single floats for better readability and performance (vector operations)

  PVector direction, distance, location;

  LinkedList<Kreis> childs;
  Kreis parent;
  boolean influencedByParent = false;

  void generateChildren(int amount) {
    for (int i=1;i<= amount;i++) {
      Kreis neuerKreis = new Kreis(this.location.x + random(60), this.location.y + random(60), random(1, 4), (this.durchmesser/1.5), this);
      this.childs.add(neuerKreis); 
      allCircles.add(neuerKreis);
    }
  }

  void removeKreis(Kreis k) { 
    for (Kreis c : k.childs)
      c.parent = c; 

    k.parent.childs.remove(k); 
    allCircles.remove(k);

    //set the new parent for the childs
  }




  Kreis(float initialX, float initialY, float initialSpeed, float r, Kreis p) {
    super();
    this.location = new PVector(initialX, initialY, 0);
    this.speed = initialSpeed;

    this.direction = new PVector(random(width-50), random(height-50), 0);
    this.durchmesser = r;
    this.parent = p;
    if (p == null)
      this.parent = this;


    //if object has no parent
    //generate new color for itself and its children
    //and set a new groupID for identification
    if (parent == this) {
      this.c = color(random(1000), random(500, 1000), random(600, 1000), 100);
      groupsCount++;
      this.groupID = groupsCount;
    }
    else {
      this.c = parent.c;
      this.groupID = parent.groupID;
    }

    childs = new LinkedList();
  }

  boolean collision(Kreis k1, Kreis k2) {
    return (PVector.dist(k1.location, k2.location) < (k1.durchmesser + k2.durchmesser)/2);
  }




  void update() {
    if (this.parent != this)
      this.c = parent.c;


    //if kreis is too small, remove it
    if (this.durchmesser < 4) 
      removeKreis(this);

    //if kreis too big, let it explode into 3 colors (1 including the original
    if (this.durchmesser > (width + height)/7) {
      Kreis newK1 = new Kreis(this.location.x + random(60), this.location.y + random(60), 1, 30, this);
      Kreis newK2 = new Kreis(this.location.x + random(60), this.location.y + random(60), 1, 30, null);
      Kreis newK3 = new Kreis(this.location.x + random(60), this.location.y + random(60), 1, 30, null);
      allCircles.add(newK1); 
      allCircles.add(newK2); 
      allCircles.add(newK3); 
      newK1.generateChildren(2); 
      newK2.generateChildren(4); 
      newK3.generateChildren(5);
      removeKreis(this);
    }

    //changing parents if durchmesser changes
    if (this.durchmesser > this.parent.durchmesser) {
      //the parent of the parent
      for (int i=0; i<this.parent.childs.size(); i++) {
        Kreis child = this.parent.childs.get(i);
        child.parent = this;
      }
      this.parent.parent = this;
      this.parent = this;
    }

    stroke(this.c);
    strokeWeight(0.4);


    if (random(1700) < 2) {
      if (allCircles.size() < maxCircles)
        generateChildren((int)random(1, 2.1));
    }

    for (int i = 0; i < allCircles.size(); i++) {
      Kreis k = allCircles.get(i);

      //ueberpruefe Kollision
      // kollidiere nur mit anderen Gruppen/Farben
      //bei entfernungen als naechstes noch kinder und eltern überpruefen und fixen, weil verbinden zerstoert werden

      if ( collision(k, this)) {
        if ( !(k == this) && !(this.groupID == k.groupID)) {

          k.direction.set(random(width), random(height), 0);
          this.direction.cross(k.direction );
          Kreis smallerK = (k.durchmesser < this.durchmesser) ? k : this;
          Kreis biggerK = (k.durchmesser >= this.durchmesser) ? k : this;
          smallerK.durchmesser = smallerK.durchmesser - smallerK.durchmesser/60;
          biggerK.durchmesser = biggerK.durchmesser + smallerK.durchmesser/60; 

          //chance to destroy the smaller one or assimilate the smaller one
          int r = (int)random(1, 1100);
          if (r < 1000 && !(smallerK.parent == smallerK && biggerK.parent == biggerK) ) {

            //add smaller circle to the bigger circles list and groupID etc
            if (r > 860 ) {
              smallerK.groupID = biggerK.groupID;
              smallerK.c = biggerK.c;
              smallerK.parent = biggerK;
              if (smallerK.parent != smallerK) {
                smallerK.parent.childs.remove(smallerK);
                biggerK.childs.add(smallerK);
              }
              //set the new parent for the childs
              /*for (int j=0; j<k.childs.size(); j++) {
               Kreis child = k.childs.get(j);
               k.parent.childs.add(child);
               child.parent = k.parent;
               }*/
            }
            //else the bigger one eats the smaller one
            else if (r > 790) {
              biggerK.durchmesser = biggerK.durchmesser + smallerK.durchmesser/2.5;
              removeKreis(smallerK);
              //sonst erstelle neuen kreis/neue gruppe
            } 
            else if (r > 760 && allCircles.size() < 150 && abs(k.durchmesser - this.durchmesser) < 30) {
              Kreis neuerKreis = new Kreis(biggerK.location.x + random(20), biggerK.location.y + random(20), 1, (smallerK.durchmesser + biggerK.durchmesser)/2, null);
              Kreis neuerKreis2 = new Kreis(biggerK.location.x + random(20), biggerK.location.y + random(20), 1, (smallerK.durchmesser + biggerK.durchmesser)/3, null);
              allCircles.add(neuerKreis);
              removeKreis(biggerK);
              neuerKreis.generateChildren((int)random(1, 3));
              neuerKreis2.generateChildren((int)random(1, 3));
            }
            else if(r > 750){
              removeKreis(k);
            }
          }
           //continue;
        }
      }
    }
    //check if childs distance to parent is too far away
    //if so, restrict the current object
    //improvement: might be checked only for each child, for better speed/scalability
    //atm we have two loops. Also might use point objects
    this.influencedByParent = (PVector.dist(this.location, this.parent.location) > maxChildParentDistance) ? true : false;

    if ( this.influencedByParent == true) {
      //draw extra strong line to indicate distance
      stroke(this.c);
      strokeWeight(2);

      this.direction.set(this.parent.location);
      this.direction.cross(this.location);
      this.speed = 0.2;//  = this.parent + random(-20, 20);
      //this.direction.y = 500;//this.direction.y + random(-20, 20);
      //this.direction.add(random(-20, 20),random(-20, 20),0);
    } 
    else
    {
      stroke(this.c);
      strokeWeight(0.3);
    }    
    //draw line to all childs
    if (!(this == this.parent) && !(this.parent.childs.size() == 0))
      line(this.location.x, this.location.y, this.parent.location.x, this.parent.location.y);
    noStroke();

    //check wether going minus or positiv
    //get the distance to use for the position increment
    distance = PVector.sub(this.direction, this.location);

    //if circle is very close to its destination set its coordinates to its destination
    if ( PVector.dist(this.location, this.direction) <= 20.0) {
      //this.direction.set(this.location);
      this.direction.set(random(width), random(height), 0);
      this.speed = 0.3;
    } 
    distance = PVector.sub(this.direction, this.location);
    //move circle

    //this.x = (this.x + (speed*(distX/steps)) ); etc.
    PVector newLocationPart = new PVector();
    newLocationPart.set(this.distance);
    newLocationPart.div(steps);
    newLocationPart.mult(speed);
    this.location.add(newLocationPart);
    //this.location.x += random(-0.7, 0.7) * noise( this.location.x,  this.location.y);
    //this.location.y += random(-0.7, 0.7) * noise( this.location.x,  this.location.y);
    
  }
}

void draw() {
  //System.out.println(allCircles.size());
  background(250);
  //lights();

  for (int i = 0; i < allCircles.size(); i++) {
    Kreis k = allCircles.get(i);
    k.update();
    //color the object
    fill(k.c);
    //fill(this.x,this.y,this.z);

    ellipse(k.location.x, k.location.y, k.durchmesser, k.durchmesser);
    /*pushMatrix();
     translate(k.location.x, k.location.y, 0);
     
     sphere(k.durchmesser); 
     popMatrix();*/
  }

  if (mousePressed == true && kreisCreatedAfterMousePressed != null) {
    kreisCreatedAfterMousePressed.durchmesser = kreisCreatedAfterMousePressed.durchmesser+1;
    kreisCreatedAfterMousePressed.location.set((float)mouseX, (float)mouseY, 0);
  }
}

void mousePressed() {

  kreisCreatedAfterMousePressed = new Kreis(mouseX, mouseY, 0, 5, null);
  allCircles.add(kreisCreatedAfterMousePressed);
}



void mouseReleased() {
  kreisCreatedAfterMousePressed.speed = random(1, 3);
  kreisCreatedAfterMousePressed = null;
}

