Moving 'backwards through time' of animation frame by frame?

I’m trying to tab through the ‘frames’ of my animation, as it is useful for troubleshooting.

Stepping forward in time, one frame at a time, is easy enough:

void keyPressed()
{
if (key == CODED)
  {
    if (keyCode == RIGHT)  // right arrow executes code within draw() one time
    {
      redraw();
    }
  }
}

But stepping backwards in time is trickier. I thought I’d ask the community here. What are simple ways to go about this?

My initial thoughts below:

  • looking at it kind of like an ‘undo’ function… not sure how those typically work in normal application coding…
  • create a variable (maybe frameCount) that counts each iteration of the draw loop, clear background, create a while loop for executing my code up until the frame previous to the one I’m at.

I guess it depend on different factors a little bit, but if the code wasn’t too complex, it seems like that last point would work. I am still a newb to creative coding and coding in general, so many thanks to any answers that are obvious.

Peace!
Cam

Hi @CamSos,

Imagine the simplest way. S.th like

int myFrame;

void setup () {
   size(500,500);
   myFrame=0;
}

void keyPressed() {
   if (key == 'd') {
      myFrame=contrain(myFrame+1,0,300);
   }
   else if (key == 'a') {
      myFrame=contrain(myFrame-1,0,300);
   }
}

void draw() {
  background (0);
  stroke(0,255,0);
  strokeWeight(5);
  line(50,250,50+myFrame,250);
}

As you see here, you control the frames in a minimalistic version. You can make it depending one a frame count what your sketch should do. Also possibe depending on time by time index etc…

In principle you need to control your variables state somehow in relation to the frame you are.

Hope that helps you to understand…

Cheers
— mnse

Hello @CamSos,

Some fun with existing frameCount variable:

void setup () 
  {
  size(400, 400);
  strokeWeight(5);
  println("S: ", frameCount);
  noLoop();
  }

void draw() 
  {
  background (255);
  
  translate(width/2, height/2);
  
  float theta = frameCount * TWO_PI/360;
  float x = 100*cos(theta);
  float y = 100*sin(theta);
  
  point(x, y);
  println("D: ", frameCount);
  //noLoop();
  }

void keyPressed() 
  {
  if (key == 'j') 
    frameCount = frameCount-2; // See console output
  //if (key == 'l')  // Already increments so not needed!
  //  frameCount = frameCount; // See console output
  redraw();  
  //loop();
  }

Reference:

I have explored resetting things and running setup() again with:

void setup() 
  {
  println("S", frameCount);
  frameRate(2);
  }

void draw() 
  {
  background(0);
  println("D", frameCount);
  }

void keyPressed()
  {
  frameCount = -1; // Run from setup() again
  }

May be useful one day…

:)

1 Like

Thank you @mnse and @glv for the great examples and explanations!

I see now that to travel through time one must create a variable for “time” (be it frameCount, millis, manual interactions, etc.) and make it so the code can be dependent on that variable.

That reset trick is very handy @glv ! Seems like frameCount = 0 is built in as conditional to run code starting from setup again? Super useful – playing around with it. :slightly_smiling_face:

Hi @CamSos,

Maybe one additional hint…

If your sketch is non deterministic you need to track the state of the variables/actions in a buffer per step.

Imagine a text editor or a paint app, where a user determines what happens and you want to provide a undo/redo functionality.

Cheers
— mnse

1 Like

Ah interesting, good hint, lots to explore! Thanks @mnse

@mnse @glv

What about when your sketch build on itself (no background clearing each draw loop)? If the sketch is deterministic, is the best way to ‘undo’ to clear the background and quickly while-loop the entire sketch up until the previous frame?

What about if it is **non-**deterministic and builds (no background clearing)? That’s like the sketch I’ve been working on. I get that I need to figure out some way to capture my variable states from each step in buffer memory. But once I decide to actually undo, since this is a sketch that builds on itself, would I have to quickly run through all steps of the buffer memory up until the step previous to what I was on? That seems excessive, but I’m coming up short with the right way to go about it.

Let me stop being abstract and give an MRE:

int x = 0;
int y = 0;
boolean paused = false;

void setup() {
  frameRate(2);
  size (700,700);
  translate (width/2, height/2);
  ellipse(x, y, 50, 50);
}

void draw () {
  x+=random(-20,20);
  y+=random(-20,20);
  translate (width/2, height/2);
  ellipse(x, y, 50, 50);
}

void keyPressed()
{
  if (key == ' ' )  // spacebar toggle pausing of animation
  {
    paused = !paused;
    if (paused)
    {
      noLoop();
    } else
    {
      loop();
    }
  }
}

So to reiterate, the question is how would I, after pausing, be able to tab backwards, undoing frame by frame?

Cheers,
Cam

Here’s another concept for your consideration. Arrows with min/max/init values are used to control direction of animation. Could probably use keyboard arrows if you didn’t want to use separate classes.

color BLUE = color(64, 124, 188);
color BLACK = color(0, 0, 0);
color WHITE = color(255, 255, 255);
color GREEN = color(32, 175, 47);

