Alvaro Cassinelli
/
skinGames_II
save loops
Diff: elasticLoop.cpp
- Revision:
- 0:df6fdd9b99f0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/elasticLoop.cpp Tue Dec 02 04:39:15 2014 +0000 @@ -0,0 +1,1031 @@ +/* + * 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... +} +