home processing download documents tutorial python tutorial gallery source about
 Tutorials (back to the list of tutorials)

Particle-based Branching Algorithm with Spring Force(requires iGeo version 0.9.1.5)

     Line Agent with Particle and Spring

This page shows branching algorithm with particle and spring integrating particle-based agents and reproduction-based agents.

The first code below has a line agent class LineAgent which reproduces a child line agent in a specific direction but the agent class inherits IParticle class instead of inheriting IAgent class in the previous line agent classes like the ones in this page. In the update method, the agent instantiates ISpringLine which is a line with spring force to maintain the distance of two end point particles instead of a simple line in the previous line agents.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  new LineAgent(new IParticle(0,0,0).hide().fric(0.2),
		new IVec(0,2,0));
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{
  LineAgent parent;
  IVec dir;
  
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }

  void update(){
    if(time()==0 && alive()){
      if(parent!=null && parent.alive()){
        //line between this and parent with spring force 100
        new ISpringLine(this,parent,100).clr(0); 
      }
      new LineAgent(this, dir);
    }
  }
}

The below codes shows the response of particle-based line agents to some force fields like attractors.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  new LineAgent(new IParticle(0,0,0).hide().fric(0.2),
		new IVec(0,2,0));
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,0,0,200,400,0)).linear(100).intensity(-200); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{
  LineAgent parent;
  IVec dir;
  
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }

  void update(){
    if(time()==0 && alive()){
      if(parent!=null && parent.alive()){
        new ISpringLine(this,parent,100).clr(0); //line with spring force
      }
      new LineAgent(this, dir);
    }
  }
}


     Branching Particle-based Line Agent

The code below adds the branching logic in the update method with random probability of 5% and shows the response of the all branching lines to the force fields. It also adds coloring logic to the line based on time.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,0,0,200,400,0)).linear(100).intensity(-200); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void update(){
    if(time()==0 && alive()){
      if(parent!=null && parent.alive()){
        ISpringLine ln = new ISpringLine(this, parent,100); //line with spring force
        double t = -cos(IG.time()*0.06)*0.5+0.5; 
        ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2); //color by time
      }
      new LineAgent(this, dir);
      IVec dir2 = dir.cp();
      dir2.rot(PI*0.12);
      if( IRand.pct(5) ){ // 5% branching probability
        new LineAgent(this,dir2);
      }
    }
  }
}


     Collision Detection

The code below adds the logic of collision detection for branching lines not to intersect each other during the growth. The interaction method is added in LineAgent to check collision with other LineAgent instances by calculating intersection of line segments. The result of the collision detection is stored in the boolean variable isColliding in the interact method and used in the update method either to delete the agent instance or to make a spring line and child agents.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,0,0,200,400,0)).linear(100).intensity(-200); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  boolean isColliding; 
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void interact(ArrayList agents){ 
    if(time()==0){ //only in the first time 
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if(intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.06)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 1.0); // color by time
        }
        new LineAgent(this, dir);
        IVec dir2 = dir.cp();
        dir2.rot(PI*0.12);
        if( IRand.pct(5) ){ // 5% branching probability
          new LineAgent(this,dir2);
        }
      }
    }
  }
}


     Branching and Growth Angle Control

To change the branch geometry not too straight or parallel, the next codes changes the way of defining the child agents' direction. Now both of two child agents are bent one direction and the opposite direction. Bending the first child agent towards the left or the right is determined randomly.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-100); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void interact(ArrayList agents){ 
    if(time()==0){ //only in the first time 
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.06)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 1.0); // color by time
        }
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2);
        }
      }
    }
  }
}

     Setting Spring Length

The code below explicitly set the length of the spring line with len() method. When the length is not set by len() method, the spring length is set to the initial length of the line. This line in the code
ln.len(ln.len()*2);
set the spring length to be the double of the initial length.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-100); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void interact(ArrayList agents){ 
    if(time()==0){ //only in the first time
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.06)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 1.0);
          ln.len(ln.len()*2); //set spring length longer
        }
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2);
        }
      }
    }
  }
}

As a result of setting the spring length to be the double of the original branch length, it expands after it's created making the lines jaggy.


     Smoothing Branch Lines with Straightener

To make the branch lines smoother, the agent in the next code adds straightening force which applies force to three particle points of the agent, the parent agent and the grandparent agent. IStraightenerCurve applies the straightener force and also creates a curve out of three points.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2));
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-30); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
  }
  
  void interact(ArrayList agents){ 
    if(time()==0){ //only in the first time 
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.015)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.5); // color by time
          if(parent.parent!=null && parent.parent.alive()){
            IStraightenerCurve st = 
		new IStraightenerCurve(this,parent,parent.parent).tension(100);
            st.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.5);
          }
        }
      }
      if(time()==4){
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2);
        }
      }
    }
  }
}


     Bundling Close Branches

