Tutorials | (back to the list of tutorials) |
Swarm Formation by Force Fields
A(g)ntense by ATLV
Venice Beach Canopy by ATLV
First, the code below generates swarm formation curves by two different swarm classes and by force fields generated randomly in the codes. Multiple force fields are combined by compound fields to respond the closest force field. There are a compound attractor field and a compound curl field for MyBoidA and also for MyBoidB. The rotation directions of the curl fields are opposite for MyBoidA and MyBoidB.
![]()
![]()
![]()
import igeo.*;
import processing.opengl.*;
void setup(){
size(480,360,IG.GL);
for(int i=0; i < 25; i++){
new MyBoidA(IRand.pt(-300,-300,0,300,300,0), IRand.pt(-10,-10,-20,10,10,-20));
new MyBoidB(IRand.pt(-300,-300,0,300,300,0), IRand.pt(-10,-10,-20,10,10,-20));
}
// generating force fields by attractors and curl fields
ICompoundField curlA = new ICompoundField();
ICompoundField curlB = new ICompoundField();
ICompoundField attrA = new ICompoundField();
ICompoundField attrB = new ICompoundField();
curlA.target(MyBoidA.class);
curlB.target(MyBoidB.class);
attrA.target(MyBoidA.class);
attrB.target(MyBoidB.class);
for(int i=0; i < 10; i++){
IVec pt = IRand.pt(-100,-100,-800,100,100,-100);
curlA.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(20));
curlB.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(-20));
attrA.add(new IAttractor(pt).intensity(20));
attrB.add(new IAttractor(pt).intensity(20));
}
new IGravity(0,0,-2);
}
class MyBoid extends IBoidTrajectory{
MyBoid(IVec p, IVec v){
super(p,v);
alignment(0, 0);
cohesion(2, 40);
separation(4, 50);
fric(0.01);
}
}
class MyBoidA extends MyBoid{
MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}
class MyBoidB extends MyBoid{
MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}
Swarm Formation and Force Field from Input Geometry
The example code below uses an input Rhino file to generate force fields and
set initial positions of swarm agents.
Two sets of points on specific layer names are imported and the code puts
swarm agents of MyBoidA and MyBoidB at the point locations.
Curves on the six different layers are imported to generate force fields.
Curve attractor fields, curve tangent fields, curve curl fields for MyBoidA and MyBoidB are
instantiated.
The sample input file used in the code is this file.
field_lines1.3dm
![]()
![]()
![]()
import igeo.*;
import processing.opengl.*;
void setup(){
size(480,360,IG.GL);
// import rhino file to set up force fields
IG.open("field_lines1.3dm");
// points to specify intial position of swarm
IPoint[] ptsA = IG.layer("startPointA").points();
IPoint[] ptsB = IG.layer("startPointB").points();
for(int i=0; i < ptsA.length; i++){
new MyBoidA(ptsA[i].pos().cp(), new IVec(0,0,0));
}
for(int i=0; i < ptsB.length; i++){
new MyBoidB(ptsB[i].pos().cp(), new IVec(0,0,0));
}
// curves to specify force fields
ICurve[] attrFieldA = IG.layer("attractorA").curves();
ICurve[] attrFieldB = IG.layer("attractorB").curves();
ICurve[] curlFieldA = IG.layer("curlFieldA").curves();
ICurve[] curlFieldB = IG.layer("curlFieldB").curves();
ICurve[] tanFieldA = IG.layer("tangentFieldA").curves();
ICurve[] tanFieldB = IG.layer("tangentFieldB").curves();
ICompoundField attrA = new ICompoundField();
ICompoundField attrB = new ICompoundField();
ICompoundField curlA = new ICompoundField();
ICompoundField curlB = new ICompoundField();
ICompoundField tanA = new ICompoundField();
ICompoundField tanB = new ICompoundField();
attrA.target(MyBoidA.class);
attrB.target(MyBoidB.class);
curlA.target(MyBoidA.class);
curlB.target(MyBoidB.class);
tanA.target(MyBoidA.class);
tanB.target(MyBoidB.class);
for(int i=0; i < attrFieldA.length; i++){
attrA.add(new ICurveAttractorField(attrFieldA[i]).intensity(35));
}
for(int i=0; i < attrFieldB.length; i++){
attrB.add(new ICurveAttractorField(attrFieldB[i]).intensity(35));
}
for(int i=0; i < curlFieldA.length; i++){
curlA.add(new ICurveCurlField(curlFieldA[i]).intensity(20));
}
for(int i=0; i < curlFieldB.length; i++){
curlB.add(new ICurveCurlField(curlFieldB[i]).intensity(-20));
}
for(int i=0; i < tanFieldA.length; i++){
tanA.add(new ICurveTangentField(tanFieldA[i]).intensity(28));
}
for(int i=0; i < tanFieldB.length; i++){
tanB.add(new ICurveTangentField(tanFieldB[i]).intensity(28));
}
}
class MyBoid extends IBoidTrajectory{
MyBoid(IVec p, IVec v){
super(p,v);
alignment(0, 0);
cohesion(2, 30);
separation(4, 40);
fric(0.01);
}
}
class MyBoidA extends MyBoid{
MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}
class MyBoidB extends MyBoid{
MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}
Self-Optimizing Physics Simulation AgentsAs result of nodes staying on a plane, being separated by compression structure, and pulled by tension structure between sections, nodes, sticks and wires are moved to equilibrium locations under the forces perpendicular to the section planes.
![]()
![]()
![]()
import igeo.*;
import processing.opengl.*;
void setup(){
size(480,360,IG.GL);
IConfig.syncDrawAndDynamics = true;
for(int i=0; i < 25; i++){
new MyBoidA(IRand.pt(-300,-300,0,300,300,0), IRand.pt(-10,-10,-20,10,10,-20));
new MyBoidB(IRand.pt(-300,-300,0,300,300,0), IRand.pt(-10,-10,-20,10,10,-20));
}
ICompoundField curlA = new ICompoundField();
ICompoundField curlB = new ICompoundField();
ICompoundField attrA = new ICompoundField();
ICompoundField attrB = new ICompoundField();
curlA.target(MyBoidA.class);
curlB.target(MyBoidB.class);
attrA.target(MyBoidA.class);
attrB.target(MyBoidB.class);
for(int i=0; i < 10; i++){
IVec pt = IRand.pt(-100,-100,-800,100,100,-100);
curlA.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(20));
curlB.add(new IPointCurlField(pt, new IVec(0,0,-1)).intensity(-20));
attrA.add(new IAttractor(pt).intensity(20));
attrB.add(new IAttractor(pt).intensity(20));
}
new IGravity(0,0,-2);
// section towards negative Z axis
SectionAgent sectAgent =
new SectionAgent(new IVec(0,0,-80), new IVec(0,0,-1), 80);
sectAgent.maxSectionNum = 10; // only for 10 sections
}
class MyBoid extends IBoidTrajectory{
ArrayList< Node > nodes;
IVec prevPos;
MyBoid(IVec p, IVec v){
super(p,v);
alignment(0, 0);
cohesion(2, 40);
separation(4, 50);
fric(0.01);
nodes = new ArrayList< Node >();
Node n = new Node(p.cp(), new IVec(1,0,0));
n.fix().show();
nodes.add(n); // first & fixed node
}
void update(){
prevPos = pos().cp();
}
void addNode(Node n){
// connecting nodes with tension line
new ITensionLine(n, nodes.get(nodes.size()-1)).tension(50).clr(this);
nodes.add(n);
}
}
class MyBoidA extends MyBoid{
MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}
class MyBoidB extends MyBoid{
MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}
// particle agent for self-optimiztion which moves only on a specified plane.
class Node extends IParticle{
IVec planePt;
IVec planeDir;
Node(IVec p, IVec planeDirection) {
super(p);
planePt = p.cp();
planeDir = planeDirection;
fric(0.5);
hide();
}
void postupdate(){ // postupdate is automatically called after all update() is done
// keeping particle on a specified plane
pos().projectToPlane(planeDir, planeDir, planePt);
}
}
// agent to check planar intersection of swarm and generate nodes and compression structure.
class SectionAgent extends IAgent{
IVec sectOrig; // first section point
IVec sectNml; // normal vector of intersection plane of section
double interval; // interval distance between sections
ArrayList< ArrayList< Node >> nodes;
ArrayList< ArrayList< IStickLine >> sticks;
ArrayList< Boolean > updateSection;
int maxSectionNum = -1; // limit of sections counting from origin. if -1, no limit.
SectionAgent(IVec sectOrigin, IVec sectNormal, double sectInterval){
sectOrig = sectOrigin;
interval = sectInterval;
sectNml = sectNormal.cp().len(interval);
nodes = new ArrayList< ArrayList< Node >>();
sticks = new ArrayList< ArrayList< IStickLine >>();
updateSection = new ArrayList< Boolean >();
}
void interact(ArrayList< IDynamics > agents){
for(int i=0; i < agents.size(); i++){
if(agents.get(i) instanceof MyBoid){
MyBoid b = (MyBoid)agents.get(i);
if(b.prevPos!=null){
double planeDist = b.pos().dif(sectOrig).dot(sectNml)/sectNml.len(); // dist to plane
double prevPlaneDist = b.prevPos.dif(sectOrig).dot(sectNml)/sectNml.len();
//check if swarm agent passes through any section planes
if(planeDist >= 0 && prevPlaneDist >= 0 && (int)(planeDist/interval) != (int)(prevPlaneDist/interval) || prevPlaneDist <= 0 && planeDist > 0){
int index1 = (int)(prevPlaneDist/interval);
int index2 = (int)(planeDist/interval);
int inc=1;
if(prevPlaneDist<=0) index1=-1;
if(index1>index2){ inc = -1; }
for(int j=index1+inc; inc > 0 && j <= index2 || inc < 0 && j >= index2; j+=inc){
addIntersection(b, j);
}
}
}
}
}
for(int i=0; i < updateSection.size(); i++){
if(updateSection.get(i)){ // update only if new swarm enters into the section
for(int j=0; j < sticks.get(i).size(); j++){
sticks.get(i).get(j).del();
}
sticks.get(i).clear(); // clear old springs
IDelaunay2D.maxDistToCheck = 200; // edge length limit
// connect nodes with compression structures at Delaunay edges
IVecI[][] edges = IDelaunay2D.getEdges(nodes.get(i).toArray(new Node[nodes.get(i).size()]), sectNml);
for(int j=0; j < edges.length; j++){
// add stick (compression) line structure between nodes
sticks.get(i).add(new IStickLine((Node)edges[j][0],(Node)edges[j][1]).clr(.5,0,1));
}
updateSection.set(i,false);
}
}
}
void addIntersection(MyBoid b, int idx){
if(idx < maxSectionNum){
IVec lineDir = b.pos().dif(b.prevPos);
IVec sectPos = sectOrig.cp().add(sectNml, idx);
// calculate intersection
IVec itxn = IVec.intersectPlaneAndLine(sectNml, sectPos,lineDir,b.pos());
// create new node at the intersection
Node n = new Node(itxn, sectNml);
b.addNode(n);
for(int i=nodes.size(); i <= idx; i++){ // set the size of array to idx+1
nodes.add(new ArrayList< Node >());
sticks.add(new ArrayList< IStickLine >());
updateSection.add(false);
}
nodes.get(idx).add(n);
updateSection.set(idx, true); //set flag to update the section
if(idx==maxSectionNum-1){
b.del(); //at the last section, delete swarm agent
}
}
}
}
The sample input file used below is below.
field_lines2.3dm
![]()
![]()
![]()
import igeo.*;
import processing.opengl.*;
void setup(){
size(480,360,IG.GL);
IConfig.syncDrawAndDynamics = true;
// import rhino file to set up force fields
IG.open("field_lines2.3dm");
// points to specify intial position of swarm
IPoint[] ptsA = IG.layer("startPointA").points();
IPoint[] ptsB = IG.layer("startPointB").points();
for(int i=0; i < ptsA.length; i++){
new MyBoidA(ptsA[i].pos().cp(), new IVec(0,0,0));
}
for(int i=0; i < ptsB.length; i++){
new MyBoidB(ptsB[i].pos().cp(), new IVec(0,0,0));
}
// curves to specify force fields
ICurve[] attrFieldA = IG.layer("attractorA").curves();
ICurve[] attrFieldB = IG.layer("attractorB").curves();
ICurve[] curlFieldA = IG.layer("curlFieldA").curves();
ICurve[] curlFieldB = IG.layer("curlFieldB").curves();
ICurve[] tanFieldA = IG.layer("tangentFieldA").curves();
ICurve[] tanFieldB = IG.layer("tangentFieldB").curves();
ICompoundField attrA = new ICompoundField();
ICompoundField attrB = new ICompoundField();
ICompoundField curlA = new ICompoundField();
ICompoundField curlB = new ICompoundField();
ICompoundField tanA = new ICompoundField();
ICompoundField tanB = new ICompoundField();
attrA.target(MyBoidA.class);
attrB.target(MyBoidB.class);
curlA.target(MyBoidA.class);
curlB.target(MyBoidB.class);
tanA.target(MyBoidA.class);
tanB.target(MyBoidB.class);
for(int i=0; i < attrFieldA.length; i++){
attrA.add(new ICurveAttractorField(attrFieldA[i]).intensity(35));
//attrFieldA[i].hide();
}
for(int i=0; i < attrFieldB.length; i++){
attrB.add(new ICurveAttractorField(attrFieldB[i]).intensity(35));
//attrFieldB[i].hide();
}
for(int i=0; i < curlFieldA.length; i++){
curlA.add(new ICurveCurlField(curlFieldA[i]).intensity(20));
//curlFieldA[i].hide();
}
for(int i=0; i < curlFieldB.length; i++){
curlB.add(new ICurveCurlField(curlFieldB[i]).intensity(-20));
//curlFieldB[i].hide();
}
for(int i=0; i < tanFieldA.length; i++){
tanA.add(new ICurveTangentField(tanFieldA[i]).intensity(28));
//tanFieldA[i].hide();
}
for(int i=0; i < tanFieldB.length; i++){
tanB.add(new ICurveTangentField(tanFieldB[i]).intensity(28));
//tanFieldB[i].hide();
}
new IGravity(0,0,-2);
// section towards negative Z axis
SectionAgent sectAgent =
new SectionAgent(new IVec(0,0,-80), new IVec(0,0,-1), 80);
sectAgent.maxSectionNum = 10; // only for 10 sections
}
class MyBoid extends IBoidTrajectory{
ArrayList< Node > nodes;
IVec prevPos;
MyBoid(IVec p, IVec v){
super(p,v);
alignment(0, 0);
cohesion(2, 40);
separation(4, 50);
fric(0.01);
nodes = new ArrayList< Node >();
Node n = new Node(p.cp(), new IVec(1,0,0));
n.fix().show();
nodes.add(n); // first & fixed node
}
void update(){
prevPos = pos().cp();
}
void addNode(Node n){
// connecting nodes with tension line
new ITensionLine(n, nodes.get(nodes.size()-1)).tension(50).clr(this);
nodes.add(n);
}
}
class MyBoidA extends MyBoid{
MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}
class MyBoidB extends MyBoid{
MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}
// particle agent for self-optimiztion which moves only on a specified plane.
class Node extends IParticle{
IVec planePt;
IVec planeDir;
Node(IVec p, IVec planeDirection) {
super(p);
planePt = p.cp();
planeDir = planeDirection;
fric(0.5);
hide();
}
void postupdate(){ // postupdate is automatically called after all update() is done
// keeping particle on a specified plane
pos().projectToPlane(planeDir, planeDir, planePt);
}
}
// agent to check planar intersection of swarm and generate nodes and compression structure.
class SectionAgent extends IAgent{
IVec sectOrig; // first section point
IVec sectNml; // normal vector of intersection plane of section
double interval; // interval distance between sections
ArrayList< ArrayList< Node >> nodes;
ArrayList< ArrayList< IStickLine >> sticks;
ArrayList< Boolean > updateSection;
int maxSectionNum = -1; // limit of sections counting from origin. if -1, no limit.
SectionAgent(IVec sectOrigin, IVec sectNormal, double sectInterval){
sectOrig = sectOrigin;
interval = sectInterval;
sectNml = sectNormal.cp().len(interval);
nodes = new ArrayList< ArrayList< Node >>();
sticks = new ArrayList< ArrayList< IStickLine >>();
updateSection = new ArrayList< Boolean >();
}
void interact(ArrayList< IDynamics > agents){
for(int i=0; i < agents.size(); i++){
if(agents.get(i) instanceof MyBoid){
MyBoid b = (MyBoid)agents.get(i);
if(b.prevPos!=null){
double planeDist = b.pos().dif(sectOrig).dot(sectNml)/sectNml.len(); // dist to plane
double prevPlaneDist = b.prevPos.dif(sectOrig).dot(sectNml)/sectNml.len();
//check if swarm agent passes through any section planes
if(planeDist >= 0 && prevPlaneDist >= 0 && (int)(planeDist/interval) != (int)(prevPlaneDist/interval) || prevPlaneDist <= 0 && planeDist > 0){
int index1 = (int)(prevPlaneDist/interval);
int index2 = (int)(planeDist/interval);
int inc=1;
if(prevPlaneDist<=0) index1=-1;
if(index1>index2){ inc = -1; }
for(int j=index1+inc; inc > 0 && j <= index2 || inc < 0 && j >= index2; j+=inc){
addIntersection(b, j);
}
}
}
}
}
for(int i=0; i < updateSection.size(); i++){
if(updateSection.get(i)){ // update only if new swarm enters into the section
for(int j=0; j < sticks.get(i).size(); j++){
sticks.get(i).get(j).del();
}
sticks.get(i).clear(); // clear old springs
IDelaunay2D.maxDistToCheck = 200; // edge length limit
// connect nodes with compression structures at Delaunay edges
IVecI[][] edges = IDelaunay2D.getEdges(nodes.get(i).toArray(new Node[nodes.get(i).size()]), sectNml);
for(int j=0; j < edges.length; j++){
// add stick (compression) line structure between nodes
sticks.get(i).add(new IStickLine((Node)edges[j][0],(Node)edges[j][1]).clr(.5,0,1));
}
updateSection.set(i,false);
}
}
}
void addIntersection(MyBoid b, int idx){
if(idx < maxSectionNum){
IVec lineDir = b.pos().dif(b.prevPos);
IVec sectPos = sectOrig.cp().add(sectNml, idx);
// calculate intersection
IVec itxn = IVec.intersectPlaneAndLine(sectNml, sectPos,lineDir,b.pos());
// create new node at the intersection
Node n = new Node(itxn, sectNml);
b.addNode(n);
for(int i=nodes.size(); i <= idx; i++){ // set the size of array to idx+1
nodes.add(new ArrayList< Node >());
sticks.add(new ArrayList< IStickLine >());
updateSection.add(false);
}
nodes.get(idx).add(n);
updateSection.set(idx, true); //set flag to update the section
if(idx==maxSectionNum-1){
b.del(); //at the last section, delete swarm agent
}
}
}
}
Self-Optimizing Physics Simulation Agents for Bridging Structure ![]()
![]()
![]()
import igeo.*;
import processing.opengl.*;
void setup(){
size(480,360,IG.GL);
IConfig.syncDrawAndDynamics = true;
// import rhino file to set up force fields
IG.open("field_lines1.3dm");
// points to specify intial position of swarm
IPoint[] ptsA = IG.layer("startPointA").points();
IPoint[] ptsB = IG.layer("startPointB").points();
for(int i=0; i < ptsA.length; i++){
new MyBoidA(ptsA[i].pos().cp(), new IVec(0,0,0));
}
for(int i=0; i < ptsB.length; i++){
new MyBoidB(ptsB[i].pos().cp(), new IVec(0,0,0));
}
// curves to specify force fields
ICurve[] attrFieldA = IG.layer("attractorA").curves();
ICurve[] attrFieldB = IG.layer("attractorB").curves();
ICurve[] curlFieldA = IG.layer("curlFieldA").curves();
ICurve[] curlFieldB = IG.layer("curlFieldB").curves();
ICurve[] tanFieldA = IG.layer("tangentFieldA").curves();
ICurve[] tanFieldB = IG.layer("tangentFieldB").curves();
ICompoundField attrA = new ICompoundField();
ICompoundField attrB = new ICompoundField();
ICompoundField curlA = new ICompoundField();
ICompoundField curlB = new ICompoundField();
ICompoundField tanA = new ICompoundField();
ICompoundField tanB = new ICompoundField();
attrA.target(MyBoidA.class);
attrB.target(MyBoidB.class);
curlA.target(MyBoidA.class);
curlB.target(MyBoidB.class);
tanA.target(MyBoidA.class);
tanB.target(MyBoidB.class);
for(int i=0; i < attrFieldA.length; i++){
attrA.add(new ICurveAttractorField(attrFieldA[i]).intensity(35));
}
for(int i=0; i < attrFieldB.length; i++){
attrB.add(new ICurveAttractorField(attrFieldB[i]).intensity(35));
}
for(int i=0; i < curlFieldA.length; i++){
curlA.add(new ICurveCurlField(curlFieldA[i]).intensity(20));
}
for(int i=0; i < curlFieldB.length; i++){
curlB.add(new ICurveCurlField(curlFieldB[i]).intensity(-20));
}
for(int i=0; i < tanFieldA.length; i++){
tanA.add(new ICurveTangentField(tanFieldA[i]).intensity(28));
}
for(int i=0; i < tanFieldB.length; i++){
tanB.add(new ICurveTangentField(tanFieldB[i]).intensity(28));
}
// gravity is applied only to nodes
new IGravity(0,0,-6).target(Node.class);
//sections towards X axis
SectionAgent sectAgent =
new SectionAgent(new IVec(120,0,0), new IVec(1,0,0), 60);
sectAgent.maxSectionNum = 18; //only for 18 sections
}
class MyBoid extends IBoidTrajectory{
ArrayList< Node > nodes;
IVec prevPos;
MyBoid(IVec p, IVec v){
super(p,v);
alignment(0, 0);
cohesion(2, 40);
separation(4, 50);
fric(0.01);
nodes = new ArrayList< Node >();
Node n = new Node(p.cp(), new IVec(1,0,0));
n.fix().show();
nodes.add(n); // first & fixed node
}
void update(){
prevPos = pos().cp();
}
void addNode(Node n){
// connecting nodes with tension line
new ITensionLine(n, nodes.get(nodes.size()-1)).tension(50).clr(this);
nodes.add(n);
}
}
class MyBoidA extends MyBoid{
MyBoidA(IVec p, IVec v){ super(p,v); clr(0.3); }
}
class MyBoidB extends MyBoid{
MyBoidB(IVec p, IVec v){ super(p,v); clr(1.0); }
}
// particle agent for self-optimiztion which moves only on a specified plane.
class Node extends IParticle{
IVec planePt;
IVec planeDir;
Node(IVec p, IVec planeDirection) {
super(p);
planePt = p.cp();
planeDir = planeDirection;
fric(0.5);
hide();
}
void postupdate(){ // postupdate is automatically called after all update() is done
// keeping particle on a specified plane
pos().projectToPlane(planeDir, planeDir, planePt);
}
}
// agent to check planar intersection of swarm and generate nodes and compression structure.
class SectionAgent extends IAgent{
IVec sectOrig; // first section point
IVec sectNml; // normal vector of intersection plane of section
double interval; // interval distance between sections
ArrayList< ArrayList< Node >> nodes;
ArrayList< ArrayList< IStickLine >> sticks;
ArrayList< Boolean > updateSection;
int maxSectionNum = -1; // limit of sections counting from origin. if -1, no limit.
SectionAgent(IVec sectOrigin, IVec sectNormal, double sectInterval){
sectOrig = sectOrigin;
interval = sectInterval;
sectNml = sectNormal.cp().len(interval);
nodes = new ArrayList< ArrayList< Node >>();
sticks = new ArrayList< ArrayList< IStickLine >>();
updateSection = new ArrayList< Boolean >();
}
void interact(ArrayList< IDynamics > agents){
for(int i=0; i < agents.size(); i++){
if(agents.get(i) instanceof MyBoid){
MyBoid b = (MyBoid)agents.get(i);
if(b.prevPos!=null){
double planeDist = b.pos().dif(sectOrig).dot(sectNml)/sectNml.len(); // dist to plane
double prevPlaneDist = b.prevPos.dif(sectOrig).dot(sectNml)/sectNml.len();
//check if swarm agent passes through any section planes
if(planeDist >= 0 && prevPlaneDist >= 0 && (int)(planeDist/interval) != (int)(prevPlaneDist/interval) || prevPlaneDist <= 0 && planeDist > 0){
int index1 = (int)(prevPlaneDist/interval);
int index2 = (int)(planeDist/interval);
int inc=1;
if(prevPlaneDist<=0) index1=-1;
if(index1>index2){ inc = -1; }
for(int j=index1+inc; inc > 0 && j <= index2 || inc < 0 && j >= index2; j+=inc){
addIntersection(b, j);
}
}
}
}
}
for(int i=0; i < updateSection.size(); i++){
if(updateSection.get(i)){ // update only if new swarm enters into the section
for(int j=0; j < sticks.get(i).size(); j++){
sticks.get(i).get(j).del();
}
sticks.get(i).clear(); // clear old springs
IDelaunay2D.maxDistToCheck = 200; // edge length limit
// connect nodes with compression structures at Delaunay edges
IVecI[][] edges = IDelaunay2D.getEdges(nodes.get(i).toArray(new Node[nodes.get(i).size()]), sectNml);
for(int j=0; j < edges.length; j++){
// add stick (compression) line structure between nodes
sticks.get(i).add(new IStickLine((Node)edges[j][0],(Node)edges[j][1]).clr(.5,0,1));
}
updateSection.set(i,false);
}
}
}
void addIntersection(MyBoid b, int idx){
if(idx < maxSectionNum){
IVec lineDir = b.pos().dif(b.prevPos);
IVec sectPos = sectOrig.cp().add(sectNml, idx);
// calculate intersection
IVec itxn = IVec.intersectPlaneAndLine(sectNml, sectPos,lineDir,b.pos());
// create new node at the intersection
Node n = new Node(itxn, sectNml);
b.addNode(n);
for(int i=nodes.size(); i <= idx; i++){ // set the size of array to idx+1
nodes.add(new ArrayList< Node >());
sticks.add(new ArrayList< IStickLine >());
updateSection.add(false);
}
nodes.get(idx).add(n);
updateSection.set(idx, true); //set flag to update the section
if(idx==maxSectionNum-1){
b.del(); //at the last section, delete swarm agent
n.fix().show(); // last node is also fixed in addition to the first
}
}
}
}
Branching Swarm Agents and Self-Optimizing Agents![]()
![]()
![]()
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
}
}
}
}
HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java /
Python)
GALLERY
SOURCE CODE(GitHub)
PRIVACY POLICY
ABOUT/CONTACT