save loops

Dependencies:   mbed

elasticLoop.cpp

Committer:
mbedalvaro
Date:
2014-12-02
Revision:
1:3be7b7d050f4
Parent:
0:df6fdd9b99f0

File content as of revision 1:3be7b7d050f4:

/*
 *  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 clearing the vector container?
        massesLoop.clear();
        loopSpringArray.clear();
        hairVector.clear();
        lightForce.clear();
        centralSpringArray.clear();
        displaySensingBuffer.lsdTrajectory.clear();
}

void elasticLoop::showChildParameters() {
   // pc.printf("Mirror delay :%d\n", displaySensingBuffer.delayMirrorSamples);
   // pc.printf("Angle correction force :%d\n", angleCorrectionForceLoop);
    pc.printf("Integration Step Loop :%f\n", integrationStepLoop);
    pc.printf("Integration Step Anchor :%f\n", integrationStepAnchor);
    }

void elasticLoop::createBlob(int _id, ElasticLoopMode _elasticBlobMode, vector2Df _initPos, vector2Df _initSpeed) {
    // (1) set ID:
    identifier=_id;

    startCenter=_initPos;
    startSpeed=_initSpeed;

// (2) Initialize common variables of all blobs (base class):
//   initCommonVariables();

    // (3) initialize common variables for the elastic blob types:
    integrationStepLoop=0.22;
    integrationStepAnchor=0.4;
    
    slidingDirection=true; //  (will change when touching wall)
    // Sending data:
    periodSendingData=15; // in ms
    sendingLoopPositions=false;
    sendingBlobArea=true;
    sendingKineticEnergy=true;
    sendingBlobMaxMin=true;
     // send ALWAYS, regardless of the fact the blob is being touched or not, in case of elastic loops:
    sendingOnlyWhenTouch=false;

// (3) Initialize secondary variables depending on the blob type and mode:

// NOTE (!): the mode does not affect the update method; in fact, all these elastic loops have different behaviours because of different parameters (but the booleans modes could
// actually be "condensed" in a mode...)

    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);
            setColor(0x04);
            blueTouch=true;

            // default (initial) shape (the scafold belongs to the base class):
            startRadius=400;
            bluePrint.buildCircularScafold(startRadius, vector2Dd(0,0), 40); //(float _radius, vector2Dd _pos, 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=startRadius;// 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;

            // per-blob mirror delay (if things were well adjusted - in particular mirror waiting times, then this could be 0.
            //But in case of unique blobs, it may be interesting to accelerate display AND correct the delay by software):
            displaySensingBuffer.setDelayMirrors(2);

            break;

        case CONTRACT:

            sprintf(spotName,"loop_contract"); //this is an relaxing elastic loop

            setColor(0x07);//0x04+0x02>>i);
             blueTouch=true;
             
            // default (initial) shape:
            startRadius =400;
            bluePrint.buildCircularScafold(startRadius,  vector2Dd(0,0), 40); //(float _radius, vector2Dd _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=startRadius;
            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;

            // per-blob mirror delay (if things were well adjusted - in particular mirror waiting times, then this could be 0.
            //But in case of unique blobs, it may be interesting to accelerate display AND correct the delay by software):
            displaySensingBuffer.setDelayMirrors(2);    // per-blob mirror delay (if things were well adjusted - in particular mirror waiting times, then this could be 0.

            break;
        case CONTRACT_CENTRAL:  // this is the "big mouth"
                      
            integrationStepLoop=0.4;
            integrationStepAnchor=0.4;
    
            sprintf(spotName,"contract_central");

            //setColor(0x07);//0x04+0x02>>i);
            setColor(0x04);
             blueTouch=true;

            // default (initial) shape:
            startRadius=400;
            bluePrint.buildCircularScafold(startRadius,  vector2Dd(0,0), 45); //(float _radius, vector2Dd _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=startRadius;
            interSpringK=0.54;//46;
            interSpringRelax=25;//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=false ; //true; !!!!!!!!!!!!!!! 
            angleCorrectionForceNucleus=0;// in deg
            recenteringForceOnNucleus=false;//true;

            factorLightForce=8.0;//4.3;
            factorRecenteringAnchorMass= 20.0/bluePrint.scafold.size();
            factorRecenteringLoopMass=0.045;
            factorPressureLoopMass=1.5;
            factorForceBorder=150;

            // per-blob mirror delay (if things were well adjusted - in particular mirror waiting times, then this could be 0.
            //But in case of unique blobs, it may be interesting to accelerate display AND correct the delay by software):
            displaySensingBuffer.setDelayMirrors(1);

            break;
            
        case CONTRACT_CENTRAL_FAST:
        
                      //setColor(0x07);//0x04+0x02>>i);
                      setColor(0x04);
                       blueTouch=true;

                      // default (initial) shape:
                      startRadius=150;
                      bluePrint.buildCircularScafold(startRadius,  vector2Dd(0,0), 40); //(float _radius, vector2Dd _pos,vector2D _vel, int _numScafoldPoints);

                      // Numeric parameters for the simulated mechanical system:
                      massLoopParticle=0.06;
                      dampMotionMassesLoop=0.021;//0.17;
                      massAnchor=0.5;
                      dampMotionAnchorMass=0.01;
                      // Springs:
                      centralSpringK=0.3;
                      centralSpringRelax=startRadius;
                      interSpringK=0.54;//46;
                      interSpringRelax=40;
                      // for "zack-like" blob:
                      interParticleRange=150;
                      factorInterParticleForce=160.0;

                      searchActive=false;
                      pseudopodesMode=false; // this is for contour following.

                      // Active/Inactive Forces:
                      springForcesOnLoop= true;
                      lightForcesOnLoop= true;
                      forceBorderOnLoop=false;
                      nuclearForceOnLoop=false;
                      interParticleForceOnLoop=true;  //!!!
                      forceInternalPressureOnLoop=false; // (when true, either constant force or calculated area using Green function or approximation by bounding box)
                      // Recentering vector:
                      angleCorrectionForceLoop=90;// in deg
                      recenteringForceOnLoop=true;
                      angleCorrectionForceNucleus=0;// in deg
                      recenteringForceOnNucleus=false;//true;

                      factorLightForce=-4;//3.0;//8.0;
                      factorRecenteringAnchorMass= 20.0/bluePrint.scafold.size();
                      factorRecenteringLoopMass=0.06;
                      factorPressureLoopMass=1.5;
                      factorForceBorder=150;
                      
                     displaySensingBuffer.setDelayMirrors(1);
            break;

        case CONTOUR_FOLLOWING:
            sprintf(spotName,"following"); //this is a contour-following loop

            integrationStepLoop=0.22;
            integrationStepAnchor=0.4;
            
            //setColor(0x07);//0x04+0x02>>i);
            setColor(0x04);
             blueTouch=true;

            // default (initial) shape:
            startRadius=100;
            bluePrint.buildCircularScafold(startRadius, vector2Dd(0,0), 20); //(float _radius, vector2Dd _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=100;//bluePrint.radius;
            interSpringK=0.4;
            interSpringRelax=0.7*startRadius*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=0;// in deg
            recenteringForceOnLoop=true;
            angleCorrectionForceNucleus=180;// in deg
            recenteringForceOnNucleus=false;//true;

            factorLightForce=2.4;//3.0;//8.0;
            factorRecenteringAnchorMass=1.0;//20.0/scafold.size();
            factorRecenteringLoopMass=0.2;
            factorPressureLoopMass=1.5;
            factorForceBorder=150;
            // per-blob mirror delay (if things were well adjusted - in particular mirror waiting times, then this could be 0.
            //But in case of unique blobs, it may be interesting to accelerate display AND correct the delay by software):
            displaySensingBuffer.setDelayMirrors(2);

            break;
        case CONTOUR_FOLLOWING_FAST:
            sprintf(spotName,"following_fast");

            setColor(0x07);//0x04+0x02>>i);
            blueTouch=true;
            
            // default (initial) shape:
            startRadius=100;
            bluePrint.buildCircularScafold(startRadius,  vector2Dd(0,0), 30); //(float _radius, vector2Dd _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=100;//bluePrint.radius;
            interSpringK=0.5;//46;
            interSpringRelax=0.7*startRadius*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;

            // per-blob mirror delay (if things were well adjusted - in particular mirror waiting times, then this could be 0.
            //But in case of unique blobs, it may be interesting to accelerate display AND correct the delay by software):
            displaySensingBuffer.setDelayMirrors(2);
            break;
        case BOUNCING:
            sprintf(spotName,"bouncing");

            setColor(0x07);//0x04+0x02>>i);
             blueTouch=true;
             
            // default (initial) shape:
            startRadius=70;
            bluePrint.buildCircularScafold(startRadius, vector2Dd(0,0), 20); //(float _radius, vector2Dd _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=70;//bluePrint.radius;
            interSpringK=0.4;//46;
            interSpringRelax==1.0*startRadius*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;

            // per-blob mirror delay (if things were well adjusted - in particular mirror waiting times, then this could be 0.
            //But in case of unique blobs, it may be interesting to accelerate display AND correct the delay by software):
            displaySensingBuffer.setDelayMirrors(2);
            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 - NO NEEDED ANYMORE: now calling to static member method of pointMass...)
    setRegionMotion(MIN_AD_MIRRORS, MIN_AD_MIRRORS, MAX_AD_MIRRORS, MAX_AD_MIRRORS);
    
    // draw it once on the display buffer for good initialization:
    draw();
}

void elasticLoop::speedFactor(float speedfactor) {
  // This method is more appropiate for rigid loop, but we can "simulate" speed up in case of elastic loop by changing some parameters, even if the loop is not
  // set in "contour following" mode. 
  factorRecenteringLoopMass*=speedfactor;
}

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
    // NOTE: to save memory, we can drop hairVector (use lightForce instead)
    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++) {
        massesLoop[i].setIntegrationStep(integrationStepLoop);//22);//19); // VERY IMPORTANT! in the case of verlet integration, we need to set dt BEFORE setting the initial speed.
        massesLoop[i].setInitialCondition(startCenter.x+bluePrint.scafold[i].x,startCenter.y+bluePrint.scafold[i].y, startSpeed.x, startSpeed.y);
        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.setIntegrationStep(0.3); // VERY IMPORTANT! in the case of verlet integration, we need to set dt BEFORE setting the initial speed.
    anchorMass.setInitialCondition(startCenter, startSpeed);
    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(float mmix, float mmiy, float mmax, float 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);
    */
    
    // Use the static method of the class pointMass: 
    // pointMass::setWallLimits(mmix+10, mmiy+10, mmax-10, mmay-10);
     pointMass::setWallLimits(mmix+10, mmiy+10, mmax-10, mmay-10);
}

