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