bleuje

Loops, creative coding.

Replacement technique with 2D grid of objects (Processing)

2021-09-04


The replacement technique was already explained and illustrated in the previous tutorial. This one shows both how to use it with objects that have random parameters, and also with a 2D grid structure: something I have used a lot.

I chose this gif as example for this tutorial:

digits version


In the gif the positions of the tiles on one axis (x-axis) never change, and it moves along another axis (y-axis). We’re going to use a 2D grid of tiles of size 70 x 4. The idea is that every object (tile) will take the place 4 tile later along y-axis at the end of the loop.

First let’s make the grid of tiles and show them.

Here is some code to draw that:

float t;
float L;
int numberOfFrames = 100;
int ny = 4;
int nx = 70;
class Tile
{
int xIndex;
int yIndex;
Tile(int i,int j)
{
xIndex = i;
yIndex = j;
}
void show()
{
push();
translate(L*(xIndex-nx/2),L*yIndex,-L/2);
fill(0);
stroke(255);
strokeWeight(1.5);
box(0.95*L,0.95*L,0.1*L); // a bit of space between tiles, and small z length
pop();
}
};
Tile [][] array = new Tile[nx][ny];
void setup(){
size(600,600,P3D);
randomSeed(12345);
L = 1.0*width/15;
for(int i=0;i<nx;i++)
for(int j=0;j<ny;j++) array[i][j] = new Tile(i,j);
}
void draw(){
//t = 1.0*mouseX/width; // to control time with mouse
t = 1.0*(frameCount-1)/numberOfFrames;
background(0);
push();
translate(width/2,height/2);
rotateX(0.85);
rotateZ(0.4);
for(int i=0;i<nx;i++)
for(int j=0;j<ny;j++) array[i][j].show();
pop();
if(frameCount<=numberOfFrames)
{
saveFrame("fr###.gif");
}
if(frameCount==numberOfFrames)
{
stop();
}
}

There’s already the setup to render the animation although the tiles don’t move yet. I also already rotated the view as it is at the end. I made gifs at 50 fps (delay=2). L defines the size of the tiles.

So far it looks like this:

tiles still

Now let’s use replacement technique with our grid taking the next grid position when time is increased by 1.

In Tile class:

void show(float p)
{
push();
translate(L*(xIndex-nx/2),L*yIndex,-L/2);
translate(0,-p*L*ny);
fill(0);
stroke(255);
strokeWeight(1.5);
box(0.95*L,0.95*L,0.1*L);
pop();
}
int K = 10;
void show()
{
for(int i=-K;i<=K;i++)
{
float p = i+t;
show(p);
}
}

We get something like this:

moving tiles

It does not look amazing but we’ve got a nice structure behind and a large part of the work is done.

Let’s introduce a delay member in the Tile class that will determine when a tile starts to flip:

class Tile
{
int xIndex;
int yIndex;
float delay;
Tile(int i,int j)
{
xIndex = i;
yIndex = j;
delay = 0;
}
void show(float p)
{
push();
translate(L*(xIndex-nx/2),L*yIndex,-L/2);
translate(0,-p*L*ny);
float changer = constrain(map(p-delay,0,2.5,0,1),0,1);
float xRot = PI+PI*changer;
// starts being rotated by PI (starts flipped to hide digits)
// and ends being rotated by TWO_PI
fill(0);
stroke(255);
strokeWeight(1.5);
rotateX(xRot);
box(0.95*L,0.95*L,0.1*L);
pop();
}
int K = 10;
void show()
{
for(int i=-K;i<=K;i++)
{
float p = i+t;
show(p);
}
}
};

The variable changer is 0 before p=delay, then its value increases to 1 until p=delay+2.5, then its value stays at 1. So this is used to rotate the tiles around X axis. The result looks like this:

introduced delay

Let’s try to have a delay that changes according to the y index so that the 4x70 grid structure is less apparent.

Tile(int i,int j)
{
xIndex = i;
yIndex = j;
delay = 1.0*yIndex/ny;
}

Maybe I can make this formula logical by saying that when y and yIndex increase, we want the tile to move later, so with more delay.

The fix looks like this:

delay with yIndex

Now let’s add some gaussian noise to the delay of each tile to get something that looks more complex (and by the way the delay is a bit decreased with a constant).

Tile(int i,int j)
{
xIndex = i;
yIndex = j;
delay = 0.5*randomGaussian() + 1.0*yIndex/ny - 0.3;
}

gaussian delay

To make this a bit more exciting we can use easing functions, you can find some on this page.

float easeOutElastic(float x)
{
float c4 = (2*PI)/3;
if(x<=0) return 0;
if(x>=1) return 1;
return pow(2, -10 * x) * sin((x * 10 - 0.75) * c4) + 1;
}
// in-out easing of strength g
float ease(float p, float g) {
if (p < 0.5)
return 0.5 * pow(2*p, g);
else
return 1 - 0.5 * pow(2*(1 - p), g);
}

Easing functions basically distort values between 0 and 1 and can be useful to get nice transitions. Here it’s directly applied to the changer variable.

void show(float p)
{
push();
translate(L*(xIndex-nx/2),L*yIndex,-L/2);
translate(0,-p*L*ny);
float changer = constrain(map(p-delay,0,2.5,0,1),0,1);
changer = ease(changer,1.4); // classic in-out easing with stength 1.4
float elasticChanger = easeOutElastic(changer); // then elastic easing
float xRot = PI+PI*elasticChanger;
fill(0);
stroke(255);
strokeWeight(1.5);
rotateX(xRot);
box(0.95*L,0.95*L,0.1*L);
pop();
}

