Time synched animation

Hello everyone, here I’m trying to divide the screen with a grid, then fill each tile in a pre-determined amount of time: (this is the most simplified - even silly - example)

float timeToConsume = 7, timeToCancelBin;
int timer, counter;
ArrayList<GridPoint> points = new ArrayList<GridPoint>();
float tileWidth = 20, tileHeight = 20;

void setup() {
  size(1280, 720);
  noStroke();
  // creo una griglia in base a width e height e alla larghezza dei riquadri tileWidth e tileHeight
  // li aggiungo all'arraylist points in modo da poterli richiamare in seguito
  for (float x = 0; x < width; x += tileWidth) {
    for (float y = 0; y < height; y += tileHeight) {
      points.add(new GridPoint(x, y, tileWidth, tileHeight));
    }
  }
  
  timeToCancelBin = (timeToConsume  * 1000) / points.size();
  println(points.size() + " " + timeToConsume + " " + timeToCancelBin);
}

void draw() {
  background(255);

  // controllo se la variabile black sia vera o falsa, nel caso colora il rettangolo di nero
  for (int i = 0; i < points.size(); i++) {
    if (points.get(i).black) {
      fill(0);
    } else {
      noFill();
    }
    points.get(i).show();
  }

  if (millis() - timer  >= timeToCancelBin) {
    ruin();
    timer = millis();
  }
}

void ruin() {
  if (counter < points.size()) {
    GridPoint p = points.get(counter);
    p.black = true;
  }

  counter++;

  if (counter == (points.size())) {
    println(millis());
  }
}

class GridPoint {
  float x, y, tileWidth, tileHeight;
  boolean black = false;

  GridPoint(float x, float y, float tileWidth, float tileHeight) {
    this.x = x;
    this.y = y;
    this.tileWidth = tileWidth;
    this.tileHeight = tileHeight;
  }

  void show() {
    rect(x, y, tileWidth, tileHeight);
  }
}

My problem is that it doesn’t fill all the tiles in the given amount of time, and if you modify the width and the height (so the total number) of tiles it vary also the time…
What am I doing wrong here? Thank you

1 Like

The problem is that processing makes only 60 frames per second.

Hence, one frame is approx. 20 millis.

I made a print of the millis of every frame:

2304
 7.0 
timeToCancelBin: 3.0381944

140
162
177
194
211
228
245
261
278

Your timeToCancelBin is 3.03 millis, so much less than needed for one frame.

