Tutorials | (back to the list of tutorials) |
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);
}
}
}
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 } } }
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); } } } }
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); } } } }
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);
}
}
}
}
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(); } }
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.