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

Network Growth Agent (requires iGeo version 0.9.0.3)

     Reproduction Based Agent and Branching

This tutorial describes design of agents who connect each other to form network linkage structure and also to seek emergence of clusters and network hubs.

We start with reroduction-base agents to design networking agents and later it's integrated with particle-based agents. The code below defines a reproduction-base agent who a new agent in a random location in a specified distance and make connection to it. One agent makes just one child agent and as a result, it becomes just a single string of agents. The agent inherets IPointAgent which has a property of point and a methodd pos().

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

void setup(){
  size(480,360,IG.GL);
  new NodeAgent(new IVec(0,0,0));
}

class NodeAgent extends IPointAgent{
  NodeAgent(IVec p){
    super(p);
  }
  
  void update(){
    if(time()==0){ //just once when it's created
      IVec dir = IRand.dir(10); //random direction with length 10
      IVec pos2 = pos().cp().add(dir); //new position by adding dir
      new ICurve(pos(), pos2); //link line
      new NodeAgent(pos2); //child agent
    }
  }
}

The agent in the next code creates a child agent in a different timing from the previous one. The previous code creates a child agent just once in the first time frame after it's created. On the other hand next one tries to create a child agent every time frame in a random probability of 1%.

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

void setup(){
  size(480,360,IG.GL);
  new NetworkAgent(new IVec(0,0,0));
}

class NetworkAgent extends IPointAgent{
  NetworkAgent(IVec p){
    super(p);
  }
  
  void update(){
    if(IRand.pct(1)){ //random probability of 1%
      IVec dir = IRand.dir(10);
      IVec pos2 = pos().cp().add(dir);
      new ICurve(pos(), pos2);
      new NetworkAgent(pos2);
    }
  }
}

As a result of this algorithm, one agent could create multiple child agents over time and branches of linked lines. The topological form of the network generated here is a tree structure and there is no loop in the network.

The next code generates the same network topology in a tree structure but the geometrical layout is controlled by orienting the direction to put a child agent towards the specified vector (1,0,0) (x-axis). The direction is calculated by the method IRand.dir(orientation, length, angle_range) and this method returns a vector towards a random direction within the angle range from the specified orientation and the vector length is also specified. As a result, it generates branches towards x-axis direction.

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

void setup(){
  size(480,360,IG.GL);
  new NodeAgent(new IVec(0,0,0));
}

class NodeAgent extends IPointAgent{
  NodeAgent(IVec p){
    super(p);
  }
  
  void update(){
    if(IRand.pct(1)){
      //random vector towards 1,0,0 within PI/4 range
      IVec dir = IRand.dir(new IVec(1,0,0), 10, PI/4); 
      IVec pos2 = pos().cp().add(dir);
      new ICurve(pos(), pos2);
      new NodeAgent(pos2);
    }
  }
}


     Connecting Existing Adjacent Agents

The codes in the previous section produce only tree structure. The following code allow an agent to connect to other existing agents than its parent or new child agents, which creates loops in the network. To implement this behavior, it uses the interact method which receives all existing agents and an agent can influence other agents' states and/or it changes its state influenced by others.

In the interact method, an agent checks all existing agents and pick ones whose type is NodeAgent. Then after excluding itself, it measures the distance between the agent and the other. If it's closer than the parameter linkThreshold, then draws a line as a link in 0.1% probability.

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

void setup(){
  size(480,360,IG.GL);
  new NodeAgent(new IVec(0,0,0));
}

class NodeAgent extends IPointAgent{
  double linkLength = 10; //distance of child agent
  double linkThreshold = 10; //distance threshold to connect existing agent

  NodeAgent(IVec p){
    super(p);
  }

  // connecting to existing agent
  void interact(ArrayList < IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof NodeAgent){ //check type of agent
        NodeAgent node = (NodeAgent)agents.get(i); //cast agent to NodeAgent type
        if(node != this){ //exclude itself
          if(node.pos().dist(pos()) < linkThreshold){ //closer than threshold
            if(IRand.pct(0.1)){ //in a probability of 0.1%
              new ICurve(node.pos(), pos()).clr(1.0,0,0); //red line
            }
          }
        }
      }
    }
  }

  // creating a child agent
  void update(){
    if(IRand.pct(1)){
      IVec dir = IRand.dir(linkLength);
      IVec pos2 = pos().cp().add(dir);
      new ICurve(pos(), pos2); //gray line
      new NodeAgent(pos2); //child agent
    }
  }
}

The next code simply add the interact method to the branching agent oriented towards x-axis direction in the previous section.

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

void setup(){
  size(480,360,IG.GL);
  new NodeAgent(new IVec(0,0,0));
}

class NodeAgent extends IPointAgent{
  double linkLength = 10; //distance of child agent
  double linkThreshold = 5; //distance threshold to connect existing agent

  NodeAgent(IVec p){
    super(p);
  }

  // connecting to existing agent
  void interact(ArrayList < IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof NodeAgent){ //check type of agent
        NodeAgent node = (NodeAgent)agents.get(i); //cast agent to NodeAgent type
        if(node != this){ //exclude itself
          if(node.pos().dist(pos()) < linkThreshold){ //closer than threshold
            if(IRand.pct(0.1)){ //in a probability of 0.1%
              new ICurve(node.pos(), pos()).clr(1.0,0,0); //red line
            }
          }
        }
      }
    }
  }

  // creating a child agent
  void update(){
    if(IRand.pct(1)){
      IVec dir = IRand.dir(new IVec(1,0,0), linkLength, PI/4);
      IVec pos2 = pos().cp().add(dir);
      new ICurve(pos(), pos2); //gray line
      new NodeAgent(pos2); //child agent
    }
  }
}

