Distort a time graph sample. Transform a shape recorded for 2 seconds into a perfect movement

Dear everyones :wink:

The idea is to record any movement for 2 sec, transform it to loop it (so that the movement is ‘full’) and play it back for exactly 2 sec. To replay the transformed movement, the movement must be distorted so that it loops exactly for 2 sec.

The first program below registers a movement and completes it by creating a bezier curve between the first and the last point.
You press one the mouse, and the program will record a shape for 2 sec.

The second records a movement and plays it back but it does not fill the loop.

I have to combine these two programs, but I have no idea how to do it. should not be too complicated for the people managing the classes

I really need this option, I don’t know how to thank you.

Benjamin

int actualSec,lastSec,measure,measureToStartRecording;
boolean preActivateRecording = false;
boolean endRecording =  false;
boolean mouseRecorded = false;

final Path path = new Path();

void setup() {
   size( 800, 800, P3D );
   frameRate( 30 ); 
}

void draw() {
  background(0);
  drawShape();
  
  if    (actualSec!=lastSec){
         lastSec=actualSec;
         measure++;
     }
         actualSec =(int) (millis()*0.001);  // 
         
  if (measure>=measureToStartRecording+2  && actualSec!=lastSec && endRecording == true ) { 
      preActivateRecording = false;
      mouseRecorded = false;
      endSampling();
      endRecording = false;
     }
  println ( " preActivateRecording ", preActivateRecording, " measure",  measure, " measureToStartRecording", measureToStartRecording); 
  path.draw();
}

void drawShape() {
  path.mouseDragged();
}

void mousePressed()  {
  mouseRecorded =true;
  path.startNewSample();
}

void endSampling() {
  path.endSampling();
}

import java.util.function.Predicate;

final class Automater {

  private static final int STEPS = 60;

  private final ArrayList<PVector> anchors;
  private final Anchor head;
  private final Anchor tail;

  Automater(ArrayList<PVector> anchors) {
    this.anchors = new ArrayList<>(anchors);
    head = new Anchor(true);
    tail = new Anchor(false);
  }
  
  ArrayList<PVector> build() {
    final ArrayList<PVector> xys = new ArrayList<>();
    final float x1 = head.loc.x;
    final float y1 = head.loc.y;
    final float x2 = tail.loc.x;
    final float y2 = tail.loc.y;
    for (int i = 0; i < STEPS; i++) {
      final float t = i / (float) STEPS;
      final float x = bezierPoint(
        x1, x1 + head.vel.x,
        x2 + tail.vel.x, x2, t);
      final float y = bezierPoint(
        y1, y1 + head.vel.y,
        y2 + tail.vel.y, y2, t);
      xys.add(new PVector(x, y));
    }
    return xys;
  }

  private final class Anchor {

    private final PVector loc;
    private final PVector vel;

    Anchor(boolean head) {
      final int id = (head) ? anchors.size() - 1 : 0;
      loc = anchors.get(id);
      vel = initVel(head, id).mult((head) ? 1 : -1);
    }

    private PVector initVel(boolean head, int id) {
      final Direction xDir = new Direction();
      final Direction yDir = new Direction();
      for (int i = 1; i < (anchors.size() / 2); i++) {
        final int curr = (head) ? id - i : i;
        final PVector currLoc = anchors.get(curr);
        final PVector prevLoc = anchors.get(curr - 1);
        xDir.update(currLoc.x, prevLoc.x);
        yDir.update(currLoc.y, prevLoc.y);
        if (xDir.done || yDir.done) {
          break;
        }
      }
      return new PVector(xDir.total, yDir.total);
    }

    private class Direction {

      Predicate<Float> test;
      boolean done;
      boolean lock;
      float total;

      private void update(float xyCurr, float xyPrev) {
        final Float vel = xyCurr - xyPrev;
        if (!lock && vel != 0) {
          lock = true;
          test = (vel > 0) ? e -> e >= 0 : e -> e <= 0;
        }
        if (lock) {
          if (!test.test(vel)) {
            done = true;
            return;
          }
        }
        total += vel;
      }
    }
  }
}

final class Path {

  private static final int ANCHOR_SIZE = 8;

  private final ArrayList<PVector> autoAnchors = new ArrayList<>();
  private final ArrayList<PVector> userAnchors = new ArrayList<>();

