CSCI 150 - Lab 11 - Graphics and Animation

CSCI 150 - Lab 11
Graphics and Animation


Materials

Overview

We have created graphics and patterns using the turtle module in Python. This lab explores a complete graphical animation framework called Processing, using the Python module.

For today's lab, download the latest version of Processing (version 3.3.7 as of April 2018) for your operating system. (You can also obtain it from your instructor on a USB memory stick.) Once downloaded and opened, you will find in the upper-left or upper-right corner of the screen a box that says either "Java" or "Python". If it already says "Python", or if you can click on it and select "Python", you can move on to Step 1. If "Java" is the only choice, select the box and choose "Add Mode". Select "Python Mode for Processing 3" and then click "Install" in the lower-right portion of the Mode window. After it finishes installing, click on the "Java" box again and select "Python". You are now ready to work through the lab.

Step 1: Faces

When we draw on the screen, we first need to be familiar with the coordinate system. We denote the width as x and the height as y. Most computer graphics modules, Processing included, specify that the x, y origin (0,0) is located in the upper-left corner of the screen, with x increasing to the right and y increasing downwards.

There are three common things to draw in Processing: an ellipse, a rectangle, and a line. Write the following lines of code in Processing and press the Play button.

ellipse(10, 10, 20, 20)
rect(10, 10, 20, 20)
line(10, 10, 20, 20)

You should see an image like this:

An ellipse takes four arguments, the first two being the x, y center of the ellipse, followed by the width, then the height. A rect also takes four arguments, the first two being the upper-left coordinate of the rectangle, again followed by width and height. A line takes four arguments, the first two being the starting x, y coordinate, and the last two being the ending x, y coordinate.

Also, notice that the first thing to be drawn is covered by the images that follow. This indicates that the way that images are drawn, their Z-order, is based on time.

Task

Use ellipse, rect, and line to draw a face on the screen. At a minimum, the face should have eyes, ears, a mouth and a nose. You can find more shapes on the Python Reference for Processing page.

Step 2: Colors

Three different portions of our drawing can be altered to include colors. Recall the hexadecimal representation of colors discussed earlier in class, where the computer understands a color to be composed of three components, Red, Green, and Blue. Each of the three functions below take a hex string as an argument denoting the color.

The background function will paint the whole screen the given color. The fill function changes the color to be painted within an ellipse or rect. The stroke function changes the color to use for making a line or for making the border of an ellipse or rect.

background("#FFFFFF")
fill("#00FFFF")
stroke("#FF0000")

There is only one pen being used for Processing, so if we want to change colors of the things we draw, we need to call fill or stroke in between each of our drawing calls.

It will be convenient to have some of our basic colors defined in a dictionary.

COLORS = {"black":"#000000", "white":"#FFFFFF",
          "red":"#FF0000", "green":"#00FF00",
          "blue":"#0000FF", "yellow":"#FFFF00",
          "orange":"#FFA500", "hendrixorange":"#F58025",
          "purple":"#9B30FF"}

Task

Use the above shapes and color functions, along with any others you might find useful on the Python Processing Reference page, to enhance your earlier image of a face.

Step 3: Functions

As our program is about to get more complicated, it is a good idea to start using functions. There are two important functions that are treated specially by Processing.

Setup

If you define a setup function, Processing will call it first when running your sketch. In this function, you should first set up the screen size.

The default screen size is 100 x 100. This can be changed by adding the following function call at the beginning of your code, for example, to make a screen with a width of 640 pixels and height of 480 pixels:

def setup():
    size(640, 480)

Draw

The draw function is called by Processing for every frame of the animation. Put your earlier face code in this draw function, and press play. For example:

def draw():
    background(COLORS["black"])
    fill(COLORS["red"])
    stroke(COLORS["green"])
    ellipse(50, 50, 100, 100)
    fill(COLORS["yellow"])
    ellipse(25, 25, 35, 35)
    ellipse(75, 25, 35, 35)
    stroke(COLORS["orange"])
    line(30, 80, 70, 80)

(You should use your own face code; the above is just an example.) It should look exactly the same as it did above, but now we will have the ability to change the face for each frame.

Step 4: Faces in Space

All of our images so far have been static. To add animations, we need to start remembering the state of our images and creating methods for how it will change. The natural way to do this is to create new object, and in particular, we will be representing Faces floating in space.

