Python 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.
add_library('igeo') def setup() : size(480, 360, IG.GL) MyAgent(IG.v(), IG.v(0,1,0)) class MyAgent(IAgent) : def __init__(self, pt, dir) : self.pt1 = pt self.pt2 = pt.cp(dir) def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(0) #geometry representation dir = self.pt2.dif(self.pt1) MyAgent(self.pt2, dir) #reproduce
add_library('igeo') def setup() : size(480, 360, IG.GL) MyAgent(IG.v(), IG.v(0,1,0)) class MyAgent(IAgent) : def __init__(self, pt, dir) : self.pt1 = pt self.pt2 = pt.cp(dir) def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(0) #geometry representation dir = self.pt2.dif(self.pt1) dir.rot(PI/100) # transformation rule 1: rotate dir.mul(0.999) # transformation rule 2: scale down MyAgent(self.pt2, dir) #reproduce
add_library('igeo') def setup() : size(480, 360, IG.GL) MyAgent(IG.v(), IG.v(0,1,0)) class MyAgent(IAgent) : def __init__(self, pt, dir) : self.pt1 = pt self.pt2 = pt.cp(dir) def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(0) #geometry representation dir = self.pt2.dif(self.pt1) dir.rot(PI/100) # transformation rule 1: rotate dir.mul(0.999) # transformation rule 2: scale down MyAgent(self.pt2, dir) # branch 1 dir2 = self.pt2.dif(self.pt1) dir2.rot(-PI/10) MyAgent(self.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.
add_library('igeo') def setup() : size(480, 360, IG.GL) MyAgent(IG.v(), IG.v(0,1,0)) class MyAgent(IAgent) : def __init__(self, pt, dir) : self.pt1 = pt self.pt2 = pt.cp(dir) def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(0) #geometry representation dir = self.pt2.dif(self.pt1) dir.rot(PI/100) # transformation rule 1: rotate dir.mul(0.999) # transformation rule 2: scale down MyAgent(self.pt2, dir) # branch 1 if IRand.pct(3) : # 3% probability dir2 = self.pt2.dif(self.pt1) dir2.rot(-PI/10) MyAgent(self.pt2, dir2) # branch 2
!intxn.eq(pt1) // intersection and pt1 are not at the same location
add_library('igeo') def setup() : size(480, 360, IG.GL) MyAgent(IG.v(), IG.v(0,1,0)) class MyAgent(IAgent) : def __init__(self, pt, dir) : self.pt1 = pt self.pt2 = pt.cp(dir) def interact(self, agents) : if self.time()==0 : for agent in agents : if isinstance(agent, MyAgent) : if agent is not self : intxn = IVec.intersectSegment(agent.pt1,agent.pt2,self.pt1,self.pt2) if intxn is not None and not intxn.eq(self.pt1) : self.del() def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(0) #geometry representation dir = self.pt2.dif(self.pt1) dir.rot(PI/100) # transformation rule 1: rotate dir.mul(0.999) # transformation rule 2: scale down MyAgent(self.pt2, dir) # branch 1 if IRand.pct(3) : # 3% probability dir2 = self.pt2.dif(self.pt1) dir2.rot(-PI/10) MyAgent(self.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.
add_library('igeo') def setup() : size(480, 360, IG.GL) MyAgent(IG.v(), IG.v(0,1,0), True) class MyAgent(IAgent) : def __init__(self, pt, dir, straight) : self.pt1 = pt self.pt2 = pt.cp(dir) self.isStraight = straight def interact(self, agents) : if self.time()==0 : for agent in agents : if not self.alive() : return if isinstance(agent, MyAgent) : if agent is not self : intxn = IVec.intersectSegment(agent.pt1,agent.pt2,self.pt1,self.pt2) if intxn is not None and not intxn.eq(self.pt1) : self.del() def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(0) #geometry representation dir = self.pt2.dif(self.pt1) if IRand.pct(0.8) : self.isStraight = not self.isStraight #toggle boolean switch if self.isStraight : MyAgent(self.pt2,dir,self.isStraight) else : dir.rot(PI/100) dir.mul(0.999) MyAgent(self.pt2,dir,self.isStraight) if IRand.pct(3) : dir2 = self.pt2.dif(self.pt1) dir2.rot(-PI/10) MyAgent(self.pt2, dir2, self.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.
add_library('igeo') def setup() : size(480, 360, IG.GL) MyAgent(IG.v(), IG.v(0,1,0), True, 0) class MyAgent(IAgent) : def __init__(self, pt, dir, straight, ang) : self.pt1 = pt self.pt2 = pt.cp(dir) self.isStraight = straight self.angle = ang def interact(self, agents) : if self.time()==0 : for agent in agents : if not self.alive() : return if isinstance(agent, MyAgent) : if agent is not self : intxn = IVec.intersectSegment(agent.pt1,agent.pt2,self.pt1,self.pt2) if intxn is not None and not intxn.eq(self.pt1) : self.del() def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(0) dir = self.pt2.dif(self.pt1) if IRand.pct(0.8) : self.isStraight = not self.isStraight if self.isStraight : MyAgent(self.pt2, dir, self.isStraight, self.angle) else : dir.rot(self.angle) dir.mul(0.999) if IRand.pct(1) : self.angle += IRand.get(-0.05, 0.05) self.angle = -self.angle # flip bending direction MyAgent(self.pt2, dir, self.isStraight, self.angle) if IRand.pct(3) : dir2 = self.pt2.dif(self.pt1) dir2.rot(-PI/10) MyAgent(self.pt2, dir2, self.isStraight, self.angle)
add_library('igeo') def setup() : size(480, 360, IG.GL) MyAgent(IG.v(), IG.v(0,1,0), True, 0) for i in range(10) : Attractor(IRand.pt(-200,200,200,400)).clr(1.0,0,0) class MyAgent(IAgent) : def __init__(self, pt, dir, straight, ang) : self.pt1 = pt self.pt2 = pt.cp(dir) self.isStraight = straight self.angle = ang self.target = None def interact(self, agents) : if self.time()==0 : minDist = -1 for agent in agents : if not self.alive() : return if isinstance(agent, MyAgent) : if agent is not self : intxn = IVec.intersectSegment(agent.pt1,agent.pt2,self.pt1,self.pt2) if intxn is not None and not intxn.eq(self.pt1) : self.del() elif isinstance(agent, Attractor) : # find the closest attractor in front dist = agent.pos.dist(self.pt2) if minDist < 0 : # first time to check Attractor minDist = dist self.target = agent elif dist < minDist : minDist = dist self.target = agent def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(0) dir = self.pt2.dif(self.pt1) if IRand.pct(0.8) : self.isStraight = not self.isStraight if self.isStraight : if self.target is not None : targetDir = self.target.pos.dif(self.pt2) if dir.cp().rot(self.angle).angle(targetDir) > \ dir.cp().rot(-self.angle).angle(targetDir) : self.angle = -self.angle # angle closer to target dir.rot(self.angle) # rotate toward target MyAgent(self.pt2, dir, self.isStraight, self.angle) # branch 1 else : if IRand.pct(1) : self.angle += IRand.get(-0.05, 0.05) self.angle = -self.angle if self.target is not None : targetDir = self.target.pos.dif(self.pt2) if dir.cp().rot(self.angle).angle(targetDir) > \ dir.cp().rot(-self.angle).angle(targetDir) : self.angle = -self.angle # angle closer to target dir.rot(self.angle) # rotate toward target dir.mul(0.999) MyAgent(self.pt2, dir, self.isStraight, self.angle) # branch 1 else : dir.rot(self.angle) dir.mul(0.999) if IRand.pct(99) : MyAgent(self.pt2, dir, self.isStraight, self.angle) # branch 1 if IRand.pct(3) : # 3% probability dir2 = self.pt2.dif(self.pt1) dir2.rot(-PI/10) MyAgent(self.pt2, dir2, self.isStraight, self.angle) # branch 2 class Attractor(IAgent) : def __init__(self, p) : self.pos = p def update(self) : if self.time()==0 : IPoint(self.pos).clr(self.clr())
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("agent_lines1.3dm") # input file to initialize agents roots = IG.layer("root").curves() bounds = IG.layer("boundary").curves() attractors = IG.layer("attractor").points() for crv in roots : MyAgent(crv.start(), crv.end().dif(crv.start()), 0) for pt in attractors : Attractor(pt.pos()).clr(IRand.clr()) pt.del() for crv in bounds : BoundaryAgent(crv) IG.bg(0) class MyAgent(IAgent) : def __init__(self, pt, dir, ang) : self.pt1 = pt self.pt2 = pt.cp(dir) self.isStraight = True self.angle = ang self.target = None def interact(self, agents) : if self.time()==0 : minDist = -1 for agent in agents : if not self.alive() : return if isinstance(agent, MyAgent) : if agent is not self : intxn = IVec.intersectSegment(agent.pt1,agent.pt2,self.pt1,self.pt2) if intxn is not None and not intxn.eq(self.pt1) : self.del() elif isinstance(agent, Attractor) : # find the closest attractor in front dist = agent.pos.dist(self.pt2) if minDist < 0 : # first time to check Attractor minDist = dist self.target = agent elif dist < minDist : minDist = dist self.target = agent def update(self) : if self.time()==0 : ICurve(self.pt1, self.pt2).clr(self.clr().cp()) dir = self.pt2.dif(self.pt1) if self.isStraight : # just go straight if self.target is not None : self.clr().blend(self.target.clr(),0.02) # blend 2% color MyAgent(self.pt2, dir, self.angle).clr(self.clr().cp()) # branch 1 else : if IRand.pct(1) : self.angle += IRand.get(-0.05, 0.05) self.angle = -self.angle if self.target is not None : targetDir = self.target.pos.dif(self.pt2) if dir.cp().rot(self.angle).angle(targetDir) > \ dir.cp().rot(-self.angle).angle(targetDir) : self.angle = -self.angle # angle closer to target dir.rot(self.angle) # rotate toward target dir.mul(0.999) self.clr().blend(self.target.clr(),0.02) # blend 2% color MyAgent(self.pt2, dir, self.angle).clr(self.clr().cp()) # branch 1 else : dir.rot(self.angle) dir.mul(0.999) if IRand.pct(100) : MyAgent(self.pt2, dir, self.angle).clr(self.clr().cp()) # branch 1 if IRand.pct(3) : # 3% probability dir2 = self.pt2.dif(self.pt1) dir2.rot(-PI/10) MyAgent(self.pt2, dir2, self.angle).clr(self.clr().cp()) # branch 2 class Attractor(IAgent) : def __init__(self, p) : self.pos = p def update(self) : if self.time()==0 : IPoint(self.pos).clr(self.clr()) class BoundaryAgent(IAgent) : def __init__(self, crv) : self.boundary = crv def interact(self, agents) : for agent in agents : if isinstance(agent, MyAgent) : if agent.time()==0 : # check only what's just created if self.boundary.isInside2d(agent.pt2) : # check if it's inside agent.isStraight=False