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

Generative and Self-Optimizing Agents (Swarm-Generated Tensile Structure)(requires iGeo version 0.9.1.5)

     Swarm Formation by Force Fields

This tutorial shows examples of combining active and generative agents (swarm agents and branching agents) and passive and optimizing agetns (physics simulating particle, tension and compression agents) to generate geometry which could be built with tensile and compressive materials in vertically suspended structure or horizontal suspension bridge structure like the two examples below.


A(g)ntense by ATLV


Venice Beach Canopy by ATLV

First, the code below generates swarm formation curves by two different swarm classes and by force fields generated randomly in the codes. Multiple force fields are combined by compound fields to respond the closest force field. There are a compound attractor field and a compound curl field for MyBoidA and also for MyBoidB. The rotation directions of the curl fields are opposite for MyBoidA and MyBoidB.

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

void setup(){
  size(480,360,IG.GL);
  
  for(int i=0; i < 25; i++){
    new MyBoidA(IRand.pt(-300,-300,0,300,300,0), IRand.pt(-10,-10,-20,10,10,-20));
    new MyBoidB(IRand.pt(-300,-300,0,300,300,0), IRand.pt(-10,-10,-20,10,10,-20));
  }
  
  // generating force fields by attractors and curl fields
  ICompoundField curlA = new ICompoundField();
  ICompoundField curlB = new ICompoundField();
  ICompoundField attrA = new ICompoundField();
  ICompoundField attrB = new ICompoundField();
  curlA.target(MyBoidA.class);
  curlB.target(MyBoidB.class);
  attrA.target(MyBoidA.class);
  attrB.target(MyBoidB.class);
  for(int i=0; i < 10; i++){
    IVec pt = IRand.pt(-100,-100,-800,100,100,-100);
    curlA.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(20));
    curlB.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(-20));
    attrA.add(new IAttractor(pt).intensity(20));
    attrB.add(new IAttractor(pt).intensity(20));
  }
  new IGravity(0,0,-2);
}

class MyBoid extends IBoidTrajectory{
  MyBoid(IVec p, IVec v){
    super(p,v);
    alignment(0, 0);
    cohesion(2, 40);
    separation(4, 50);
    fric(0.01);
  }
}

class MyBoidA extends MyBoid{
  MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}

class MyBoidB extends MyBoid{
  MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}


     Swarm Formation and Force Field from Input Geometry

The example code below uses an input Rhino file to generate force fields and set initial positions of swarm agents. Two sets of points on specific layer names are imported and the code puts swarm agents of MyBoidA and MyBoidB at the point locations. Curves on the six different layers are imported to generate force fields. Curve attractor fields, curve tangent fields, curve curl fields for MyBoidA and MyBoidB are instantiated. The sample input file used in the code is this file.
field_lines1.3dm

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

void setup(){
  size(480,360,IG.GL);
  // import rhino file to set up force fields
  IG.open("field_lines1.3dm");
  // points to specify intial position of swarm
  IPoint[] ptsA = IG.layer("startPointA").points();
  IPoint[] ptsB = IG.layer("startPointB").points();
  for(int i=0; i < ptsA.length; i++){
    new MyBoidA(ptsA[i].pos().cp(), new IVec(0,0,0));
  }
  for(int i=0; i < ptsB.length; i++){
    new MyBoidB(ptsB[i].pos().cp(), new IVec(0,0,0));
  }
  // curves to specify force fields
  ICurve[] attrFieldA = IG.layer("attractorA").curves();
  ICurve[] attrFieldB = IG.layer("attractorB").curves();
  ICurve[] curlFieldA = IG.layer("curlFieldA").curves();
  ICurve[] curlFieldB = IG.layer("curlFieldB").curves();
  ICurve[] tanFieldA = IG.layer("tangentFieldA").curves();
  ICurve[] tanFieldB = IG.layer("tangentFieldB").curves();
  
  ICompoundField attrA = new ICompoundField();
  ICompoundField attrB = new ICompoundField();
  ICompoundField curlA = new ICompoundField();
  ICompoundField curlB = new ICompoundField();
  ICompoundField tanA = new ICompoundField();
  ICompoundField tanB = new ICompoundField();
  attrA.target(MyBoidA.class);
  attrB.target(MyBoidB.class);
  curlA.target(MyBoidA.class);
  curlB.target(MyBoidB.class);
  tanA.target(MyBoidA.class);
  tanB.target(MyBoidB.class);
  
  for(int i=0; i < attrFieldA.length; i++){
    attrA.add(new ICurveAttractorField(attrFieldA[i]).intensity(35));
  }
  for(int i=0; i < attrFieldB.length; i++){
    attrB.add(new ICurveAttractorField(attrFieldB[i]).intensity(35));
  }
  for(int i=0; i < curlFieldA.length; i++){
    curlA.add(new ICurveCurlField(curlFieldA[i]).intensity(20));
  }
  for(int i=0; i < curlFieldB.length; i++){
    curlB.add(new ICurveCurlField(curlFieldB[i]).intensity(-20));
  }
  for(int i=0; i < tanFieldA.length; i++){
    tanA.add(new ICurveTangentField(tanFieldA[i]).intensity(28));
  }
  for(int i=0; i < tanFieldB.length; i++){
    tanB.add(new ICurveTangentField(tanFieldB[i]).intensity(28));
  }
}

class MyBoid extends IBoidTrajectory{
  MyBoid(IVec p, IVec v){
    super(p,v);
    alignment(0, 0);
    cohesion(2, 30);
    separation(4, 40);
    fric(0.01);
  }
}

class MyBoidA extends MyBoid{
  MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}

class MyBoidB extends MyBoid{
  MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}


     Self-Optimizing Physics Simulation Agents

