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.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(20); MyParticle pt1 = new MyParticle(IG.v(-80,80,0),IG.v(0,0,0)); MyParticle pt2 = new MyParticle(IG.v(80,-80,0),IG.v(0,0,0)); pt1.clr(0); pt2.clr(1.0,0,0); new MyTension(pt1, pt2, 3.0); } class MyTension extends IAgent{ MyParticle particle1, particle2; double tension; // proportial coefficient MyTension(MyParticle p1, MyParticle p2, double t){ particle1 = p1; particle2 = p2; tension = t; } void interact(ArrayList < IDynamics > agents){ IVec dif = particle2.pos().dif(particle1.pos()); dif.mul(tension); particle1.push(dif); particle2.pull(dif); //opposite force } } class MyParticle extends IParticle{ MyParticle(IVec pos, IVec vel){ super(pos,vel); } void update(){ new IPoint(pos.cp()).clr(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.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(15); MyParticle pt1 = new MyParticle(IG.v(-80,80,0),IG.v(0,0,0)); MyParticle pt2 = new MyParticle(IG.v(80,-80,0),IG.v(0,0,0)); pt1.clr(0); pt2.clr(1.0,0,0); new MyTension(pt1, pt2, 3.0).clr(1,0.5,0); } class MyTension extends IAgent{ MyParticle particle1, particle2; double tension; // proportial coefficient ICurve line; MyTension(MyParticle p1, MyParticle p2, double t){ particle1 = p1; particle2 = p2; tension = t; } void interact(ArrayList < IDynamics > agents){ IVec dif = particle2.pos().dif(particle1.pos()); dif.mul(tension); particle1.push(dif); particle2.pull(dif); //opposite force } void update(){ if(line==null){ line = new ICurve(particle1.pos(),particle2.pos()).clr(clr()); } else{ line.updateGraphic(); } } } class MyParticle extends IParticle{ MyParticle(IVec pos, IVec vel){ super(pos,vel); } void update(){ new IPoint(pos.cp()).clr(clr()); // putting point every frame } }
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(15); MyParticle pt1 = new MyParticle(IG.v(-80,80,0),IG.v(0,0,0)); MyParticle pt2 = new MyParticle(IG.v(80,-80,0),IG.v(0,0,0)); pt1.clr(0); pt2.clr(1.0,0,0); new ITensionLine(pt1, pt2, 3.0).clr(1,0.5,0); } class MyParticle extends IParticle{ MyParticle(IVec pos, IVec vel){ super(pos,vel); } void update(){ new IPoint(pos.cp()).clr(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.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(800); MyParticle pt1 = new MyParticle(IG.v(-80,80,0),IG.v(0,0,0)); MyParticle pt2 = new MyParticle(IG.v(80,-80,0),IG.v(0,0,0)); MyParticle pt3 = new 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); new ITensionLine(pt1, pt2, 3.0).clr(1,0.5,0); new ITensionLine(pt2, pt3, 3.0).clr(1,0.5,0); } class MyParticle extends IParticle{ IVec prevPos; MyParticle(IVec pos, IVec vel){ super(pos,vel); } void update(){ IVec curPos = pos().cp(); if(prevPos != null){ new ICurve(prevPos,curPos).clr(clr()); } prevPos = curPos; } }
The next example changes one of 2 tensions to have smaller tension strength.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(600); MyParticle pt1 = new MyParticle(IG.v(-80,80,0),IG.v(0,0,0)); MyParticle pt2 = new MyParticle(IG.v(80,-80,0),IG.v(0,0,0)); MyParticle pt3 = new 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); new ITensionLine(pt1, pt2, 3.0).clr(1,0.5,0); new ITensionLine(pt2, pt3, 1.5).clr(1,0.5,0); } class MyParticle extends IParticle{ IVec prevPos; MyParticle(IVec pos, IVec vel){ super(pos,vel); } void update(){ IVec curPos = pos().cp(); if(prevPos != null){ new ICurve(prevPos,curPos).clr(clr()); } 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.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(1200); MyParticle pt1 = new MyParticle(IG.v(-80,80,0),IG.v(0,0,0)); MyParticle pt2 = new MyParticle(IG.v(80,-80,0),IG.v(0,0,0)); MyParticle pt3 = new 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); new ITensionLine(pt1, pt2, 3.0).clr(1,0.5,0); new ITensionLine(pt2, pt3, 3.0).clr(1,0.5,0); } class MyParticle extends IParticle{ IVec prevPos; MyParticle(IVec pos, IVec vel){ super(pos,vel); } void update(){ IVec curPos = pos().cp(); if(prevPos != null){ new ICurve(prevPos,curPos).clr(clr()); } 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.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(800); int num = 10; MyParticle[][] pts = new MyParticle[num+1][num+1]; for(int i=0; i <= num; i++){ for(int j=0; j <= num; j++){ pts[i][j] = new 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 new ITensionLine(pts[i-1][j], pts[i][j],1).clr(0); } if(j > 0){ //tension line in Y new 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 extends IParticle{ IVec prevPos; MyParticle(IVec pos, IVec vel){ super(pos,vel); } }
The code below creates a triangulated orthogonal grid of tensile lines and nodes of the grid is randomly removed.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(500); int num = 30; MyParticle[][] pts = new MyParticle[num+1][num+1]; for(int i=0; i <= num; i++){ for(int j=0; j <= num; j++){ if(IRand.pct(87)){ pts[i][j] = new MyParticle(IG.v(10*i,10*j,0), IG.v(0,0,0)); pts[i][j].fric(0.01); //friction if(i > 0 && pts[i-1][j]!=null){ new ITensionLine(pts[i-1][j], pts[i][j],1).clr(0); } if(j > 0 && pts[i][j-1]!=null){ new ITensionLine(pts[i][j-1], pts[i][j],1).clr(0); } if(i > 0 && j > 0 && pts[i-1][j-1]!=null){ new ITensionLine(pts[i-1][j-1], pts[i][j],1).clr(0); } if(i==0 || j==0 || i==num || j==num){ // edge pts[i][j].fix().clr(0.5,0,0); } } } } } class MyParticle extends IParticle{ IVec prevPos; MyParticle(IVec pos, IVec vel){ super(pos,vel); } }
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.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(2000); int num = 30; MyParticle[][] pts = new MyParticle[num+1][num+1]; for(int i=0; i <= num; i++){ for(int j=0; j <= num; j++){ if(IRand.pct(75)){ pts[i][j] = new MyParticle(IG.v(10*i,10*j,0), IG.v(0,0,0)); pts[i][j].fric(0.01); //friction if(i > 0 && pts[i-1][j]!=null){ new ITensionLine(pts[i-1][j], pts[i][j],1).clr(0); } if(j > 0 && pts[i][j-1]!=null){ new ITensionLine(pts[i][j-1], pts[i][j],1).clr(0); } if(i > 0 && j > 0 && pts[i-1][j-1]!=null){ new ITensionLine(pts[i-1][j-1], pts[i][j],1).clr(0); } if(i==0 || j==0 || i==num || j==num){ // edge pts[i][j].fix().clr(0.5,0,0); } } } } new MyGravity(IG.v(0,0,-10)); } class MyGravity extends IAgent{ IVec gravity; MyGravity(IVec g){ gravity=g; } void interact(IDynamics agent){ if(agent instanceof MyParticle){ MyParticle particle = (MyParticle)agent; particle.push(gravity); } } } class MyParticle extends IParticle{ IVec prevPos; MyParticle(IVec pos, IVec vel){ super(pos,vel); } }
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.
import processing.opengl.*; import igeo.*; void setup(){ size(480, 360, IG.GL); IG.duration(1000); int num = 30; MyParticle[][] pts = new MyParticle[num+1][num+1]; for(int i=0; i <= num; i++){ for(int j=0; j <= num; j++){ if(IRand.pct(75)){ pts[i][j] = new MyParticle(IG.v(10*i,10*j,0), IG.v(0,0,0)); pts[i][j].fric(0.01); //friction if(i > 0 && pts[i-1][j]!=null){ new ITensionLine(pts[i-1][j], pts[i][j],1).clr(0); } if(j > 0 && pts[i][j-1]!=null){ new ITensionLine(pts[i][j-1], pts[i][j],1).clr(0); } if(i > 0 && j > 0 && pts[i-1][j-1]!=null){ new ITensionLine(pts[i-1][j-1], pts[i][j],1).clr(0); } if(i==0 || j==0 || i==num || j==num){ // edge pts[i][j].fix().clr(0.5,0,0); } } } } for(int i=0; i < 10; i++){ new RepulsionAgent(IRand.pt(-50,-50,-10,350,350,-10)).clr(1.,0,0); } } class RepulsionAgent extends IPointAgent{ double threshold = 100; double minDist = 1.0; double repulsion = 400; RepulsionAgent(IVec v){ super(v); } void interact(IDynamics agent){ if(agent instanceof MyParticle){ MyParticle particle = (MyParticle)agent; IVec dif = particle.pos().dif(pos()); //force from here to particle double dist = dif.len(); if(dist < threshold){ if(dist < minDist){ dist = minDist; } double strength = repulsion/dist; //the closer the larger IVec force = dif.len(strength); particle.push(force); } } } } class MyParticle extends IParticle{ IVec prevPos; MyParticle(IVec pos, IVec vel){ super(pos,vel); } }