Squash & Stretch simple (pre-objects)

Some students have been struggling to understand the principle of squash & stretch. The first concept to retain is animation. A squash & stretch needs an initial deformation (squash). Typically crush. Followed by an animation that stretches (beyond the original size). And to finish the loop, you have to re-crush to the original size. This animation must take place at least once.

Now see in action:

So… how does this translate into code? Two ways. The first, more mechanical is to use a set of ifs.

Let's consider a circle (as if it were a button or a ball)

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

void draw() {
  background(200);
  
  fill(40);
  radar (300, 200, 50);
}

void radar (int x, int y, int w) {
  
  if(dist(mouseX, mouseY, x, y)< w/2 ) {
    fill (200, 0, 0);
    
    if (mousePressed) {
      w = w/2;
    }    
    
  }
  ellipse(x, y, w, w);
}

This ball basically responds to the mouse's entry inside by changing the color. And when you click inside, it already seems to make the start of the animation. From here we need to create the conditions for when we drop the mouse it expands beyond the original limit and returns to normal.

Attention that we are not using objects yet. But, the position and size values of the ball drawing are being stored in vectors (arrays). So our program will actually look more like this:

in[][]t balls;int n;

void setup(){
 size (500, 500);
 noStroke();
 n = 1;
 balls = new int[n][3];
 
 for (int i = 0; i< n; i++) {
    balls[i][0] = int (random(width) );
    balls[i][1] = int (random(height) );
    balls[i][2] = int (random(20, 50) );
  }
}

void draw() {
  background(200);
  
  fill(40);
  
  for (int i = 0; i< n; i++) {
    radar (ball[i][0]s, ball[i][1]s, ball[i][2]s);
  }
  
}

void radar (int x, int y, int w) {
  
  if(dist(mouseX, mouseY, x, y)< w/2 ) {
    fill (200, 0, 0);
    
    if (mousePressed) {
      w = w/2;
    }    
    
  }
  ellipse(x, y, w, w);
}

If we can do with what Processing "remembers" things like the positions and sizes of each ball, then we just need him to also know if he's: 1) animate; 2) Squash for stretch; 3) From stretch to original position. For this, let's add a few more values to the vector, okay? We can still use ints. If the animation = 0 is stopped and vice versa. This allows you to control the start and end of the animation. Then one more for if you are st[de squash para]retch and one more for if you are in t[de stretch para]he normal position.

Here's what we're going to do. If the mouse is in and pressed, the animation is ready. As soon as we release, it starts a growth cycle (for example by adding an increment to the size). As soon as the ball grows too inverts until it returns to normal.

The example is raw, but already demonstrates a slight animation.

in[][]t balls;int n;

void setup() {
  size (500, 500);
  noStroke();
  n = 1;
  balls = new int[n][6];

  for (int i = 0; i< n; i++) {
    balls[i][0] = int (random(width) );
    balls[i][1] = int (random(height) );
    balls[i][2] = int (random(20, 50) );

    squash & stretch properties
    ball[i][3]s = 0; animation
    ball[i][4]s = 0; stretching
    ball[i][5]s = 0; squashing
  }
}

void draw() {
  background(200);

  fill(40);

  for (int i = 0; i< n; i++) {
    radar(i, 
      balls[i][0], balls[i][1], balls[i][2], 
      balls[i][3], balls[i][4], balls[i][5]);
  }
}

void radar (int id, int x, int y, int w, int an, int sq, int st) {
  
  check if the mouse is inside the ball
  if (dist(mouseX, mouseY, x, y)< w/2 ) {
    fill (200, 0, 0);

    if it "clicks" inside the ball, squash it
    if (mousePressed) {
      w = w/2;
      ball[id][3]s = 1; activate the "I am going to animate" property
    }
    
    if the mouse is still inside, but is not pressed
    check if it was pressed by also verifying if it started the animation
    if (!mousePressed & an == 1) {
      println("animation");
      
      if it started animating
      if (sq == 0) {
        ball[id][2]s += 2;  grow the size (stretch)
      }
      
      if the size reaches a certain limit (here is sitll hardcoded...)
      if (balls[id][2] >= 75) {  
        ball[id][4]s = 1;      stop stretching. start squashing 
      }
      
      if it is squashing
      if (sq == 1) {
        ball[id][2]s -= 2;    shrink the size (squash)
      }
      
      if the size reaches a certain limit (here is sitll hardcoded we need an original value backup...)
      if (ball[id][2]s<= 10) {
        ball[id][5]s = 1;      squashing stop
      }
      
      if it has stretched, and squashed... restore normality (add backup values in the array to restore correctly)
      if (st == 1) {
        balls[id][3] = 0;
        balls = 0;
        balls = 0;
      }
    }
  }
  ellipse(x, y, w, w);
}

This is one way to do it. With ifs. It's not very efficient. It's "raw" and "hard." And the animation is not organic.

Last year I demonstrated this with an animation with a rotation cycle.