So when your condition if (millis() - timer >= timeToCancelBin) { is met, much more time than timeToCancelBin has passed. But counter gets only increased by one. It should be increased by the amount of timeToCancelBin that has passed (like 15 times, with a for loop). That is one solution for example.

My version

Another approach:

Instead, I made a version were each rect knows the time when to turn black.

Much more than 1 rect gets black every frame.


float timeToConsume = 7.0, timeToCancelBin;
// int timer, counter;
ArrayList<GridPoint> points = new ArrayList<GridPoint>();
float tileWidth = 20, tileHeight = 20;

void setup() {
  size(1280, 720);



  noStroke();
  // creo una griglia in base a width e height e alla larghezza dei riquadri tileWidth e tileHeight
  // li aggiungo all'arraylist points in modo da poterli richiamare in seguito
  int k=0; 
  for (float x = 0; x < width; x += tileWidth) {
    for (float y = 0; y < height; y += tileHeight) {
      points.add(new GridPoint(x, y, tileWidth, tileHeight, 0));
      k++;
    }
  }

  timeToCancelBin = (timeToConsume  * 1000.0f) / points.size();
  println(points.size() + "\n " + timeToConsume + " \ntimeToCancelBin: " + timeToCancelBin);

  // tell each rect when to turn black 
  for (int i = 0; i < points.size(); i++) {
    points.get(i).time=i*timeToCancelBin;
  }
}

void draw() {
  background(255);

  for (int i = 0; i < points.size(); i++) {
    points.get(i).show();
  }

  // finished? 
  if (points.get(points.size()-1).black) {
    println("finished  "+millis());
    noLoop();
  }
}

// ======================================================================================

class GridPoint {

  float x, y, 
    tileWidth, tileHeight;
  boolean black = false;

  float time; 

  GridPoint(float x, float y, 
    float tileWidth, float tileHeight, 
    float time) {

    this.x = x;
    this.y = y;
    this.tileWidth = tileWidth;
    this.tileHeight = tileHeight;

    this.time = time;
  }//constr 

  void show() {
    if (millis() >= time)
      black=true; 

    // controllo se la variabile black sia vera o falsa, nel caso colora il rettangolo di nero
    if (black) {
      fill(0);
    } else {
      noFill();
    }

    rect(x, y, 
      tileWidth, tileHeight);
  }
  //
}//class
//

In another version, we can see how much rects turn black every frame (one star * is one rect in the list, one line is a new frame):

******
-----------------
*****
-----------------
******
-----------------
*****
-----------------
******
-----------------
*****
-----------------
*****

Chrisir

2 Likes

This different point of filling the rects is very clever I must admit! I’m not sure if the result of having block of rects filling up is something as acceptable in terms of “beauty” instead as having them go black one by one, but the approach is really interesting! :grin:

Talking about my original method, your thought is that in theory I just need my timeToCancelBin to be greater than 20ms, in order to match the frameRate and it should works. But aren’t millis() and frameRate independent? I mean let assume that timeToCancelBin = 10 ms, then if one frame last 20 ms then every frame I’ll get 2 filled rect. If timeToCancelBin = 5 ms then in the next frame I should see 4 rect black filled.

What I think is that the computer is too fast on keeping track of millis() that the fraction of time that needs to count 3 ms is so little that it miss the if statement, and all this very little milliseconds delays sums up and in a very short amount of time they become seconds.

Another consideration:
with this approach the animation starts in the exact moment millis() starts.
Is there a way to control when the animation should start / pause / reverse?
The following code doesn’t work because of the behaviour of millis() (and the for (int i = points.size()-1; i > 0; i--) starts straight from the end and not from the point where i press the mouse) but it should give an idea:

float timeToConsume = 7.0, timeToCancelBin;
ArrayList<GridPoint> points = new ArrayList<GridPoint>();
float tileWidth = 20, tileHeight = 20;
int counter;

void setup() {
  size(1280, 720);
  noStroke();

  for (float x = 0; x < width; x += tileWidth) {
    for (float y = 0; y < height; y += tileHeight) {
      points.add(new GridPoint(x, y, tileWidth, tileHeight, 0));
    }
  }

  timeToCancelBin = (timeToConsume  * 1000.0f) / points.size();

  for (int i = 0; i < points.size(); i++) {
    points.get(i).time = i * timeToCancelBin;
  }
}

void draw() {
  background(255);

  if (change) {
    for (int i = 0; i < points.size(); i++) {
      points.get(i).black();
    }
  } else {
    for (int i = points.size()-1; i > 0; i--) {
      points.get(i).notBlack();
    }
  }
}

boolean change = true;

void mousePressed() {
  change = !change;
}

class GridPoint {
  float x, y, tileWidth, tileHeight;
  boolean black = false;
  float time;

  GridPoint(float x, float y, float tileWidth, float tileHeight, float time) {
    this.x = x;
    this.y = y;
    this.tileWidth = tileWidth;
    this.tileHeight = tileHeight;
    this.time = time;
  }

  void black() {
    if (millis() >= time)
      black = true; 
    if (black) {
      fill(0);
    } else {
      noFill();
    }
    rect(x, y, 
      tileWidth, tileHeight);
  }

  void notBlack() {
    if (millis() >= time)
      black=false; 
    if (black) {
      fill(0);
    } else {
      noFill();
    }
    rect(x, y, 
      tileWidth, tileHeight);
  }
}

I don’t have time now

But to your very first post again

I tried to explain:

after every frame approx. 20 millis have passed. Condition

  if (millis() - timer  >= timeToCancelBin) {

is ALWAYS true.

BUT counter is only increased by one.

Test it with println()

Chrisir

1 Like

something as acceptable in terms of “beauty” instead as having them go black one by one

Err… there are too many rects than you can fill within 7 seconds. The frameRate is too low.

That’s wrong because counter gets only increased by one in every frame.

As I said you could do a for loop to increase counter by 2 or even 4.

That’s wrong because the if checks for >= so it cannot miss it.

It misses them because of the frame rate.

Well if i run this:

int timer;
void draw() {
  if (millis() - timer >= 20){
    println(millis() - timer);
    timer = millis();
  }
}

I get this values in the post window:

181
33
33
34
33
34
33
33
34
33
33
33
33
33
34
34
32
34
33
33
34
33
33
34
33
34
33
33
34
34
33
33
34
33
32
35
32
34
41

That means that there is something that skip through the 20 ms limits. And this is with no other threads or calculations. If I try the real sketch I get reasonably values only with limits around 150 ms

Try please

println(millis() );

This is the result I get running this code:

int timer;
void draw() {
  if (millis() - timer >= 20){
    println(millis());
    timer = millis();
  }
}
192
224
258
290
324
358
390
458
490
524
557
590
624
656
691
724
758
790
823
857
891
923
957
992
1023
1058
1090
1123
1157

Thank you for helping :slight_smile:

What does this prove? Where have I been wrong?

To me, this seems to be the case where too fast calcutions are asked… counting milliseconds may be a demanding task and as you can see the most basic sketch (at least on my pc) can count millis() with a ~30ms jump.
This makes me think that if I set timeToCancelBin too low, ex 15 the pc isn’t fast enough to stop at 15, because it immediatly jump at 30. A 15ms offset, in a second becomes ~400ms and in 10s there will be a 4s offset

maybe we just mean the same thing.

Your code if (millis() - timer >= 20) asks to print something every 20 mills.

Your data

shows that processing doesn’t reach this speed because it’s too slow. ( the output 258 - 290 - 324 shows the same).

In your sketch with the rects the sketch also was too slow and therefore to slowly doing the counter++ command. To tackle this, you could measure how often the timeToCancelBin has been missed since the last call. You need to increase counter by this number.

So, no, the the computer isn’t too fast as you wrote previously, if it were, the measurement of millis() would stop him.

It’s too slow.

Hello,

Some code to show what happens each frame (framecount) at the 20 ms condition for 60 fps and 30 fps:

int timer;
int timeLast;

void setup()
  {
  //frameRate(60);  //1000/60 = 16 (integer) 16.666666 float
  //println(1000/60);
  //println(1000.0/60);
  
  frameRate(30);  //1000/30 = 16 (integer)  float 33.333332
  println(1000/30);
  println(1000.0/30);
  
  delay(2000);
  }

void draw() 
  { 
  //Prints only when condition met; once every two frames  
  if (millis() - timer >= 20)
    {
    println();
    println(frameCount, millis());
    timer = millis();
    }
  //Prints time between frames; once per frame
  println(frameCount, millis()- timeLast);  
  timeLast = millis();
  }

30 fps (left) and 60 fps (right):

:slight_smile:

1 Like

Thanks glv, this can be useful to know if the animation is delaying each frame!
I tried to lowering the time condition to just 3 ms and this is what I got:

int timer;
int timeLast;

void setup(){
  frameRate(60);  //1000/60 = 16 (integer) 16.666666 float
  println(1000/60);
  println(1000.0/60);

  //frameRate(30);  //1000/30 = 16 (integer)  float 33.333332
  //println(1000/30);
  //println(1000.0/30);

  delay(2000);
}

void draw() { 
  //Prints only when condition met; once every two frames  
  if (millis() - timer >= 3)
  {
    println();
    println("TIMER "+ frameCount, millis() - timer);
    timer = millis();
  }
  //Prints time between frames; once per frame
  
  println("FRAME " + frameCount, millis() - timeLast);  
  timeLast = millis();
}
TIMER 2 18
FRAME 2 18

TIMER 3 17
FRAME 3 17

TIMER 4 16
FRAME 4 16

TIMER 5 17
FRAME 5 17

TIMER 6 17
FRAME 6 17

TIMER 7 17
FRAME 7 17

Does this means that you can’t have a condition less than 16 ms at 60 fps and less than 33 ms at 30 fps?

1 Like

Yes, 1000 milliseconds divided by 60 frames = 16.667 milliseconds per frame.

If you are only going to perform one operation per frame, then you only get 60 operations a second. If you insist on changing only one unit per frame at 60fps then you cannot set a time goal – you can only find out how long it will take given the constraints you chose. If there are 600 then it will take ~10 seconds or more – on average and when not under load, frame rate varies and is a minimum, not a maximum.

You can set any conditions you want, but you can’t expect them to perform. For example, I can say “I want people to enter the subway through this single turnstile one at a time, one per second. Also, I want to let 100,000 people in an hour.” Well, that would take one person moving through the turnstile every 0.036 seconds. Turnstiles and people can’t move that fast, so that isn’t going to happen. You set the rules, but your goal doesn’t matter – what will actually happen is that you well let in about 3600 people. You can either let in fewer people per hour, OR you can add more turnstiles. Either one, your choice. If you want to install rocket turnstiles that go 300 miles per hour (frameRate(300)) you may need special hardware!

You can also make the approach adaptive by looking at your goal, estimating how many frame you have until your goal, and addressing that many units (1, 10, whatever). This is what grocery stores try to do when they open extra checkout lanes. When the lines start building up, they change their customer throughput.

2 Likes