home processing download documents tutorial python tutorial gallery source about
 Python Tutorials (back to the list of tutorials)

Tension to Particles (requires iGeo version 7.5.4 or higher)

     Force Vector of Tension

A force of tension between two particles can be defined by calculation of vectors. The direction of the tension force is same with the difference vector between two particles. The amount of the force is proportional to the distance between them because when they are farther, the tension is larger like a rubber band pulled apart.

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.


     Tension Agent Class

Implementation of tension to particle agents can be done by creating tension agent class. The following code shows the tension agent class MyTension and its fields contain two vector variable of particle agents particle1 and particle2, and one scalar variable of proportional coefficient of tension tension.

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


     Tension Line

The tension agent class in the previous code didn't have any geometry to show where a tension force is applied. If you want to show the tension agent as a line connecting two particle agents, you can add a field of ICurve for the tension agent to contain geometry and update the geometry in each time frame when the position of particle changes as the following example.

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


     Using ITensionLine Class

iGeo library has a class to simulate a tensile force and represent it as a line connecting two particles. It is ITensionLine class. You can use this to simulate tensile force behaviors if you don't need to define your custom tension behavior.

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


     Interaction of Particles with Tension

The following three examples shows behaviors of particles when they are connected with tension. They show traces of particle agents and the behavior of particles can be tuned by positions and velocities of particles and the coefficient of tension strength.

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


     Tensile Line Network

Using tension line agents, you can set up a larger network of tensile lines. The following code creates an orthogonal grid of lines and simulates tensile behavior between particle nodes of the grid. (The particle agent below doesn't generate lines because it's intended to show the state of the moment, not the trace of all particles in time).

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


     Combining Tension and Other Forces

The tutorial codes below show examples to combine tensile forces and other type of forces to the particle agents. It's done by creating agent classes for each type of force.

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


(back to the list of tutorials)

HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java / Python)
GALLERY
SOURCE CODE(GitHub)
ABOUT