The next code modifies the previous one a little bit by adding a new property dir for each agent and let it set the property when it's created at the constructor NodeAgent(IVec p, IVec d). Then this property dir is used as orientation of the direction to create a new child.

In setup() method, two instances of NodeAgent are created with different starting locations and opposite direction for the input of dir. When two networks grow out of two different parents towards opposite direction, two networks are interconnected by the algorithm defined in interact method.

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

void setup(){
  size(480,360,IG.GL);
  new NodeAgent(new IVec(0,0,0), new IVec(1,0,0));
  new NodeAgent(new IVec(100,0,0), new IVec(-1,0,0));
}

class NodeAgent extends IPointAgent{
  double linkLength = 10; //distance of child agent
  double linkThreshold = 5; //distance threshold to connect existing agent

  IVec dir;

  NodeAgent(IVec p, IVec d){
    super(p);
    dir = d;
  }

  // connecting to existing agent
  void interact(ArrayList < IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof NodeAgent){ //check type of agent
        NodeAgent node = (NodeAgent)agents.get(i); //cast agent to NodeAgent type
        if(node != this){ //exclude itself
          if(node.pos().dist(pos()) < linkThreshold){ //closer than threshold
            if(IRand.pct(0.1)){ //in a probability of 0.1%
              new ICurve(node.pos(), pos()).clr(1.0,0,0); //red line
            }
          }
        }
      }
    }
  }

  // creating a child agent
  void update(){
    if(IRand.pct(1)){
      IVec dir2 = IRand.dir(dir, linkLength, PI/4);
      IVec pos2 = pos().cp().add(dir2);
      new ICurve(pos(), pos2); //gray line
      new NodeAgent(pos2,dir); //child agent
    }
  }
}


     Network Layout by Repulsion Force

The geometrical layout of the network produced so far was based on randomness. To have smoother network layout and nicely distributed network nodes, the node agent is changed to inherit IParticle class to move freely and generate repulsion force to other node agents. The link lines are also replaced with ITensionLine to cause tensile force between linked nodes. The code limit the time to let agents make new links and grow network by growthTime, to have time to smooth the network layout by repulsion and tensile forces.

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

void setup() {
  size(480, 360, IG.GL);
  new NodeAgent(new IVec(0, 0, 0));
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 8.5;
  double tension = 10; // tensile force of link
  double repulsion = 200; // repulsion force of node
  int growthTime = 500; // network growth stops at this time

  NodeAgent(IVec p) {
    super(p);
    fric(0.1); // 10% friction
  }
  
  void interact(ArrayList < IDynamics > agents) {
    for (int i=0; i < agents.size(); i++) {
      if (agents.get(i) instanceof NodeAgent) {
        NodeAgent node = (NodeAgent)agents.get(i);
        if (node != this) {
          if (IG.time() < growthTime) {
            if (node.pos().dist(pos()) < linkThreshold) {
              if (IRand.pct(0.1)) {
                new ITensionLine(node, this, tension).clr(1.0, 0, 0);
              }
            }
          }
          IVec dif = node.pos().dif(pos()); //vector to the other node
          if (dif.len() > 0) {
            dif.len(repulsion/dif.len2() ); //the closer, the larger
            node.push(dif);
          }
        }
      }
    }
  }
  
  void update() {
    if (IG.time() < growthTime) { //only for limited time
      if (IRand.pct(1)) {
        IVec dir = IRand.dir(linkLength);
        IVec pos2 = pos().cp().add(dir);
        NodeAgent child = new NodeAgent(pos2);
        new ITensionLine(this, child, tension);
      }
    }
  }
}

The code below is the directed version of node agent and two networks run against each other with their roots fixed by fix() method inside setup() method.

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

void setup() {
  size(480, 360, IG.GL);
  new NodeAgent(new IVec(0, 0, 0), new IVec(1,0,0)).fix().clr(0,0,1.0);
  new NodeAgent(new IVec(100, 0, 0), new IVec(-1,0,0)).fix().clr(0,0,1.0);
}

class NodeAgent extends IParticle {
  double linkLength = 20;
  double linkThreshold = 8.5;
  double tension = 10; // tensile force of link
  double repulsion = 200; // repulsion force of node
  int growthTime = 600; // network growth stops at this time

  IVec dir;

  NodeAgent(IVec p, IVec d) {
    super(p);
    dir = d;
    fric(0.1); // 10% friction
  }

  void interact(ArrayList < IDynamics > agents) {
    for (int i=0; i < agents.size(); i++) {
      if (agents.get(i) instanceof NodeAgent) {
        NodeAgent node = (NodeAgent)agents.get(i);
        if (node != this) {
          if (IG.time() < growthTime) {
            if (node.pos().dist(pos()) < linkThreshold) {
              if (IRand.pct(0.1)) {
                new ITensionLine(node, this, tension).clr(1.0, 0, 0);
              }
            }
          }
          IVec dif = node.pos().dif(pos()); //vector to the other node
          if (dif.len() > 0) {
            dif.len(repulsion/dif.len2() ); //the closer, the larger
            node.push(dif);
          }
        }
      }
    }
  }
  
  void update() {
    if (IG.time() < growthTime) { //only for limited time
      if (IRand.pct(1)) {
        IVec dir2 = IRand.dir(dir, linkLength, PI/4);
        IVec pos2 = pos().cp().add(dir2);
        NodeAgent child = new NodeAgent(pos2, dir);
        new ITensionLine(this, child, tension);
      }
    }
  }
}


     Preferential Attachment

Scientific research of networks in complex science revealed characteristics of networks observed in nature and human society, one of which is the power law distribution of degree of network nodes. This means that degree (number of connection) of most of nodes is very small but very small number of nodes have very high degree. This node with high degree is called 'hub' and provides important function in a network to connect various parts of network.

The procedure to generate this type of network is also researched and Barabasi-Albert model is known to produce the power law distribution in degree of nodes and it's based on preferential attachment process