The code below adds lines of codes in the interact method after collision detection codes to tie close agents together with ISpringLine. The code also introduces the variable root to know which branch each agent belongs to. The tie is made only when the two agents belong to different branches. The length of the ISpringLine instance connecting two agents is set to be dir.len()*1.5 to keep consistent distance when branches are tied together. As a result of this logic, the branches are bundled together in a smooth and consistent way.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2), null);
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-30); //repulsion force
  }
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent, root;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir, LineAgent root){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
    if(root!=null){ this.root = root; }
    else{ this.root = this; }
  }
  
  void interact(ArrayList< IDynamics > agents){ 
    if(time()==0){ //only in the first time
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
    else if(time()==1){
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this && lineAgent.alive()){ //agents include "this"
            if(lineAgent.root != root){ // different branch
              IVec dif = lineAgent.pos().dif(pos());
              if(dif.len() < dir.len()*2.5 && dif.len()>dir.len()){ // within threshold
                ISpringLine sp = 
			new ISpringLine(lineAgent,this,5,dir.len()*1.5).tension(5);
                double t = -cos(IG.time()*0.015)*0.5+0.5;   
                sp.hsb(0.7-t*0.2, 1, 0.8-t*0.2, 0.2);
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.015)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.2); // color by tim
          if(parent.parent!=null && parent.parent.alive()){
            IStraightenerCurve st
		= new IStraightenerCurve(this,parent,parent.parent).tension(100);
            st.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.2);
          }
        }
      }
      if(time()==4){
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir, root);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2, null);
        }
      }
    }
  }
}


     Applying More Force Field

To demonstrate a variation of branch formation, the next code adds one more force field of IPointCurlField in the beginning. This single force fields is applied to all branch agents and deform the whole branch to the twisted formation.

import igeo.*;
import processing.opengl.*;

void setup(){ 
  size(480,360,IG.GL);
  for(int i=0; i < 4; i++){
    new LineAgent(new IParticle(IRand.pt(-40,-40,0,40,40,0)).hide().fric(0.2), IRand.dir(IG.zaxis).len(2), null);
  }
  for(int i=0; i < 20; i++){
    new IAttractor(IRand.pt(-200,-200,0,200,200,0)).linear(50).intensity(-30);//repulsion force
  }
  new IPointCurlField(new IVec(0,0,0), new IVec(0,0,1)).intensity(50); //spinning force
  IG.bg(1.0,1.0,1.0);
  IG.top();
}

class LineAgent extends IParticle{ 
  LineAgent parent, root;
  boolean isColliding;
  IVec dir;
    
  LineAgent(IParticle parent, IVec dir, LineAgent root){
    super(parent.pos().cp(dir));
    if(parent instanceof LineAgent){
      this.parent = (LineAgent)parent;
    }
    isColliding=false;
    hide(); // hide point
    this.dir = dir;
    fric(0.2);
    if(root!=null){ this.root = root; }
    else{ this.root = this; }
  }
  
  void interact(ArrayList< IDynamics > agents){ 
    if(time()==0){ //only in the first time
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this){ //agents include "this"
            if(lineAgent.parent!=null && lineAgent.pos().dist(pos()) < pos().dist(parent.pos())*2){
              IVec intxn = IVec.intersectSegment(lineAgent.parent.pos(),lineAgent.pos(),parent.pos(),pos());
              if( intxn != null && !intxn.eq(parent.pos()) && !lineAgent.isColliding ){
                isColliding = true;
                return;
              }
            }
          }
        }
      }
    }
    else if(time()==1){
      for(int i=0; i < agents.size(); i++){
        if(agents.get(i) instanceof LineAgent){
          LineAgent lineAgent = (LineAgent)agents.get(i); 
          if(lineAgent!=this && lineAgent.alive()){ //agents include "this"
            if(lineAgent.root != root){ // different branch
              IVec dif = lineAgent.pos().dif(pos());
              if(dif.len() < dir.len()*2.5 && dif.len()>dir.len()){ // within threshold
                ISpringLine sp = 
			new ISpringLine(lineAgent,this,5,dir.len()*1.5).tension(5);
                double t = -cos(IG.time()*0.015)*0.5+0.5;   
                sp.hsb(0.7-t*0.2, 1, 0.8-t*0.2, 0.2);
              }
            }
          }
        }
      }
    }
  }
  
  void update(){
    if( isColliding ){ 
      del();
    }
    else{
      if(time()==0 && alive()){
        if(parent!=null && parent.alive()){
          ISpringLine ln = new ISpringLine(this, parent,100);
          double t = -cos(IG.time()*0.015)*0.5+0.5; 
          ln.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.2); // color by tim
          if(parent.parent!=null && parent.parent.alive()){
            IStraightenerCurve st
		= new IStraightenerCurve(this,parent,parent.parent).tension(100);
            st.hsb( 0.7-t*0.2, 1, 0.8-t*0.2, 0.2);
          }
        }
      }
      if(time()==4){
        IVec dir2 = dir.cp();
        double angle = PI*0.12 ;
        if( IRand.pct(50) ){ 
          dir.rot(angle);
          dir2.rot(-angle);
        }
        else{
          dir.rot(-angle);
          dir2.rot(angle);
        }
        new LineAgent(this, dir, root);
        if( IRand.pct(20) ){ // 20% branching probability
          new LineAgent(this,dir2, null);
        }
      }
    }
  }
}


(back to the list of tutorials)

HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java / Python)
GALLERY
SOURCE CODE(GitHub)
ABOUT