https://www.instagram.com/p/BxyFaEPhEGw/?utm_source=ig_web_button_share_sheet

Unfortunately, I can't find the original code, so here's a quick rebuild.

The idea is to have a form that "reacts to click". When the user clicks, the object uses an "inner rotation" to affect the width value and shrink, stretch, and shrink back. This is achieved through the properties of the Sine and Cosine of trigonometry, as I explained in the last article. If they rotate between 45º and 315º it shrinks, stretches and shrinks.

int a;

void setup() {
  size (500, 500);
  a = 0;
  rectMode(CORNERS);
}

void draw() {
  background(200);
  
  noFill();
  stroke(40);
  ellipse (width/2, height/2, 300, 300);
  
  fill(40);
  noStroke();
  int tx = width/2 + int (cos(radians(a) * 300/2);
  int ty = height/2 + int (sin(radians(a) * 300/2);
  
  ellipse (tx, ty, 30, 30);
  
  fill(255);
  rect (tx, height/2-25, width/2, height/2+25); 
  a++;
}

Just now adapt this small piece of code (not letting the objects shrink too much) to our previous code.

in[][]t balls;int n;

void setup() {
  size (500, 500);
  noStroke();
  n = 10;
  balls = new int[n][5];

  for (int i = 0; i< n; i++) {
    balls[i][0] = int (random(100, width-100) );
    balls[i][1] = int (random(100, height-100) );
    balls[i][2] = int (random(30, 70) );

    squash & stretch properties
    ball[i][3]s = 0; animation
    ball[i][4]s = 0; rotation
    
  }
}

void draw() {
  background(200);

  for (int i = 0; i< n; i++) {
    fill(40);
    radar(i, 
      balls[i][0], balls[i][1], balls[i][2], 
      balls[i][3], balls[i][4]);
  }
}

void radar (int id, int x, int y, int w, int an, int rot) {
  
  calculate a positive/negative width increment to the ball
  float inc = W*COS (Radians(rot) );
  
  check if the mouse is inside the ball
  if (dist(mouseX, mouseY, x, y)< w/2 ) {
    fill (200, 0, 0);

    if it "clicks" inside the ball, squash it by rotating the increment to a specific angle
    if (mousePressed) {
      balls[id][4] = 270;
      ball[id][3]s = 1; activate the "I am going to animate" property
    }
    
    if the mouse is still inside, but is not pressed
    check if it was pressed by also verifying if it started the animation
    if (!mousePressed & an == 1) {
      println("animation"+inc);
      
      ++ba[id][4]lls;   increment the rotation
      
      if (ball[id][4]s > 540) { // if it has completed one or more cycles, restore the values
        balls[id][3] = 0;
        balls = 0;
      }
      
    }
  }
  ellipse (x, y, w + inc, w + inc);
}

in[][]t balls;int n;

void setup() {
  size (500, 500);
  noStroke();
  n = 1;
  balls = new int[n][5];

  for (int i = 0; i< n; i++) {
    balls[i][0] = int (random(100, width-100) );
    balls[i][1] = int (random(100, height-100) );
    balls[i][2] = int (random(50, 100) );

    squash & stretch properties
    ball[i][3]s = 0; animation
    ball[i][4]s = 0; rotation
    
  }
}

void draw() {
  background(200);

  fill(40);

  for (int i = 0; i< n; i++) {
    radar(i, 
      balls[i][0], balls[i][1], balls[i][2], 
      balls[i][3], balls[i][4]);
  }
}

void radar (int id, int x, int y, int w, int an, int rot) {
  
  calculate a positive/negative width increment to the ball
  float inc = W*COS (Radians(rot) );
  
  check if the mouse is inside the ball
  if (dist(mouseX, mouseY, x, y)< w/2 ) {
    fill (200, 0, 0);

    if it "clicks" inside the ball, squash it by rotating the increment to a specific angle
    if (mousePressed) {
      balls[id][4] = 270;
      ball[id][3]s = 1; activate the "I am going to animate" property
    }
    
    if the mouse is still inside, but is not pressed
    check if it was pressed by also verifying if it started the animation
    if (!mousePressed & an == 1) {
      println("animation"+inc);
      
      ++ba[id][4]lls;   increment the rotation
      
      if (ball[id][4]s > 540) { // if it has completed one or more cycles, restore the values
        balls[id][3] = 0;
        balls = 0;
      }
      
    }
  }
  ellipse (x, y, w + inc, w + inc);
}

The code wasn't perfect. Far from it. But I think it's good to demonstrate the principle. There are still some mechanisms to maintain size and affect only the increment correctly, but the time is advanced and tomorrow I need to be "fresh" for a synchronous session first thing in the morning. And, not least, tomorrow we will approach this problem in a similar way, but in a more organized and efficient way creating and manipulating (finally!) objects. This kind of object-oriented approach will allow us to control this type of individual properties much more easily.

Leave a comment

Your email address will not be published.