Tutorials | (back to the list of tutorials) |
The below is a template to define an interact method.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); MyAgent agent = new MyAgent(); } class MyAgent extends IAgent{ void interact(ArrayList< IDynamics > agents){ // definition of interact behavior } void update(){ // definition of update behavior } }
You can write any code you want inside interact method to define the behavior of interaction but most of the time when you have only one agent class to interact, the code would look like the following template to have for-loop iteration to check all other agents.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); MyAgent agent = new MyAgent(); } class MyAgent extends IAgent{ void interact(ArrayList< IDynamics > agents){ for(int i=0; i < agents.size(); i++){ //check all existing agents if(agents.get(i) instanceof MyAgent){ //type check MyAgent agent = (MyAgent)agents.get(i); //casting type if(agent!= this){ //agents include this instance itself // definition of interact behavior } } } } void update(){ // definition of update behavior } }
The interact method starts with the line
This for-loop on the first line is to iterate
through all the existing agents in side the agents
variable.
In the for-loop, the number is counted up to agents.size(),
which is total number of member inside the variable array.
for(int i=0; i < agents.size(); i++){
This if-condition on the second line is to check if the variable inside the variable-length array agents is an instance of MyAgent. agents.get(i) is to access the i-th member inside the array. The keyword "instanceof" is to check if the variable is an instance of the class MyAgent because the input argument agents could contain any type of agents.
if(agents.get(i) instanceof MyAgent){
The third line is to "cast" the variable of an unknown class into a variable of MyAgent class. The "casting" is a process to convert an instance of a superclass into that of subclass. Casting can be done by putting "(" + name of class + ")" in front of the variable. For more description about casting, please see this Java tutorial.
MyAgent agent = (MyAgent)agents.get(i);
Then on the fourth line it exclude the case the variable contained inside the variable-length array agents is the same instance with the one which is checking others right now. "this" refers to the currently executing instance itself.
if(agent!= this){
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); MyAgent agent = new MyAgent(); } class MyAgent extends IAgent{ void interact(IDynamics agent){ if(agent instanceof MyAgent){ //type check MyAgent myAgent = (MyAgent)agent; //casting type // definition of interact behavior } } void update(){ // definition of update behavior } }
This shorter interact method takes an input argument of one instance of IDynamics, instead of a variable-length array of IDynamics. The iGeo system feeds each of all other agents into this method and you don't need to take for-loop to iterate through other agents. This interact method is executed as many as all other agents in the system.
There are two shortcomings of this method. One is that the speed of execution is slow. Another is that there's certain algorithms which require an access to all other agents at once and in this case it is not possible to write this algorithm inside this short version of interact method. So if either the speed matters or you write that type of algorhtm, please use the longer definition of interact method taking the input argument of all existing agents in a variable-length array ArrayList < IDynamics > agents.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(120); LineAgent agent = new LineAgent(new IVec(0,0,0), new IVec(1,0,0)); } static class LineAgent extends IAgent{ static double length = 2; static double clearance = 1.99; //less than length IVec pt1, pt2; boolean isColliding=false; LineAgent(IVec pt, IVec dir){ pt1 = pt; pt2 = pt.dup().add(dir.dup().len(length)); } void interact(IDynamics agent){ if(time == 0){ //only in the first time if(agent instanceof LineAgent){ LineAgent lineAgent = (LineAgent)agent; // checking clearance of end point if(lineAgent.pt2.dist(pt2) < clearance){ isColliding=true; } } } } void update(){ if(isColliding){ del(); } else if(time == 0){ //if not colliding new ICurve(pt1,pt2).clr(0); IVec dir = pt2.dif(pt1); if(IRandom.percent(40)){ //bend new LineAgent(pt2, dir.dup().rot(IG.zaxis, PI/3)); } if(IRandom.percent(40)){ //bend the other way new LineAgent(pt2, dir.dup().rot(IG.zaxis, -PI/3)); } if(IRandom.percent(80)){ //straight new LineAgent(pt2, dir.dup()); } } } }
In this algorithm, the interact method is checking collision with any existing LineAgent by this if-condition.
if(lineAgent.pt2.dist(pt2) < clearance){
In the update method, if it finds any collision, i.e. the variable isColliding is true, it delete itself and doesn't create a line. Otherwise, it puts a line and creates 3 child agents randomly in the direction of 60 degrees (PI/3), -60 degrees and straight. This inteaction and update logics are described in the following diagram.
All of this collision check and geometry and child agents generation is only done once at the time frame of 0 by the if-condition "if(time == 0){" which appears on both of interact and update method. The variable "time" is an instance field of IAgent showing how many updating cycles have elapsed since the instance is created.
The below shows the same algorithm written with the longer definition of the interact method in case the speed of the execution matters.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(120); LineAgent agent = new LineAgent(new IVec(0,0,0), new IVec(1,0,0)); } static class LineAgent extends IAgent{ static double length = 2; static double clearance = 1.99; //less than length IVec pt1, pt2; boolean isColliding=false; LineAgent(IVec pt, IVec dir){ pt1 = pt; pt2 = pt.dup().add(dir.dup().len(length)); } void interact(ArrayList< IDynamics > agents){ if(time == 0){ //only in the first time for(int i=0; i < agents.size(); i++){ if(agents.get(i) instanceof LineAgent){ LineAgent lineAgent = (LineAgent)agents.get(i); if(lineAgent != this){ // checking clearance of end point if(lineAgent.pt2.dist(pt2) < clearance){ isColliding=true; } } } } } } void update(){ if(isColliding){ del(); } else if(time == 0){ //if not colliding new ICurve(pt1,pt2).clr(0); IVec dir = pt2.dif(pt1); if(IRandom.percent(40)){ //bend new LineAgent(pt2, dir.dup().rot(IG.zaxis, PI/3)); } if(IRandom.percent(40)){ //bend the other way new LineAgent(pt2, dir.dup().rot(IG.zaxis, -PI/3)); } if(IRandom.percent(80)){ //straight new LineAgent(pt2, dir.dup()); } } } }
Please note that the range of random angle of branching is from PI/3 to PI/3*2 because if the angle is less than PI/3, the end point would collide with straight member. Or if the angle is more than PI/3*2, it would collide with the parent line when you have LineAgent.length and LineAgent.clearance close. LineAgent.clearance cannot be larger than LineAgent.length because an agent would judge that the line always collides with the parent line.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(250); LineAgent agent = new LineAgent(new IVec(0,0,0), new IVec(1,0,0)); } static class LineAgent extends IAgent{ static double length = 2; static double clearance = 1.99; //less than length IVec pt1, pt2; boolean isColliding=false; LineAgent(IVec pt, IVec dir){ pt1 = pt; pt2 = pt.dup().add(dir.dup().len(length)); } void interact(IDynamics agent){ if(time == 0){ //only in the first time if(agent instanceof LineAgent){ LineAgent lineAgent = (LineAgent)agent; // checking clearance of end point if(lineAgent.pt2.dist(pt2) < clearance){ isColliding=true; } } } } void update(){ if(isColliding){ del(); } else if(time == 0){ //if not colliding new ICurve(pt1,pt2).clr(0); IVec dir = pt2.dif(pt1); if(IRandom.percent(40)){ //bend new LineAgent(pt2, dir.dup().rot(IG.zaxis, IRandom.get(PI/3,PI/3*2))); } if(IRandom.percent(40)){ //bend the other way new LineAgent(pt2, dir.dup().rot(IG.zaxis, -IRandom.get(PI/3,PI/3*2))); } if(IRandom.percent(80)){ //straight new LineAgent(pt2, dir.dup()); } } } }
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(150); new LineAgent(new IVec(0,0,0), new IVec(1,0,0)); } static class LineAgent extends IAgent{ static double length = 2; static double clearance = 1.99; //less than length IVec pt1, pt2; boolean isColliding=false; LineAgent(IVec pt, IVec dir){ pt1 = pt; pt2 = pt.dup().add(dir.dup().len(length)); } void interact(IDynamics agent){ if(time == 0){ //only in the first time if(agent instanceof LineAgent){ LineAgent lineAgent = (LineAgent)agent; // checking clearance of end point if(lineAgent.pt2.dist(pt2) < clearance){ isColliding=true; } } } } void update(){ if(isColliding){ del(); } else if(time == 0){ //if not colliding new ICurve(pt1,pt2).clr(0); IVec dir = pt2.dif(pt1); //rotation axis with random direction IVec axis = IRandom.pt(-1,1).len(1); if(IRandom.percent(50)){ //bend new LineAgent(pt2, dir.dup().rot(axis, IRandom.get(PI/3,PI/3*2))); } if(IRandom.percent(50)){ //bend the other way new LineAgent(pt2, dir.dup().rot(axis, -IRandom.get(PI/3,PI/3*2))); } if(IRandom.percent(80)){ //straight new LineAgent(pt2, dir.dup()); } } } }
import processing.opengl.*; import igeo.*; void setup(){ size(480,360,IG.GL); IG.duration(400); new MySphereAgent(IRandom.pt(-10,10),IRandom.get(5,20)); new MySphereAgent(IRandom.pt(-10,10),IRandom.get(5,20)); IG.fill(); } class MySphereAgent extends IAgent{ IVec pos; double radius; ISphere sphere; boolean changed=true; MySphereAgent(IVec p, double rad){ pos = p; radius = rad; } void interact(IDynamics agent){ if(agent instanceof MySphereAgent){ MySphereAgent sa = (MySphereAgent)agent; double dist = sa.pos.dist(pos); if(dist < radius+sa.radius){ IVec dif = pos.dif(sa.pos); //amount of overlap is this radius plus other radius minus distance between two centers dif.len(radius+sa.radius-dist); pos.add(dif); //only this agent is moved, not others changed=true; //state variable is updated } } } void update(){ if(changed){ // update sphere if(sphere!=null) sphere.del(); //shpere is null first sphere = new ISphere(pos, radius).clr(clr()); changed=false; } if(time==5){ //delayed to create the next agent til time==5 // next agent's direction IVec dir = IRandom.pt(-1, 1); double nextRadius = IRandom.get(5, 20); // amount of move is the current radius + the next one dir.len(radius+nextRadius); new MySphereAgent(pos.cp(dir),nextRadius).clr(IRandom.clr()); } } }
import processing.opengl.*; import igeo.*; void setup() { size(480, 360, IG.GL); IG.duration(500); int num=20; for (int i=0; i < num; i++) { new RectAgent(IRandom.pt(-100,-100, 0,100,100, 0), 20, 20).clr(IRandom.clr()); } } static class RectAgent extends IAgent { static double gap = 1.0; static double minSize = 1.0; static double maxSize = 20.0; IVec pos; double width, height; boolean anyChange=true; ISurface rect; RectAgent(IVec pt, double w, double h) { pos = pt; width=w; height=h; } void interact(IDynamics agent) { // shrink the size when it collides with others. if (agent instanceof RectAgent) { RectAgent ra = (RectAgent) agent; // is it overlapping? if (ra.pos.x+ra.width+gap > pos.x && ra.pos.x < pos.x + width+gap && ra.pos.y+ra.height+gap > pos.y && ra.pos.y < pos.y + height+gap) { // both x and y overlapping? if (ra.pos.x >= pos.x && ra.pos.y >= pos.y) { if ( ra.pos.x - pos.x > ra.pos.y - pos.y ) { width = ra.pos.x - pos.x - gap; } else { height = ra.pos.y - pos.y - gap; } anyChange = true; } // x is right of pos else if (ra.pos.x > pos.x) { width = ra.pos.x - pos.x - gap; anyChange = true; } // y is top of pos else if (ra.pos.y > pos.y) { height = ra.pos.y - pos.y - gap; anyChange = true; } } } } void update() { // update geometry only when the size changes if (anyChange) { if (rect != null) rect.del(); if (width >= minSize && height >= minSize) { rect = IG.plane(pos, width, height).clr(clr()); } // if too small, removed else { del(); } anyChange=false; } if (time==0) { new RectAgent(pos.cp(IRandom.pt(-10,-10, 0,10,10, 0)), IRandom.get(minSize,maxSize), IRandom.get(minSize,maxSize)).clr(clr()); } } }
import processing.opengl.*; import igeo.*; void setup() { size(480, 360, IG.GL); IG.duration(100); new MyAutomaton(new IVec(0, 0, 0)); } static class MyAutomaton extends IAgent { static double size = 10; IVec pos; int state = 1; MyAutomaton leftAutomaton=null, rightAutomaton=null; int lstate = 0, rstate = 0; MyAutomaton(IVec p) { pos = p; } void interact(IDynamics agent){ int leftState=0, rightState=0; //searching left and right automaton //if not found leftState & rightState are zero if(agent instanceof MyAutomaton){ MyAutomaton automaton = (MyAutomaton)agent; if(automaton.pos.eqX(pos.dup().sub(size,0,0))){ leftAutomaton = automaton; lstate = leftAutomaton.state; } else if(automaton.pos.eqX(pos.dup().add(size,0,0))){ rightAutomaton = automaton; rstate = rightAutomaton.state; } } } void update() { // when state==1, put a box, otherwise no box if(state == 1){ new IBox(pos.dup(), size, size, size).clr(0); } // update state with a state transition table if(lstate==0 && state==0 && rstate==0){ state=0; } else if(lstate==0 && state==0 && rstate==1){ state=1; } else if(lstate==0 && state==1 && rstate==0){ state=1; } else if(lstate==0 && state==1 && rstate==1){ state=1; } else if(lstate==1 && state==0 && rstate==0){ state=1; } else if(lstate==1 && state==0 && rstate==1){ state=0; } else if(lstate==1 && state==1 && rstate==0){ state=0; } else if(lstate==1 && state==1 && rstate==1){ state=0; } //move down pos.add(0,-size,0); //new automaton if(leftAutomaton==null){ new MyAutomaton(pos.dup().sub(size,0,0)); } if(rightAutomaton==null){ new MyAutomaton(pos.dup().add(size,0,0)); } } }
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(250); new LineAgent(new IVec(0,0,0), new IVec(0,1,0)).clr(0); } static class LineAgent extends IAgent{ IVec pt1, pt2; boolean isColliding=false; LineAgent(IVec pt, IVec dir){ pt1 = pt; pt2 = pt.cp(dir); } void interact(ArrayList< IDynamics > agents){ if(time == 0){ //only in the first time for(int i=0; i < agents.size() && !isColliding; i++){ if(agents.get(i) instanceof LineAgent){ LineAgent a = (LineAgent)agents.get(i); if(a != this){ if(!a.isColliding){ IVec intxn = IVec.intersectSegment(pt1, pt2, a.pt1,a.pt2); if(intxn!=null){ //intersection exists if(!intxn.eq(pt1)){ //not parent agent isColliding=true; } } } } } } } } void update(){ if(isColliding){ del(); } else if(time == 0){ //if not colliding new ICurve(pt1,pt2).clr(clr()); IVec dir = pt2.dif(pt1); double r = red()+IRand.get(0,0.01); double g = green()+IRand.get(0,0.01); double b = blue()+IRand.get(0,0.01); if(IRandom.percent(40)){ //bend new LineAgent(pt2, dir.dup().rot(IG.zaxis, IRandom.get(0,PI/20))).clr(r,g,b); } if(IRandom.percent(40)){ //bend the other way new LineAgent(pt2, dir.dup().rot(IG.zaxis, -IRandom.get(0,PI/20))).clr(r,g,b); } if(IRandom.percent(40)){ //straight new LineAgent(pt2, dir.dup()).clr(r,g,b); } } } }
An intersection of two line segments can be calculated by the following method.
IVec.intersectSegment(line1Pt1, line1Pt2, line2Pt1, line2Pt2);
If two line segments intersect, it returns one vector (IVec) as the location of intersection. If not, it returns a null value. Note that there is another intersect method for infinite lines.
IVec.intersect(line1Pt1, line1Pt2, line2Pt1, line2Pt2);
Another note is that those two intersection methods are intersection of lines in 3D space and if they are not exactly intersecting in 3D within the tolerance (defined by IConfig.tolerance), it returns a null value. If you care about intersections only on 2D, you can use intersection methods in IVec2 class. It can be slightly faster to run.
IVec2.intersectSegment(line1Pt1, line1Pt2, line2Pt1, line2Pt2);
All input arguments and a return value are instances of IVec2, not IVec. To apply this 2D intersection in the previous code, you need to convert IVec to IVec2 by to2d() method as following. To convert IVec2 to IVec, you can use to3d() method.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(250); new LineAgent(new IVec(0,0,0), new IVec(0,1,0)).clr(0); } static class LineAgent extends IAgent{ IVec pt1, pt2; boolean isColliding=false; LineAgent(IVec pt, IVec dir){ pt1 = pt; pt2 = pt.cp(dir); } void interact(ArrayList< IDynamics > agents){ if(time == 0){ //only in the first time for(int i=0; i < agents.size() && !isColliding; i++){ if(agents.get(i) instanceof LineAgent){ LineAgent a = (LineAgent)agents.get(i); if(a != this){ if(!a.isColliding){ IVec2 intxn = IVec2.intersectSegment(pt1.to2d(),pt2.to2d(), a.pt1.to2d(),a.pt2.to2d()); if(intxn!=null){ //intersection exists if(!intxn.eq(pt1.to2d())){ //not parent agent isColliding=true; } } } } } } } } void update(){ if(isColliding){ del(); } else if(time == 0){ //if not colliding new ICurve(pt1,pt2).clr(clr()); IVec dir = pt2.dif(pt1); double r = red()+IRand.get(0,0.01); double g = green()+IRand.get(0,0.01); double b = blue()+IRand.get(0,0.01); if(IRandom.percent(40)){ //bend new LineAgent(pt2, dir.dup().rot(IG.zaxis, IRandom.get(0,PI/20))).clr(r,g,b); } if(IRandom.percent(40)){ //bend the other way new LineAgent(pt2, dir.dup().rot(IG.zaxis, -IRandom.get(0,PI/20))).clr(r,g,b); } if(IRandom.percent(40)){ //straight new LineAgent(pt2, dir.dup()).clr(r,g,b); } } } }