The next example adds two classes; Node class and SectionAgent class. Node class is a particle-based class which stays only on a specified plane. SectionAgent is instantiated once at the setup method and observes all swarm agents at its interact method. When a swarm agent pass through a specified section plane, (1) generate Node instance at the intersection, (2) collect all nodes on the same section plane and calculate Delaunay triangulation and (3) connect nodes on the Delaunay edges with IStickLine agents which simulate compression stick structure. When a new node is created, the swarm agent stores it and connects it to the previous node with ITensionLine agent which simulate tension wire structure.

As result of nodes staying on a plane, being separated by compression structure, and pulled by tension structure between sections, nodes, sticks and wires are moved to equilibrium locations under the forces perpendicular to the section planes.

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

void setup(){
  size(480,360,IG.GL);
  IConfig.syncDrawAndDynamics = true;
  
  for(int i=0; i < 25; i++){
    new MyBoidA(IRand.pt(-300,-300,0,300,300,0), IRand.pt(-10,-10,-20,10,10,-20));
    new MyBoidB(IRand.pt(-300,-300,0,300,300,0), IRand.pt(-10,-10,-20,10,10,-20));
  }
  
  ICompoundField curlA = new ICompoundField();
  ICompoundField curlB = new ICompoundField();
  ICompoundField attrA = new ICompoundField();
  ICompoundField attrB = new ICompoundField();
  curlA.target(MyBoidA.class);
  curlB.target(MyBoidB.class);
  attrA.target(MyBoidA.class);
  attrB.target(MyBoidB.class);
  for(int i=0; i < 10; i++){
    IVec pt = IRand.pt(-100,-100,-800,100,100,-100);
    curlA.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(20));
    curlB.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(-20));
    attrA.add(new IAttractor(pt).intensity(20));
    attrB.add(new IAttractor(pt).intensity(20));
  }
  new IGravity(0,0,-2);
  
  // section towards negative Z axis
  SectionAgent sectAgent = 
    new SectionAgent(new IVec(0,0,-80), new IVec(0,0,-1), 80);
  sectAgent.maxSectionNum = 10; // only for 10 sections
}

class MyBoid extends IBoidTrajectory{
  ArrayList< Node > nodes;
  IVec prevPos;
  MyBoid(IVec p, IVec v){
    super(p,v);
    alignment(0, 0);
    cohesion(2, 40);
    separation(4, 50);
    fric(0.01);
    
    nodes = new ArrayList< Node >();
    Node n = new Node(p.cp(), new IVec(1,0,0));
    n.fix().show();
    nodes.add(n); // first & fixed node
  }
  
  void update(){
    prevPos = pos().cp();
  }
  
  void addNode(Node n){
    // connecting nodes with tension line
    new ITensionLine(n, nodes.get(nodes.size()-1)).tension(50).clr(this);
    nodes.add(n);
  }
}

class MyBoidA extends MyBoid{
  MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}

class MyBoidB extends MyBoid{
  MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}

// particle agent for self-optimiztion which moves only on a specified plane. 
class Node extends IParticle{
  IVec planePt;
  IVec planeDir;
  
  Node(IVec p, IVec planeDirection) {
    super(p);
    planePt = p.cp();
    planeDir = planeDirection;
    fric(0.5);
    hide();
  }
  
  void postupdate(){ // postupdate is automatically called after all update() is done
    // keeping particle on a specified plane
    pos().projectToPlane(planeDir, planeDir, planePt);
  }
}

// agent to check planar intersection of swarm and generate nodes and compression structure.
class SectionAgent extends IAgent{
  IVec sectOrig; // first section point 
  IVec sectNml; // normal vector of intersection plane of section
  double interval; // interval distance between sections
  ArrayList< ArrayList< Node >> nodes;
  ArrayList< ArrayList< IStickLine >> sticks;
  ArrayList< Boolean > updateSection;
  int maxSectionNum = -1; // limit of sections counting from origin. if -1, no limit.
  
  SectionAgent(IVec sectOrigin, IVec sectNormal, double sectInterval){
    sectOrig = sectOrigin;
    interval = sectInterval;
    sectNml = sectNormal.cp().len(interval);
    nodes = new ArrayList< ArrayList< Node >>();
    sticks = new ArrayList< ArrayList< IStickLine >>();
    updateSection = new ArrayList< Boolean >();
  }
  
  void interact(ArrayList< IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof MyBoid){
        MyBoid b = (MyBoid)agents.get(i);
        if(b.prevPos!=null){
          double planeDist = b.pos().dif(sectOrig).dot(sectNml)/sectNml.len(); // dist to plane
          double prevPlaneDist = b.prevPos.dif(sectOrig).dot(sectNml)/sectNml.len();
          //check if swarm agent passes through any section planes
          if(planeDist >= 0 && prevPlaneDist >= 0 && (int)(planeDist/interval) != (int)(prevPlaneDist/interval) || prevPlaneDist <= 0 && planeDist > 0){
            int index1 = (int)(prevPlaneDist/interval);
            int index2 = (int)(planeDist/interval);
            int inc=1;
            if(prevPlaneDist<=0) index1=-1;
            if(index1>index2){ inc = -1; }
            for(int j=index1+inc; inc > 0 && j <= index2 || inc < 0 && j >= index2; j+=inc){
              addIntersection(b, j);
            }
          }
        }
      }
    }
    
    for(int i=0; i < updateSection.size(); i++){
      if(updateSection.get(i)){ // update only if new swarm enters into the section
        for(int j=0; j < sticks.get(i).size(); j++){
          sticks.get(i).get(j).del();
        }
        sticks.get(i).clear(); // clear old springs
        IDelaunay2D.maxDistToCheck = 200; // edge length limit
        // connect nodes with compression structures at Delaunay edges
        IVecI[][] edges = IDelaunay2D.getEdges(nodes.get(i).toArray(new Node[nodes.get(i).size()]), sectNml);
        for(int j=0; j < edges.length; j++){
           // add stick (compression) line structure between nodes
           sticks.get(i).add(new IStickLine((Node)edges[j][0],(Node)edges[j][1]).clr(.5,0,1));
        }
        updateSection.set(i,false);
      }
    }
  }
  
  void addIntersection(MyBoid b, int idx){
    if(idx < maxSectionNum){
      IVec lineDir = b.pos().dif(b.prevPos);
      IVec sectPos = sectOrig.cp().add(sectNml, idx);
      // calculate intersection
      IVec itxn = IVec.intersectPlaneAndLine(sectNml, sectPos,lineDir,b.pos());
      // create new node at the intersection
      Node n = new Node(itxn, sectNml); 
      b.addNode(n);
      for(int i=nodes.size(); i <= idx; i++){ // set the size of array to idx+1
        nodes.add(new ArrayList< Node >());
        sticks.add(new ArrayList< IStickLine >());
        updateSection.add(false);
      }
      nodes.get(idx).add(n);
      updateSection.set(idx, true); //set flag to update the section
      if(idx==maxSectionNum-1){
        b.del(); //at the last section, delete swarm agent
      }
    }
  }
}