void elasticLoop::update(vector2Df referencePos) {

    // (I) Process loop geometry (compute "hair vectors", area and first order moment):
    processLoopData();

    // (II) Process sensing buffer and compute light forces
    // displaySensingBuffer.processSensedData();

    // (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++) {
        // NOTE: to save memory, we can drop hairVector...
        lightForce[i]=hairVector[i]*factorLightForce*displaySensingBuffer.lsdTrajectory[i].lightZone;
        lightForce[i].rotateDeg(angleCorrectionForceLoop); // correction by hand (interactive)
        // lightForce[i]=lightForce[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
    }
    recenteringVectorLoop=totalLightForce;//.getRotated(angleCorrectionForceLoop);
    //== (2) Compute the "recentering vector" from the total light force:
    // 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, to "ATTACH" like a zip to the black drawing:
        if (lightForcesOnLoop) {
            int sign=1;
            for (int i = 0; i < numMasses; i++) {
                if ((i%2)==0) sign*=-1;
                //sign=5*cos(6*2*PI*1.0*i/(numMasses-1))-2;
                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]*2.3); // 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?): THIS IS RESPONSIBLE FOR THE "FOLLOWING" BEHAVIOUR
        if (recenteringForceOnLoop) {

            vector2Df auxForce= (slidingDirection? recenteringVectorLoop.getRotatedDeg(-145) : recenteringVectorLoop.getRotatedDeg(145))*factorRecenteringLoopMass*1;
            //vector2Df auxForce2= (slidingDirection? totalLightForce.getRotatedDeg(-90) : totalLightForce.getRotatedDeg(90))*factorRecenteringLoopMass*1.5;
            //vector2Df auxForce3= (slidingDirection? totalLightForce.getRotatedDeg(-90) : totalLightForce.getRotatedDeg(90))*factorRecenteringLoopMass*1.5;
            vector2Df auxForce2= (slidingDirection? recenteringVectorLoop.getRotatedDeg(-50) : recenteringVectorLoop.getRotatedDeg(50))*factorRecenteringLoopMass*0.5;//*1.8;
            vector2Df auxForce3= (slidingDirection? recenteringVectorLoop.getRotatedDeg(-30) : recenteringVectorLoop.getRotatedDeg(30))*factorRecenteringLoopMass*0.6;//1.2;
            
           
            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 : auxForce3)); // auxForce3: 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) {
            vector2Df auxForce= (slidingDirection? recenteringVectorLoop.getRotatedDeg(-90) : recenteringVectorLoop.getRotatedDeg(90))*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);
        auxsign=-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;
                vector2Df 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 (or only one...), 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:
    // (1) current color: change with touch? NO
   // if (displaySensingBuffer.lightTouched)  
   //     transientBlobColor=blobColor|0x02; // set green ON on the trajectory, regardless of the initial color
   //  else 
        transientBlobColor=blobColor; // just the original blob color
    
    // 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= (unsigned short)( massesLoop[i].pos.x ); // note: it should be an unsigned short
        displaySensingBuffer.lsdTrajectory[i].y= (unsigned short)( massesLoop[i].pos.y );
        
        //displaySensingBuffer.lsdTrajectory[i]=  massesLoop[i].pos.y; // NOTE: doing this means converting from unsigned short to float (vector2Dd to vector2Df)
        
        //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=transientBlobColor;
}

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++) {
        vector2Df diff;
        diff.set(massesLoop[(i+1)%numMasses].pos-massesLoop[i].pos);
        // normalize and rotate 90 deg:
        // NOTE: to save memory, we can drop hairVector...
        hairVector[i]=diff.getPerpendicularNormed(CW);
        //lightForce[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();
    
    // (c) Compute kinetic energy:
    totalKineticEnergy=0;
    for (int i = 0; i < numMasses; i++){
        totalKineticEnergy+=massesLoop[i].getSpeed().squareLength();
    }
}


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;

    // approx area:
    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) {

        // (new) Total kinetic energy:
         if (sendingKineticEnergy) {
            sprintf(auxstring, "/k %d",identifier);
            sendMes.setSubAddress(auxstring);
            long x;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
            x=(long)(totalKineticEnergy);
            sendMes.setArgs( "i", &x);
            osc.sendOsc( &sendMes );
         }
        // (a) Anchor mass:
         if (sendingAnchorPosition) {
           sprintf(auxstring, "/p %d",identifier);
            sendMes.setSubAddress(auxstring);
            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("/a");
            long    x;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
           // x=(long)(area);//approxArea); // area or approxArea
            x=(long)(area>0? approxArea : -approxArea);
            sendMes.setArgs( "i", &x); // ATTENTION: AREA CAN BE NEGATIVE!!! (does MAX handles this well? test this!)
         */   
            // HACK for the time being (for Daito):
            sendMes.setSubAddress("/a");
            long    x, y;    //ATTENTION: parameters to setArgs should be long or unsigned long only (not int!!)
           // x=(long)(area);//approxArea); // area or approxArea
            x=(long)(w); y=(long)(h);
            sendMes.setArgs( "ii", &x, &y); // ATTENTION: AREA CAN BE NEGATIVE!!! (does MAX handles this well? test this!)
            
            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) {
            if (displaySensingBuffer.lightTouched) {
                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...
}