Reverse scale size with Geomerative

Hello,
I am working on a text deformed and scaled using Geomerative library. I am on hurry…
I would like to reverse these transformations when the sound loop. How should I proceed ?
Register the RPoints [i][j] positions at the begining and after the transformations RPoint startingPoints
and RPoint [][]endingPoints and then lerp between them?!
Thanks a lot for your help.
L

import processing.pdf.*;
import geomerative.*;
import ddf.minim.analysis.*;
import ddf.minim.*;


Minim minim;
AudioPlayer[] soundsFR;
FFT fftFR;
float bandHeightFR;
float PosX, PosY;

int x, y;
String []message={
  "On me dit de te haïr et je m'y efforce", 
  "Je t'imagine cruel, violent, implacable", 
};

color textColor=0;
RFont f;

float fontSize=60;
int splitGlyph = 120;
RPoint[][] pointsP;
float r = random(5, 20);
WordAttractor attractorW;
// We create 3 new lists parallel to pointsP
// List of attractors, one per glyph
WordAttractor [][] listWordAttractors;
// List of all the distances between all the attractors and the pointsP
float [][] listDistMap;
// List of vectors to replace the pointsP coordinates
PVector [][] listPV;
int tChildCount;

characterSpec []characters = new characterSpec[message.length];
RShape [] gShape = new RShape[message.length+5]; 
int sentenceCurrentlyModified;
int indexPhrase;
int indexPhraseSet;

