just a test

Dependencies:   mbed

Fork of scoreLight_Advanced by Alvaro Cassinelli

Revision:
0:345b3bc7a0ea
Child:
1:a4050fee11f7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/elasticLoop.cpp	Wed Mar 28 14:40:01 2012 +0000
@@ -0,0 +1,851 @@
+/*
+ *  elasticLoop.cpp
+ *  laserBlobPure
+ *
+ *  Created by CASSINELLI ALVARO on 5/20/11.
+ *  Copyright 2011 TOKYO UNIVERSITY. All rights reserved.
+ *
+ */
+
+#include "elasticLoop.h"
+
+// SHOULD NOT BE HERE: (only because I am using AD_MIRRIOR... max and min in the set region function that should not be here)
+#include "hardwareIO.h"
+
+elasticLoop::elasticLoop() {
+}
+
+elasticLoop::~elasticLoop() {
+/* // no need to do clear, this is done by default when deleting the vector container
+    massesLoop.clear();
+    loopSpringArray.clear(); 
+    hairVector.clear(); 
+    lightForce.clear();
+    centralSpringArray.clear(); 
+     displaySensingBuffer.lsdTrajectory.clear(); 
+*/
+    
+}
+  
+
+ void elasticLoop::createBlob(int _id, int _elasticBlobMode, vector2D _initPos) {
+    // (1) set ID:
+    identifier=_id;
+   
+ // (2) Initialize common variables of all blobs (base class):
+  initCommonVariables();
+  
+  // (3) initialize common variables for the elastic blob types:
+  slidingDirection=true; //  (will change when touching wall)
+ 
+ // (3) Initialize secondary variables depending on the blob type and mode:
+ // ElasticLoopMode test=CONTOUR_FOLLOWING;
+ switch(_elasticBlobMode) {
+  case RELAX:
+  
+    // Name of this kind of spot: 
+    sprintf(spotName,"loop_relax"); //this is an relaxing elastic loop
+  
+    // Color: (use parameter in the future):   
+    setColor(0x07);//0x04+0x02>>i);
+  
+    // default (initial) shape (the scafold belongs to the base class): 
+    bluePrint.buildCircularScafold(400, _initPos, vector2D(0,0), 50); //(float _radius, vector2D _pos,vector2D _vel, int _numScafoldPoints);
+   
+   // Numeric parameters for the simulated mechanical system: 
+   massLoopParticle=0.25; 
+   dampMotionMassesLoop=0.025;//0.17;
+   massAnchor=2.0;
+   dampMotionAnchorMass=0.001;
+    // Springs:
+   centralSpringK=0.3;
+   centralSpringRelax=bluePrint.radius; // use the radius of the scafold
+   interSpringK=0.46;
+   interSpringRelax=20;
+    // for "zack-like" blob:
+   interParticleRange=100;
+   factorInterParticleForce=18.0;
+   
+    searchActive=false;
+    pseudopodesMode=false; // this is for contour following.
+   
+    // Active/inactive forces:
+    springForcesOnLoop=true;
+    lightForcesOnLoop=true;
+    forceBorderOnLoop=false;
+   nuclearForceOnLoop=false;//true;
+   interParticleForceOnLoop=false;
+   forceInternalPressureOnLoop=false; // (when true, either constant force or calculated area using Green function or approximation by bounding box)
+    // Recentering vector:
+   angleCorrectionForceLoop=0;// in deg
+   recenteringForceOnLoop=false;
+   angleCorrectionForceNucleus=0;// in deg
+   recenteringForceOnNucleus=false;//true;
+   
+   factorLightForce=4.0;//3.0;//8.0; 
+   factorRecenteringAnchorMass=20.0/bluePrint.scafold.size(); // use number of points in the scafold
+   factorRecenteringLoopMass=0.3;
+   factorPressureLoopMass=1.0;
+   factorForceBorder=4.5;
+   
+  break;
+  
+  case CONTRACT:
+  
+    sprintf(spotName,"loop_contract"); //this is an relaxing elastic loop
+    
+    setColor(0x07);//0x04+0x02>>i); 
+     
+    // default (initial) shape: 
+    bluePrint.buildCircularScafold(400, _initPos, vector2D(0,0), 50); //(float _radius, vector2D _pos,vector2D _vel, int _numScafoldPoints);
+    
+   // Numeric parameters for the simulated mechanical system:
+    massLoopParticle=0.25; 
+    dampMotionMassesLoop=0.024;//0.17;
+    massAnchor=2.0;
+    dampMotionAnchorMass=0.001;
+      // Springs:
+    centralSpringK=0.5;
+    centralSpringRelax=bluePrint.radius;
+    interSpringK=0.4;//46;
+    interSpringRelax=30;
+    // for "zack-like" blob:
+    interParticleRange=100;
+    factorInterParticleForce=18.0;
+    
+    searchActive=false;
+    pseudopodesMode=false; // this is for contour following.
+
+    // Active/Inactive Forces:
+    springForcesOnLoop=true;
+    lightForcesOnLoop=true;
+    forceBorderOnLoop=false;
+    nuclearForceOnLoop=true;//true;
+    interParticleForceOnLoop=false;
+    forceInternalPressureOnLoop=false; // (when true, either constant force or calculated area using Green function or approximation by bounding box)
+    // Recentering vector:
+    angleCorrectionForceLoop=0;// in deg
+    recenteringForceOnLoop=false;
+    angleCorrectionForceNucleus=0;// in deg
+    recenteringForceOnNucleus=false;//true;
+    
+    factorLightForce=6.0;//3.0;//8.0; 
+    factorRecenteringAnchorMass=20.0/bluePrint.scafold.size();
+    factorRecenteringLoopMass=0.3;
+    factorPressureLoopMass=1.0;
+    factorForceBorder=4.5;
+     
+  break;
+  case CONTRACT_CENTRAL:
+    sprintf(spotName,"loop_contract_central");
+    
+    setColor(0x07);//0x04+0x02>>i); 
+     
+    // default (initial) shape: 
+    bluePrint.buildCircularScafold(400, _initPos, vector2D(0,0), 50); //(float _radius, vector2D _pos,vector2D _vel, int _numScafoldPoints);
+    
+   // Numeric parameters for the simulated mechanical system:
+    massLoopParticle=0.3; 
+    dampMotionMassesLoop=0.023;//0.17;
+    massAnchor=0.5;
+    dampMotionAnchorMass=0.001;
+      // Springs:
+    centralSpringK=0.3;
+    centralSpringRelax=bluePrint.radius;
+    interSpringK=0.54;//46;
+    interSpringRelax=30;
+    // for "zack-like" blob:
+    interParticleRange=100;
+    factorInterParticleForce=18.0;
+    
+    searchActive=false;
+    pseudopodesMode=false; // this is for contour following.
+ 
+    // Active/Inactive Forces:
+    springForcesOnLoop=true;
+    lightForcesOnLoop=true;
+    forceBorderOnLoop=false;
+    nuclearForceOnLoop=false;//true;
+    interParticleForceOnLoop=false;
+    forceInternalPressureOnLoop=false; // (when true, either constant force or calculated area using Green function or approximation by bounding box)
+    // Recentering vector:
+    angleCorrectionForceLoop=0;// in deg
+    recenteringForceOnLoop=true;
+    angleCorrectionForceNucleus=0;// in deg
+    recenteringForceOnNucleus=false;//true;
+    
+    factorLightForce=4.3;//3.0;//8.0; 
+    factorRecenteringAnchorMass=20.0/bluePrint.scafold.size();
+    factorRecenteringLoopMass=0.045;
+    factorPressureLoopMass=1.5;
+    factorForceBorder=150;
+  
+  break; 
+  case CONTOUR_FOLLOWING:
+   sprintf(spotName,"following"); //this is a contour-following loop
+    
+    setColor(0x07);//0x04+0x02>>i); 
+     
+    // default (initial) shape: 
+   bluePrint.buildCircularScafold(100, _initPos, vector2D(0,0), 20); //(float _radius, vector2D _pos,vector2D _vel, int _numScafoldPoints);
+    
+   // Numeric parameters for the simulated mechanical system:
+    massLoopParticle=0.05; 
+    dampMotionMassesLoop=0.27;//0.17;
+    massAnchor=3.0;
+    dampMotionAnchorMass=0.03;
+      // Springs:
+    centralSpringK=0.4;
+    centralSpringRelax=bluePrint.radius;
+    interSpringK=0.4;//46;
+    interSpringRelax=0.7*bluePrint.radius*2*sin(1.0* PI/ bluePrint.scafold.size()); // if factor=1, this makes for a perfect polygon at relax for all springs...
+    // for "zack-like" blob:
+    interParticleRange=70;
+    factorInterParticleForce=4.0;
+    
+    searchActive=true;
+    pseudopodesMode=true; // this is for contour following.
+ 
+    // Active/Inactive Forces:
+    springForcesOnLoop=true;
+    lightForcesOnLoop=true;
+    forceBorderOnLoop=false;
+    nuclearForceOnLoop=false;//true;
+    interParticleForceOnLoop=true;
+    forceInternalPressureOnLoop=false; // (when true, either constant force or calculated area using Green function or approximation by bounding box)
+    // Recentering vector:
+    angleCorrectionForceLoop=239;// in deg
+    recenteringForceOnLoop=true;
+    angleCorrectionForceNucleus=180;// in deg
+    recenteringForceOnNucleus=false;//true;
+    
+    factorLightForce=2.3;//3.0;//8.0; 
+    factorRecenteringAnchorMass=1.0;//20.0/scafold.size();
+    factorRecenteringLoopMass=0.09;
+    factorPressureLoopMass=1.5;
+    factorForceBorder=150;
+
+ break;
+  case CONTOUR_FOLLOWING_FAST:
+   sprintf(spotName,"following_fast"); 
+    
+    setColor(0x07);//0x04+0x02>>i); 
+     
+    // default (initial) shape: 
+    bluePrint.buildCircularScafold(100, _initPos, vector2D(0,0), 30); //(float _radius, vector2D _pos,vector2D _vel, int _numScafoldPoints);
+    
+   // Numeric parameters for the simulated mechanical system:
+    massLoopParticle=0.05; 
+    dampMotionMassesLoop=0.27;//0.17;
+    massAnchor=3.0;
+    dampMotionAnchorMass=0.03;
+      // Springs:
+    centralSpringK=-200;
+    centralSpringRelax=bluePrint.radius;
+    interSpringK=0.5;//46;
+    interSpringRelax=0.7*bluePrint.radius*2*sin(1.0* PI/bluePrint.scafold.size()); // if factor=1, this makes for a perfect polygon at relax for all springs...
+    // for "zack-like" blob:
+    interParticleRange=80;
+    factorInterParticleForce=4.0;
+    
+    searchActive=false;
+    pseudopodesMode=true; // this is for contour following.
+  
+    // Active/Inactive Forces:
+    springForcesOnLoop=true;
+    lightForcesOnLoop=true;
+    forceBorderOnLoop=false;
+    nuclearForceOnLoop=false;//true;
+    interParticleForceOnLoop=false;
+    forceInternalPressureOnLoop=false; // (when true, either constant force or calculated area using Green function or approximation by bounding box)
+    // Recentering vector:
+    angleCorrectionForceLoop=243;// in deg
+    recenteringForceOnLoop=true;
+    angleCorrectionForceNucleus=180;// in deg
+    recenteringForceOnNucleus=false;//true;
+    
+    factorLightForce=2.3;//3.0;//8.0; 
+    factorRecenteringAnchorMass=1.0;//20.0/bluePrint.scafold.size();
+    factorRecenteringLoopMass=0.09;
+    factorPressureLoopMass=1.5;
+    factorForceBorder=150;
+
+ break;
+  case BOUNCING:
+   sprintf(spotName,"bouncing");
+    
+    setColor(0x07);//0x04+0x02>>i); 
+     
+    // default (initial) shape: 
+    bluePrint.buildCircularScafold(70, _initPos, vector2D(0,0), 20); //(float _radius, vector2D _pos,vector2D _vel, int _numScafoldPoints);
+    
+   // Numeric parameters for the simulated mechanical system:
+    massLoopParticle=5.0; 
+    dampMotionMassesLoop=0.001;//0.17;
+    massAnchor=1.0;
+    dampMotionAnchorMass=0.002;
+      // Springs:
+    centralSpringK=1.0;
+    centralSpringRelax=bluePrint.radius;
+    interSpringK=0.4;//46;
+    interSpringRelax==1.0*bluePrint.radius*2*sin(1.0* PI/bluePrint.scafold.size()); // if factor=1, this makes for a perfect polygon at relax for all springs...
+    // for "zack-like" blob:
+    interParticleRange=100;
+    factorInterParticleForce=3.0;
+    
+    searchActive=false;
+    pseudopodesMode=false; // this is for contour following.
+    
+    // Active/Inactive Forces:
+    springForcesOnLoop=true;
+    lightForcesOnLoop=true;
+    forceBorderOnLoop=true;
+    nuclearForceOnLoop=true;//true;
+    interParticleForceOnLoop=false;
+    forceInternalPressureOnLoop=false; // (when true, either constant force or calculated area using Green function or approximation by bounding box)
+    // Recentering vector:
+    angleCorrectionForceLoop=0;// in deg
+    recenteringForceOnLoop=false;
+    angleCorrectionForceNucleus=0;// in deg
+    recenteringForceOnNucleus=false;//true;
+    
+    factorLightForce=0.6;//3.0;//8.0; 
+    factorRecenteringAnchorMass=100.0/bluePrint.scafold.size();
+    factorRecenteringLoopMass=5.0;
+    factorPressureLoopMass=2.0;
+    factorForceBorder=4.5;
+    
+   break;
+ }
+ 
+   // Finally, we can create the loop using these parameters, and the positions given in the scafold: 
+     createLoopFromScafold(); // this sets the number of masses
+     // Excursion limits (ATTN!!! this will set the limits for all the masses, so we need FIRT to call to createLoopFromScafold)
+    setRegionMotion(MIN_AD_MIRRORS, MIN_AD_MIRRORS, MAX_AD_MIRRORS, MAX_AD_MIRRORS);
+  
+   // !!!!!!!!!!!!!!!!!!!!! :: 
+   // The following is not nice here (should be in the classLaserSensingTrajectory, not even on the simpleLaserRenderer), but for the time being let's leave it here
+    setDelayMirrors(4); // ATTN!!! needs to be called AFTER setting the number of points!!! (note: 5 seemed good)
+ } 
+
+void elasticLoop::initSizeBlob(int _numMasses) {
+    // Iinitialize blob size (number of points for the loop, as well as other structures such as lsdTrajectory) 
+    numMasses=_numMasses;
+    // Since this is an elastic loop object, let's create an elastic loop of masses:
+    massesLoop.resize(numMasses);
+    loopSpringArray.resize(numMasses); // springs connecting consecutive masses 
+    hairVector.resize(numMasses); // the perpendiculars to the loop 
+    lightForce.resize(numMasses); //  light force in each particle
+    //vector2D totalLightForce; // this belongs to the base class now
+    centralSpringArray.resize(numMasses); // springs connecting each mass to the anchorMass. 
+    
+    // Sensing and Display trajectory:
+    displaySensingBuffer.lsdTrajectory.resize(numMasses); // the lsdTrajectory and the elastic loop will have the same number of points (this could be different - decimation?). 
+  }
+    
+// We will build the masses from the scafold shape (and maybe render it once on the lsdTrajectory to initialize this array?)
+void elasticLoop::createLoopFromScafold(void)  {
+     initSizeBlob(bluePrint.scafold.size()); // important: we will have here the same number of points in the scafold and the elastic loop (massLoop)
+     
+    // Initial conditions for the loop masses:
+    for (int i = 0; i < numMasses; i++) {
+       // float x =bluePrint.center.x + bluePrint.radius * cos ( (1.0*i / numMasses) * 2 * PI);
+       // float y =bluePrint.center.y + bluePrint.radius * sin ( (1.0*i / numMasses) * 2 * PI);
+       // massesLoop[i].setInitialCondition(x,y ,bluePrint.speed.x, bluePrint.speed.y);
+        massesLoop[i].setInitialCondition(bluePrint.scafold[i].x, bluePrint.scafold[i].y, bluePrint.speed.x, bluePrint.speed.y);
+        massesLoop[i].setIntegrationStep(0.23);//18);
+        massesLoop[i].mass=massLoopParticle;
+        massesLoop[i].dampMotion=dampMotionMassesLoop;
+    }
+
+    // Springs for the loop:
+    for (int i = 0; i<numMasses; i++) {
+        loopSpringArray[i].distance        =interSpringRelax;
+        // if we want an perfect polygon: =startRadius*2*sin(1.0* PI/ numMasses);
+        // loopSpringArray[i].distance = startRadius*2*sin(1.0* PI/ numMasses);
+        loopSpringArray[i].springiness    = interSpringK;//*(i%5==0? .6 : 1);//0.4;//4f;
+        loopSpringArray[i].massA = & (massesLoop[i  ]);
+        loopSpringArray[i].massB = & (massesLoop[(i+1) % numMasses]);
+    }
+
+    // Central (anchor mass):
+    anchorMass.setInitialCondition(bluePrint.center.x, bluePrint.center.y,bluePrint.speed.x, bluePrint.speed.y);
+    anchorMass.setIntegrationStep(0.3);
+    anchorMass.mass=massAnchor;
+    anchorMass.dampMotion = dampMotionAnchorMass;
+
+
+    // Initial conditions for central springs:
+    for (int i = 0; i<numMasses; i++) {
+        centralSpringArray[i].distance       =centralSpringRelax;// + 60* cos ( (1.0*i / numMasses) * 7* 2 * PI);
+        centralSpringArray[i].springiness    =centralSpringK;// 0.4f;
+        centralSpringArray[i].massA = & (anchorMass);
+        centralSpringArray[i].massB = & (massesLoop[i]);
+    }
+}
+
+
+void elasticLoop::setRegionMotion(int mmix, int mmiy, int mmax, int mmay) { // Attention: the initial position should be INSIDE this...
+    for (int i = 0; i<numMasses; i++) {
+        massesLoop[i].setWallLimits(mmix, mmiy, mmax, mmay);
+    }
+    anchorMass.setWallLimits(mmix+10, mmiy+10, mmax-10, mmay-10);
+}
+
+void elasticLoop::update() {
+
+    // (I) Process loop geometry (compute "hair vectors", area and first order moment):
+    processLoopData();
+
+    // (III) Reset all forces:
+    for (int i = 0; i < numMasses; i++) {
+        massesLoop[i].resetForce();
+    }
+    anchorMass.resetForce();
+
+    // (IV) COMPUTE FORCES (motion is not update yet):
+    //== (1) Compute each particle light force as well as total light force (this will be stored separatedly from the final total particle force to send to OSC):
+    totalLightForce.set(0,0);
+    for (int i = 0; i < numMasses; i++) {
+        lightForce[i]=hairVector[i]*factorLightForce*displaySensingBuffer.lsdTrajectory[i].lightZone;
+        //compute total light force, not only on lighted zones, because it will mean AWAY from black zones:
+        totalLightForce+=lightForce[i]; // note: bad value choice (negative means TOUCH, and equal to -1), TO CHANGE this in future implementations
+    }
+    //== (2) Compute the "recentering vector" from the total light force, by rotating by the angleCorrection (this will give different behaviours):
+    recenteringVectorLoop= totalLightForce.getRotatedDeg(slidingDirection? angleCorrectionForceLoop : 140); // the hard coded value is a hack for the time being...
+    // Compute redundant quantities:
+    normRecenteringVector=recenteringVectorLoop.length();
+    angleRecenteringVector=recenteringVectorLoop.angleDegHoriz();
+    recenteringVectorNucleus=totalLightForce.getRotatedDeg(angleCorrectionForceNucleus);
+    //== (3) Compute forces on the loop:
+    //----(a) Nearest neighbour inter-particle springs on the loop (always? we can have still another mode, following the center mass only, etc...)
+    if (springForcesOnLoop) {
+        for (int i = 0; i < numMasses; i++) { // if putting -1, the loop is broken
+            loopSpringArray[i].update();// this add forces to the particles
+        }
+    }
+    //----(b) Direct forces from light pressure (COULD BE MERGED WITH FORCE RECENTERING!!)
+    if (pseudopodesMode) {
+        // special "patches" on blob membrane:
+        if (lightForcesOnLoop) {
+            int sign=1;
+            for (int i = 0; i < numMasses; i++) {
+                if ((i%2)==0) sign*=-1;
+                if (displaySensingBuffer.lsdTrajectory[i].lightZone>0) // this means touching something black: make SOME points attracted by it (pseudopodes!!) - but not all!
+                    massesLoop[i].addForce(lightForce[i]*(sign<0? -1.24 : 1.4)); // sign<0 means this is a pseudopode attracted by dark zones
+                else // this means something white: do nothing, all forces are towards the exterior
+                    massesLoop[i].addForce(lightForce[i]*1.6); // this force tends to make the blob "inflate", but is not "directional"
+            }
+        }
+        //----(c) Forces from the recentering vector on each particle (WITH PATCHES on the loop?):
+        if (recenteringForceOnLoop) {
+
+            vector2D auxForce= recenteringVectorLoop*factorRecenteringLoopMass*1.0;
+            vector2D auxForce2= totalLightForce.getRotatedDeg(80)*factorRecenteringLoopMass*(slidingDirection? 0 : 1)*1.8;
+            int sign=1;
+            for (int i = 0; i < numMasses; i++) {
+                if ((i%2)==0) sign*=-1;
+                if (displaySensingBuffer.lsdTrajectory[i].lightZone>0) {// this means touching something black: behaviour may depend on the pseudopode presence:
+                    massesLoop[i].addForce((sign<0? auxForce2 : auxForce2)); // nothing, or sign, or corrected angle
+                } else
+                    massesLoop[i].addForce(auxForce); // this force is responsible for the behaviour (contour following or not)
+            }
+        }
+    } else { // no special zones in the "cell membrane":
+        if (lightForcesOnLoop) {
+            for (int i = 0; i < numMasses; i++) {
+                massesLoop[i].addForce(lightForce[i]);
+            }
+        }
+        //----(c') Forces from the recentering vector on each particle:
+        if (recenteringForceOnLoop) {
+            vector2D auxForce= recenteringVectorLoop*factorRecenteringLoopMass;
+            for (int i = 0; i < numMasses; i++) massesLoop[i].addForce(auxForce);
+        }
+    }
+
+    //----(d) Forces from the anchorMass (depending on how we set the equilibrium position for each central spring, we can have a nice blob shape at equilibrium... like a gear for instance)
+    if (nuclearForceOnLoop) {
+        // Springs:
+        for (int i = 0; i < numMasses; i++) centralSpringArray[i].update();//assymetricUpdate();
+        // note: if using centralSpringArray[i].update(), we will add forces to the particles AND to the anchor mass...
+        // Inverse square (attractive):
+        //for (int i = 0; i < numMasses; i++)  massesLoop[i].addInterInvSquareForce(anchorMass, 10, 300, centralSpringK);
+    }
+    //----(d) Inter loop-particles forces (Zach-Liebermann-like blob):
+    if (interParticleForceOnLoop) {
+        for (int i = 0; i < numMasses; i++) {
+            for (int j = 0; j < i-1; j++) massesLoop[i].addInterSpringForce(massesLoop[j], interParticleRange, factorInterParticleForce);
+        }
+    }
+    //----(e) Internal blob pressure force (my faster method to have a blob-like behaviour):
+    if (forceInternalPressureOnLoop) {
+        // NOTE on the Physics of the thing: the force on the membrane of a ballon is proportional to the DIFFERENCE of pressures (outside and inside):
+        // so: f= factor/area - cte, with cte=factor/area0, with area0 being the area at equilibrium.
+        // (And of course, to make it even more exact, we should do pressure*surface, but this will be considered constant)
+        // float area0=30000; // area in pixels when at equilibrium
+        //float factorPressureLoopMass=-0.1*(1.0/area-1.0/area0);
+        //float factorPressureLoopMass=500000.0*(1.0/(area*area)-1.0/(area0*area0));
+        //float factorPressureLoopMass=20000.0*(1.0/sqrt(area)-1.0/sqrt(area0));
+        // Constant force seems to work well too... but produces an annoying blob reversal (probably solved by using negative light forces instead of internal blob pressure):
+        //float factorPressureLoopMass=2.5;//4.8;
+        // Now, add the pressure force proportional to the inverse of the area to all particles, or just a signed constant:
+        int auxsign=(area>=0? -1: 1);
+        for (int i = 0; i < numMasses; i++) massesLoop[i].addForce( hairVector[i] * factorPressureLoopMass* auxsign);
+    }
+    //----(f) force from border:
+    if (forceBorderOnLoop) {
+        for (int i = 0; i < numMasses; i++) {
+            if (massesLoop[i].bWallCollision) massesLoop[i].addForce(massesLoop[i].innerCollitionDirection*factorForceBorder);
+        }
+    }
+
+    //== (4) Compute forces on the anchor mass:
+    //----(a) Force from data send by OSC? (ex: from mouse?)
+    // anchorMass.addSpringForce(mx, my, 500, -10.2f);
+    // or direct control:
+    // anchorMass.pos.x=mx;anchorMass.pos.y=my;
+    //----(b) Force from the total light force (aka, the "recentering vector"!):
+    if (recenteringForceOnNucleus) {
+        anchorMass.addForce(recenteringVectorNucleus*factorRecenteringAnchorMass);
+    }
+
+    // when nothing is touching it for a while:
+    if (searchActive) {
+        if (!displaySensingBuffer.lightTouched) {
+            if (firstTimeNoTouch) {
+                firstTimeNoTouch=false;
+                computeBoundingBox();
+                randomForce.set(2000-cx,2000-cy);
+                randomForce.normalize();
+                randomForce= randomForce.getRotatedDeg(rand()%50-25);
+            }
+            if (noTouchedCounter>0) {
+                // add random force, modulated:
+                float aux=1.0*noTouchedCounter/1150;
+                vector2D randf=randomForce.getRotatedDeg(40.0*sin(aux*2*PI*2))*20.0;//*(1.0-aux)*0.3;
+                for (int i = 0; i < 1; i=i+1) { // only on some of the particles, and better if these are in the "black attractive" patch!
+                    massesLoop[i].addForce(randf);
+                }
+                // and a special point?
+                //massesLoop[numMasses/2].addForce(randf);
+                // plus amoeba effect ?
+                // for (int i = 0; i < numMasses; i++) {
+                //   massesLoop[i].addForce(hairVector[i]*18*cos( (0.0*noTouchedCounter/1000 + 1.0*i/(numMasses-1)*2*PI*3)));
+                //}
+
+                if ((noTouchedCounter>1150)||(blobWallCollision)) {
+                    noTouchedCounter=0;
+                    // compute force towards the center, slightly rotated to make the blob wander about:
+                    computeBoundingBox();
+                    randomForce.set(2000-cx,2000-cy);
+                    randomForce.normalize();
+                    randomForce= randomForce.getRotatedDeg(rand()%50-25);
+                }
+            }
+        } else {
+            firstTimeNoTouch=true;
+            noTouchedCounter=0;
+        }
+        noTouchedCounter++;
+    }
+
+    // (V) UPDATE DYNAMICS
+    //== (1) particules on the loop:
+    for (int i = 0; i < numMasses; i++) {
+#ifndef VERLET_METHOD
+        massesLoop[i].addDampingForce(); // only in case of EULER method (damping in VERLET mode is done automatically when updating)
+#endif
+        massesLoop[i].update(); // unconstrained
+        massesLoop[i].bounceOffWalls(); // constrain position (and compute wall "hit")
+    }
+    //== (2) For the anchorMass:
+#ifndef VERLET_METHOD
+    anchorMass.addDampingForce();  // // only in case of EULER method (damping in VERLET mode is done automatically when updating)
+#endif
+    anchorMass.update();  // unconstrained
+    anchorMass.bounceOffWalls(); // constrain position (and compute wall "hit")
+
+    // OTHER PARTICULAR THINGS:
+    // change sliding direction (for countour following):
+    if (blobWallCollision) {
+        if (wallCounter>10) {
+            slidingDirection=!slidingDirection;
+            wallCounter=0;
+        }
+    }
+    wallCounter++;
+}
+
+// Drawing the graphics - this will in fact use the graphic renderer - if any - and produce the trajectory to be displayed by the laser
+void elasticLoop::draw() {
+    // for the time being, there is no "opengl" like renderer, so we just copy the coordinates of the mass into the lsdTrajectory:
+    for (int i = 0; i < numMasses; i++) {
+        displaySensingBuffer.lsdTrajectory[i].x= int( massesLoop[i].pos.x ); // note: it should be an integer
+        displaySensingBuffer.lsdTrajectory[i].y= int( massesLoop[i].pos.y );
+       //displaySensingBuffer.lsdTrajectory[i].color=blobColor; // perhaps per point color is not a good idea for the time being...
+    }
+    // global color for the whole loop:
+    displaySensingBuffer.displayColor=blobColor;
+}
+
+
+void elasticLoop::processLoopData() {
+
+    // (0) Check if the blob touched the borders:
+    blobWallCollision=false;
+    for (int i = 0; i < numMasses; i++) blobWallCollision= (blobWallCollision || massesLoop[i].bWallCollision);
+
+    // (1) Compute all the "hairvectors" for the loop (this is, the normals to the particles, pointing outwards).
+    //     This will be approximated by taking the 90 deg rotated difference between contiguous particles positions.
+    for (int i = 0; i < numMasses; i++) {
+        vector2D diff;
+        diff.set(massesLoop[(i+1)%numMasses].pos-massesLoop[i].pos);
+        // normalize and rotate 90 deg:
+        hairVector[i]=diff.getPerpendicularNormed(CW);
+    }
+
+    // (2) Compute area:
+    // (a) using Green method:
+    /*
+    area=0;
+    float dx;
+    for (int i = 0; i < numMasses-1; i++){
+        dx=massesLoop[i].pos.x-massesLoop[i+1].pos.x;
+        area+=dx*massesLoop[i].pos.y;
+    }
+    // to avoid computation problems:
+    // if (area<=0) area=1; // or just norm: area CAN be negative! (a loop that is larger than the original blob...)
+    */
+    
+    // (b) Compute approximate area from enclosing rectangle:
+    computeBoundingBox();
+}
+
+
+void elasticLoop::computeBoundingBox() {
+    float minx=4096, maxx=-1, miny=4096, maxy=-1;
+    for (int i = 0; i < numMasses; i++) {
+        if (i == 0) {
+            minx = massesLoop[i].pos.x;
+            maxx = massesLoop[i].pos.x;
+            miny = massesLoop[i].pos.y;
+            maxy = massesLoop[i].pos.y;
+        } else {
+
+            minx = min(minx, massesLoop[i].pos.x);
+            maxx = max(maxx, massesLoop[i].pos.x);
+            miny = min(miny, massesLoop[i].pos.y);
+            maxy = max(maxy, massesLoop[i].pos.y);
+        }
+    }
+
+    // final results:
+    w = maxx - minx;
+    h = maxy - miny;
+    cx = minx+0.5*w; // note: center will be initialized with posX and posY when calling setInitialPos() of blobConfig
+    cy = miny+0.5*h;
+
+    // area and:
+    approxArea=w*h;
+}
+
+void elasticLoop::sendDataSpecific() { 
+   char auxstring[10]; 
+   myled2=1; // for tests...
+   
+   // First, set the top address of the message to the ID of the blob (not the name):
+  sprintf(auxstring, "%d", identifier);
+  sendMes.setTopAddress("0");//auxstring);
+  
+    // =====================   OSC  ======================
+  if (sendOSC) {
+    
+     // (a) Anchor mass: 
+      if (sendingAnchorPosition) {
+       sendMes.setSubAddress("/apos"); 
+       long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+       x=(long)(anchorMass.pos.x); y=(long)(anchorMass.pos.y);
+       sendMes.setArgs( "ii", &x, &y);
+       osc.sendOsc( &sendMes );
+      }
+       if (sendingAnchorForce) {
+       sendMes.setSubAddress("/aforce"); 
+       long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+       x=(long)(anchorMass.totalForce.x); y=(long)(anchorMass.totalForce.y);
+       sendMes.setArgs( "ii", &x, &y);
+       osc.sendOsc( &sendMes );
+      }
+      if (sendingAnchorTouchWall) {// note: not an else (we can send different data simultaneously)
+        sendMes.setSubAddress("/awall"); 
+       long wall=(long)(anchorMass.bWallCollision? 1 : 0);
+       sendMes.setArgs( "i", &wall);
+      osc.sendOsc( &sendMes );
+   }
+     // (b) data from blob points:
+     if (sendingLoopPositions) { 
+     #ifdef SEND_AS_POINTS
+      long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+      for (int i = 0; i < numMasses; i++){
+            sprintf(auxstring, "/p%d", i); // auxstring read as "/p1", "/p2", ...
+            sendMes.setSubAddress(auxstring);  // ATTENTION: the host computer needs to know in advance how many points are in the loop (I did not implement "bundle" messages yet...)
+            x=(long)(massesLoop[i].pos.x); 
+            y=(long)(massesLoop[i].pos.y);
+            sendMes.setArgs( "ii", &x, &y);
+            osc.sendOsc( &sendMes );
+      }
+      #endif
+      #ifdef SEND_AS_BLOB
+      sendMes.clearArgs(); // no need, we won't use osc.sendOsc()...
+      uint8_t blobdata[4*numMasses]; // 2 bytes per coordinate, and 2 coordinates
+      for (int i = 0; i < numMasses; i++ ){
+            // note: massesLoop[i].pos.x is a "float"
+            uint16_t x=(uint16_t)(massesLoop[i].pos.x);
+            blobdata[4*i]=(uint8_t)x>>8; // BIG ENDIAN (send FIRST the MOST SIGNIFICANT BYTE)
+            blobdata[4*i+1]=(uint8_t)x;
+            
+            uint16_t y=(uint16_t)(massesLoop[i].pos.y);
+            blobdata[4*i+2]=(uint8_t)y>>8; // BIG ENDIAN (send FIRST the MOST SIGNIFICANT BYTE)
+            blobdata[4*i+3]=(uint8_t)y;
+      }
+      osc.sendOscBlob(&(blobdata[0]), 4*numMasses, &sendMes ); // second parameter is osc blob size in bytes
+      #endif
+      #ifdef SEND_AS_STRING
+      sendMes.clearArgs(); // no need, we won't use osc.sendOsc()...
+      uint8_t blobdata[4*numMasses]; // 2 bytes per coordinate, and 2 coordinates
+      for (int i = 0; i < numMasses; i++ ){
+            // note: massesLoop[i].pos.x is a "float"
+            uint16_t x=(uint16_t)(massesLoop[i].pos.x); 
+            blobdata[4*i]=(uint8_t)x>>8; // BIG ENDIAN (send FIRST the MOST SIGNIFICANT BYTE)
+            blobdata[4*i+1]=(uint8_t)x;
+            
+            uint16_t y=(uint16_t)(massesLoop[i].pos.y);
+            blobdata[4*i+2]=(uint8_t)y>>8; // BIG ENDIAN (send FIRST the MOST SIGNIFICANT BYTE)
+            blobdata[4*i+3]=(uint8_t)y;
+      }
+      osc.sendOscString(blobdata, 4*numMasses, &sendMes ); // second parameter is osc blob size in bytes
+      #endif
+      }
+    if (sendingLoopForces) { // ATTN: the force is the TOTAL force on the point (interesting perhaps for making sound...)
+      long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+      for (int i = 0; i < numMasses; i++){
+           sprintf(auxstring, "/f%d", i); // auxstring read as "/f1", "/f2", ...
+            sendMes.setSubAddress(auxstring);  // ATTENTION: the host computer needs to know in advance how many points are in the loop (I did not implement "bundle" messages yet...)
+            x=(long)(massesLoop[i].totalForce.x); 
+            y=(long)(massesLoop[i].totalForce.y);
+            sendMes.setArgs( "ii", &x, &y);
+            osc.sendOsc( &sendMes );
+      }
+     }
+      if (sendingLoopForcesLight) { // ATTN: the force is the TOTAL force on the point (interesting perhaps for making sound...)
+      long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+      for (int i = 0; i < numMasses; i++){
+           sprintf(auxstring, "/g%d", i); // auxstring read as "/f1", "/f2", ...
+            sendMes.setSubAddress(auxstring);  // ATTENTION: the host computer needs to know in advance how many points are in the loop (I did not implement "bundle" messages yet...)
+            x=(long)(1000*lightForce[i].x); 
+            y=(long)(1000*lightForce[i].y);
+            sendMes.setArgs( "ii", &x, &y);
+            osc.sendOsc( &sendMes );
+      }
+     }
+     
+      if (sendingLoopRegions) { 
+      long    x;   //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+      for (int i = 0; i < numMasses; i++){
+            sprintf(auxstring, "/r%d", i); // auxstring read as "/f1", "/f2", ...
+            sendMes.setSubAddress(auxstring);  // ATTENTION: the host computer needs to know in advance how many points are in the loop (I did not implement "bundle" messages yet...)
+            x=(long)(displaySensingBuffer.lsdTrajectory[i].lightZone>0? 1 : 0); 
+            sendMes.setArgs( "i", &x);
+            osc.sendOsc( &sendMes );
+      }
+     }
+      if (sendingLoopTouchWall) { // global touch wall for the loop (not per point)
+      long    wall;   //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+            sprintf(auxstring, "/bWall");
+            sendMes.setSubAddress(auxstring);  
+            wall=(long)(blobWallCollision? 1 : 0);
+            sendMes.setArgs( "i", &wall);
+            osc.sendOsc( &sendMes );
+     }
+     // (c) Blob geometry:
+     if (sendingBlobArea) { 
+         sendMes.setSubAddress("/area"); 
+       long    x;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+       x=(long)(area);//approxArea); // area or approxArea
+       sendMes.setArgs( "i", &x);
+       osc.sendOsc( &sendMes );
+    }
+     if (sendingBlobNormals) { 
+      long    x, y;   //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+      for (int i = 0; i < numMasses; i++){
+             sprintf(auxstring, "nf%d", i); // auxstring read as "/f1", "/f2", ...
+            sendMes.setSubAddress(auxstring);  // ATTENTION: the host computer needs to know in advance how many points are in the loop (I did not implement "bundle" messages yet...)
+            x=(long)(hairVector[i].x); y=(long)(hairVector[i].y);
+            sendMes.setArgs( "ii", &x, &y);
+            osc.sendOsc( &sendMes );
+      }
+     }
+     if (sendingBlobAngles) { 
+      long   x;   //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+      for (int i = 0; i < numMasses; i++){
+             sprintf(auxstring, "/a%d", i); // auxstring read as "/f1", "/f2", ...
+            sendMes.setSubAddress(auxstring);  // ATTENTION: the host computer needs to know in advance how many points are in the loop (I did not implement "bundle" messages yet...)
+            x=(long)(hairVector[i].angleDegHoriz());
+            sendMes.setArgs( "i", &x);
+            osc.sendOsc( &sendMes );
+      }
+     }
+   // (d) Light sensing statistics: 
+    if (sendingBlobMaxMin) { 
+         sendMes.setSubAddress("/maxmin"); 
+       long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+       x=(long)(displaySensingBuffer.maxI);  y=(long)(displaySensingBuffer.minI);
+       sendMes.setArgs( "ii", &x, &y);
+       osc.sendOsc( &sendMes );
+    }
+    if (sendingLightForce) { 
+       sendMes.setSubAddress("/lforce"); 
+       long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+       x=(long)(totalLightForce.x);  y=(long)(totalLightForce.y);
+       sendMes.setArgs( "ii", &x, &y);
+       osc.sendOsc( &sendMes );
+    }
+  // (e) Recentering vector: (note: redundant with sendingLightForce, IF the correction angle is known). 
+    if (sendingRecenteringVector) { 
+       sendMes.setSubAddress("/rvector"); 
+       long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+       x=(long)(recenteringVectorLoop.x);  y=(long)(recenteringVectorLoop.y);
+       sendMes.setArgs( "ii", &x, &y);
+       osc.sendOsc( &sendMes );
+    }
+     if (sendingRecenteringAngle) { 
+       sendMes.setSubAddress("/rangle"); 
+       long    x;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+       x=(long)(angleRecenteringVector); 
+       sendMes.setArgs( "i", &x);
+       osc.sendOsc( &sendMes );
+    }
+     if (sendingRecenteringNorm) { 
+       sendMes.setSubAddress("/rnorm"); 
+       long    x;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
+       x=(long)(normRecenteringVector); 
+       sendMes.setArgs( "i", &x);
+       osc.sendOsc( &sendMes );
+    }
+    
+     if (sendingTouched) { 
+       sendMes.clearArgs(); // there are no arguments to send
+       sendMes.setSubAddress("/touched"); 
+       osc.sendOsc( &sendMes );
+    }
+  
+  } // end of OSC sending per-spot
+  
+  // =====================   SERIAL  ======================
+ if (sendSerial) {    
+    //.. to do
+  }
+
+myled2=0; // for tests...
+}
+