We try to simulate the behavior to generate a network with similar degree distribution to those types of networks. For this purpose, we calculate the probability to make a connection by a degree. We also need to keep track of connected nodes to calculate a degree, which is a total number of connected nodes. Connected nodes and links are stored in ArrayList properties called children and links. The part of code to make a connection and to calculate a degree is implemented as methods of connect(NodeAgnet node) and degree().

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

void setup() {
  size(480, 360, IG.GL);
  new NodeAgent(new IVec(0, 0, 0));
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 10;
  double repulsion = 200;
  int growthTime = 350;
  ArrayList< NodeAgent > children; //to keep track of connected nodes
  ArrayList< ITensionLine > links; //to keep track of links

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >(); // initialize array list
    links = new ArrayList< ITensionLine >(); // initialize array list
  }
  // adding a method to make connection
  void connect(NodeAgent node) {
    if (!children.contains(node)) { //only when not connected yet
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(node.degree()*0.1, 0, 0);
    }
  }
  // adding a method to return degree (number of connection)
  int degree(){ return children.size(); }
  
  void interact(ArrayList < IDynamics > agents) {
    for (int i=0; i < agents.size(); i++) {
      if (agents.get(i) instanceof NodeAgent) {
        NodeAgent node = (NodeAgent)agents.get(i);
        if (node != this) {
          if (IG.time() < growthTime) {
            if (node.pos().dist(pos()) < linkThreshold) {
              double probability = sqrt(node.degree()-5)*0.05;
              if (IRand.pct(probability)) { //higher degree is more probable
                connect(node);
              }
            }
          }
          IVec dif = node.pos().dif(pos());
          if (dif.len() > 0) {
            dif.len(repulsion * degree() / dif.len2() );
            node.push(dif);
          }
        }
      }
    }
  }
  
  void update() {
    if (IG.time() < growthTime) {
      double probability = sqrt(degree()+1); 
      if (IRand.pct(probability)) { //higher degree is more probable
        IVec dir = IRand.dir(linkLength);
        IVec pos2 = pos().cp().add(dir);
        NodeAgent child = new NodeAgent(pos2);
        connect(child);
      }
    }
  }
}


     Reconstructing Network from File

When you generate a network by scripting, your network might not be fully connected but have some separated and fragmented pieces or there might be something you want to edit like removing / adding some specific links or nodes.

To do this, it's best to save the network in Processing into a Rhino file once as points and lines. Then you can edit it by removing / adding points and lines. The following script can read a Rhino 3DM file (version 4) and convert all points into NodeAgent and check all lines to find which nodes are connected. It can also check which node should be fixed by a layer name. If points are in "fix" layer, the node made out of these points are fixed. You can change the layer name in the script as well. Note that the script assumes all curve in the edited model are lines and if there are NURBS curves or polylines, the result might be unexpected. If you add new lines and if it contains polylines, they should be exploded in Rhino.

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

void setup() {
  size(480, 360, IG.GL);
  IG.open("network1.3dm");

  buildNetwork(IG.curves(), //all lines
               IG.points(), //all points (including ones in "fix" layer)
               IG.layer("fix").points()); //choosing points in "fix" layer
}

void buildNetwork(ICurve[] lines, IPoint[] points, IPoint[] fixPoints) {
  int pnum = points.length;
  int lnum = lines.length; 
  int fnum = fixPoints.length;

  NodeAgent[] agents = new NodeAgent[pnum];
  // creating node agents by points
  for (int i=0; i < pnum; i++) {
    agents[i] = new NodeAgent(points[i].pos());
  }
  // linking agents by lines
  for (int i=0; i < lnum; i++) {
    IVec pt1 = lines[i].cp(0);
    IVec pt2 = lines[i].cp(1);
    double tolerance = 0.1;
    boolean found=false;
    for (int j=0; j < pnum && !found; j++) {
      if (agents[j].pos().dist(pt1) < tolerance) {
        for (int k=0; k < pnum && !found; k++) {
          if (agents[k].pos().dist(pt2) < tolerance) {
            agents[j].connect(agents[k]); 
            found=true;
          }
        }
      }
    }
    lines[i].del();
  }
  // fixing agents
  for (int i=0; i < pnum; i++) {
    for (int j=0; j < fnum; j++) {
      if (points[i] == fixPoints[j]) {
        agents[i].fix().clr(0,1.0,1.0);
      }
    }
    points[i].del();
  }
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 10;
  double repulsion = 200;
  int growthTime = 0; // no growth
  ArrayList< NodeAgent > children;
  ArrayList< ITensionLine > links;

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >();
    links = new ArrayList< ITensionLine >();
  }

  void connect(NodeAgent node) {
    if (!children.contains(node)) {
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(degree()*0.1, 0, 0);
    }
  }
  
  int degree(){ return children.size(); }
  
  void interact(ArrayList < IDynamics > agents) {
    for (int i=0; i < agents.size(); i++) {
      if (agents.get(i) instanceof NodeAgent) {
        NodeAgent node = (NodeAgent)agents.get(i);
        if (node != this) {
          if (IG.time() < growthTime) {
            if (node.pos().dist(pos()) < linkThreshold) {
              double probability = sqrt(node.degree()-5)*0.05;
              if (IRand.pct(probability)) {
                connect(node);
              }
            }
          }
          IVec dif = node.pos().dif(pos());
          if (dif.len() > 0) {
            dif.len(repulsion * degree() / dif.len2() );
            node.push(dif);
          }
        }
      }
    }
  }

  void update() {
    if (IG.time() < growthTime) {
      double probability = sqrt(degree()+1);
      if (IRand.pct(probability)) {
        IVec dir = IRand.dir(linkLength);
        IVec pos2 = pos().cp().add(dir);
        NodeAgent child = new NodeAgent(pos2);
        connect(child);
      }
    }
  }
}