ForwardArrow _fwdArrw;
BackArrow _backArrw;
ValueField _valueFld;

final int _txtSize = 22;

final int _initValue = 20;
final int _maxValue = 480;
final int _minValue = 0;

int x = 0;
int y = 0;
int value = _initValue;

class ValueField {
  float x, y, w, h;
  String title;
  color fldColor;
  color txtColor;

  // Constructor
  ValueField(int xpos, int ypos, float wt, float ht, String valueStr, color background, color foreground) {
    x = xpos;
    y = ypos;
    w = wt;
    h = ht;
    title = valueStr;
    fldColor = background;
    txtColor = foreground;
  }

  void display(int val) {
    // **** Value Field **** //
    fill(fldColor); // erase old value
    rect(x, y, w, h);
    fill(txtColor); // text color
    textSize(_txtSize);
    textAlign(CENTER);
    text(str(val), x, y, w, h);
    // **** Animation Rectangle **** //
    fill(255);
    rect(val, 100, 20, 20);
  }
}

class ForwardArrow {
  float x, y, w, h;
  color arrwColor;

  // Constructor
  ForwardArrow(int xpos, int ypos, float wt, float ht, color background) {
    x = xpos;
    y = ypos;
    w = wt;
    h = ht;
    arrwColor = background;
  }

  void display() {
    fill(arrwColor); // arrow color
    noStroke();
    triangle(x, y, x, y + h, x + w, y + h/2 );
  }
}

class BackArrow {
  float x, y, w, h;
  color arrwColor;

  // Constructor
  BackArrow(int xpos, int ypos, float wt, float ht, color background) {
    x = xpos;
    y = ypos;
    w = wt;
    h = ht;
    arrwColor = background;
  }

  void display() {
    fill(arrwColor); // arrow color
    noStroke();
    triangle(x, y + h/2, x + w, y, x + w, y + h );
  }
}

void setup() {
  size(500, 250);
  background(BLUE);
  _backArrw = new BackArrow(300, 40, 30, 30, GREEN);
  _fwdArrw = new ForwardArrow(340, 40, 30, 30, GREEN);
  _valueFld = new ValueField(380, 40, 50, 30, str(_initValue), WHITE, BLACK);
}

void draw() {
  background(BLUE);
  _valueFld.display(value);
  _fwdArrw.display();
  _backArrw.display();

  // FwdArrw Long Press
  if ((mouseX >= _fwdArrw.x) && (mouseX <= _fwdArrw.x + _fwdArrw.w) && (mouseY >= _fwdArrw.y) && (mouseY <= _fwdArrw.y + _fwdArrw.h)) {
    if (mousePressed == true) {
      value++;
      if (value > _maxValue) {
        value = _maxValue;
      }
      _valueFld.display(value);
    }
  }
  // BackArrw Long Press
  if ((mouseX >= _backArrw.x) && (mouseX <= _backArrw.x + _backArrw.w) && (mouseY >= _backArrw.y) && (mouseY <= _backArrw.y + _backArrw.h)) {
    if (mousePressed == true) {
      value--;
      if (value < _minValue) {
        value = _minValue;
      }
      _valueFld.display(value);
    }
  }
}

Another example of the same principle using keyboard arrows:

final int _initX = 20;
final int _minX = 0;
final int _maxX = 480;

final int _initY = 100;
final int _minY = 0;
final int _maxY = 180;

int x = _initX;
int y = _initY;
int xSpeed = 3;
int ySpeed = 3;

void setup(){
  size(500,200);
}

void draw(){
  background(0);
  fill(255);
  rect(x, y, 20, 20);
}

void keyPressed() {
if (key == CODED) {
    if (keyCode == RIGHT){
      x += xSpeed;
      if (x > _maxX) {
        x = _maxX;
      }
      println("x = " + x);
    }
    if (keyCode == LEFT){
      x -= xSpeed;
       if (x < _minX) {
        x = _minX;
      }
      println("x = " + x);
    }
    
    if (keyCode == UP){
      y -= ySpeed;
      if (y < _minY) {
        y = _minY;
      }
      println("y = " + y);
    }
    
    if (keyCode == DOWN){
      y += ySpeed;
      if (y > _maxY) {
        y = _maxY;
      }
      println("y = " + y);
    }
  }
}

If you have non-deterministic behavior that occurs every frame, then you’ll have to record the data for those variables every frame. random() is pseudo-deterministic in that you can start it off with a known seed and then generate the same sequence of values each time you re-seed it, but then you would have to, as you said, restart from the beginning and run up to frame n-1 to step backwards. If you have any external inputs such as from user input or network data, then you will have to record what changes occur at which frames in order to either play forward from the beginning or to roll back to previous states. For simple animations you can do it, but in general, it’ll be a mess.

An alternative that’s more broadly functional, is to render your output to an image and simply maintain an array of each of the past frame images. For long animations if you want to roll back arbitrarily, it’ll take gobs of memory, but if you’re willing to limit yourself to just a few seconds of rollback, you can use a circular buffer to save just the last few hundred frame images.