The sample input file used below is below.
field_lines2.3dm

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

void setup(){
  size(480,360,IG.GL);
  IConfig.syncDrawAndDynamics = true;
  // import rhino file to set up force fields
  IG.open("field_lines2.3dm");
  // points to specify intial position of swarm
  IPoint[] ptsA = IG.layer("startPointA").points();
  IPoint[] ptsB = IG.layer("startPointB").points();
  for(int i=0; i < ptsA.length; i++){
    new MyBoidA(ptsA[i].pos().cp(), new IVec(0,0,0));
  }
  for(int i=0; i < ptsB.length; i++){
    new MyBoidB(ptsB[i].pos().cp(), new IVec(0,0,0));
  }
  // curves to specify force fields
  ICurve[] attrFieldA = IG.layer("attractorA").curves();
  ICurve[] attrFieldB = IG.layer("attractorB").curves();
  ICurve[] curlFieldA = IG.layer("curlFieldA").curves();
  ICurve[] curlFieldB = IG.layer("curlFieldB").curves();
  ICurve[] tanFieldA = IG.layer("tangentFieldA").curves();
  ICurve[] tanFieldB = IG.layer("tangentFieldB").curves();
  
  ICompoundField attrA = new ICompoundField();
  ICompoundField attrB = new ICompoundField();
  ICompoundField curlA = new ICompoundField();
  ICompoundField curlB = new ICompoundField();
  ICompoundField tanA = new ICompoundField();
  ICompoundField tanB = new ICompoundField();
  attrA.target(MyBoidA.class);
  attrB.target(MyBoidB.class);
  curlA.target(MyBoidA.class);
  curlB.target(MyBoidB.class);
  tanA.target(MyBoidA.class);
  tanB.target(MyBoidB.class);
  
  for(int i=0; i < attrFieldA.length; i++){
    attrA.add(new ICurveAttractorField(attrFieldA[i]).intensity(35));
    //attrFieldA[i].hide();
  }
  for(int i=0; i < attrFieldB.length; i++){
    attrB.add(new ICurveAttractorField(attrFieldB[i]).intensity(35));
    //attrFieldB[i].hide();
  }
  for(int i=0; i < curlFieldA.length; i++){
    curlA.add(new ICurveCurlField(curlFieldA[i]).intensity(20));
    //curlFieldA[i].hide();
  }
  for(int i=0; i < curlFieldB.length; i++){
    curlB.add(new ICurveCurlField(curlFieldB[i]).intensity(-20));
    //curlFieldB[i].hide();
  }
  for(int i=0; i < tanFieldA.length; i++){
    tanA.add(new ICurveTangentField(tanFieldA[i]).intensity(28));
    //tanFieldA[i].hide();
  }
  for(int i=0; i < tanFieldB.length; i++){
    tanB.add(new ICurveTangentField(tanFieldB[i]).intensity(28));
    //tanFieldB[i].hide();
  }
  new IGravity(0,0,-2);
  
  // section towards negative Z axis
  SectionAgent sectAgent = 
    new SectionAgent(new IVec(0,0,-80), new IVec(0,0,-1), 80);
  sectAgent.maxSectionNum = 10; // only for 10 sections
}

class MyBoid extends IBoidTrajectory{
  ArrayList< Node > nodes;
  IVec prevPos;
  MyBoid(IVec p, IVec v){
    super(p,v);
    alignment(0, 0);
    cohesion(2, 40);
    separation(4, 50);
    fric(0.01);
    
    nodes = new ArrayList< Node >();
    Node n = new Node(p.cp(), new IVec(1,0,0));
    n.fix().show();
    nodes.add(n); // first & fixed node
  }
  
  void update(){
    prevPos = pos().cp();
  }
  
  void addNode(Node n){
    // connecting nodes with tension line
    new ITensionLine(n, nodes.get(nodes.size()-1)).tension(50).clr(this);
    nodes.add(n);
  }
}

class MyBoidA extends MyBoid{
  MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}

class MyBoidB extends MyBoid{
  MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}

// particle agent for self-optimiztion which moves only on a specified plane. 
class Node extends IParticle{
  IVec planePt;
  IVec planeDir;
  
  Node(IVec p, IVec planeDirection) {
    super(p);
    planePt = p.cp();
    planeDir = planeDirection;
    fric(0.5);
    hide();
  }
  