You can edit a network by deleting points, adding points, deleting lines , drawing lines, moving points in "fix" layer in Rhino. When you reconstruct the edited model in Processing, you see new geometric network form under the new topology you made.

The following example does not only reconstruct a network but also adds some forces to deform the geometric network form. The following code adds IGravity upwards to simulate influence of the force in the tensile line network.

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

void setup() {
  size(480, 360, IG.GL);
  IG.open("network2.3dm");

  buildNetwork(IG.curves(), IG.points(), IG.layer("fix").points());

  new IGravity(0, 0, 0.2); // upward gravity
}

void buildNetwork(ICurve[] lines, IPoint[] points, IPoint[] fixPoints) {
  int pnum = points.length;
  int lnum = lines.length; 
  int fnum = fixPoints.length;

  NodeAgent[] agents = new NodeAgent[pnum];
  // creating node agents by points
  for (int i=0; i < pnum; i++) {
    agents[i] = new NodeAgent(points[i].pos());
  }
  // linking agents by lines
  for (int i=0; i < lnum; i++) {
    IVec pt1 = lines[i].cp(0);
    IVec pt2 = lines[i].cp(1);
    double tolerance = 0.1;
    boolean found=false;
    for (int j=0; j < pnum && !found; j++) {
      if (agents[j].pos().dist(pt1) < tolerance) {
        for (int k=0; k < pnum && !found; k++) {
          if (agents[k].pos().dist(pt2) < tolerance) {
            agents[j].connect(agents[k]); 
            found=true;
          }
        }
      }
    }
    lines[i].del();
  }
  // fixing agents
  for (int i=0; i < pnum; i++) {
    for (int j=0; j < fnum; j++) {
      if (points[i] == fixPoints[j]) {
        agents[i].fix().clr(0,1.0,1.0);
      }
    }
    points[i].del();
  }
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 10;
  double repulsion = 200;
  int growthTime = 0; // no growth
  ArrayList< NodeAgent > children;
  ArrayList< ITensionLine > links;

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >();
    links = new ArrayList< ITensionLine >();
  }

  void connect(NodeAgent node) {
    if (!children.contains(node)) {
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(degree()*0.1, 0, 0);
    }
  }
  
  int degree(){ return children.size(); }
  
  void interact(ArrayList < IDynamics > agents) {
    for (int i=0; i < agents.size(); i++) {
      if (agents.get(i) instanceof NodeAgent) {
        NodeAgent node = (NodeAgent)agents.get(i);
        if (node != this) {
          if (IG.time() < growthTime) {
            if (node.pos().dist(pos()) < linkThreshold) {
              double probability = sqrt(node.degree()-5)*0.05;
              if (IRand.pct(probability)) {
                connect(node);
              }
            }
          }
          IVec dif = node.pos().dif(pos());
          if (dif.len() > 0) {
            dif.len(repulsion * degree() / dif.len2() );
            node.push(dif);
          }
        }
      }
    }
  }

  void update() {
    if (IG.time() < growthTime) {
      double probability = sqrt(degree()+1);
      if (IRand.pct(probability)) {
        IVec dir = IRand.dir(linkLength);
        IVec pos2 = pos().cp().add(dir);
        NodeAgent child = new NodeAgent(pos2);
        connect(child);
      }
    }
  }
}


     Adding Geometry on Nodes and Links

When you get a network modeled by those methods above, the result you get is lines and points. If you want to make a 3D model with surfaces and solids, you need to model those out of points and lines.

If you can model surfaces and solids only by points and lines without checking the connection, you don't need to reconstruct the network but if you use information of connection like number of connection in each node or direction of lines connected to a node, you need to reconstruct a whole network once and then use the node and connection information to model new geometries.

The following code add a sphere at each node and its radius is calculated by a number of connection of the node. A node with more connection gets a larger sphere. The lines are simply converted into cylindrical pipe surfaces with a constant pipe radius.

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

void setup() {
  size(480, 360, IG.GL);
  IG.open("network3.3dm");
  IG.duration(0); // no force simulation
  buildNetwork(IG.curves(), IG.points(), IG.layer("fix").points());
}

void buildNetwork(ICurve[] lines, IPoint[] points, IPoint[] fixPoints) {
  int pnum = points.length;
  int lnum = lines.length; 
  int fnum = fixPoints.length;

  NodeAgent[] agents = new NodeAgent[pnum];
  // creating node agents by points
  for (int i=0; i < pnum; i++) {
    agents[i] = new NodeAgent(points[i].pos());
  }
  // linking agents by lines
  for (int i=0; i < lnum; i++) {
    IVec pt1 = lines[i].cp(0);
    IVec pt2 = lines[i].cp(1);
    double tolerance = 0.1;
    boolean found=false;
    for (int j=0; j < pnum && !found; j++) {
      if (agents[j].pos().dist(pt1) < tolerance) {
        for (int k=0; k < pnum && !found; k++) {
          if (agents[k].pos().dist(pt2) < tolerance) {
            agents[j].connect(agents[k]); 
            found=true;
          }
        }
      }
    }
    lines[i].del();
  }
  // fixing agents
  for (int i=0; i < pnum; i++) {
    for (int j=0; j < fnum; j++) {
      if (points[i] == fixPoints[j]) {
        agents[i].fix().clr(0,1.0,1.0);
      }
    }
    points[i].del();
  }
  // adding geometry at nodes
  for(int i=0; i < pnum; i++){
    new ISphere(agents[i].pos(), agents[i].degree());
    agents[i].hide(); // hide points
  }
  // adding geometry on links
  for(int i=0; i < lnum; i++){
    IG.pipe(lines[i].cp(0), lines[i].cp(1), 0.5);
  }
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 10;
  double repulsion = 200;
  int growthTime = 0; // no growth
  ArrayList< NodeAgent > children;
  ArrayList< ITensionLine > links;

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >();
    links = new ArrayList< ITensionLine >();
  }

  void connect(NodeAgent node) {
    if (!children.contains(node)) {
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(degree()*0.1, 0, 0);
    }
  }
  
  int degree(){ return children.size(); }
}