use of easing

Then I’ve done something with colors:

void show(float p)
{
push();
translate(L*(xIndex-nx/2),L*yIndex,-L/2);
translate(0,-p*L*ny);
float changer = constrain(map(p-delay,0,2.5,0,1),0,1);
changer = ease(changer,1.4); // classic in-out easing with stength 1.4
float elasticChanger = easeOutElastic(changer); // then elastic easing
float xRot = PI+PI*elasticChanger;
fill(0);
stroke(255*(1-changer)); // <--- change here
strokeWeight(1.5);
rotateX(xRot);
box(0.95*L,0.95*L,0.1*L);
pop();
}

(note: changer variable, and not elasticChanger, is used to change colors. That’s why I use two different variables)

Here is the result:

color changes

Because we can do more random stuff for each object than the gaussian noise, I thought about using different digits. You could also give a random color to each tile, random different easing, random size/shape, etc…

The Tile class with the digits (very few changes):

class Tile
{
int xIndex;
int yIndex;
float delay;
int digit = floor(random(10));
Tile(int i,int j)
{
xIndex = i;
yIndex = j;
delay = 0.5*randomGaussian() + 1.0*yIndex/ny - 0.3;
}
void show(float p)
{
push();
translate(L*(xIndex-nx/2),L*yIndex,-L/2);
translate(0,-p*L*ny);
float changer = constrain(map(p-delay,0,2.5,0,1),0,1);
changer = ease(changer,1.4); // classic in-out easing with stength 1.4
float elasticChanger = easeOutElastic(changer); // then elastic easing
float xRot = PI+PI*elasticChanger;
fill(0);
stroke(255*(1-changer));
strokeWeight(1.5);
rotateX(xRot);
box(0.95*L,0.95*L,0.1*L);
translate(0,0,0.1*L+0.5); // small z translate before drawing digit
stroke(255);
fill(255);
textSize(25);
text(digit,-L/6,L/6);
pop();
}
int K = 10;
void show()
{
for(int i=-K;i<=K;i++)
{
float p = i+t;
show(p);
}
}
};

digits version

One thing I sometimes do is to have something in each object that changes with 1D noise and the “p” variable in this code (and with a different seed for each object).

Important: The “changer” thing that is used here to trigger something is not necessary at all for 2D grid replacement loops: objects are more generally changing with the parameter p.

The grid is a quite abstract structure, it doesn’t have to look like as much as a grid as here, and I use the same technique to make some tunnel gifs.

Thanks for reading and I hope you found this interesting or helpful.

Here is the entire code for the above finished gif of this tutorial:

float t;
float L;
int numberOfFrames = 100;
int ny = 4;
int nx = 70;
float easeOutElastic(float x)
{
float c4 = (2*PI)/3;
if(x<=0) return 0;
if(x>=1) return 1;
return pow(2, -10 * x) * sin((x * 10 - 0.75) * c4) + 1;
}
float ease(float p, float g) {
if (p < 0.5)
return 0.5 * pow(2*p, g);
else
return 1 - 0.5 * pow(2*(1 - p), g);
}
class Tile
{
int xIndex;
int yIndex;
float delay;
int digit = floor(random(10));
Tile(int i,int j)
{
xIndex = i;
yIndex = j;
delay = 0.5*randomGaussian() + 1.0*yIndex/ny - 0.3;
}
void show(float p)
{
push();
translate(L*(xIndex-nx/2),L*yIndex,-L/2);
translate(0,-p*L*ny);
float changer = constrain(map(p-delay,0,2.5,0,1),0,1);
changer = ease(changer,1.4); // classic in-out easing with stength 1.4
float elasticChanger = easeOutElastic(changer); // then elastic easing
float xRot = PI+PI*elasticChanger;
fill(0);
stroke(255*(1-changer));
strokeWeight(1.5);
rotateX(xRot);
box(0.95*L,0.95*L,0.1*L);
translate(0,0,0.1*L+0.5); // small z translate before drawing digit
stroke(255);
fill(255);
textSize(25);
text(digit,-L/6,L/6);
pop();
}
int K = 10;
void show()
{
for(int i=-K;i<=K;i++)
{
float p = i+t;
show(p);
}
}
};
Tile [][] array = new Tile[nx][ny];
void setup(){
size(600,600,P3D);
randomSeed(12345);
L = 1.0*width/15;
for(int i=0;i<nx;i++)
for(int j=0;j<ny;j++) array[i][j] = new Tile(i,j);
}
void draw(){
//t = 1.0*mouseX/width; // to control time with mouse
t = 1.0*(frameCount-1)/numberOfFrames;
background(0);
push();
translate(width/2,height/2);
rotateX(0.85);
rotateZ(0.4);
for(int i=0;i<nx;i++)
for(int j=0;j<ny;j++) array[i][j].show();
pop();
if(frameCount<=numberOfFrames)
{
saveFrame("fr###.gif");
}
if(frameCount==numberOfFrames)
{
stop();
}
}

Here’s a pretty similar gif I made:

constructing column gif

Also this one:

rolling grid


Tutorials list Next tutorial