  void postupdate(){ // postupdate is automatically called after all update() is done
    // keeping particle on a specified plane
    pos().projectToPlane(planeDir, planeDir, planePt);
  }
}

// agent to check planar intersection of swarm and generate nodes and compression structure.
class SectionAgent extends IAgent{
  IVec sectOrig; // first section point 
  IVec sectNml; // normal vector of intersection plane of section
  double interval; // interval distance between sections
  ArrayList< ArrayList< Node >> nodes;
  ArrayList< ArrayList< IStickLine >> sticks;
  ArrayList< Boolean > updateSection;
  int maxSectionNum = -1; // limit of sections counting from origin. if -1, no limit.
  
  SectionAgent(IVec sectOrigin, IVec sectNormal, double sectInterval){
    sectOrig = sectOrigin;
    interval = sectInterval;
    sectNml = sectNormal.cp().len(interval);
    nodes = new ArrayList< ArrayList< Node >>();
    sticks = new ArrayList< ArrayList< IStickLine >>();
    updateSection = new ArrayList< Boolean >();
  }
  
  void interact(ArrayList< IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof MyBoid){
        MyBoid b = (MyBoid)agents.get(i);
        if(b.prevPos!=null){
          double planeDist = b.pos().dif(sectOrig).dot(sectNml)/sectNml.len(); // dist to plane
          double prevPlaneDist = b.prevPos.dif(sectOrig).dot(sectNml)/sectNml.len();
          //check if swarm agent passes through any section planes
          if(planeDist >= 0 && prevPlaneDist >= 0 && (int)(planeDist/interval) != (int)(prevPlaneDist/interval) || prevPlaneDist <= 0 && planeDist > 0){
            int index1 = (int)(prevPlaneDist/interval);
            int index2 = (int)(planeDist/interval);
            int inc=1;
            if(prevPlaneDist<=0) index1=-1;
            if(index1>index2){ inc = -1; }
            for(int j=index1+inc; inc > 0 && j <= index2 || inc < 0 && j >= index2; j+=inc){
              addIntersection(b, j);
            }
          }
        }
      }
    }
    
    for(int i=0; i < updateSection.size(); i++){
      if(updateSection.get(i)){ // update only if new swarm enters into the section
        for(int j=0; j < sticks.get(i).size(); j++){
          sticks.get(i).get(j).del();
        }
        sticks.get(i).clear(); // clear old springs
        IDelaunay2D.maxDistToCheck = 200; // edge length limit
        // connect nodes with compression structures at Delaunay edges
        IVecI[][] edges = IDelaunay2D.getEdges(nodes.get(i).toArray(new Node[nodes.get(i).size()]), sectNml);
        for(int j=0; j < edges.length; j++){
           // add stick (compression) line structure between nodes
           sticks.get(i).add(new IStickLine((Node)edges[j][0],(Node)edges[j][1]).clr(.5,0,1));
        }
        updateSection.set(i,false);
      }
    }
  }
  
  void addIntersection(MyBoid b, int idx){
    if(idx < maxSectionNum){
      IVec lineDir = b.pos().dif(b.prevPos);
      IVec sectPos = sectOrig.cp().add(sectNml, idx);
      // calculate intersection
      IVec itxn = IVec.intersectPlaneAndLine(sectNml, sectPos,lineDir,b.pos());
      // create new node at the intersection
      Node n = new Node(itxn, sectNml); 
      b.addNode(n);
      for(int i=nodes.size(); i <= idx; i++){ // set the size of array to idx+1
        nodes.add(new ArrayList< Node >());
        sticks.add(new ArrayList< IStickLine >());
        updateSection.add(false);
      }
      nodes.get(idx).add(n);
      updateSection.set(idx, true); //set flag to update the section
      if(idx==maxSectionNum-1){
        b.del(); //at the last section, delete swarm agent
      }
    }
  }
}


     Self-Optimizing Physics Simulation Agents for Bridging Structure

The code below changes the direction of the section in SectionAgent towards X axis and the formation of swarm trajectory is controlled by force fields generated from the imported Rhino file as shown in the earlier example. In the previous example, nodes at the start points of swarm agents are fixed. In this example the nodes on the last section (defined by maxSectionNum) are fixed as well as the start points. The sample input file used below is below.
field_lines1.3dm

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

void setup(){
  size(480,360,IG.GL);
  IConfig.syncDrawAndDynamics = true;
  // import rhino file to set up force fields
  IG.open("field_lines1.3dm");
  // points to specify intial position of swarm
  IPoint[] ptsA = IG.layer("startPointA").points();
  IPoint[] ptsB = IG.layer("startPointB").points();
  for(int i=0; i < ptsA.length; i++){
    new MyBoidA(ptsA[i].pos().cp(), new IVec(0,0,0));
  }
  for(int i=0; i < ptsB.length; i++){
    new MyBoidB(ptsB[i].pos().cp(), new IVec(0,0,0));
  }
  // curves to specify force fields
  ICurve[] attrFieldA = IG.layer("attractorA").curves();
  ICurve[] attrFieldB = IG.layer("attractorB").curves();
  ICurve[] curlFieldA = IG.layer("curlFieldA").curves();
  ICurve[] curlFieldB = IG.layer("curlFieldB").curves();
  ICurve[] tanFieldA = IG.layer("tangentFieldA").curves();
  ICurve[] tanFieldB = IG.layer("tangentFieldB").curves();
  
  ICompoundField attrA = new ICompoundField();
  ICompoundField attrB = new ICompoundField();
  ICompoundField curlA = new ICompoundField();
  ICompoundField curlB = new ICompoundField();
  ICompoundField tanA = new ICompoundField();
  ICompoundField tanB = new ICompoundField();
  attrA.target(MyBoidA.class);
  attrB.target(MyBoidB.class);
  curlA.target(MyBoidA.class);
  curlB.target(MyBoidB.class);
  tanA.target(MyBoidA.class);
  tanB.target(MyBoidB.class);
  
  for(int i=0; i < attrFieldA.length; i++){
    attrA.add(new ICurveAttractorField(attrFieldA[i]).intensity(35));
  }
  for(int i=0; i < attrFieldB.length; i++){
    attrB.add(new ICurveAttractorField(attrFieldB[i]).intensity(35));
  }
  for(int i=0; i < curlFieldA.length; i++){
    curlA.add(new ICurveCurlField(curlFieldA[i]).intensity(20));
  }
  for(int i=0; i < curlFieldB.length; i++){
    curlB.add(new ICurveCurlField(curlFieldB[i]).intensity(-20));
  }
  for(int i=0; i < tanFieldA.length; i++){
    tanA.add(new ICurveTangentField(tanFieldA[i]).intensity(28));
  }
  for(int i=0; i < tanFieldB.length; i++){
    tanB.add(new ICurveTangentField(tanFieldB[i]).intensity(28));
  }
  // gravity is applied only to nodes
  new IGravity(0,0,-6).target(Node.class);

  //sections towards X axis
  SectionAgent sectAgent = 
    new SectionAgent(new IVec(120,0,0), new IVec(1,0,0), 60);
  sectAgent.maxSectionNum = 18; //only for 18 sections
}