The following code add a polygon mesh polyhedron at each node. Each vertex of the polyhedron is on the connection line and the size of polyhedron is proportional to a number of connection. The lines have triangular mesh stick with a constant width.

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

void setup() {
  size(480, 360, IG.GL);
  IG.open("network3.3dm");
  IG.duration(0); // no force simulation
  buildNetwork(IG.curves(), IG.points(), IG.layer("fix").points());
}

void buildNetwork(ICurve[] lines, IPoint[] points, IPoint[] fixPoints) {
  int pnum = points.length;
  int lnum = lines.length; 
  int fnum = fixPoints.length;

  NodeAgent[] agents = new NodeAgent[pnum];
  // creating node agents by points
  for (int i=0; i < pnum; i++) {
    agents[i] = new NodeAgent(points[i].pos());
  }
  // linking agents by lines
  for (int i=0; i < lnum; i++) {
    IVec pt1 = lines[i].cp(0);
    IVec pt2 = lines[i].cp(1);
    double tolerance = 0.1;
    boolean found=false;
    for (int j=0; j < pnum && !found; j++) {
      if (agents[j].pos().dist(pt1) < tolerance) {
        for (int k=0; k < pnum && !found; k++) {
          if (agents[k].pos().dist(pt2) < tolerance) {
            agents[j].connect(agents[k]); 
            found=true;
          }
        }
      }
    }
    lines[i].del();
  }
  // fixing agents
  for (int i=0; i < pnum; i++) {
    for (int j=0; j < fnum; j++) {
      if (points[i] == fixPoints[j]) {
        agents[i].fix().clr(0,1.0,1.0);
      }
    }
    points[i].del();
  }
  // adding geometry at nodes
  for (int i=0; i < pnum; i++) {
    int deg = agents[i].degree();
    if (deg > 0) {
      IVec[] vertices = new IVec[deg];
      for (int j=0; j < deg; j++) {
        IVec childPos = agents[i].children.get(j).pos();
        // set vertex distance by degree
        vertices[j] = childPos.dif(agents[i].pos()).len(deg).add(agents[i].pos());
      }
      if ( deg == 3) {
        IVec nml = vertices[0].nml(vertices[1], vertices[2]);
        IVec center = agents[i].pos().cp();
        center.add(nml.len(vertices[0].dist(center)/2));
        IVec[] vertices2 = new IVec[4];
        for (int j=0; j < 3; j++) { vertices2[j] = vertices[j]; } 
        vertices2[3] = center;
        vertices = vertices2;
      }
      else if ( deg == 2) {
        IVec dif = vertices[1].dif(vertices[0]);
        IVec t = dif.cross(IG.zaxis);
        if (t.len()==0) { t = dif.cross(IG.yaxis); }
        t.len(dif.len()/4);
        IVec[] vertices2 = new IVec[5];
        for (int j=0; j < 2; j++) { vertices2[j] = vertices[j]; } 
        vertices2[2] = agents[i].pos().cp().add(t);
        t.rot(dif, PI*2/3);
        vertices2[3] = agents[i].pos().cp().add(t);
        t.rot(dif, PI*2/3);
        vertices2[4] = agents[i].pos().cp().add(t);
        vertices = vertices2;
      }
      else if (deg == 1) {
        IVec dif = vertices[0].dif(agents[i].pos());
        IVec t = dif.cross(IG.zaxis);
        if (t.len()==0) { t = dif.cross(IG.yaxis); }
        t.len(dif.len()/2);
        IVec[] vertices2 = new IVec[4];
        vertices2[0] = vertices[0];
        vertices2[1] = agents[i].pos().dup().add(t);
        t.rot(dif, PI*2/3);
        vertices2[2] = agents[i].pos().dup().add(t);
        t.rot(dif, PI*2/3);
        vertices2[3] = agents[i].pos().dup().add(t);
        vertices = vertices2;
      }
      IMesh.polyhedron(vertices);
    }
    agents[i].hide(); // hide points
  }
  // adding geometry on links
  for (int i=0; i < lnum; i++) {
    IG.meshPolygonStick(lines[i].cp(0), lines[i].cp(1), 1, 3);
  }
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 10;
  double repulsion = 200;
  int growthTime = 0; // no growth
  ArrayList< NodeAgent > children;
  ArrayList< ITensionLine > links;

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >();
    links = new ArrayList< ITensionLine >();
  }

  void connect(NodeAgent node) {
    if (!children.contains(node)) {
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(degree()*0.1, 0, 0);
    }
  }
  
  int degree(){ return children.size(); }
}

The following code is very similar to the previous one but this one add a polygon mesh polyhedron whose vertices are at the location of other connected nodes.

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

void setup() {
  size(480, 360, IG.GL);
  IG.open("network3.3dm");
  IG.duration(0); // no force simulation
  buildNetwork(IG.curves(), IG.points(), IG.layer("fix").points());
}

