Tutorials | (back to the list of tutorials) |
On the other hand, a particle based agent moves by itself. The movement follows the physical rules in Newton's law as described on the tutorial page about particles. Particles move around following forces applied to them. The result of the system are created as trajectories or some geometries built on the course of particle movement and their interaction.
This page shows examples to build a custom reproduction based agent. As the starting point, the following code defines a simple reproduction based agent with two point properties with IVec variables. The update method creates one line with the two points as geometry representation of an agent. It also contains a rule to reproduce one child agent in the same direction at the end point of the parent agent.
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); new MyAgent(IG.v(), IG.v(0,1,0)); } class MyAgent extends IAgent { IVec pt1, pt2; MyAgent(IVec pt, IVec dir) { pt1 = pt; pt2 = pt.cp(dir); } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(0); //geometry representation IVec dir = pt2.dif(pt1); new MyAgent(pt2, dir); //reproduce } } }
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); new MyAgent(IG.v(), IG.v(0,1,0)); } class MyAgent extends IAgent { IVec pt1, pt2; MyAgent(IVec pt, IVec dir) { pt1 = pt; pt2 = pt.cp(dir); } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(0); IVec dir = pt2.dif(pt1); dir.rot(PI/100); // transformation rule 1: rotate dir.mul(0.999); // transformation rule 2: scale down new MyAgent(pt2, dir); } } }
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); new MyAgent(IG.v(), IG.v(0,1,0)); } class MyAgent extends IAgent { IVec pt1, pt2; MyAgent(IVec pt, IVec dir) { pt1 = pt; pt2 = pt.cp(dir); } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(0); IVec dir = pt2.dif(pt1); dir.rot(PI/100); dir.mul(0.999); new MyAgent(pt2, dir); // branch 1 IVec dir2 = pt2.dif(pt1); dir2.rot(-PI/10); new MyAgent(pt2, dir2); // branch 2 } } }
You can see that the result shows too many agents and if you run the code it gets very slow quickly because it generates exponential number of agents every time frame. This means creating two agents every time frame is too much. There are many ways to control number of child agents. The following code is one example to control branching by random number. It creates the second child agent only in 3% probability.
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); new MyAgent(IG.v(), IG.v(0,1,0)); } class MyAgent extends IAgent { IVec pt1, pt2; MyAgent(IVec pt, IVec dir) { pt1 = pt; pt2 = pt.cp(dir); } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(0); IVec dir = pt2.dif(pt1); dir.rot(PI/100); dir.mul(0.999); new MyAgent(pt2, dir); // branch 1 if( IRand.pct(3) ){ // 3% probability IVec dir2 = pt2.dif(pt1); dir2.rot(-PI/10); new MyAgent(pt2, dir2); // branch 2 } } } }
!intxn.eq(pt1) // intersection and pt1 are not at the same location
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); new MyAgent(IG.v(), IG.v(0,1,0)); } class MyAgent extends IAgent { IVec pt1, pt2; MyAgent(IVec pt, IVec dir) { pt1 = pt; pt2 = pt.cp(dir); } void interact(ArrayListagents){ if(time()==0){ for(int i=0; i < agents.size() && alive(); i++){ if(agents.get(i) instanceof MyAgent){ MyAgent a = (MyAgent)agents.get(i); if(a!=this){ IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2); if(intxn!=null && !intxn.eq(pt1)){ del(); } } } } } } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(0); IVec dir = pt2.dif(pt1); dir.rot(PI/100); dir.mul(0.999); new MyAgent(pt2, dir); // branch 1 if( IRand.pct(3) ){ // 3% probability IVec dir2 = pt2.dif(pt1); dir2.rot(-PI/10); new MyAgent(pt2, dir2); // branch 2 } } } }
boolean isStraight;
The constructor of MyAgent class adds another input argument of boolean variable straight as the line below.
MyAgent(IVec pt, IVec dir, boolean straight){
Then the new agent instance property of isStraight is initialized by this input argument straight. In the update method, this property is used to differentiate the agent behavior. When isStraight is true, it doesn't apply the transformation rule to bend the direction and it only creates one child agent. If false, it applys the original behavior to the agent with the transformation rule and the branching rule. This property isStraight is passed through the child agents as well to keep the state. The update method contains another rule to toggle this boolean state randomly in low probability as the following line.
if(IRand.pct(0.8)){ isStraight = !isStraight; } // toggle boolean switch
Then isStraight is fed to child agents to pass the current state of the parent agent.
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); new MyAgent(IG.v(), IG.v(0,1,0), true); } class MyAgent extends IAgent { IVec pt1, pt2; boolean isStraight; MyAgent(IVec pt, IVec dir, boolean straight) { pt1 = pt; pt2 = pt.cp(dir); isStraight = straight; } void interact(ArrayListagents){ if(time()==0){ for(int i=0; i < agents.size() && alive(); i++){ if(agents.get(i) instanceof MyAgent){ MyAgent a = (MyAgent)agents.get(i); if(a!=this){ IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2); if(intxn!=null && !intxn.eq(pt1)){ del(); } } } } } } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(0); IVec dir = pt2.dif(pt1); if(IRand.pct(0.8)){ isStraight = !isStraight; } //toggle boolean switch if(isStraight){ new MyAgent(pt2, dir, isStraight); } else{ dir.rot(PI/100); dir.mul(0.999); new MyAgent(pt2, dir, isStraight); if( IRand.pct(3) ){ IVec dir2 = pt2.dif(pt1); dir2.rot(-PI/10); new MyAgent(pt2, dir2, isStraight); } } } } }
double angle;
The example codes above so far had a constant number for the bending angle. Now introducing the angle property makes it possible to change the bending angle gradiently through branching and propagation of agents. Inside the update method, the angle is sometimes randomly increased or decreased and also flipped to positive or negative number to flip the bending direction.
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); new MyAgent(IG.v(), IG.v(0,1,0), true, 0); } class MyAgent extends IAgent { IVec pt1, pt2; boolean isStraight; double angle; MyAgent(IVec pt, IVec dir, boolean straight, double ang) { pt1 = pt; pt2 = pt.cp(dir); isStraight = straight; angle = ang; } void interact(ArrayList agents){ if(time()==0){ for(int i=0; i < agents.size() && alive(); i++){ if(agents.get(i) instanceof MyAgent){ MyAgent a = (MyAgent)agents.get(i); if(a!=this){ IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2); if(intxn!=null && !intxn.eq(pt1)){ del(); } } } } } } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(0); IVec dir = pt2.dif(pt1); if(IRand.pct(0.8)){ isStraight = !isStraight; } if(isStraight){ new MyAgent(pt2, dir, isStraight, angle); } else{ dir.rot(angle); dir.mul(0.999); if(IRand.pct(1)){ angle += IRand.get(-0.05, 0.05); angle = -angle; // flip bending direction } new MyAgent(pt2, dir, isStraight, angle); if( IRand.pct(3) ){ IVec dir2 = pt2.dif(pt1); dir2.rot(-PI/10); new MyAgent(pt2, dir2, isStraight, angle); } } } } }
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); new MyAgent(IG.v(), IG.v(0,1,0), true, 0); for(int i=0; i < 10; i++){ new Attractor(IRand.pt(-200,200,200,400)).clr(1.0,0,0); } } class MyAgent extends IAgent { IVec pt1, pt2; boolean isStraight; double angle; Attractor target; MyAgent(IVec pt, IVec dir, boolean straight, double ang) { pt1 = pt; pt2 = pt.cp(dir); isStraight = straight; angle = ang; target = null; } void interact(ArrayList agents){ if(time()==0){ double minDist=-1; for(int i=0; i < agents.size() && alive(); i++){ if(agents.get(i) instanceof MyAgent){ MyAgent a = (MyAgent)agents.get(i); if(a!=this){ IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2); if(intxn!=null && !intxn.eq(pt1)){ del(); } } } else if (agents.get(i) instanceof Attractor) { Attractor attr = (Attractor)agents.get(i); // find the closest attractor in front double dist = attr.pos.dist(pt2); if(minDist < 0){ // first time to check Attractor minDist = dist; target = attr; } else if(dist < minDist){ minDist = dist; target = attr; } } } } } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(0); IVec dir = pt2.dif(pt1); if(IRand.pct(0.8)){ isStraight = !isStraight; } // switch if(isStraight){ if(target!=null){ IVec targetDir = target.pos.dif(pt2); if(dir.cp().rot(angle).angle(targetDir) > dir.cp().rot(-angle).angle(targetDir)){ angle = -angle; // angle closer to target } dir.rot(angle); // rotate toward target } new MyAgent(pt2, dir, isStraight, angle); // branch 1 } else{ if(IRand.pct(1)){ angle += IRand.get(-0.05, 0.05); angle = -angle; } if(target!=null){ IVec targetDir = target.pos.dif(pt2); if(dir.cp().rot(angle).angle(targetDir) > dir.cp().rot(-angle).angle(targetDir)){ angle = -angle; // angle closer to target } dir.rot(angle); // rotate toward target dir.mul(0.999); new MyAgent(pt2, dir, isStraight, angle); // branch 1 } else{ dir.rot(angle); dir.mul(0.999); if(IRand.pct(99)) new MyAgent(pt2, dir, isStraight, angle); // branch 1 } if( IRand.pct(3) ){ // 3% probability IVec dir2 = pt2.dif(pt1); dir2.rot(-PI/10); new MyAgent(pt2, dir2, isStraight, angle); // branch 2 } } } } } class Attractor extends IAgent{ IVec pos; Attractor(IVec p) { pos = p; } void update(){ if(time()==0) new IPoint(pos).clr(clr()); } }
import igeo.*; import processing.opengl.*; void setup() { size(480, 360, IG.GL); IG.open("agent_lines1.3dm"); // input file to initialize agents ICurve[] roots = IG.layer("root").curves(); ICurve[] bounds = IG.layer("boundary").curves(); IPoint[] attractors = IG.layer("attractor").points(); for(int i=0; i < roots.length; i++){ new MyAgent(roots[i].start(), roots[i].end().dif(roots[i].start()), 0); } for(int i=0; i < attractors.length; i++){ new Attractor(attractors[i].pos()).clr(IRand.clr()); attractors[i].del(); } for(int i=0; i < bounds.length; i++){ new BoundaryAgent(bounds[i]); } IG.bg(0); } class MyAgent extends IAgent { IVec pt1, pt2; boolean isStraight=true; double angle; Attractor target; MyAgent(IVec pt, IVec dir, double ang) { pt1 = pt; pt2 = pt.cp(dir); angle = ang; target = null; } void interact(ArrayList agents){ if(time()==0){ double minDist=-1; for(int i=0; i < agents.size() && alive(); i++){ if(agents.get(i) instanceof MyAgent){ MyAgent a = (MyAgent)agents.get(i); if(a!=this){ IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2); if(intxn!=null && !intxn.eq(pt1)){ del(); } } } else if (agents.get(i) instanceof Attractor) { Attractor attr = (Attractor)agents.get(i); // find the closest attractor in front double dist = attr.pos.dist(pt2); if(minDist < 0){ // first time to check Attractor minDist = dist; target = attr; } else if(dist < minDist){ minDist = dist; target = attr; } } } } } void update() { if (time()==0) { new ICurve(pt1, pt2).clr(clr().cp()); IVec dir = pt2.dif(pt1); if(isStraight){ // just go straight if(target!=null){ clr().blend(target.clr(),0.02); // blend 2% color } new MyAgent(pt2, dir, angle).clr(clr().cp()); // branch 1 } else{ if(IRand.pct(1)){ angle += IRand.get(-0.05, 0.05); angle = -angle; } if(target!=null){ IVec targetDir = target.pos.dif(pt2); if(dir.cp().rot(angle).angle(targetDir) > dir.cp().rot(-angle).angle(targetDir)){ angle = -angle; // angle closer to target } dir.rot(angle); // rotate toward target dir.mul(0.999); clr().blend(target.clr(),0.02); // blend 2% color new MyAgent(pt2, dir, angle).clr(clr().cp()); // branch 1 } else{ dir.rot(angle); dir.mul(0.999); if(IRand.pct(100)) new MyAgent(pt2, dir, angle).clr(clr().cp()); // branch 1 } if( IRand.pct(3) ){ // 3% probability IVec dir2 = pt2.dif(pt1); dir2.rot(-PI/10); new MyAgent(pt2, dir2, angle).clr(clr().cp()); // branch 2 } } } } } class Attractor extends IAgent{ IVec pos; Attractor(IVec p) { pos = p; } void update(){ if(time()==0) new IPoint(pos).clr(clr()).clr(clr()); } } class BoundaryAgent extends IAgent{ ICurve boundary; BoundaryAgent(ICurve crv){ boundary = crv; } void interact(ArrayList < IDynamics > agents){ for(int i=0; i < agents.size(); i++){ if(agents.get(i) instanceof MyAgent){ MyAgent agent = (MyAgent)agents.get(i); if(agent.time()==0){ // check only what's just created if(boundary.isInside2d(agent.pt2)){ // check if it's inside agent.isStraight=false; } } } } } }