class MyBoid extends IBoidTrajectory{
  ArrayList< Node > nodes;
  IVec prevPos;
  MyBoid(IVec p, IVec v){
    super(p,v);
    alignment(0, 0);
    cohesion(2, 40);
    separation(4, 50);
    fric(0.01);
    
    nodes = new ArrayList< Node >();
    Node n = new Node(p.cp(), new IVec(1,0,0));
    n.fix().show();
    nodes.add(n); // first & fixed node
  }
  
  void update(){
    prevPos = pos().cp();
  }
  
  void addNode(Node n){
    // connecting nodes with tension line
    new ITensionLine(n, nodes.get(nodes.size()-1)).tension(50).clr(this);
    nodes.add(n);
  }
}

class MyBoidA extends MyBoid{
  MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}

class MyBoidB extends MyBoid{
  MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}

// particle agent for self-optimiztion which moves only on a specified plane. 
class Node extends IParticle{
  IVec planePt;
  IVec planeDir;
  
  Node(IVec p, IVec planeDirection) {
    super(p);
    planePt = p.cp();
    planeDir = planeDirection;
    fric(0.5);
    hide();
  }
  
  void postupdate(){ // postupdate is automatically called after all update() is done
    // keeping particle on a specified plane
    pos().projectToPlane(planeDir, planeDir, planePt);
  }
}

// agent to check planar intersection of swarm and generate nodes and compression structure.
class SectionAgent extends IAgent{
  IVec sectOrig; // first section point 
  IVec sectNml; // normal vector of intersection plane of section
  double interval; // interval distance between sections
  ArrayList< ArrayList< Node >> nodes;
  ArrayList< ArrayList< IStickLine >> sticks;
  ArrayList< Boolean > updateSection;
  int maxSectionNum = -1; // limit of sections counting from origin. if -1, no limit.
  
  SectionAgent(IVec sectOrigin, IVec sectNormal, double sectInterval){
    sectOrig = sectOrigin;
    interval = sectInterval;
    sectNml = sectNormal.cp().len(interval);
    nodes = new ArrayList< ArrayList< Node >>();
    sticks = new ArrayList< ArrayList< IStickLine >>();
    updateSection = new ArrayList< Boolean >();
  }
  
  void interact(ArrayList< IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof MyBoid){
        MyBoid b = (MyBoid)agents.get(i);
        if(b.prevPos!=null){
          double planeDist = b.pos().dif(sectOrig).dot(sectNml)/sectNml.len(); // dist to plane
          double prevPlaneDist = b.prevPos.dif(sectOrig).dot(sectNml)/sectNml.len();
          //check if swarm agent passes through any section planes
          if(planeDist >= 0 && prevPlaneDist >= 0 && (int)(planeDist/interval) != (int)(prevPlaneDist/interval) || prevPlaneDist <= 0 && planeDist > 0){
            int index1 = (int)(prevPlaneDist/interval);
            int index2 = (int)(planeDist/interval);
            int inc=1;
            if(prevPlaneDist<=0) index1=-1;
            if(index1>index2){ inc = -1; }
            for(int j=index1+inc; inc > 0 && j <= index2 || inc < 0 && j >= index2; j+=inc){
              addIntersection(b, j);
            }
          }
        }
      }
    }
    
    for(int i=0; i < updateSection.size(); i++){
      if(updateSection.get(i)){ // update only if new swarm enters into the section
        for(int j=0; j < sticks.get(i).size(); j++){
          sticks.get(i).get(j).del();
        }
        sticks.get(i).clear(); // clear old springs
        IDelaunay2D.maxDistToCheck = 200; // edge length limit  
        // connect nodes with compression structures at Delaunay edges
        IVecI[][] edges = IDelaunay2D.getEdges(nodes.get(i).toArray(new Node[nodes.get(i).size()]), sectNml);
        for(int j=0; j < edges.length; j++){
           // add stick (compression) line structure between nodes
           sticks.get(i).add(new IStickLine((Node)edges[j][0],(Node)edges[j][1]).clr(.5,0,1));
        }
        updateSection.set(i,false);
      }
    }
  }
  
  void addIntersection(MyBoid b, int idx){
    if(idx < maxSectionNum){
      IVec lineDir = b.pos().dif(b.prevPos);
      IVec sectPos = sectOrig.cp().add(sectNml, idx);
      // calculate intersection
      IVec itxn = IVec.intersectPlaneAndLine(sectNml, sectPos,lineDir,b.pos());
      // create new node at the intersection
      Node n = new Node(itxn, sectNml); 
      b.addNode(n);
      for(int i=nodes.size(); i <= idx; i++){ // set the size of array to idx+1
        nodes.add(new ArrayList< Node >());
        sticks.add(new ArrayList< IStickLine >());
        updateSection.add(false);
      }
      nodes.get(idx).add(n);
      updateSection.set(idx, true); //set flag to update the section
      if(idx==maxSectionNum-1){
        b.del(); //at the last section, delete swarm agent
        n.fix().show();  // last node is also fixed in addition to the first
      }
    }
  }
}

     Branching Swarm Agents and Self-Optimizing Agents