void buildNetwork(ICurve[] lines, IPoint[] points, IPoint[] fixPoints) {
  int pnum = points.length;
  int lnum = lines.length; 
  int fnum = fixPoints.length;

  NodeAgent[] agents = new NodeAgent[pnum];
  // creating node agents by points
  for (int i=0; i < pnum; i++) {
    agents[i] = new NodeAgent(points[i].pos());
  }
  // linking agents by lines
  for (int i=0; i < lnum; i++) {
    IVec pt1 = lines[i].cp(0);
    IVec pt2 = lines[i].cp(1);
    double tolerance = 0.1;
    boolean found=false;
    for (int j=0; j < pnum && !found; j++) {
      if (agents[j].pos().dist(pt1) < tolerance) {
        for (int k=0; k < pnum && !found; k++) {
          if (agents[k].pos().dist(pt2) < tolerance) {
            agents[j].connect(agents[k]); 
            found=true;
          }
        }
      }
    }
    lines[i].del();
  }
  // fixing agents
  for (int i=0; i < pnum; i++) {
    for (int j=0; j < fnum; j++) {
      if (points[i] == fixPoints[j]) {
        agents[i].fix().clr(0,1.0,1.0);
      }
    }
    points[i].del();
  }
  // adding geometry at nodes
  for (int i=0; i < pnum; i++) {
    int deg = agents[i].degree();
    if (deg > 0) {
      IVec[] vertices = new IVec[deg];
      for (int j=0; j < deg; j++) {
        IVec childPos = agents[i].children.get(j).pos();
        // set vertex distance by degree
        vertices[j] = childPos;
      }
      if ( deg == 3) {
        IVec nml = vertices[0].nml(vertices[1], vertices[2]);
        IVec center = agents[i].pos().cp();
        center.add(nml.len(vertices[0].dist(center)/2));
        IVec[] vertices2 = new IVec[4];
        for (int j=0; j < 3; j++) { vertices2[j] = vertices[j]; } 
        vertices2[3] = center;
        vertices = vertices2;
      }
      else if ( deg == 2) {
        IVec dif = vertices[1].dif(vertices[0]);
        IVec t = dif.cross(IG.zaxis);
        if (t.len()==0) { t = dif.cross(IG.yaxis); }
        t.len(dif.len()/4);
        IVec[] vertices2 = new IVec[5];
        for (int j=0; j < 2; j++) { vertices2[j] = vertices[j]; } 
        vertices2[2] = agents[i].pos().cp().add(t);
        t.rot(dif, PI*2/3);
        vertices2[3] = agents[i].pos().cp().add(t);
        t.rot(dif, PI*2/3);
        vertices2[4] = agents[i].pos().cp().add(t);
        vertices = vertices2;
      }
      else if (deg == 1) {
        IVec dif = vertices[0].dif(agents[i].pos());
        IVec t = dif.cross(IG.zaxis);
        if (t.len()==0) { t = dif.cross(IG.yaxis); }
        t.len(dif.len()/2);
        IVec[] vertices2 = new IVec[4];
        vertices2[0] = vertices[0];
        vertices2[1] = agents[i].pos().dup().add(t);
        t.rot(dif, PI*2/3);
        vertices2[2] = agents[i].pos().dup().add(t);
        t.rot(dif, PI*2/3);
        vertices2[3] = agents[i].pos().dup().add(t);
        vertices = vertices2;
      }
      IMesh.polyhedron(vertices);
    }
    agents[i].hide(); // hide points
  }
  // adding geometry on links
  for (int i=0; i < lnum; i++) {
    IG.meshPolygonStick(lines[i].cp(0), lines[i].cp(1), 1, 3);
  }
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 10;
  double repulsion = 200;
  int growthTime = 0; // no growth
  ArrayList< NodeAgent > children;
  ArrayList< ITensionLine > links;

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >();
    links = new ArrayList< ITensionLine >();
  }

  void connect(NodeAgent node) {
    if (!children.contains(node)) {
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(degree()*0.1, 0, 0);
    }
  }
  
  int degree(){ return children.size(); }
}

The following code uses an input Rhino file. The rhino file contains one polygon mesh and this mesh is copied to each node and scaled by a factor porportional to a number of connection, and rotated towards one of link directions. Triangular truss like geometries are modeled programatically out of input lines but the truss width is calculated also by a number of connection of end point nodes.

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

void setup() {
  size(480, 360, IG.GL);
  IG.open("network3.3dm");
  IG.open("mesh_part1.3dm");
  IG.duration(0); // no force simulation
  buildNetwork(IG.curves(), 
	       IG.points(), 
	       IG.layer("fix").points(), 
	       IG.mesh(0));
}