  void draw() {
    pushStyle();
    ellipseMode(CENTER);
    stroke(0);
    strokeWeight(1);
    drawAnchors(userAnchors, #FFFFFF);
    drawAnchors(autoAnchors, #FF8C00);
    popStyle();
  }

  private void drawAnchors(ArrayList<PVector> anchors, color c) {
    fill(c);
    anchors.stream().forEach(e -> ellipse(e.x, e.y, ANCHOR_SIZE, ANCHOR_SIZE));
  }

  void mouseDragged() {
    
  if  ( preActivateRecording){  //draw vector
        userAnchors.add(new PVector(mouseX, mouseY));
     }
  }
  
  void startNewSample() {
    measureToStartRecording=measure;
    preActivateRecording=true;  
    autoAnchors.clear();
    userAnchors.clear();
    endRecording = true;
 }
 
  void endSampling() {
    final Automater automater = new Automater(userAnchors);
    ArrayList<PVector> anchors = automater.build();
    anchors.stream().forEach(e -> autoAnchors.add(new PVector(e.x, e.y)));
  }
}
int actualSec,lastSec,measure,measureToStartRecording;;
boolean bRecording = false;
boolean mouseRecorded =  true;


class Sample {
  int t, x, y;
  Sample( int t, int x, int y ) {
    this.t = t;  this.x = x;  this.y = y;
  }
}

class Sampler {
  
  ArrayList<Sample> samples;
  int startTime;
  int playbackFrame;
  
  Sampler() {
    samples = new ArrayList<Sample>();
    startTime = 0;
  }
  void beginRecording() {   
    samples = new ArrayList<Sample>();
    playbackFrame = 0;
  }
  void addSample( int x, int y ) {  // add sample when bRecording
    int now = millis();
    if( samples.size() == 0 ) startTime = now;
    samples.add( new Sample( now - startTime, x, y ) );
  }
  int fullTime() {
    return ( samples.size() > 1 ) ? 
    samples.get( samples.size()-1 ).t : 0;
  }
  void beginPlaying() {
    startTime = millis();
    playbackFrame = 0;
    println( samples.size(), "samples over", fullTime(), "milliseconds" );
  }
  
  
  void draw() {
    stroke( 255 );
    
    //**RECORD
    beginShape(LINES);
    for( int i=1; i<samples.size(); i++) {
      vertex( samples.get(i-1).x, samples.get(i-1).y ); // replace vertex with Pvector
      vertex( samples.get(i).x, samples.get(i).y );
    }
    endShape();
    //**ENDRECORD
    
    //**REPEAT
    int now = (millis() - startTime) % fullTime();
    if( now < samples.get( playbackFrame ).t ) playbackFrame = 0;
    while( samples.get( playbackFrame+1).t < now )
      playbackFrame = (playbackFrame+1) % (samples.size()-1);
    Sample s0 = samples.get( playbackFrame );
    Sample s1 = samples.get( playbackFrame+1 );
    float t0 = s0.t;
    float t1 = s1.t;
    float dt = (now - t0) / (t1 - t0);
    float x = lerp( s0.x, s1.x, dt );
    float y = lerp( s0.y, s1.y, dt );
    circle( x, y, 10 );
  }
  
}

Sampler sampler;

void setup() {  
  size( 800, 800, P3D );
  frameRate( 30 );  
  sampler = new Sampler();
}

void draw() {
  background( 0 );
  activeSampling();
  stopSampling();
  
  if  (actualSec!=lastSec){
       lastSec=actualSec;
       measure++;
       textSize (100);
       text (measure, 100, 100 );
     }
       actualSec =(int) (millis()*0.001);  // 
 
  if( bRecording) { // draw circle
    circle( mouseX, mouseY, 10 );
    sampler.addSample( mouseX, mouseY );
  }
  
  else {    
  if( sampler.fullTime() > 0 )
        sampler.draw();
  }
}

void mousePressed() {
  bRecording = true;   // draw circle
  mouseRecorded = true;
  measure=0;
}
  
void activeSampling() { 
   if (measure<=1 && measure>=1 &&actualSec!=lastSec && mouseRecorded == true) {
  sampler.beginRecording();
  }
}

void stopSampling() { 
   if (measure<=3 && measure>=3  && actualSec!=lastSec) {  
  mouseRecorded = false;
     //**REPEAT
  bRecording = false;
  sampler.beginPlaying();
  }
}
1 Like