The last example below adds branching behavior to the swarm agent. The nodes, tension and compression structure agents are generated in the same way with the previous examples by SectionAgent but the agent connects nodes of branched swarm agents back to the same parent node to have branched tension structure as well. In this example, swarm agents fly from bottom towards top and the first node is not fixed but the last node at the top plane is fixed to simulate suspended structure at the top.

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

void setup(){
  size(480,360,IG.GL);
  IConfig.syncDrawAndDynamics = true;
  
  for(int i=0; i < 3; i++){
    new MyBoidA(IRand.pt(-100,-100,0,100,100,0), IRand.pt(-10,-10,20,10,10,20));
    new MyBoidB(IRand.pt(-100,-100,0,100,100,0), IRand.pt(-10,-10,20,10,10,20));
  }
  
  ICompoundField curlA = new ICompoundField();
  ICompoundField curlB = new ICompoundField();
  ICompoundField attrA = new ICompoundField();
  ICompoundField attrB = new ICompoundField();
  curlA.target(MyBoidA.class);
  curlB.target(MyBoidB.class);
  attrA.target(MyBoidA.class);
  attrB.target(MyBoidB.class);
  for(int i=0; i < 10; i++){
    IVec pt = IRand.pt(-100,-100,1000,100,100,100);
    curlA.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(20));
    curlB.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(-20));
    attrA.add(new IAttractor(pt).intensity(20));
    attrB.add(new IAttractor(pt).intensity(20));
  }
  new IGravity(0,0,2);
  
  SectionAgent sectAgent = new SectionAgent(new IVec(0,0,80), new IVec(0,0,1), 80);
  sectAgent.maxSectionNum = 10; // only for 10 sections
}

class MyBoid extends IBoidTrajectory{
  ArrayList< Node > nodes;
  IVec prevPos;
  MyBoid(IVec p, IVec v){
    super(p,v);
    alignment(0, 0);
    cohesion(2, 30);
    separation(4, 40);
    fric(0.01);
    
    nodes = new ArrayList< Node >();
    // no initial node is created nor fixed
  }
  
  MyBoid duplicate(){ //to be overriden by child class
    return new MyBoid(pos().cp(), vel().cp());
  }
  
  void update(){
    prevPos = pos().cp();
    // branching every 10 time frame at 7% probability
    if(IG.time()%10==0 && IRand.pct(7)){
      MyBoid b = duplicate(); // create new swarm
      // push away randomly
      b.push(IRand.dir(new IVec(0,0,1)).len(1000)); 
      if(nodes.size()>0){
        // share the last node to connect tension line from both
        b.nodes.add(nodes.get(nodes.size()-1));
      }
    }    
  }
  
  void addNode(Node n){
    if(nodes.size()>0){
      // connecting nodes with tension line
      new ITensionLine(n, nodes.get(nodes.size()-1)).tension(50).clr(this);
    }
    nodes.add(n);
  }
}

class MyBoidA extends MyBoid{
  MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
  MyBoid duplicate(){ //overriding parent's method
    return new MyBoidA(pos().cp(), vel().cp());
  }
}

class MyBoidB extends MyBoid{
  MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
  MyBoid duplicate(){ //overriding parent's method
    return new MyBoidB(pos().cp(), vel().cp());
  }
}

// particle agent for self-optimiztion which moves only on a specified plane. 
class Node extends IParticle{
  IVec planePt;
  IVec planeDir;
  
  Node(IVec p, IVec planeDirection) {
    super(p);
    planePt = p.cp();
    planeDir = planeDirection;
    fric(0.5);
    hide();
  }
  
  void postupdate(){ // postupdate is automatically called after all update() is done
    // keeping particle on a specified plane
    pos().projectToPlane(planeDir, planeDir, planePt);
  }
}

// agent to check planar intersection of swarm and generate nodes and compression structure.
class SectionAgent extends IAgent{
  IVec sectOrig; // first section point 
  IVec sectNml; // normal vector of intersection plane of section
  double interval; // interval distance between sections
  ArrayList< ArrayList< Node >> nodes;
  ArrayList< ArrayList< IStickLine >> sticks;
  ArrayList< Boolean > updateSection;
  int maxSectionNum = -1; // limit of sections counting from origin. if -1, no limit.
  
  SectionAgent(IVec sectOrigin, IVec sectNormal, double sectInterval){
    sectOrig = sectOrigin;
    interval = sectInterval;
    sectNml = sectNormal.cp().len(interval);
    nodes = new ArrayList< ArrayList< Node >>();
    sticks = new ArrayList< ArrayList< IStickLine >>();
    updateSection = new ArrayList< Boolean >();
  }
  
