Tuesday 8 May 2018

Introduction to Animation with P5JS

Processing is great for drawing shapes - lines, dots, circles, rectangles - and, when combined with colour, we can build up quite sophisticated works of art using just these tools.

Many artists want to explore movement and animation, not just static objects that don't move once they've been placed.

This blog post will introduce the key ideas we need to understand about how to create motion with Processing.


Children's Flip Book

As a child, you may remember making flip books to create animation. Each page of the flip book had a drawing which was similar to the one on the pages before and after it. Similar - but not exactly the same.

When the pages were flipped so we saw each page for a very short time, the small differences in the drawings gave the appearance of smooth movement.

Here's a video reminding us how flip books are made.


The main idea that makes flip books work is simple - and luckily, that simple idea is the same one that makes digital animation work too.


Digital Flip Book

Before we jump into writing any code, let's think a bit more about that idea of a flip book, and how it might work digitally.

Let's start with three pages of our flip book.


We can see that the first page of the flip book has a red ball near the top. The next page has the red ball moved slightly down. The third page has the red ball further down.

If we flicked the book we'd see the ball appear to move from the top towards the bottom.

In digital animation, we have the same idea of a page, but we call it a frame. That's why the picture above has the three pages labelled as frame 1, frame 2 and frame 3. The word frame is familiar to anyone who did film photography or recorded movies using film. Each frame was a snapshot taken by the film.

What's the equivalent of a frame in Processing? Where do we paint our red ball?

The answer may be surprising - it's the canvas we've been using all along to paint our non-moving shapes. We can think of each of the frames in the picture above as a Processing canvas.


If the frames are just a canvas, we already know how to set the canvas size, set the background colour, and draw shapes on it. In the picture above, the frame's background is set to a light yellow with the familiar background() instruction, and the red ball is drawn with the familiar ellipse() instruction.

This last point is important. Anything and everything we could paint on a canvas - like circles, lines, and even more complex patterns and textures - we can use on our animation frames.


Making Movement

Ok - we now know that an animation frame is just a canvas, and we already know how to draw shapes on a canvas.

How do we take the next step to make movement?

Looking back at that last picture, we remember that we need to draw several frames, each one slightly different to the one before it.

In our example, the position of the red ball changes between each frame. We already know how to draw a red circle at different locations. Here's we would be drawing the red ball with a vertical position that grew larger with each frame - remembering that in Processing the vertical coordinate starts at the top of canvas and grows downwards.

The next picture shows the vertical y-coordinate of the ball getting larger as the ball moves down the canvas.


So far so good. But we still don't know how we're going to flip the frames, like we flip the pages of a children's flip book.

We could draw the red ball, pause for a very short time, and then blank the canvas and draw the ball again at a slightly different position, .. and repeat this for as long as we needed. That would work.

Luckily, Processing helps us with flipping the pages.

Remember how every Processing sketch has a setup() function and a draw() function?


We used setup() to set up our drawing environment - setting things up like the size of the canvas and the background colour. And we kept draw() separate for the instructions that actually did the drawing - for things like circles and rectangles.

What we might not have realised is that setup() is called once, at the beginning .. and draw() is called repeatedly, again and again, forever.

Why didn't we notice that draw() is called again and again? If our code simply draws a circle in the middle of the canvas, for example, and draw() repeatedly draws it again and again, we won't see any difference.

So - if Processing is calling draw() repeatedly, we can take advantage of that and consider each call to draw() as an opportunity to draw a single frame. The next call to draw() will be our opportunity to draw the next frame, and so on...


Processing will take care of pausing and calling draw() repeatedly. All we have to worry about is the more creative job of deciding what to animate.


First Animaion Code

We've learned how Processing enables us to animate objects - by repeatedly calling draw() which allows us to draw each frame of an animation.

Let's try to write some simple code to draw a little red ball, and move it down the canvas, just like the drawings above did.

Have a look at the following simple code:

function setup() {
createCanvas(800, 600);
background('white');
noStroke();
fill('red');
}

function draw() {
ellipse(400, 100, 50);
}

The setup() function is simple - we're creating a canvas of width 800 and height 600, and giving it a background colour of white. We also set outline stroke to be invisible and fill colour to be red.

In the draw() function we have a very simple ellipse() instruction to draw a circle with diameter 50, centred at (400,100). The result shouldn't surprise it at all:


We know that ball is being redrawn again and again at the exact same location, because we now know the draw() function is being called again and again.

Now let's introduce some movement. At the first frame the ball will be drawn at (400, 100). At the next frame we want the ball to be drawn slightly further down the canvas, perhaps at (400, 101). That's a very tiny difference, but it is a difference. You may remember from the handmade flip books that if the difference between pages was big, then the animation wasn't smooth.

How we do draw the ball at (400, 101) at the second frame? In fact, how do we draw the ball at (400,102) at the third frame .. and at (400, 103) at the fourth frame, .. and so on?

If we think about it, we need to keep track of the ball's position, so that we can draw it at the next position during the next frame. If we don't keep track, we can't know what the current position is, and that means we can't decide what the new position of the ball should be.

This means we need a variable to keep track of the ball's vertical position. Have a look at the following code:

// variable to keep track of ball's vertical position
var y = 100;

function setup() {
createCanvas(800, 600);
background('white');
noStroke();
fill('red');
}

function draw() {
// draw ball
ellipse(400, y, 50);

//update vertical position
y = y + 1;
}