void buildNetwork(ICurve[] lines, IPoint[] points, IPoint[] fixPoints, IMesh nodeGeometry) {
  int pnum = points.length;
  int lnum = lines.length; 
  int fnum = fixPoints.length;

  NodeAgent[] agents = new NodeAgent[pnum];
  // creating node agents by points
  for (int i=0; i < pnum; i++) {
    agents[i] = new NodeAgent(points[i].pos());
  }
  // linking agents by lines
  NodeAgent[][] linkedNodePairs = new NodeAgent[lnum][2];
  for (int i=0; i < lnum; i++) {
    IVec pt1 = lines[i].cp(0);
    IVec pt2 = lines[i].cp(1);
    double tolerance = 0.1;
    boolean found=false;
    for (int j=0; j < pnum && !found; j++) {
      if (agents[j].pos().dist(pt1) < tolerance) {
        for (int k=0; k < pnum && !found; k++) {
          if (agents[k].pos().dist(pt2) < tolerance) {
            agents[j].connect(agents[k]); 
            linkedNodePairs[i][0] = agents[j];
            linkedNodePairs[i][1] = agents[k];
            found=true;
          }
        }
      }
    }
    lines[i].del();
  }
  // fixing agents
  for (int i=0; i < pnum; i++) {
    for (int j=0; j < fnum; j++) {
      if (points[i] == fixPoints[j]) {
        agents[i].fix().clr(0,1.0,1.0);
      }
    }
    points[i].del();
  }
  // adding geometry at nodes
  for (int i=0; i < pnum; i++) {
    int deg = agents[i].degree();
    if (deg > 0) {
      IMesh nodeMesh = nodeGeometry.cp();
      nodeMesh.add(agents[i].pos().dif(nodeGeometry.center()));
      nodeMesh.scale(agents[i].pos(), agents[i].degree()*1.5);
      double angle = agents[i].children.get(0).pos().dif(agents[i].pos()).angle(IG.xaxis, IG.zaxis);
      nodeMesh.rot(agents[i].pos(), IG.zaxis, angle); 
      nodeMesh.clr(agents[i].degree()*0.03); 
    }
    agents[i].hide(); // hide points
  }
  nodeGeometry.del();
  // adding geometry on links
  for (int i=0; i < lnum; i++) {
    IVec pt1 = linkedNodePairs[i][0].pos();
    IVec pt2 = linkedNodePairs[i][1].pos();
    int deg1 = linkedNodePairs[i][0].degree();
    int deg2 = linkedNodePairs[i][1].degree();
    
    IVec dir = pt2.dif(pt1);
    IVec sideDir = dir.cross(IG.zaxis);
    if(dir.isParallel(IG.zaxis)){ sideDir = new IVec(1,0,0); }
    
    int trussLineNum = 3;
    double radius1 = deg1*0.8;
    double radius2 = deg2*0.8;
    ICurve[] trussLines = new ICurve[trussLineNum];
    for(int j=0; j < trussLineNum; j++){
      IVec linePt1 = pt1.cp().add(sideDir.cp().rot(dir, 2*PI*j/trussLineNum).len(radius1));
      IVec linePt2 = pt2.cp().add(sideDir.cp().rot(dir, 2*PI*j/trussLineNum).len(radius2));
      trussLines[j] = new ICurve(linePt1, linePt2);
    }
    double trussSpacing = 10;
    double length = dir.len();
    int trussSegNum = (int)(length/trussSpacing);
    if(trussSegNum == 0){ trussSegNum = 1; }
    double trussRadius = 0.3;
    
    for(int j=0; j < trussLineNum; j++){
      for(int k=0; k <= trussSegNum; k++){
        IVec trussPt1 = trussLines[j].pt( 1.0*k/trussSegNum );
        IVec trussPt2 = trussLines[(j+1)%trussLineNum].pt( 1.0*k/trussSegNum );
        IG.meshSquareStick(trussPt1, trussPt2, trussRadius);
        if(k < trussSegNum){
          IVec trussPt3 = trussLines[j].pt( 1.0*(k+1)/trussSegNum );
          IG.meshSquareStick(trussPt3, trussPt2, trussRadius);
        }
      }
      IG.meshSquareStick(trussLines[j].pt(0), trussLines[j].pt(1), trussRadius);
    }
  }
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 10;
  double repulsion = 200;
  int growthTime = 0; // no growth
  ArrayList< NodeAgent > children;
  ArrayList< ITensionLine > links;

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >();
    links = new ArrayList< ITensionLine >();
  }

  void connect(NodeAgent node) {
    if (!children.contains(node)) {
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(degree()*0.1, 0, 0);
    }
  }
  
  int degree(){ return children.size(); }
}


     Swarm and Network

Besides making geometry out of network nodes and lines directly, you can also use network geometry as an environment for swarm to interact with and then let swarm agents make geomtry.

The next code reconstruct a network and convert network link lines as curve tangent fields. Boid agents are created at nodes which have many connections and at fixed nodes, and they fly around the network influenced by the force field made by the network.

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

void setup() {
  size(480, 360, IG.GL);
  IG.open("network3.3dm");
  IG.bg(0);
  buildNetwork(IG.curves(), IG.points(), IG.layer("fix").points());
}

void buildNetwork(ICurve[] lines, IPoint[] points, IPoint[] fixPoints) {
  int pnum = points.length;
  int lnum = lines.length; 
  int fnum = fixPoints.length;

  NodeAgent[] agents = new NodeAgent[pnum];
  // creating node agents by points
  for (int i=0; i < pnum; i++) {
    agents[i] = new NodeAgent(points[i].pos());
  }
  // linking agents by lines
  for (int i=0; i < lnum; i++) {
    IVec pt1 = lines[i].cp(0);
    IVec pt2 = lines[i].cp(1);
    double tolerance = 0.1;
    boolean found=false;
    for (int j=0; j < pnum && !found; j++) {
      if (agents[j].pos().dist(pt1) < tolerance) {
        for (int k=0; k < pnum && !found; k++) {
          if (agents[k].pos().dist(pt2) < tolerance) {
            agents[j].connect(agents[k]); 
            found=true;
          }
        }
      }
    }
    lines[i].del();
  }
  // fixing agents
  for (int i=0; i < pnum; i++) {
    for (int j=0; j < fnum; j++) {
      if (points[i] == fixPoints[j]) {
        agents[i].fix().clr(0,1.0,1.0);
      }
    }
    points[i].del();
  }
  // making force field
  ICompoundField tangentField = new ICompoundField();
  ICompoundField attractorField = new ICompoundField();
  for(int i=0; i < lnum; i++){
    tangentField.add(new ICurveTangentField(lines[i]).intensity(200).gauss(50).bidirectional(true));
    attractorField.add(new ICurveAttractorField(lines[i]).intensity(100).gauss(100));
  }

  for(int i=0; i < pnum; i++){
    if(agents[i].degree() > 6){
      int particleNum = agents[i].degree()*5;
      for(int j=0; j < particleNum; j++){
        IVec particlePos = agents[i].pos().cp().add(IRand.dir(2.0)); 
        IVec particleVel = IRand.dir(30);
        IParticleTrajectory particle = new IParticleTrajectory(particlePos, particleVel);
        particle.fric(0.1).clr(1.0,0.5);
      } 
    }
    else if( agents[i].fixed() ){
      int particleNum = agents[i].degree()*5;
      for(int j=0; j < particleNum; j++){
        IVec particlePos = agents[i].pos().cp().add(IRand.dir(2.0)); 
        IVec particleVel = IRand.dir(IG.zaxis, 50, PI/4);
        IParticleTrajectory particle = new IParticleTrajectory(particlePos, particleVel);
        particle.fric(0.1).clr(1.0,0.5);
      }
    }
  }
  
  for(int i=0; i < pnum; i++){
    agents[i].del(); // not to move
  }
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 0;
  double repulsion = 200;
  int growthTime = 0; // no growth
  ArrayList< NodeAgent > children;
  ArrayList< ITensionLine > links;

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >();
    links = new ArrayList< ITensionLine >();
  }

  void connect(NodeAgent node) {
    if (!children.contains(node)) {
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(degree()*0.1, 0, 0);
    }
  }
  
  int degree(){ return children.size(); }
}