We will represent a Face with a class in Python. The face will need to remember the x,y coordinates for the center of the face. We will also remember the base color used for the face. In the example above, this was red. Notice how the draw function abstracts away the initial face to be centered around any x,y coordinates. This will let us move the face around the screen. Of course, you should use your own face-drawing code in place of the example code shown below.

class Face:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color

    def draw(self):
        fill(self.color)
        stroke(COLORS["green"])
        ellipse(self.x, self.y, 100, 100)
        fill(COLORS["yellow"])
        ellipse(self.x - 25, self.y - 25, 35, 35)
        ellipse(self.x + 25, self.y - 25, 35, 35)
        stroke(COLORS["orange"])
        line(self.x - 20, self.y + 30, self.x + 20, self.y + 30)

We will collect our Face instances in a list called faces, declared outside of any of the above functions. (Note, this is the only global variable we will have in the whole program! If we wanted to add more stuff to our animation, we could create a WorldState class which kept track of everything, and have a single WorldState object declared as a global variable.)

faces = []

We will also change our setup and draw functions to add a single face to the list and draw it, respectively.

def setup():
    size(640, 480)
    faces.append(Face(50, 50, COLORS["red"]))

def draw():
    background(COLORS["black"])
    for f in faces:
        f.draw()

Try moving your face around the screen, and changing its color, by altering the arguments you use to create the Face in the setup function.

Task 4.1: Multiple faces

Change the setup function to add multiple faces at random locations and with random colors around the screen. Make sure your locations are chosen so that the face is always completely visible on the screen.

Step 5: Movement

With the faces being drawn by an object, we can now make these objects move. Add new components to your Face, called velx and vely, to capture the velocity of the Face. For now, initialize them to 1, so they will be moving at a speed of 1 pixel per update, as shown here:

        self.velx = 1.0
        self.vely = 1.0

Next, add a new method to your Face called update. When called, this function will change the values of self.x and self.y by the velocity, and thus simulate movement.

    def update(self):
        self.x += self.velx
        self.y += self.vely

Finally, inside the main draw function, add a call to the update method of each face before the individual draw method. Your draw function should now look like this:

def draw():
    background(COLORS["black"])
    for f in faces:
        f.update()
        f.draw()

Woah, it is moving! This illusion happens because for each frame, the window is completely wiped out through the call to the background function, and then the faces are drawn at the new locations. Verify this is happening by commenting out the call to background. Now you will see the faces repeatedly drawn, but shifted down and to the left by 1 pixel each iteration.

Task 5.1: Bouncing

Currently, the faces disappear after a while, because they moves off the bottom of the screen. We would like to keep them bouncing inside the window. Add in checks to the update method to reverse the appropriate velocity component when a face hits a wall, by multiplying the velocity in that dimension by -1.

Task 5.2: Random speeds

Abstract the velocities so they are initialized by parameters in the __init__ method, and augment your setup function to choose random velocities between -1 and 1 for both the x and y dimension for each Face created. You should now have faces moving in all directions and bouncing off of all the walls of the window.

Step 6: Extensions

Research two of the following extensions on the Python Processing Reference page and augment your animation above.

Task 6.1: Face sizes

Add a size component to your Face, so the setup method can also initialize the size. Change your draw method of Face to account for this new size component, and test it out by drawing smaller and larger faces. Make sure your locations are still chosen so that the face is always completely visible on the screen!

Task 6.2: Mouse and keyboard input

Make your animations interactive by reacting to input from the mouse and the keyboard. For example you could have new Faces appear when and where the mouse is clicked, or you could increase or decrease the velocity of all the Faces when certain keys are pressed.

Task 6.3: Animated faces

Alter your update method of the Face to change the internal pieces of the face in a cyclic pattern. For example, the face could smile for a few timesteps, then frown, and then go back to smiling. Or the eyeballs could be moving up and down, left and right.

Task 6.4: Face collisions

Right now all the faces pass through each other when they move. Add a collision detection method, that checks each pair of faces to see if they are intersecting, and if so, makes the smaller one disappear.

Task 6.5: Images

Learn how to import images into your Processing sketch, and make them move around the screen as we did with the Face above.

Task 6.6: Other transformations

Learn about rotations and translations, and apply these dynamically to your images, making the Faces spin and shift.

What to Hand In

Save your project in some known location. Note that Processing will save your project not as a file, but as a folder containing multiple files. You should make this folder into a zip archive (ask for help if you do not know how to do this) and turn in the resulting zip file via Moodle. Make sure you have followed the Python Style Guide, and have run your project through the Automated Style Checker.

Grading


© Mark Goadrich and Brent Yorgey, Hendrix College