At the top of the code, we've created a new variable called y, and given it an initial value of 100. Inside the draw() function we've changed the ellipse() instruction to use y as the vertical position of the ball. When draw() is first called, the ball will be drawn at (400,100) just like before, because the value of y is100.

After drawing that ball, we increase the value of y by 1, using y = y + 1. That leaves y with a value of 101.

The next time draw() is called, the ball is drawn at (400,101) because y was left at 101. This is the second frame of our animation, and the position of the ball has been shifted down the canvas a tiny bit - just as we wanted! After the ball is drawn, y is creased to 102.

The next time draw() is called, the ball is drawn at ... you guessed it, (400,102). And y is increased to 103.

You can see how the ball is being drawn further and further down the canvas for each frame. Let's see the result:


Hmmm. That's not quite what we expected. What happened?

We can see the red circle being drawn further and further down the canvas. But at each frame, the canvas isn't being blanked. Blanking a canvas is just like starting a new drawing on a new blank page of our paper flip book.

Let's blank the canvas at the beginning of the draw() function, to start a fresh canvas for each frame.

// variable to keep track of ball's vertical position
var y = 100;

function setup() {
createCanvas(800, 600);
noStroke();
fill('red');
}

function draw() {
// blank canvas for each frame
background('white');

// draw ball
ellipse(400, y, 50);

//update vertical position
y = y + 1;

// border
stroke(0); noFill(); rect(0,0, width-1, height-1);noStroke(); fill('red');
}

Let's see the results now:


We now have animation!

Although this is a simple example, the simplicity makes clear the key points:

  • draw() is called repeatedly by Processing - this allows us to draw frames of an animation
  • by changing the image slightly between each frame, we achieve the effect of a smooth animation
  • variables can be very helpful in keeping track of where were are in the animation
  • we mustn't forget to blank the canvas before drawing a frame


If we were to improve that simple example, we'd probably fix the fact that the variable y will keep getting bigger and bigger. The ball will move off the bottom of the canvas. Even after that, y will keep getting bigger .. until the code crashes. We could easily write code to check that y hadn't reached an upper limit before increasing it every frame.

//update vertical position
if (y <500) {
y = y + 1;
}

This time the ball moves down the canvas from y=100 until it reaches y=500 and then stays there because y is no longer increased every frame:


The p5js sketch for the code we've developed so far is online at:




Controlling Speed

How do we control the speed of objects as they move? That's a very good question.

In the simple example above, the ball moved slowly but surely down the canvas. With every frame, the y-coordinate increased by 1. So after 100 frames, the y-coordinate would have increased by 100.

We can start to see how we might alter the speed of the ball. If after 100 frames, we want the y-coordinate to have increased by 500, we would have had to increase the y-coordinate by 5 every frame.

That makes intuitive sense. A faster ball moves further than a slow ball in any given period of time.

Let's create a sketch with two balls, one whose y-coordinate increases by 1 every frame just like before, and the other whose y-coordinate increases by 5 every frame.

// variable to keep track of ball's vertical position
var y1 = 100;
var y2 = 100;

function setup() {
createCanvas(800, 600);
noStroke();
fill('red');
}

function draw() {
// blank canvas for each frame
background('white');

// draw ball
ellipse(300, y1, 50);
ellipse(500, y2, 50);

//update vertical position
if (y1 <500) {
y1 = y1 + 1;
}
if (y2 <500) {
y2 = y2 + 5;
}
}

This time we create two variables, y1 and y2, one for each ball. You can see that y1 is increased by 1 every frame, and y2 is increased by 5 every frame.


We can see clearly that second ball moves faster.

The p5js sketch illustrating how we can control the speed of animated objects is online at:



What we've covered so far are the key ideas behind creating digital animation. Next we'll just have a bit of fun!


More Fun

Let's use a mathematical function to control the position of a ball, based on a parameter t which we start at 0 and increase by 1 every frame:

  • x = 200 * sin(2 * t  / 100)
  • y = 200 * cos(3 * t / 100)


Here's some simple code that does this:

// variable to keep track of "time-like" parameter
var t = 0;

function setup() {
createCanvas(800, 600);
noStroke();
}

function draw() {
// blank canvas for each frame
background('white');

// calculate coordinates of ball
var x = 200 * cos(2 * t/100);
var y = 200 * sin(3 * t/100);

// draw ball
fill(255, 0, 0);
ellipse(400+x, 300-y, 20);

// update parameter
t = t + 1;
}

Notice that the multipliers inside the cos() and sin() are different. If they were the same, the ball would trace out a circle of radius 200 - try it. The following shows the path traced out as a result of these different multipliers:


That's an interesting looping movement. Let's make it more interesting by having several balls, each one trailing the next, and coloured with a lighter shade of red to emphasise the sequence.


How cool is that!

A mesmerising animation made of simple circles, following an interesting path determined by simple mathematical functions.

We won't dig into how this works in detail as this introductory blog post, but the idea is the same as the single ball. All we've done is used a loop to create 10 balls, and the parameter t is adjusted slightly based on the ball - in effect moving it along the path followed by all the balls. The translucency of the red fill colour is also linked to the ball number.

The p5js code for this sketch is online at:



1 comment:

  1. Make Your Own Algorithmic Art: Introduction To Animation With P5Js >>>>> Download Now

    >>>>> Download Full

    Make Your Own Algorithmic Art: Introduction To Animation With P5Js >>>>> Download LINK

    >>>>> Download Now

    Make Your Own Algorithmic Art: Introduction To Animation With P5Js >>>>> Download Full

    >>>>> Download LINK wA

    ReplyDelete