  void interact(ArrayList< IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof MyBoid){
        MyBoid b = (MyBoid)agents.get(i);
        if(b.prevPos!=null){
          double planeDist = b.pos().dif(sectOrig).dot(sectNml)/sectNml.len(); // dist to plane
          double prevPlaneDist = b.prevPos.dif(sectOrig).dot(sectNml)/sectNml.len();
          //check if swarm agent passes through any section planes
          if(planeDist >= 0 && prevPlaneDist >= 0 && (int)(planeDist/interval) != (int)(prevPlaneDist/interval) || prevPlaneDist <= 0 && planeDist > 0){
            int index1 = (int)(prevPlaneDist/interval);
            int index2 = (int)(planeDist/interval);
            int inc=1;
            if(prevPlaneDist<=0) index1=-1;
            if(index1>index2){ inc = -1; }
            for(int j=index1+inc; inc > 0 && j <= index2 || inc < 0 && j >= index2; j+=inc){
              addIntersection(b, j);
            }
          }
        }
      }
    }
    
    for(int i=0; i < updateSection.size(); i++){
      if(updateSection.get(i)){ // update only if new swarm enters into the section
        for(int j=0; j < sticks.get(i).size(); j++){
          sticks.get(i).get(j).del();
        }
        sticks.get(i).clear(); // clear old springs
        IDelaunay2D.maxDistToCheck = 250; // edge length limit
        // connect nodes with compression structures at Delaunay edges
        IVecI[][] edges = IDelaunay2D.getEdges(nodes.get(i).toArray(new Node[nodes.get(i).size()]), sectNml);
        for(int j=0; j < edges.length; j++){
           // add stick (compression) line structure between nodes
           sticks.get(i).add(new IStickLine((Node)edges[j][0],(Node)edges[j][1]).clr(.5,0,1));
        }
        updateSection.set(i,false);
      }
    }
  }
  
  void addIntersection(MyBoid b, int idx){
    if(idx < maxSectionNum){
      IVec lineDir = b.pos().dif(b.prevPos);
      IVec sectPos = sectOrig.cp().add(sectNml, idx);
      // calculate intersection
      IVec itxn = IVec.intersectPlaneAndLine(sectNml, sectPos,lineDir,b.pos());
      // create new node at the intersection
      Node n = new Node(itxn, sectNml); 
      b.addNode(n);
      for(int i=nodes.size(); i <= idx; i++){
        nodes.add(new ArrayList< Node >()); // set the size of array to idx+1
        sticks.add(new ArrayList< IStickLine >());
        updateSection.add(false);
      }
      nodes.get(idx).add(n);
      updateSection.set(idx, true); //set flag to update the section
      if(idx==maxSectionNum-1){
        b.del(); //at the last section, delete swarm agent
        n.fix().show(); //last node is fixed
      }
    }
  }
}

The sample input file used below is below.
field_lines3.3dm

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

void setup(){
  size(480,360,IG.GL);
  IConfig.syncDrawAndDynamics = true;
  
  // import rhino file to set up force fields
  IG.open("field_lines3.3dm");
  // points to specify intial position of swarm
  IPoint[] ptsA = IG.layer("startPointA").points();
  IPoint[] ptsB = IG.layer("startPointB").points();
  for(int i=0; i < ptsA.length; i++){
    new MyBoidA(ptsA[i].pos().cp(), new IVec(0,0,0));
  }
  for(int i=0; i < ptsB.length; i++){
    new MyBoidB(ptsB[i].pos().cp(), new IVec(0,0,0));
  }
  // curves to specify force fields
  ICurve[] attrFieldA = IG.layer("attractorA").curves();
  ICurve[] attrFieldB = IG.layer("attractorB").curves();
  ICurve[] curlFieldA = IG.layer("curlFieldA").curves();
  ICurve[] curlFieldB = IG.layer("curlFieldB").curves();
  ICurve[] tanFieldA = IG.layer("tangentFieldA").curves();
  ICurve[] tanFieldB = IG.layer("tangentFieldB").curves();
  
  ICompoundField attrA = new ICompoundField();
  ICompoundField attrB = new ICompoundField();
  ICompoundField curlA = new ICompoundField();
  ICompoundField curlB = new ICompoundField();
  ICompoundField tanA = new ICompoundField();
  ICompoundField tanB = new ICompoundField();
  attrA.target(MyBoidA.class);
  attrB.target(MyBoidB.class);
  curlA.target(MyBoidA.class);
  curlB.target(MyBoidB.class);
  tanA.target(MyBoidA.class);
  tanB.target(MyBoidB.class);
  
  for(int i=0; i < attrFieldA.length; i++){
    attrA.add(new ICurveAttractorField(attrFieldA[i]).intensity(35));
    //attrFieldA[i].hide();
  }
  for(int i=0; i < attrFieldB.length; i++){
    attrB.add(new ICurveAttractorField(attrFieldB[i]).intensity(35));
    //attrFieldB[i].hide();
  }
  for(int i=0; i < curlFieldA.length; i++){
    curlA.add(new ICurveCurlField(curlFieldA[i]).intensity(20));
    //curlFieldA[i].hide();
  }
  for(int i=0; i < curlFieldB.length; i++){
    curlB.add(new ICurveCurlField(curlFieldB[i]).intensity(-20));
    //curlFieldB[i].hide();
  }
  for(int i=0; i < tanFieldA.length; i++){
    tanA.add(new ICurveTangentField(tanFieldA[i]).intensity(28));
    //tanFieldA[i].hide();
  }
  for(int i=0; i < tanFieldB.length; i++){
    tanB.add(new ICurveTangentField(tanFieldB[i]).intensity(28));
    //tanFieldB[i].hide();
  }
  new IGravity(0,0,2);
  
  SectionAgent sectAgent = new SectionAgent(new IVec(0,0,80), new IVec(0,0,1), 80);
  sectAgent.maxSectionNum = 10; // only for 10 sections
}

