Python Tutorials | (back to the list of tutorials) |
These conditions can be described with vectors. When there are two particles of particle 1 at the position P1 and particle 2 at P2, the difference vector between the vector P1 and the vector P2 is P2 - P1. Then because the amount of the force corresponds to the length of the vector, for the force to be proportional to the length, you simply multiply some constant number "k" as a proportional coefficient, as the following figure.
This tensile force F needs to be applied to both of the particles and the direction of each applied force needs to be opposite to satisfy the third law of Newton's laws of motion, the action-reaction law.
The tension force is applied to particles inside interact method. First a difference vector (dif) of two position of particles is calculated. Then the coefficient (tension) is multiplied to the difference to resize the vector as a force vector. Finally this force vector is applied to each particle with opposite direction by the particle's method push and pull.
One detailed technical note. Because the code inside interact method doesn't use its input argument agents, it would seem better to write this code in update method. However conceptually speaking, codes to influence other agents should be written inside interact method and codes to update its own internal state should be written inside update method. Technically speaking, inside iGeo server, interact method is executed first for all existing agents and then update method is executed second in each time frame. Because particle agents are updating velocities and positions out of forces they received, application of force should happen before the cycle of update method.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(20) pt1 = MyParticle(IG.v(-80,80,0),IG.v(0,0,0)) pt2 = MyParticle(IG.v(80,-80,0),IG.v(0,0,0)) pt1.clr(0) pt2.clr(1.0,0,0) MyTension(pt1, pt2, 3.0) class MyTension(IAgent) : def __init__(self, p1, p2, t) : self.particle1 = p1 self.particle2 = p2 self.tension = t # proportial coefficient def interact(self, agents) : dif = self.particle2.pos().dif(self.particle1.pos()) dif.mul(self.tension) self.particle1.push(dif) self.particle2.pull(dif) #opposite force class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) def update(self) : IPoint(self.pos().cp()).clr(self.clr()) # putting point every frame
The field line in a type of ICurve is added to the tension agent class and it creates a new line having two particles' positions as end points of the line when line is not initialized yet (When no value is assigned to field variable of some class, it contains a value of "null". Please also see this page about "null"). Then if it's already has content in line, ICurve's internal graphical representation is updated by updateGraphic() method.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(15) pt1 = MyParticle(IG.v(-80,80,0),IG.v(0,0,0)) pt2 = MyParticle(IG.v(80,-80,0),IG.v(0,0,0)) pt1.clr(0) pt2.clr(1.0,0,0) MyTension(pt1, pt2, 3.0).clr(1,0.5,0) class MyTension(IAgent) : def __init__(self, p1, p2, t) : self.particle1 = p1 self.particle2 = p2 self.tension = t # proportial coefficient self.line = None # line geometry def interact(self, agents) : dif = self.particle2.pos().dif(self.particle1.pos()) dif.mul(self.tension) self.particle1.push(dif) self.particle2.pull(dif) #opposite force def update(self) : if self.line is None : line = ICurve(self.particle1.pos(),self.particle2.pos()).clr(self.clr()) else : self.line.updateGraphic() class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) def update(self) : IPoint(self.pos().cp()).clr(self.clr()) # putting point every frame
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(15) pt1 = MyParticle(IG.v(-80,80,0),IG.v(0,0,0)) pt2 = MyParticle(IG.v(80,-80,0),IG.v(0,0,0)) pt1.clr(0) pt2.clr(1.0,0,0) ITensionLine(pt1, pt2, 3.0).clr(1,0.5,0) class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) def update(self) : IPoint(self.pos().cp()).clr(self.clr()) # putting point every frame
The first code below has 3 particles with zero initial velocity and 2 pairs of particles are connected by tension with the same tension strength.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(800) pt1 = MyParticle(IG.v(-80,80,0),IG.v(0,0,0)) pt2 = MyParticle(IG.v(80,-80,0),IG.v(0,0,0)) pt3 = MyParticle(IG.v(0,-80,0),IG.v(0,0,0)) pt1.clr(0) pt2.clr(1.0,0,0) pt3.clr(0.5,0,1) ITensionLine(pt1, pt2, 3.0).clr(1,0.5,0) ITensionLine(pt2, pt3, 3.0).clr(1,0.5,0) class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) self.prevPos = None def update(self) : curPos = self.pos().cp() if self.prevPos is not None : ICurve(self.prevPos,curPos).clr(self.clr()) self.prevPos = curPos
The next example changes one of 2 tensions to have smaller tension strength.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(600) pt1 = MyParticle(IG.v(-80,80,0),IG.v(0,0,0)) pt2 = MyParticle(IG.v(80,-80,0),IG.v(0,0,0)) pt3 = MyParticle(IG.v(0,-80,0),IG.v(0,0,0)) pt1.clr(0) pt2.clr(1.0,0,0) pt3.clr(0.5,0,1) ITensionLine(pt1, pt2, 3.0).clr(1,0.5,0) ITensionLine(pt2, pt3, 1.5).clr(1,0.5,0) class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) self.prevPos = None def update(self) : curPos = self.pos().cp() if self.prevPos is not None : ICurve(self.prevPos,curPos).clr(self.clr()) self.prevPos = curPos
The next example below has same strength coefficient in 2 tensions but one of particle agent has non-zero initial velocity towards X direction.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(1200) pt1 = MyParticle(IG.v(-80,80,0),IG.v(0,0,0)) pt2 = MyParticle(IG.v(80,-80,0),IG.v(0,0,0)) pt3 = MyParticle(IG.v(0,-80,0),IG.v(50,0,0)) pt1.clr(0) pt2.clr(1.0,0,0) pt3.clr(0.5,0,1) ITensionLine(pt1, pt2, 3.0).clr(1,0.5,0) ITensionLine(pt2, pt3, 3.0).clr(1,0.5,0) class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) self.prevPos = None def update(self) : curPos = self.pos().cp() if self.prevPos is not None : ICurve(self.prevPos,curPos).clr(self.clr()) self.prevPos = curPos
The code sets friction of particle agents to converge into one state, without oscillating. This is done by a particle agent's method fric(). Some value between 0.0 and 1.0 should be set in the friction method and 0.0 means no friction and 1.0 means full friction making the particle hardly move. Usually, very small number like 0.01 or 0.001 is enough to converge a movement behavior of particles. To fix the corners of the grid, the method of particle agent fix() is used. When a particle is fixed with this method, all the forces applied to it is ignored and it doesn't move. If you want to unfix once fixed particle, please use unfix() method.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(800) num = 10 pts = [] for i in range(num+1) : pts.append([]) for j in range(num+1) : pts[i].append(MyParticle(IG.v(10*i,10*j,0), IG.v(0,0,0))) pts[i][j].fric(0.01) #friction if i > 0 : #tension line in X ITensionLine(pts[i-1][j], pts[i][j],1).clr(0) if j > 0 : #tension line in Y ITensionLine(pts[i][j-1], pts[i][j],1).clr(0) pts[0][0].fix().clr(0.5,0,0) #fix the corner particle pts[num][0].fix().clr(0.5,0,0) #fix the corner particle pts[0][num].fix().clr(0.5,0,0) #fix the corner particle pts[num][num].fix().clr(0.5,0,0) #fix the corner particle class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) self.prevPos = None
The code below creates a triangulated orthogonal grid of tensile lines and nodes of the grid is randomly removed.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(500) num = 30 pts = [] for i in range(num+1) : for j in range(num+1) : pts.append([]) if IRand.pct(87) : pts[i].append(MyParticle(IG.v(10*i,10*j,0), IG.v(0,0,0))) pts[i][j].fric(0.01) #friction if i > 0 and pts[i-1][j] is not None : ITensionLine(pts[i-1][j], pts[i][j],1).clr(0) if j > 0 and pts[i][j-1] is not None : ITensionLine(pts[i][j-1], pts[i][j],1).clr(0) if i > 0 and j > 0 and pts[i-1][j-1] is not None : ITensionLine(pts[i-1][j-1], pts[i][j],1).clr(0) if i==0 or j==0 or i==num or j==num : # edge pts[i][j].fix().clr(0.5,0,0) else : pts[i].append(None) class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) self.prevPos = None
The code below includes an agent class of a gravity force, MyGravity. Each particle is pulled by the tensile lines as well as the gravity force.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(2000) num = 30 pts = [] for i in range(num+1) : for j in range(num+1) : pts.append([]) if IRand.pct(75) : pts[i].append(MyParticle(IG.v(10*i,10*j,0), IG.v(0,0,0))) pts[i][j].fric(0.01) #friction if i > 0 and pts[i-1][j] is not None : ITensionLine(pts[i-1][j], pts[i][j],1).clr(0) if j > 0 and pts[i][j-1] is not None : ITensionLine(pts[i][j-1], pts[i][j],1).clr(0) if i > 0 and j > 0 and pts[i-1][j-1] is not None : ITensionLine(pts[i-1][j-1], pts[i][j],1).clr(0) if i==0 or j==0 or i==num or j==num : # edge pts[i][j].fix().clr(0.5,0,0) else : pts[i].append(None) MyGravity(IG.v(0,0,-10)) class MyGravity(IAgent) : def __init__(self, g) : self.gravity = g def interact(self, agents) : for agent in agents : if isinstance(agent, MyParticle) : agent.push(self.gravity) class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) self.prevPos = None
The next code has an agent class to implement a repulsive force, RepulsionAgent. This agent has the same algorithm with attractor agents for particles. It just has opposite direction of force to be applied to particles.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.duration(1000) num = 30 pts = [] for i in range(num+1) : for j in range(num+1) : pts.append([]) if IRand.pct(75) : pts[i].append(MyParticle(IG.v(10*i,10*j,0), IG.v(0,0,0))) pts[i][j].fric(0.01) #friction if i > 0 and pts[i-1][j] is not None : ITensionLine(pts[i-1][j], pts[i][j],1).clr(0) if j > 0 and pts[i][j-1] is not None : ITensionLine(pts[i][j-1], pts[i][j],1).clr(0) if i > 0 and j > 0 and pts[i-1][j-1] is not None : ITensionLine(pts[i-1][j-1], pts[i][j],1).clr(0) if i==0 or j==0 or i==num or j==num : # edge pts[i][j].fix().clr(0.5,0,0) else : pts[i].append(None) for i in range(10) : RepulsionAgent(IRand.pt(-50,-50,-10,350,350,-10)).clr(1.,0,0) class RepulsionAgent(IPointAgent) : threshold = 100.0 minDist = 1.0 repulsion = 400.0 def __init__(self, v) : IPointAgent.__init__(self, v) def interact(self, agents) : for agent in agents : if isinstance(agent, MyParticle) : dif = agent.pos().dif(self.pos()) #force from here to particle dist = dif.len() if dist < RepulsionAgent.threshold : if dist < RepulsionAgent.minDist : dist = RepulsionAgent.minDist strength = RepulsionAgent.repulsion/dist #the closer the larger force = dif.len(strength) agent.push(force) class MyParticle(IParticle) : def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) self.prevPos = None