The following code let swarm agents build more geometry by drawing lines to close neighbors.

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

void setup() {
  size(480, 360, IG.GL);
  IG.open("network3.3dm");
  buildNetwork(IG.curves(), IG.points(), IG.layer("fix").points());
}

void buildNetwork(ICurve[] lines, IPoint[] points, IPoint[] fixPoints) {
  int pnum = points.length;
  int lnum = lines.length; 
  int fnum = fixPoints.length;

  NodeAgent[] agents = new NodeAgent[pnum];
  // creating node agents by points
  for (int i=0; i < pnum; i++) {
    agents[i] = new NodeAgent(points[i].pos());
  }
  // linking agents by lines
  for (int i=0; i < lnum; i++) {
    IVec pt1 = lines[i].cp(0);
    IVec pt2 = lines[i].cp(1);
    double tolerance = 0.1;
    boolean found=false;
    for (int j=0; j < pnum && !found; j++) {
      if (agents[j].pos().dist(pt1) < tolerance) {
        for (int k=0; k < pnum && !found; k++) {
          if (agents[k].pos().dist(pt2) < tolerance) {
            agents[j].connect(agents[k]); 
            found=true;
          }
        }
      }
    }
    lines[i].del();
  }
  // fixing agents
  for (int i=0; i < pnum; i++) {
    for (int j=0; j < fnum; j++) {
      if (points[i] == fixPoints[j]) {
        agents[i].fix().clr(0,1.0,1.0);
      }
    }
    points[i].del();
  }
  
  // making force field
  ICompoundField tangentField = new ICompoundField();
  ICompoundField attractorField = new ICompoundField();
  for(int i=0; i < lnum; i++){
    tangentField.add(new ICurveTangentField(lines[i]).intensity(200).gauss(50).bidirectional(true));
    attractorField.add(new ICurveAttractorField(lines[i]).intensity(100).gauss(100));
  }
  // making particles
  for(int i=0; i < pnum; i++){
    if(agents[i].degree() > 6){
      int particleNum = agents[i].degree()*5;
      for(int j=0; j < particleNum; j++){
        IVec particlePos = agents[i].pos().cp().add(IRand.dir(2.0)); 
        IVec particleVel = IRand.dir(30);
        MyParticle particle = new MyParticle(particlePos, particleVel);
        particle.fric(0.1).clr(1.0,0.5);
      } 
    }
    else if( agents[i].fixed() ){
      int particleNum = agents[i].degree()*5;
      for(int j=0; j < particleNum; j++){
        IVec particlePos = agents[i].pos().cp().add(IRand.dir(2.0)); 
        IVec particleVel = IRand.dir(IG.zaxis, 50, PI/4);
        MyParticle particle = new MyParticle(particlePos, particleVel);
        particle.fric(0.1).clr(1.0,0.5);
      }
    }
  }
  
  for(int i=0; i < pnum; i++){
    agents[i].del(); // not to move
  }
}

class MyParticle extends IParticle{
  int timeInterval = 2;
  IVec prevPos;
  MyParticle(IVec pos, IVec vel){
    super(pos, vel);
    prevPos = pos.cp();
    hide(); // hide point
  }
  
  void interact(ArrayList < IDynamics > agents){
    for(int i=0; i < agents.size(); i++){
      if(agents.get(i) instanceof MyParticle){
        MyParticle p = (MyParticle) agents.get(i);
        if(p != this){
          if(time()%timeInterval == 0 && time() > 10){
            if(p.pos().dist(pos()) < 3 && p.pos().dist(pos()) > 1){
              new ICurve(p.pos().cp(), pos.cp()).clr(0.2,0.7,0);
            }
          }
        }
      }
    }
  }
  
  void update(){
    if(time()%timeInterval==0 && time()>0){
      new ICurve(prevPos, pos().cp()).clr(0,0.5,0);
      prevPos = pos().cp();
    }
  }
}

class NodeAgent extends IParticle {
  double linkLength = 10;
  double linkThreshold = 25;
  double tension = 0;
  double repulsion = 200;
  int growthTime = 0; // no growth
  ArrayList< NodeAgent > children;
  ArrayList< ITensionLine > links;

  NodeAgent(IVec p) {
    super(p);
    fric(0.1);
    children = new ArrayList< NodeAgent >();
    links = new ArrayList< ITensionLine >();
  }

  void connect(NodeAgent node) {
    if (!children.contains(node)) {
      children.add(node);
      node.children.add(this);
      ITensionLine link = new ITensionLine(this, node, tension);
      links.add(link);
      node.links.add(link);
      clr(degree()*0.1, 0, 0);
      node.clr(degree()*0.1, 0, 0);
    }
  }
  
  int degree(){ return children.size(); }
}

You can put mesh pipes sticks but it would be heavy process to run at the same time and in this case, you'd separate the process by saving the lines once and run another script to put mesh geometry.

You can also combine other geometries produced by a network in a diffent way together.


(back to the list of tutorials)

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