class MyBoid extends IBoidTrajectory{
  ArrayList< Node > nodes;
  IVec prevPos;
  MyBoid(IVec p, IVec v){
    super(p,v);
    alignment(0, 0);
    cohesion(2, 30);
    separation(4, 40);
    fric(0.01);
    
    nodes = new ArrayList< Node >();
    // no initial node is created nor fixed
  }
  
  MyBoid duplicate(){ //to be overriden by child class
    return new MyBoid(pos().cp(), vel().cp());
  }
  
  void update(){
    prevPos = pos().cp();
    // branching every 10 time frame at 7% probability
    if(IG.time()%10==0 && IRand.pct(7)){
      MyBoid b = duplicate(); // create new swarm
      // push away randomly
      b.push(IRand.dir(new IVec(0,0,1)).len(1000)); 
      if(nodes.size()>0){
        // share the last node to connect tension line from both
        b.nodes.add(nodes.get(nodes.size()-1));
      }
    }    
  }
  
  void addNode(Node n){
    if(nodes.size()>0){
      // connecting nodes with tension line
      new ITensionLine(n, nodes.get(nodes.size()-1)).tension(50).clr(this);
    }
    nodes.add(n);
  }
}

class MyBoidA extends MyBoid{
  MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
  MyBoid duplicate(){ //overriding parent's method
    return new MyBoidA(pos().cp(), vel().cp());
  }
}

class MyBoidB extends MyBoid{
  MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
  MyBoid duplicate(){ //overriding parent's method
    return new MyBoidB(pos().cp(), vel().cp());
  }
}

// particle agent for self-optimiztion which moves only on a specified plane. 
class Node extends IParticle{
  IVec planePt;
  IVec planeDir;
  
  Node(IVec p, IVec planeDirection) {
    super(p);
    planePt = p.cp();
    planeDir = planeDirection;
    fric(0.5);
    hide();
  }
  
  void postupdate(){ // postupdate is automatically called after all update() is done
    // keeping particle on a specified plane
    pos().projectToPlane(planeDir, planeDir, planePt);
  }
}

// agent to check planar intersection of swarm and generate nodes and compression structure.
class SectionAgent extends IAgent{
  IVec sectOrig; // first section point 
  IVec sectNml; // normal vector of intersection plane of section
  double interval; // interval distance between sections
  ArrayList< ArrayList< Node >> nodes;
  ArrayList< ArrayList< IStickLine >> sticks;
  ArrayList< Boolean > updateSection;
  int maxSectionNum = -1; // limit of sections counting from origin. if -1, no limit.
  
  SectionAgent(IVec sectOrigin, IVec sectNormal, double sectInterval){
    sectOrig = sectOrigin;
    interval = sectInterval;
    sectNml = sectNormal.cp().len(interval);
    nodes = new ArrayList< ArrayList< Node >>();
    sticks = new ArrayList< ArrayList< IStickLine >>();
    updateSection = new ArrayList< Boolean >();
  }
  
  void interact(ArrayList< IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof MyBoid){
        MyBoid b = (MyBoid)agents.get(i);
        if(b.prevPos!=null){
          double planeDist = b.pos().dif(sectOrig).dot(sectNml)/sectNml.len(); // dist to plane
          double prevPlaneDist = b.prevPos.dif(sectOrig).dot(sectNml)/sectNml.len();
          //check if swarm agent passes through any section planes
          if(planeDist >= 0 && prevPlaneDist >= 0 && (int)(planeDist/interval) != (int)(prevPlaneDist/interval) || prevPlaneDist <= 0 && planeDist > 0){
            int index1 = (int)(prevPlaneDist/interval);
            int index2 = (int)(planeDist/interval);
            int inc=1;
            if(prevPlaneDist<=0) index1=-1;
            if(index1>index2){ inc = -1; }
            for(int j=index1+inc; inc > 0 && j <= index2 || inc < 0 && j >= index2; j+=inc){
              addIntersection(b, j);
            }
          }
        }
      }
    }
    
    for(int i=0; i < updateSection.size(); i++){
      if(updateSection.get(i)){ // update only if new swarm enters into the section
        for(int j=0; j < sticks.get(i).size(); j++){
          sticks.get(i).get(j).del();
        }
        sticks.get(i).clear(); // clear old springs
        IDelaunay2D.maxDistToCheck = 250; // edge length limit
        // connect nodes with compression structures at Delaunay edges
        IVecI[][] edges = IDelaunay2D.getEdges(nodes.get(i).toArray(new Node[nodes.get(i).size()]), sectNml);
        for(int j=0; j < edges.length; j++){
           // add stick (compression) line structure between nodes
           sticks.get(i).add(new IStickLine((Node)edges[j][0],(Node)edges[j][1]).clr(.5,0,1));
        }
        updateSection.set(i,false);
      }
    }
  }
  
  void addIntersection(MyBoid b, int idx){
    if(idx < maxSectionNum){
      IVec lineDir = b.pos().dif(b.prevPos);
      IVec sectPos = sectOrig.cp().add(sectNml, idx);
      // calculate intersection
      IVec itxn = IVec.intersectPlaneAndLine(sectNml, sectPos,lineDir,b.pos());
      // create new node at the intersection
      Node n = new Node(itxn, sectNml); 
      b.addNode(n);
      for(int i=nodes.size(); i <= idx; i++){
        nodes.add(new ArrayList< Node >()); // set the size of array to idx+1
        sticks.add(new ArrayList< IStickLine >());
        updateSection.add(false);
      }
      nodes.get(idx).add(n);
      updateSection.set(idx, true); //set flag to update the section
      if(idx==maxSectionNum-1){
        b.del(); //at the last section, delete swarm agent
        n.fix().show(); //last node is fixed
      }
    }
  }
}


(back to the list of tutorials)

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