int startTime;
int loop=0;
boolean isTransitioning = false;
boolean messageOn = false;
String []soundNamesFR = {"FR_01", "FR_02"};
boolean modifyThis;
float transitionTime = 0;
int startTransitionTime;
int transitionDuration = 1500;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void setup() {
  size(1920, 1920);
  RG.init(this);
  f = new RFont("SFNSText.ttf", int( fontSize), LEFT);

  minim=new Minim(this);
  soundsFR=new AudioPlayer[soundNamesFR.length];
  for (int i=0; i<soundNamesFR.length; i++) {
    soundsFR[i]=minim.loadFile(soundNamesFR[i]+".wav", 512);

    fftFR= new FFT(soundsFR[i].bufferSize(), soundsFR[i].sampleRate());
  }
  for (int i=0; i<message.length; i++) {
    characters[i]= new characterSpec( gShape[i], message[i]);
  }
  indexPhrase=0;
  indexPhraseSet=-1;
  startTime = millis();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void draw() {

  background(0);
  smooth();
  // we start to read the 1rst sound
  if (isTransitioning) {
    if (millis() >= startTransitionTime+transitionDuration) {
      // transition end

      if (indexPhraseSet < soundsFR.length) {
        isTransitioning=false;
        transitionTime=0; 
        //nextPhraseSetFR();
        playSound();

        // si on a lu tous les groupes on recommence à zero
      } else if (indexPhraseSet == soundsFR.length && millis() >= startTransitionTime+transitionDuration) {         
        // reset from the beginning 
        isTransitioning=false;
        transitionTime=0;    
        indexPhraseSet=0;
        indexPhrase=0;

        sentenceCurrentlyModified=0;

  
        startTime = millis();
        playSound();
      }
    } else if (millis() <= startTransitionTime+transitionDuration) {       
      //  we are transiting 
      transitionTime = map(millis(), startTransitionTime, startTransitionTime+transitionDuration, 0., 1.);
    }
  } else {
    if ( indexPhraseSet==-1) { 
      // Initialise all
      sentenceCurrentlyModified=0;
      indexPhraseSet=0;
      indexPhrase=0;
      playSound();
    } else if ( !soundsFR[indexPhrase].isPlaying()) {  
      // sound file is finished read next one
      indexPhrase++;
      sentenceCurrentlyModified++;

      if ( isTransitioning==false && indexPhrase >= soundsFR.length) {
        // If phrases'index is greater than the stanza's index then go on to the next stanza
        indexPhrase=0;// 1rst sentence
        sentenceCurrentlyModified=0;
        indexPhraseSet++;// increase stanza's index
        isTransitioning = true;
        startTransitionTime = millis();
      } else {
        //go to the next phrase   
        //nextPhrase();
      }
      if (millis() >= startTransitionTime+transitionDuration) {
        playSound();
      }
    } else { 
      // we're reading the sound file
      // analyse of the sound
      soundFFTAnalyse();
      wordAttractorToSound();
    }
  }
  println(indexPhrase);
  translate(500, 200);
  for (int i=0; i<2; i++) {
    translate(0, 50);
    if (sentenceCurrentlyModified>=i) {
      characters[i].update();
    }
    if (sentenceCurrentlyModified>=i) {
      characters[i].display();
    }
  }
}


void playSound() {
  AudioPlayer fr = soundsFR[indexPhrase];
  fr.rewind(); 
  fr.play();
  fftFR = new FFT(fr.bufferSize(), fr.sampleRate());
  PosX = 0;
  PosY =0;
  bandHeightFR = 0;
}

void soundFFTAnalyse() {
  AudioPlayer fr = soundsFR[indexPhrase];
  fftFR.forward(fr.mix);
  for (int i =0; i< fftFR.specSize(); i++) {
    float bandDBFR = 10*log(fftFR.getBand(i)/fftFR.timeSize());
    bandDBFR = constrain(bandDBFR, -1000, 1000);
    bandHeightFR = map(bandDBFR*4, 0, -220, 0, height);
  }
}

void wordAttractorToSound() {

  AudioPlayer fr = soundsFR[indexPhrase]; 
  PosX = map(fr.position(), 0, fr.length(), 100, width-1000);
  PosY= bandHeightFR/8-350;

}

class characterSpec {
  String  m;
  int tChildCount;
  RShape bShape;
  RShape letterShapes;

  characterSpec(RShape _letterShapes, String _m) {

    letterShapes=_letterShapes;
    m=_m;
    pointsP= new RPoint [m.length()][splitGlyph];
    listWordAttractors= new WordAttractor [m.length()][splitGlyph];
    listDistMap = new float [m.length()][splitGlyph];
    for (int n=0; n<m.length(); n++) {
      for (int j=0; j<splitGlyph; j++) {
        listDistMap[n][j]=100000;
      }
    }
    listPV = new PVector[m.length()][splitGlyph];
    if (m.length()>0) {
      bShape= new RShape();
      bShape= f.toShape(m);
      RShape[]letterShapes = bShape.children;
      letterShapes = bShape.children;
      tChildCount = bShape.children.length;
    }
    //}
  }

  void update() {

    for (int k=0; k<tChildCount; k++) {  
      float d= dist(PosX, 0, bShape.children[k].getCenter().x, 0);
      float s=map(d, 0, 75, 1.0, 1.04);
      if (d<75) {
        bShape.children[k].scale(s, bShape.children[k].getCenter());
      } 

      for (int j=0; j<splitGlyph; j++) { 
        float frac =(1.0/splitGlyph);
        pointsP[k][j]=bShape.children[k].getPoint(j*frac);

        float dist1 = dist(PosX-200, PosY +50, pointsP[k][j].x, pointsP[k][j].y);
        if (dist1 < listDistMap[k][j]) {
          //  On remplace toutes les distances possibles entre la souris et les points par la distance minimale
          listDistMap[k][j]=dist1;
          listPV[k][j]=new PVector(PosX -200, PosY +50);
          listWordAttractors[k][j]=new WordAttractor(listPV[k][j].x, listPV[k][j].y, pointsP[k][j]);
        }
        listWordAttractors[k][j].points=pointsP[k][j];
        listWordAttractors[k][j].attract();

        pushMatrix();
        if (m==",") {
          pointsP[k][j].x+=10*k;
        }
        popMatrix();
      }
    }
  }

  void display() {
    for (int k=0; k< tChildCount; k++) {
      for (int j=0; j<splitGlyph; j++) {
        pushMatrix();
        translate(pointsP[k][j].x, pointsP[k][j].y);
        stroke(255);
        beginShape();
        noFill();
        strokeWeight(0.5);
        float angle = TWO_PI/18;
        rotate(angle*j/4+noise(pointsP[k][j].x)/20);
        bezier(-3*(noise(5)), 3, -6*(noise(2)), 3, -4*noise(10), -3, 3, -3);
        endShape();
        popMatrix();
      }
    }
  }
  void pauseSound() {
    AudioPlayer fr = soundsFR[indexPhrase];
    fr.rewind();
    fr.pause();
    fftFR = new FFT(fr.bufferSize(), fr.sampleRate());
  }
  void reset() {
    if (indexPhrase>soundsFR.length) {
      for (int k=0; k< tChildCount; k++) {
        for (int j=0; j<splitGlyph; j++) {
          pointsP= new RPoint [m.length()][splitGlyph];
          if (m.length()>0) {
            bShape= new RShape();
            bShape= f.toShape(m);
            RShape[]letterShapes = bShape.children;
            letterShapes = bShape.children;
            tChildCount = bShape.children.length;
          }
          float fraction =(1.0/splitGlyph);
          pointsP[k][j]=bShape.children[k].getPoint(j*fraction);
        }
      }
    }
  }
}

class WordAttractor {

  float force_radious = 200;
  float maxForce = 20;
  RPoint position;
  RPoint points;
  float pX;
  float pY;

  WordAttractor(float x, float y, RPoint p) {
    points =p;
    position = new RPoint(x, y);
  }

  void attract() {

    float d= points.dist(position);
    if (d < force_radious) {   
      RPoint desired = new RPoint(points);
      desired.sub(position);
      desired.normalize();
      desired.scale(map(d, 0, force_radious, maxForce, 0));
      points.add(desired);
    }
  }
}

That sounds like a reasonable approach!

Not a geomerative example, but here is a simplified example of lerping between two point arrays.

/**
 * lerp between point arrays
 * Processing 3.4 - 2019-06-15
 * discourse.processing.org/t/reverse-scale-size-with-geomerative/12043
**/

PVector[] vecStart;
PVector[] vecEnd;
int count = 30;

void setup() {
  size(400, 400);
  vecStart = new PVector[count];
  vecEnd = new PVector[count];
  // create random points
  randomSeed(1);
  for (int i=0; i<count; i++) {
    vecStart[i] = new PVector(width/2 + random(-25, 25), height/2 + random(-25, 25));
    vecEnd[i] = vecStart[i].copy().add(random(-100, 100), random(-100, 100));
  }
}
void draw() {
  background(192);
  // draw start
  for (int i=0; i<count; i++) {
    fill(0);
    ellipse(vecStart[i].x, vecStart[i].y, 10, 10);
  }
  // draw end
  for (int i=0; i<count; i++) {
    fill(255);
    ellipse(vecEnd[i].x, vecEnd[i].y, 10, 10);
  }

  // draw midpoint
  PVector midpoint;
  float amt = abs(frameCount%180 - 90)/90.0; // triangle wave 0-1-0 every 3 sec
  for (int i=0; i<count; i++) {
    midpoint = PVector.lerp(vecStart[i], vecEnd[i], amt);
    fill(255, 0, 0);
    ellipse(midpoint.x, midpoint.y, 10, 10);
  }
}

Dear @jeremydouglass,

Thank you very much for this example of lerping between two point arrays. Between time I simply loop from start creating a new bunch of points and don’t lerp between points since I realised I have a much more crucial issue : my attractors attract all the letters of the text and deform them before they appear. Then they deform them another time, how come?! Regarding the scale function in the Character class why the 2 first lines are bigger and the rest of the text less?!
I’ve added all the lines that you can see what I mean. Thank you very much in advance for your help.
Best, L

import processing.pdf.*;
import geomerative.*;
import ddf.minim.analysis.*;
import ddf.minim.*;

//TEXT
int indexPhraseSetFR;
int indexPhraseFR;
int x, y, i, j;
String []message={
  "On me dit de te haïr et je m'y efforce", 
  "Je t'imagine cruel, violent, implacable", 
  "mais à te voir je n'ai bientôt plus de force", 
  "et de te blesser je suis bien incapable", 

  "Tous mes camarades combattent avec rage", 
  "Et pleurent la nuit au souvenir des supplices", 
  "Infligés à leurs frères qui sont du même âge", 
  "et rêvent comme eux de toucher une peau lisse", 

  "Et de pouvoir enfin caresser des obus", 
  "Autres que ceux produits par le pouvoir obtus", 
  "Je rêve de quitter ces boyaux infernaux", 

  "De laisser ces furieux des deux bords du Rhin", 
  "Et de pouvoir embrasser ta chute de rein", 
  "Et porter notre amour sur les fonts baptismaux"
};

color textColor=0;
RFont f;
float fontSize=60;
int splitGlyph = 120;
RPoint[][] pointsP;
float r = random(5, 20);
WordAttractor attractorW;
// We create 3 new lists parallel to pointsP
// List of attractors, one per glyph
WordAttractor [][] listWordAttractors;
// List of all the distances between all the attractors and the pointsP
float [][] listDistMap;
// List of vectors to replace the pointsP coordinates
PVector [][] listPV;

RShape [] gShape = new RShape[message.length]; 
characterSpec [] characters= new characterSpec[message.length];
int sentenceCurrentlyModified;
boolean modifyThis;

//SOUND
SoundManager sm;
int startTime;
int loop=0;
float bandHeightFR;
boolean isTransitioning = false;
boolean messageOn = false;
String []soundNamesFR = {"FR_01", "FR_02", "FR_03", "FR_04", "FR_05", "FR_06", "FR_07", "FR_08", "FR_09", "FR_10", "FR_11", "FR_12", "FR_13", "FR_14"};

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void setup() {
  size(1920, 1920);
  RG.init(this);
  f = new RFont("SFNSText.ttf", int( fontSize), LEFT);

  for (int i=0; i<message.length; i++) {
    characters[i]= new characterSpec( gShape[i], message[i]);
  }

  //SOUND
  sm= new SoundManager(this);
  indexPhraseFR = 0;
  indexPhraseSetFR = -1;
  // TIME
  startTime=millis();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void draw() {

  background(0);
  smooth();
  sm.update();

  translate(350, 100);

  for (int i=0; i<message.length; i++) {
    // println("i"+":"+i);

    translate(0, i+1*70);
    if (i%4==0) {
      translate(-80, 0);
    }
    if (i<3) {
      translate(i*20, 0);
    } else if (i==3) {
      translate(130, 0);
    } else if (i>3 && i<7) {
      translate(-280, 0);
      translate(i*45, 0);
    } else if (i==7) {
      translate(-150, 0);
    } else if (i==8) {
      translate(355, 0);
    } else if (i==9) {
      translate(-150, 0);
    } else if (i==10) {
      translate(200, 0);
    } else if (i>10 && i <12) {  
      translate(-160, 0);
    } else if (i==12) {
      translate(160, 0);
    } else if (i==13) {
      translate(-190, 0);
    }
    if (i<=8 && i%4==0) {
      translate(0, 40);
    }
    if (  i==11 ) {
      translate(0, 40);
    }

    if (sentenceCurrentlyModified==i) {
      characters[i].update(); 
    }
 
    if (sentenceCurrentlyModified>=i) {
      characters[i].display((sentenceCurrentlyModified == i));
    }
  }
}

class characterSpec {
  String  m;
  int tChildCount;
  RShape bShape;
  RShape letterShapes;

  characterSpec(RShape _letterShapes, String _m) {

    letterShapes=_letterShapes;
    //bShape=_bShape;
    m=_m;
    //for (int i=0; i<m.length(); i++) {
    pointsP= new RPoint [m.length()][splitGlyph];

    listWordAttractors= new WordAttractor [m.length()][splitGlyph];
    listDistMap = new float [m.length()][splitGlyph];
    for (int n=0; n<m.length(); n++) {
      for (int j=0; j<splitGlyph; j++) {
        listDistMap[n][j]=100000;
      }
    }
    listPV = new PVector[m.length()][splitGlyph];

    if (m.length()>0) {
      bShape= new RShape();
      bShape= f.toShape(m);
      RShape[]letterShapes = bShape.children;
      letterShapes = bShape.children;
      tChildCount = bShape.children.length;
    }
  }

  void update() {

    for (int k=0; k<tChildCount; k++) {  

      float d= dist(attractorPosXFR, 0, bShape.children[k].getCenter().x, 0);
      float s=map(d, 0, 75, 1.0, 1.20);
      if (d<75) {
        bShape.children[k].scale(s, bShape.children[k].getCenter());
      }
      
      for (int j=0; j<splitGlyph; j++) { 
        float frac =(1.0/splitGlyph);
        pointsP[k][j]=bShape.children[k].getPoint(j*frac);

        float dist1 = dist(attractorPosXFR-200, attractorPosYFR +50, pointsP[k][j].x, pointsP[k][j].y);
        if (dist1 < listDistMap[k][j]) {
          //  On remplace toutes les distances possibles entre la souris et les points par la distance minimale
          listDistMap[k][j]=dist1;
          listPV[k][j]=new PVector(attractorPosXFR-200, attractorPosYFR +50);
          listWordAttractors[k][j]=new WordAttractor(listPV[k][j].x, listPV[k][j].y, pointsP[k][j]);
        }
        listWordAttractors[k][j].points=pointsP[k][j];
        //listWordAttractors[k][j].attract();
        pushMatrix();
        if (m==",") {
          pointsP[k][j].x+=10*k;
        }
        popMatrix();
      }
    }
  }


  void display(boolean modifyThis) {
    for (int k=0; k< tChildCount; k++) {
      for (int j=0; j<splitGlyph; j++) {  
        float frac =(1.0/splitGlyph);
        pointsP[k][j]=bShape.children[k].getPoint(j*frac);
        
        float dist1 = dist(attractorPosXFR -200, attractorPosYFR +50, pointsP[k][j].x, pointsP[k][j].y);
        if (dist1 < listDistMap[k][j]) {
          //  On remplace toutes les distances possibles entre la souris et les points par la distance minimale
          listDistMap[k][j]=dist1;
          listPV[k][j]=new PVector(attractorPosXFR -200, attractorPosYFR +50);
          listWordAttractors[k][j]=new WordAttractor(listPV[k][j].x, listPV[k][j].y, pointsP[k][j]);
        }
        listWordAttractors[k][j].points=pointsP[k][j];
        listWordAttractors[k][j].attract();
        pushMatrix();
        if (m==",") {
          pointsP[k][j].x+=10*k;
        } 
        //pointsP[k][j].x-=120;
        //pointsP[k][j].x+=10*k;
        popMatrix();

        pushMatrix();
        translate(pointsP[k][j].x, pointsP[k][j].y);
        beginShape();
        stroke(255);
        noFill();
        strokeWeight(0.5);
        float angle = TWO_PI/18;
        rotate(angle*j/4+noise(pointsP[k][j].x)/20);
        bezier(-3*(noise(5)), 3, -6*(noise(2)), 3, -4*noise(10), -3, 3, -3);
        endShape();
        popMatrix();
        if (modifyThis) {
          if (k<=tChildCount) {
            pushMatrix();
            translate(pointsP[k][j].x, pointsP[k][j].y);
            RPoint attractorPosFR = new RPoint(attractorPosXFR, attractorPosYFR);
            attractorPosFR = new RPoint(attractorPosXFR+100, attractorPosYFR);
            float dist = pointsP[k][j].dist(attractorPosFR);
            if (dist<150) {
              dist = map(dist, 150, 0, 1, 0);
              if (attractorPosFR.x ==0) {
                stroke(255, 10) ;
              } else { 
                stroke(255, dist*255);
              }
              float angle_01 = TWO_PI*10;
              //rotate (j/angle*frameCount/10);
              rotate (j/angle_01*2);
              if (modifyThis) {
                bezier(-2*(noise(10)), 10, 10*(noise(10)), -5, 2*noise(5), -10, 10, dist*attractorPosFR.x/20);//
              } else {
                bezier(-2*(noise(10)), 10, 10*(noise(10)), -5, 2*noise(5), -10, 10, 1);//
                // print(".");
              }
            }
            popMatrix();
          }
        }
      }
    }
  }
}


import ddf.minim.analysis.*;
import ddf.minim.*;


//variable 'public'

float attractorPosXFR, attractorPosYFR;
float []linesYPositions1 ={300., 450., 600., 750.};
float []linesYPositions2 ={370., 520., 670.};


class SoundManager {
  //SOUND
  Minim minim;
  AudioPlayer background;
  AudioPlayer[]soundsFR;
  FFT fftFR;
  float transitionTime = 0;
  int startTransitionTime;
  int transitionDuration = 1500;

  SoundManager(PApplet app) {

    minim = new Minim(app);
    background = minim.loadFile("WAR.wav");
    soundsFR = new AudioPlayer[soundNamesFR.length];
    for (int i =0; i<soundNamesFR.length; i++) {
      soundsFR[i] = minim.loadFile(soundNamesFR[i]+".wav", 512);
    }
  }

  void update() {

    if (isTransitioning) {
      if (millis() >= startTransitionTime+transitionDuration) {
        // transition end
        // si c'est pas fini on passe au suivant
        if (indexPhraseSetFR < soundsFR.length) {
          isTransitioning=false;
          transitionTime=0; 
          //nextPhraseSetFR();
          playSound();

          // si on a lu tous les groupes on recommence à zero
        } else if (indexPhraseSetFR == soundsFR.length && millis() >= startTransitionTime+transitionDuration) {         
          // reset from the beginning 
          isTransitioning=false;
          transitionTime=0;    
          indexPhraseSetFR=0;
          indexPhraseFR=0;
          //nextPhraseSetFR();
          sentenceCurrentlyModified=0;
          background.shiftGain(-20, -80, 1000);
          background.rewind();
          background.pause();
          startTime = millis();
          playSound();
          background.play();
          background.shiftGain(-80, -20, 1000);
        }
      } else if (millis() <= startTransitionTime+transitionDuration) {       
        //  we are transiting 
        transitionTime = map(millis(), startTransitionTime, startTransitionTime+transitionDuration, 0., 1.);
        for (int i=0; i<message.length; i++) {
          characters[i]= new characterSpec( gShape[i], message[i]);
        }
      }
    } else {
      if ( indexPhraseSetFR==-1) { 
        // Initialise all
        sentenceCurrentlyModified=0;
        indexPhraseSetFR=0;
        indexPhraseFR=0;
        playSound();
        background.play();
        background.shiftGain(-80, -20, 1000);
      } else if ( !soundsFR[indexPhraseFR].isPlaying()) {  
        // sound file is finished read next one
        indexPhraseFR++;
        sentenceCurrentlyModified++;
        //sentenceCurrentlyModified=indexPhraseFR;

        if ( isTransitioning==false && indexPhraseFR >= soundsFR.length) {
          // If phrases'index is greater than the stanza's index then go on to the next stanza
          indexPhraseFR=0;// 1rst sentence
          sentenceCurrentlyModified=0;
          indexPhraseSetFR++;// increase stanza's index
          isTransitioning = true;
          startTransitionTime = millis();
        } else {
          //go to the next phrase   
          //nextPhrase();
        }
        if (millis() >= startTransitionTime+transitionDuration) {
          playSound();
        }
      } else { 
        // we're reading the sound file
        // analyse of the sound
        soundFFTAnalyse();
        wordAttractorToSound();
      }
    }
  }

  void pauseSound() {
    AudioPlayer fr = soundsFR[indexPhraseFR];
    fr.rewind();
    fr.pause();
    fftFR = new FFT(fr.bufferSize(), fr.sampleRate());
  }

  void playSound() {
    AudioPlayer fr = soundsFR[indexPhraseFR];
    fr.rewind(); 
    fr.play();
    fftFR = new FFT(fr.bufferSize(), fr.sampleRate());
    attractorPosXFR = 0;
    attractorPosYFR =indexPhraseFR*50;
    bandHeightFR = 0;
  }

  void soundFFTAnalyse() {
    AudioPlayer fr = soundsFR[indexPhraseFR];
    fftFR.forward(fr.mix);
    for (int i =0; i< fftFR.specSize(); i++) {
      float bandDBFR = 10*log(fftFR.getBand(i)/fftFR.timeSize());
      bandDBFR = constrain(bandDBFR, -1000, 1000);
      bandHeightFR = map(bandDBFR*4, 0, -220, 0, height);
    }
  }

  void wordAttractorToSound() {
    AudioPlayer fr = soundsFR[indexPhraseFR]; 
    //initAttractorFR(indexPhraseFR,indexPhraseFR);
     //if (message[indexPhraseSetFR].length()==4) {
    attractorW= new WordAttractor(x, y+50*indexPhraseFR, pointsP[i][j]);
    attractorPosXFR = map(fr.position(), 0, fr.length(), 100, width-100);
    //attractorPosYFR = linesYPositions1[indexPhraseFR]+bandHeightFR/8-350;
     attractorPosYFR = bandHeightFR/8-350;
    attractorW.moveTo(attractorPosXFR, attractorPosYFR);
    // }
    //attractorW.attract();
  }
}

class WordAttractor {

  float force_radious =200;
  float maxForce = 15;
  RPoint position;
  RPoint points;
  float pX;
  float pY;

  WordAttractor(float x, float y, RPoint p) {
    points =p;
    position = new RPoint(x, y);
  }

  void attract() {

      float d= points.dist(position);
      if (d < force_radious) {   
        RPoint desired = new RPoint(points);
        desired.sub(position);
        desired.normalize();
        desired.scale(map(d, 0, force_radious, maxForce, 0));
        points.add(desired);   
    }
  }
  void moveTo(float x, float y) {
    position.x=x;
    position.y=y;
  }
}