We'll use the programming language Processing to build some interactive animations of cool geoscience concepts. Processing is ideal for what we want to do because it is free, platform-independent, and open source. It is also a great language for a novice programmer to learn because you will not have to deal with all the libraries, compilers, and other system-level functions that usually have to be set up correctly by an experienced user for other programming languages to run.
The tutorial you'll work through in this course is one way to learn Processing. Another great reference is the Processing website [1].
I also suggest you invest in a good reference book as well. Here are my three favorites:
I cannot emphasize enough that in order to learn to program you really have to practice doing it. In my experience, that means typing the commands in yourself, not just cutting and pasting them and not just reading over my sample programs. While it is true that you could copy and paste my sample programs into your editing window and then run them, I recommend that instead, you follow along with my examples by actually writing each one of them or better yet, writing an interesting modification to each one of them.
This lesson introduces you to the programming language Processing. We will also read and discuss a news article about teaching computer programming in schools.
By the end of this lesson, you should be able to:
The content pages in this lesson and most of the other ones contain programs for you to copy, modify, and test out. This course is part of Penn State's Open Educational Resources initiative and I am committed to user accessibility. Therefore, wherever possible I have written out the plain text of the program or command using syntax highlighting and a font that looks like computer code. I have also taken a screenshot of the display window created by the output of the program. In the case of a more complicated program, I have made a narrated screencast explanation of its guts as well as a close-captioned version of the screencast.
If you want to learn to program, don't try to copy-and-paste all of the sample programs. Build muscle memory and increase your cognitive load threshold by typing them yourself. Your neurons will thank you later.
Assignment | Details | graded? | Due date |
---|---|---|---|
Reading: "A is for Algorithm" | Read and discuss in the Lesson 1 Reading Discussion board in Canvas | Yes. This discussion counts toward your overall discussion grade. | 17-25 May 2021 |
Exercise 1.1 | Set the size and shading of the display window | No | 25 May 2021 |
Exercise 1.2 | Tangent ellipses | No | 25 May 2021 |
Exercise 1.3 | Tangent ellipses 2 | No | 25 May 2021 |
Exercise 1.4 | Tangent ellipses 3 | Yes. Turn in to the Exercise 1.4 assignment in Canvas | 25 May 2021 |
Exercise 1.5 | Write your name with primitive shapes | Yes. Turn in to the Exercise 1.5 assignment in Canvas | 25 May 2021 |
This lesson will take us 1 week to complete. Participate multiple times in the reading discussion in a discussion board 17 - 25 May, and turn in Exercises 1.4 and 1.5 to the appropriate assignment dropboxes in Canvas by 25 May 2021.
If you have any questions, please post them to our Questions? discussion forum (not e-mail). I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
Please read the article "A is for algorithm" from the 26 April 2014 edition of The Economist.
You will find this article linked from the discussion in the Lesson 1 Module in Canvas.
As you are reading, think about the following questions for our discussion:
Enter the discussion forum called "A is for algorithm Discussion" in the Lesson 1 module in Canvas. You'll see the above questions already there to get you started. You aren't limited to these topics, however. Feel free to start a new thread with questions or comments about the article. This discussion will take place over the entire week devoted to Lesson 1. For specific dates, see this lesson's overview page.
The first thing you need to do is go to www.processing.org [1] and follow the link to download the correct version for your operating system. Once you have it, just run it. An editing window pops up that looks like this:
Type into the editing window this line exactly as I have written it:
println( "Hello, world!" ); |
println is all in lower case, then an open parenthesis, then a double quotation mark, then the phrase Hello, world!, then another double quotation mark, then a closing parenthesis, then a semicolon. Now click the "run" button. It's the one that looks like a triangle in the upper left of the editing window. The phrase Hello, world! should display in the black console on the bottom of the window, like so:
Screenshot of Processing's editing window containing the code with the output of the Hello world program.
Click for text.
Try it yourself. Did it work? Yay! You wrote a program. Not too shabby.
You probably noticed a small grey window also popped up next door to the editing window. Let's explore what that's all about next.
That little window that popped up is the display window. When you write a program that creates something to look at, it will show up there. By default, Processing sets the origin at the top left corner. The x axis increases to the right and the y axis increases downwards. Increments along the axis are given in pixels. The default window, as shown below, is 100 x 100 pixels.
Let's plot some shapes to the window. Processing has some intrinsic functions to plot primitive shapes. Here is a program that introduces four of them to get you started.
println ( "Hello, world!" ); point ( 50 , 50 ); line ( 10 , 20 , 10 , 70 ); ellipse ( 70 , 20 , 40 , 20 ); rect ( 60 , 65 , 10 , 30 ); |
There is a point at x=50, y=50. There is a line that extends from x=10, y=20 down to x=10, y=70. There is an ellipse whose center is at x=70, y=20 and whose horizontal axis has a diameter of 40 pixels and whose vertical axis has a diameter of 20 pixels. There is a rectangle whose top left corner is at x=60, y=65, and that is 10 pixels wide and 30 pixels high.
The program you just wrote had four commands, one on each line. Processing executed them in order, then stopped. The first command, println(), tells Processing to print whatever is inside the parentheses to the console. In our example, we printed text. We put all the text inside double quotes so that Processing would know that it is text. We will learn how to print other things to the console later when we explore different variable types.
The next four commands told Processing what kind of a shape to draw and where to draw it. Each of those simple shape commands needs to have a certain number of arguments in order to work. The arguments are the numbers inside the parentheses. For example, the command point() takes 2 arguments and they correspond to the x and y location where Processing should place the point. So,
point(50,50); |
tells Processing to place a point 50 pixels to the right of the origin and 50 pixels down from the origin.
The other shapes in the example program each take 4 arguments. line() needs to know the (x,y) pair of the beginning of the line and the (x,y) pair of the end of the line. ellipse() needs to know the (x,y,) coordinates of the center of the ellipse and the diameters of each axis. rect() needs to know the (x,y) coordinates of the top left corner of the rectangle, the width, and the height.
Now let's explore ways to change the look of the various primitive shapes introduced on the previous page. The size() function changes the size of the display window. It takes two integer arguments which are the width and height you want, in pixels. Use the background() function to change the color of the display window. It takes one integer argument, a number ranging from 0 (black) to 255 (white). You can also make the background an actual color, but let's save the discussion of color for the next lesson.
For dealing with the primitive shapes, stroke() works to change the color of the outline of the shape, and fill() does the same for the inside. The command strokeWeight() controls the thickness of the outlines of the shapes, in pixels.
Check out the program below and its associated screenshots similar to the one on the previous page except that I've used stroke(), fill() and strokeWeight() to change the look of the drawing.
size ( 200 , 200 ); background ( 200 ); stroke ( 255 ); strokeWeight ( 3 ); point ( 50 , 50 ); line ( 10 , 20 , 10 , 70 ); fill ( 0 ); ellipse ( 70 , 20 , 40 , 20 ); strokeWeight ( 1 ); fill ( 100 ); rect ( 60 , 65 , 10 , 30 ); |
Now is a good time to take a short digression regarding syntax when writing code. First let's talk about command order. In the program above, each line is a command, telling Processing to do something, such as to draw a shape to the display window at a certain position. Processing reads each command in order and then moves to the next one. If you specify something like the fill value or the stroke value, Processing keeps that value in its memory and continues to use it until you specify a different value and then Processing will start using the new value instead. The fill and stroke values are in greyscale that ranges from 0 (black) to 255 (white). White is different from having no value at all. If you want a shape with no fill or no stroke, you need to use the commands noFill() and noStroke().
Notice the section of the program above that goes like this:
fill ( 0 ); ellipse ( 70 , 20 , 40 , 20 ); strokeWeight ( 1 ); fill ( 100 ); rect ( 60 , 65 , 10 , 30 ); |
The first fill() command tells Processing to make the inside of any subsequent shape black. So when it draws the ellipse, that ellipse is filled with black. Then later on we change the fill color to 100, a nice medium grey, and so the rectangle we plot after that gets that nice grey for its insides.
Now let's talk about another important issue. You have noticed by now that I end all my commands with a semicolon. This is approximately equivalent to ending a sentence with a period when you write in English. The period at the end of the sentence tells the reader that this thought is over and now we are moving to the next thought. That's what the semicolon does for you in Processing. Try removing one of the semicolons and running the program again. Here's what you get:
A program with a missing semicolon and the error output. Click for text.
You don't get a display window, bummer! Processing got confused, but the editor smartly guesses that maybe a missing semicolon is the problem, tells you so, and then highlights where it thinks the problem is. A missing semicolon is one of the most common mistakes. I do it all the time. Luckily it's a really simple problem to correct.
Case matters, too. What if you wrote strokeweight instead of strokeWeight? Does it matter? Yes, it does. Try going back to your code and write that command with a lowercase "w" and see what happens.
A program with a incorrect case in strokeWeight() and the error output. Click for text.
This time, the program does not run, and an error message tells you that there is no such thing as "strokeweight." Case matters. A person reading your code would have probably said, "Oh, you meant strokeWeight, not strokeweight, fine, okay, I get it." But Processing can't do that. Human beings can understand the ambiguity inherent in human languages and that's why jokes with double entendres are funny. For example, during the runup to the 2008 presidential election, many pundits commented on Michelle Obama and her fashionable outfits. One journalist wrote, "Why are Mrs Obama's sleeveless shirts such a big topic of conversation? After all, the second amendment gives her the right to bare arms." Ha ha ha. But even if you thought this was corny, you got it right? You understand that "bear" and "bare" are homophones and that "arms" can be interpreted two different ways. But the Processing compiler would not be able to explain this joke because it can only interpret each command exactly one way. This is true in general when you write programs in any language for a computer to execute.
Now, having said that, there are some things that Processing does not care about. Whitespace is chief among these. Try doing something to the program above such as writing fill( 100 ) instead of fill(100). Processing doesn't care. Same if you hit return a bunch of times in between commands.
I think it's a good idea to keep in mind that first of all, you have to follow rules for punctuation and case or else Processing can't understand what you want it to do. Also, at some point you are going to want to share your program with another person and it is helpful if that person can read it easily. So if you monkey around by putting spurious spaces in various places, it may be true that Processing doesn't care, but it also might make it hard to read your code. This will make more difference later on when you start writing more complicated blocks of code.
Time to extend what you've learned so far. Note that I am only asking you to submit two of the exercises below for me to look at. I still recommend working through all of them because that is the best way to learn to program and also they build on each other. In fact, one way to approach this set of exercises is to write the program in Exercise 1.1, then modify that same program and turn it into Exercise 1.2, and so forth.
1.1 Write a program that makes a dark grey display window that is 200 x 400 pixels.
1.2 Tangent ellipses: Write a program that draws a display window, draws an ellipse in each corner whose outline is tangent to the edges of the window.
1.3 Tangent ellipses 2: Modify the above program to change the stroke (thickness) of the outlines of the ellipses.
1.4 Tangent ellipses 3: Modify the above program to change the fill (grey value) of each ellipse.
1.5 Write your name: Write a program that draws a display window and uses primitive shapes (points, lines, rectangles, ellipses) to write your name to the display window.
Upload the .pde files for Exercises 1.4 and 1.5 to their appropriate assignment dropboxes in Canvas by the date indicate on this lesson's overview page. Name them like this: lastname1_4.pde and lastname1_5.pde, so for example my program for Exercise 1.4 would be called: richardson1_4.pde. Note that the default file extension is already ".pde" so you don't have to add that part yourself when you are saving your file with a name.
In this lesson, you downloaded Processing and wrote your first programs! Getting started is always the highest hurdle with anything new.
You have reached the end of Lesson 1! Double-check the to-do list on the Lesson 1 Overview page to make sure you have completed all of the activities listed there before you begin Lesson 2.
Two things are going to happen in Lesson 2. First of all, we are going to read and discuss two papers (details on the next page) that give some insight into the inspiration for this course. Also, we are going to tackle the next level of programming in Processing. One thing that computers are very good at is repetitive tasks. That's why they were invented in the first place, to save time and increase accuracy when performing the same calculations over and over again. In this lesson we'll learn some basic syntax -- the for loop -- that will allow us to streamline repeated calculations.
By the end of this lesson, you should be able to write more complicated programs that involve modifying attributes of default shapes, drawing custom shapes, and performing repeated calculations.
This lesson will take us one week to complete. We will finish Lesson 2 on 1 June 2021.
If you have any questions, please post them to our Questions? discussion forum (not e-mail), located in Canvas. I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
This week we will read and discuss the two papers below. They are linked from the discussion in Canvas.
The first paper discusses the fact that successful geoscientists need to be adept at what cognitive scientists term "object visualization" and "spatial visualization." One of the challenges of designing successful learning materials for teaching geoscience concepts effectively is to recognize that these visualization skills are needed and to try to address them deliberately.
I think it is fair to say that K-12 science education is rather more focused on building vocabularic and conceptual knowledge (i.e. "List the three types of plate boundaries") rather than honing the skills used by scientists, such as the visualization skills brought up in this paper. One of my goals for this course is to explore and teach visualization skills through the medium of computer programming, so keep that in mind when you read the first paper.
The second paper is by a team of neuroscientists and my take-away from it is that even in adulthood, learning a new skill changes your brain for the better. That discovery addresses my second goal for this course which is to teach you a new skill set that will hopefully be useful to you. Even if it is not directly useful, my intent is that the learning process will be beneficial in and of itself.
As you read, consider the following questions, which we will discuss as a class:
Once you have finished the readings, engage in a class discussion that will take place over the entire week devoted to Lesson 2. For specific dates, consult the lesson overview page. This discussion will require you to participate multiple times over that period.
You will be graded on the quality of your participation. See the grading rubric [2] for specifics.
The default for a rectangle is for its origin to be at the top left corner, and the default for an ellipse is for its origin to be at its center. You can change these defaults by resetting rectMode() and ellipseMode(). For example, if you specify rectMode(CENTER), then the four arguments taken by rect() become the (x,y) coordinate of its center and then the half-width, and half-height, respectively. To revert back to the default, specify rectMode(CORNER). Read about more options at Processing's command reference page for rectMode() [3]. If you specify ellipseMode(CORNER), then the four arguments taken by ellipse() become the (x,y) coordinate of the upper left corner of a rectangle that circumscribes the ellipse. Then the other two arguments are the width and length of that bounding rectangle. Read about more options at Processing's command reference page for ellipseMode() [4].
It is my personal preference not to change these defaults. I find it easier to construct my mental map of where I want to place shapes in a composition working within the framework specified by the defaults. However, there isn't a right or wrong approach, in my opinion. See the code below for a sample of code and its corresponding display window that demonstrate what happens when rectMode() and ellipseMode() are changed.
size ( 200 , 200 ); //demonstrate rectMode rectMode ( CENTER ); rect ( 35 , 35 , 50 , 50 ); rectMode ( CORNER ); fill ( 102 ); rect ( 35 , 35 , 50 , 50 ); // demonstrate ellipseMode // demonstrate noFill ellipseMode ( CENTER ); fill ( 0 ); ellipse ( 135 , 135 , 50 , 50 ); ellipseMode ( CORNER ); fill ( 102 ); ellipse ( 135 , 135 , 50 , 50 ); ellipseMode ( CENTER ); noFill (); ellipse ( 150 , 150 , 100 , 100 ); |
My compositions are more fun if they include color. The default way to specify the color of things in Processing is with (r,g,b) values. This is red, green, and blue. Remember how if you got really close to an old picture-tube television set, you could see the red, green, and blue lights in different arrangements that made up the whole picture? Same basic idea. These colors are the opposite of paint in that if you add the full amount of each one you get white, not black (or really a muddy brown if you use actual paint). You can also specify the color by its hue, saturation, and brightness (h,s,b) or in hexadecimal. To switch to hsb from the rgb default, you have to use colorMode(HSB). If you want to use hexadecimal, just use the appropriate value (which you can find in the Tools menu or probably by googling).
I usually just use rgb values, so the rest of this discussion assumes rgb. Each value of red, green, and blue can range from 0 to 255. Trial and error is certainly one way to come up with a pleasing color palette, but a better way is to use the color selector in Processing. Go to the Tools menu and select "Color Selector" from the drop-down menu. See figure below for a snapshot of the Color Selector. Once this is open, you can play around to find the color you want. The numbers corresponding to that color are given, and you can just type them into your program in the appropriate place.
Any function that took an argument to define its greyscale can be a color instead. This includes stroke(), fill(), and background(). You can also vary how transparent or opaque objects look by changing the alpha value. This is an optional fourth argument to specifying color. It also ranges from 0 (totally transparent) to 255 (completely opaque). Completely opaque is the default so by not specifying a fourth number you will just get completely opaque colors.
Demo about adding color.
Processing has intrinsic functions to draw several shapes besides points, lines, ellipses and rectangles. I'm just going to list several of them here along with a link to each of their reference pages on Processing's website. My goal in this tutorial is not to write a textbook. Anyway, there are several good ones already out there that I already told you about, so you should read the appropriate sections of those if you want more information about any of these:
But what if you want to draw your own shape? Like, say, how about the lightning bolt on Roy Hobbs' bat that later became a patch on the whole team's uniform in The Natural? I've found a way to work a baseball reference into all my other courses, so why not? In case you don't know what I'm talking about, this is it:
Your friends for this task are beginShape(), endShape(), and vertex(). What you do is use a beginShape() endShape() pair to surround a list of vertex() commands to draw the outline of your shape point by point. Processing will connect up a series of vertices with straight lines. If your last command is endShape(CLOSE) then Processing with connect up the last point with the first point so you can make a closed shape without having to specify the first point again. On the other hand, if you don't want that, then just do endShape(). Here's my lightning bolt along with the program that draws it as a yellow lightning bolt on a black background.
size ( 100 , 200 ); background ( 0 ); noStroke (); pixelDensity( 2 ); fill ( 250 , 230 , 5 ); beginShape (); vertex ( 20 , 10 ); vertex ( 20 , 60 ); vertex ( 30 , 50 ); vertex ( 20 , 110 ); vertex ( 30 , 100 ); vertex ( 20 , 170 ); vertex ( 60 , 75 ); vertex ( 50 , 85 ); vertex ( 60 , 35 ); vertex ( 50 , 45 ); vertex ( 60 , 10 ); endShape ( CLOSE ); |
Notice in the program above there are two commands that we haven't specifically covered yet. They are pixelDensity() and noStroke().If you have a big-definition display screen on your computer, such as Apple's Retina display or Windows' High-DPI display, then using pixelDensity(2) allows your computer to use all its pixels when it renders one of your drawings. Add it in and then take it out and you'll be able to see the difference in how shapes are rendered on your screen. The noStroke() function tells Processing not to draw an outline around your shape.
2.1 Create a composition out of two or more differently colored overlapping shapes
2.2 Modify Exercise 2.1 above to change the color and thickness of the outlines of the shapes and the background color of the composition.
2.3 Create your own custom shape with vertex(), beginShape(), and endShape().
You don't have to turn in 2.1, 2.2, or 2.3. As before, I recommend working through all the exercises anyway, even the ones that you are not required to turn in.
When you were drawing your own shapes with vertex(), you probably did what I did and used trial and error to place each vertex until you got the shape to look the way you wanted it to. Or maybe you thought ahead and drew it on graph paper first. Now let's say you like that shape but you wish it was in a slightly different place in the display window. Ugh! What a pain! You'll have to go back and recalculate every vertex. (This is true even if you used graph paper. bummer!) I don't want to do this, and I'll probably mess up somewhere along the way. Ah, but if you use variables then you can solve the annoying recalculation problem!
Here's the same lightning bolt as I drew before, but now I'm going to define two variables to use as the origin. Every subsequent vertex will be written relative to that origin. For example, in the original program the first vertex was vertex(20,10). This time around I'm going to declare an integer variable named x and assign it the value of 20. I'm also going to declare another integer variable called y and assign it the value of 10.
Now that x = 20 and y =10, I can tell Processing to do vertex(x,y) instead. The second vertex was vertex(20,60). I want to write this in terms of x and y so I write vertex(x,y+50). I go through all the vertices in the shape, and with some simple mental arithmetic I transform each one into an equivalent value using x and y. Below is the lightning bolt program in which the vertices are set by variables.
size ( 100 , 200 ); background ( 0 ); noStroke (); fill ( 250 , 230 , 5 ); int x = 20 ; int y = 10 ; beginShape (); vertex (x, y); vertex (x, y + 50 ); vertex (x + 10 , y + 40 ); vertex (x, y + 100 ); vertex (x + 10 , y + 90 ); vertex (x, y + 160 ); vertex (x + 40 , y + 65 ); vertex (x + 30 , y + 75 ); vertex (x + 40 , y + 25 ); vertex (x + 30 , y + 35 ); vertex (x + 40 , y); endShape ( CLOSE ); |
Up near the beginning of the program I declared two variables and I named them "x" and "y." I gave them those names because it makes intuitive sense to me to think of horizontal positions as x values and vertical positions as y values. However, I could have named those variables anything I wanted and still used them as x and y values. I could have called them "duck" and "goose" or whatever. I could even have used "y" for horizontal and "x" for vertical (although I think this choice would confuse me a lot later -- not the best idea). In the function vertex(), the first value specified is always the horizontal position and the second value specified is always the vertical position. It does not matter what those values are named.
I will now amend the statement, "it doesn't matter what they are named," to tell you that actually there are a few rules about naming variables in Processing. The names can't start with a number and can't have spaces in them. It is also a really really good idea not to give a variable a name that Processing already uses for something else. There's a list of Processing variables at their website.
variable | okay? | comments |
---|---|---|
x | yes | |
1x | no | can't start with a number |
x1 | yes | |
x 1 | no | can't have a space inside a variable name |
x_1 | yes | an underscore is okay when you really want a space |
x_direction | yes | you can write a whole word -- makes it more readable |
xDirection | yes | some people capitalize subsequent words instead of using an underscore. This is called "camel case" |
fill | no | don't use a name that Processing already uses |
When I defined the two variables, "x" and "y" up at the top of the program, I didn't just write x = 20. I wrote int x = 20. That tells Processing that I want to make an integer variable and name it x and I am assigning the value 20 to x. An integer means a whole number with no fractional parts. 3 is an integer, -3 is an integer, 0 is an integer, but 3.5 is not an integer. You can declare a variable and assign a value to it in more than one step and some people prefer this for clarity. For example
int x; x = 20 ; |
is equivalent to
int x = 20 ; |
And,
int x, y; x = 20 ; y = 10 ; |
is equivalent to
int x = 20 ; int y = 10 ; |
You will have to experiment with the syntax that makes the most sense to you. There are other types of variables besides integers and we will get to them later on. Now back to the program.
Upon first glance it doesn't really look like we have saved ourselves any effort here compared to writing it where we named every vertex position explicitly. After all, we have typed a lot more characters by declaring variables, haven't we? Plus we had to do some addition and subtraction in our head to rewrite the position of each vertex. But let's say I want to scooch the whole lightning bolt over and down little bit so it's more centered in the display window. That is easy-peasy lemon-squeezy as my first grader says. All I have to do is change the values of x and y and recalculate nothing else at all. Let's do it. Instead of making x = 20 and y = 10, we'll make x = 30 and y = 20. The code for the new lightning bolt program after having changed the x and y assignments to 30 and 20 is below.
size ( 100 , 200 ); background ( 0 ); noStroke (); fill ( 250 , 230 , 5 ); int x = 30 ; int y = 20 ; beginShape (); vertex (x, y); vertex (x, y + 50 ); vertex (x + 10 , y + 40 ); vertex (x, y + 100 ); vertex (x + 10 , y + 90 ); vertex (x, y + 160 ); vertex (x + 40 , y + 65 ); vertex (x + 30 , y + 75 ); vertex (x + 40 , y + 25 ); vertex (x + 30 , y + 35 ); vertex (x + 40 , y); endShape ( CLOSE ); |
There. I like the way that looks better.
While you are at it, is the "Quiz yourself" exercise above building your spatial visualization skills or object visualization skills, or both, or neither?
2.4 Draw your own custom shape using variables to set the vertices.
You don't have to turn in Exercise 2.4, but you should work through it anyway.
Let's say it's a thunderstorm and we want to draw a whole bunch of our stylish lightning bolts to the screen. Luckily computers were built precisely to make repetitive calculations quickly. One way to take advantage of a computer's ability to do this is with a for loop. Check out the program below.
// use a for loop to draw several lightning bolts size ( 400 , 200 ); background ( 0 ); noStroke (); smooth (); fill ( 250 , 230 , 5 ); int y = 20 ; for ( int i = 0 ; i < 400 ; i = i + 40 ) { beginShape (); vertex (i, y); vertex (i, y + 50 ); vertex (i + 10 , y + 40 ); vertex (i, y + 100 ); vertex (i + 10 , y + 90 ); vertex (i, y + 160 ); vertex (i + 40 , y + 65 ); vertex (i + 30 , y + 75 ); vertex (i + 40 , y + 25 ); vertex (i + 30 , y + 35 ); vertex (i + 40 , y); endShape ( CLOSE ); } |
The major new thing in this program is the section that goes:
for ( int i = 0 ; i < 400 ; i = i + 40 ) { ...some commands } |
The line begins with the command for and then inside the parentheses are three parts, separated by semicolons.
The part that says int i = 0; initializes the variable to loop over. In our program we are naming that variable i and setting it equal to zero.
The next part that says i < 400; is a test. If the test is true, all the commands inside the curly braces are executed one at a time in order using the value of the loop variable wherever that variable appears. If the test fails, Processing ignores all the commands inside the curly braces and moves along to the rest of the program. In our program that test checks to see whether the value of the loop variable is less than 400 or not.
The third part that says i = i + 40 is the increment. When the for loop finishes the first time using the initial value of the loop variable, it goes back to the top of the for loop, increments the loop variable by the amount indicated (it adds 40 in this program), then it reassigns the variable to the new value. It checks if the test is still true, and if it is, it executes all the commands inside the curly braces with the new value of the loop variable. It repeats incrementing and executing until the test fails, and then it moves on and doesn't try to execute the for loop any more times.
So put it all together and here is what this for loop does. It starts out with i = 0; It runs all the commands inside the curly braces using 0 every time it sees an "i." Then it adds 40, checks whether 40 is less than 400. It is, so it runs all the commands inside the curly braces using 40 every time it sees an "i". Then it adds 40, checks whether 80 is less than 400. It is, so it runs all the commands inside the curly braces using 80 every time it sees an "i." This goes on until i gets to 400 or higher. When that happens, Processing skips all the commands inside the curly braces and moves on to see if there are any commands to run after the curly braces.
What all this accomplishes is to draw the lightning bolt over and over again, each time moving it 40 pixels to the right. See how much trouble we have saved by writing a loop rather than writing multiple beginShape/endShape chunks!
The initial variable can be a negative, zero, or positive. You can declare the variable before the for loop starts. The test can involve any relational operator, which is a fancy way of saying anything that compares two values. We will learn more about relational operators later but here are the most common ones:
The increment can be anything you want and it can be positive or negative.
Here's what it looks like when you write two for loops and the only difference between them is the initialization variable:
// for loop to demonstrate changing the initialization size ( 400 , 100 ); pixelDensity( 2 ); //makes prettier circles on a retina display int y = 20 ; //set the vertical position //this for loop makes a row of evenly-spaced circles for ( int i = 0 ; i< 400 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } y= 40 ; //shift the vertical position down //this for loop makes the same row of circles but shifted over //by 10 pixels because we changed the initialization variable for ( int i= 10 ; i< 400 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } |
The top line of circles were placed by starting at a horizontal position of zero, and a vertical position of 20. Then the horizontal position in increased by 50 and the vertical position is unchanged, and another same-sized circle is drawn. Then the horizontal position is increased by 50 more, the vertical position is unchanged, and another same-sized circle is drawn. This goes on until we reach a horizontal position of 400.
The loop says:
for ( int i = 0 ; i< 400 ; i=i+ 50 ){ ellipse (i, 20 , 20 , 20 ); } |
That first loop is the equivalent of:
ellipse ( 0 , 20 , 20 , 20 ) ellipse ( 50 , 20 , 20 , 20 ); ellipse ( 100 , 20 , 20 , 20 ); ellipse ( 150 , 20 , 20 , 20 ); ellipse ( 200 , 20 , 20 , 20 ); ellipse ( 250 , 20 , 20 , 20 ); ellipse ( 300 , 20 , 20 , 20 ); ellipse ( 350 , 20 , 20 , 20 ); |
See how much typing you save with a loop, and also the chance of making an error is less because the computer is doing the computation for you.
The second loop in the program only differs from the first one in the initialization variable (and we shifted the vertical position down so we could see both sets of circles more easily). The second loop says:
for ( int i = 10 ; i< 400 ; i=i+ 50 ){ ellipse (i, 40 , 20 , 20 ); } |
That second loop is the equivalent of:
ellipse ( 10 , 40 , 20 , 20 ) ellipse ( 60 , 40 , 20 , 20 ); ellipse ( 110 , 40 , 20 , 20 ); ellipse ( 160 , 40 , 20 , 20 ); ellipse ( 210 , 40 , 20 , 20 ); ellipse ( 260 , 40 , 20 , 20 ); ellipse ( 310 , 40 , 20 , 20 ); ellipse ( 360 , 40 , 20 , 20 ); |
Again, this would be a long program if we had set each of these circles by hand. Rule of thumb: If you find yourself typing a few commands in a row that look very repetitive, then a loop is probably a good idea. Note that when you are constructing a program, sometimes you don't start out realizing you want a loop until you notice that you've just typed a bunch of repetitive commands.
Here's what for loops look like when the only difference between them is the test.
// for loop to demonstrate negative increments size ( 400 , 150 ); pixelDensity( 2 ); //makes prettier circles on a retina display int y = 20 ; //set the vertical position //this for loop makes a row of evenly-spaced circles for ( int i = 0 ; i< 200 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } y= 40 ; //shift the vertical position down //this for loop makes a row of circles but ends in a different place for ( int i= 0 ; i<= 200 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } y= 60 ; //shift the vertical position down some more //this for loop makes a row of circles but ends in a different place for ( int i= 0 ; i<= 300 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } y= 80 ; //shift the vertical position down some more //what if the initialization and test are the same for ( int i= 0 ; i<= 0 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } y= 100 ; //shift the vertical position down some more //won't plot any circles because the test immediately fails for ( int i= 0 ; i< 0 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } |
The program above has 5 loops. The first one draws four circles, with origins at (0,20), (50,20), (100,20), and (150,20). It does not draw a circle at x = 200 because the test says to execute the loop only in cases where the increment variable is less than 200. The second loop does draw a circle at (200,40) because the the test says to execute the loop in cases where the increment variable is less than or equal to 200. The third loop draws a couple more circles because the test says to execute up to and including 300. The fourth loop has the initialization variable exactly equal to the test, so the loop executes once and then stops. (No need to write a loop in this case, and the increment doesn't even matter here. It's just for demonstration purposes.)
The 5th loop doesn't plot anything because the initialization variable fails the test right off the bat. So the loop doesn't execute at all. Note that Processing won't give you an error message in a case like this one because nothing you wrote has incorrect syntax. Writing a relational test that always fails is a mental error, but not officially a programming error, so just keep that in mind when troubleshooting your code when you get unexpected results.
Here's an example of what happens when you change the increment variable from one loop to another, but leave the rest alone.
// for loop to demonstrate changing the increment size ( 400 , 100 ); pixelDensity( 2 ); //makes prettier circles on a retina display int y = 20 ; //set the vertical position //this for loop makes a row of evenly-spaced circles for ( int i = 0 ; i< 400 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } y= 40 ; //shift the vertical position down //this for loop makes a row of circles with less space between them //because we changed the initialization variable for ( int i= 0 ; i< 400 ; i=i+ 30 ){ ellipse (i,y, 20 , 20 ); } |
The only difference between the two loops is that I made the increment smaller in the second one. This results in more circles, spaced closer together. If I had made the increment bigger, there would be fewer circles spaced farther apart.
You can start at some initial value and work backwards to a lower value, as in the program below.
// for loop to demonstrate negative increments size ( 400 , 100 ); pixelDensity( 2 ); //makes prettier circles on a retina display int y = 20 ; //set the vertical position //this for loop makes a row of evenly-spaced circles //starting in the middle and going to the right for ( int i = 200 ; i< 400 ; i=i+ 50 ){ ellipse (i,y, 20 , 20 ); } y= 40 ; //shift the vertical position down //this for loop makes a row of circles //starting in the middle and moving to the left for ( int i= 200 ; i> 0 ; i=i- 50 ){ ellipse (i,y, 20 , 20 ); } |
In the first loop, we start at (200,20), and make evenly-spaced circles that shift to right by 50 pixels, until we get to x=400, then we stop. So the first loop is the equivalent of:
ellipse ( 200 , 20 , 20 , 20 ); ellipse ( 250 , 20 , 20 , 20 ); ellipse ( 300 , 20 , 20 , 20 ); ellipse ( 350 , 20 , 20 , 20 ); |
In the second loop, we start at (200,40), and make evenly-spaced circles that shift to the left by 50 pixels each time until we get to x=0. Note that not only do we have to change the increment, but we have to change the test portion of the loop as well in order to make this work. The second loop is the equivalent of:
ellipse ( 200 , 40 , 20 , 20 ); ellipse ( 150 , 40 , 20 , 20 ); ellipse ( 100 , 40 , 20 , 20 ); ellipse ( 50 , 40 , 20 , 20 ); |
When you set up a for loop pay attention so that you don't write a loop that will never terminate. For example, what if I wrote the following loop:
for (int i = 10; i < 200; i = i - 10) |
Bad trouble! Why? The initial value of i is 10, and then I am decreasing the value of i by 10 in the increment portion. But, my test checks whether or not the value of i is less than 200 and if it is, to go ahead and execute the loop. The value of i will never exceed 200 since i is getting more negative all the time, so this loop will never end because the test will never fail.
You can write for loops inside of other for loops. I think of this kind of thing kind of like tiling a cookie tray with the dough for a batch of cookies. Let's say you've made enough dough for 1 dozen cookies and you have a cookie tray. Here's a way to think of a pair of nested loops that explains how to put the dough blobs on the tray into 4 columns of 3 blobs each. (I think following along with my written description is a spatial visualization exercise, what do you think?)You go to first position in the upper left corner of the tray, then you execute a loop 3 times so that at the end of the loop, you have put 3 dough blobs on the tray in a column. That loop finishes and then you move one position to the right. You execute the same loop three more times so that you make a new column of dough blobs next to the first column. Keep going until you've got four columns with three cookies in each column. Here's how you'd write in Processing what I just explained in English.
int doughBlobSize = 20 ; for ( int xpos = 10 ; xpos < 100 ; xpos = xpos + 25 ) { for ( int ypos = 10 ; ypos < 100 ; ypos = ypos + 40 ) { println ( "xpos =" + xpos, "ypos =" + ypos); ellipse (xpos, ypos, doughBlobSize, doughBlobSize); } } |
I put the println() command in there so that when the program runs, you can check that the loops are doing what you thought they were doing. In general, sticking a println() command into your code somewhere is a good idea when you get unexpected output or an error and you want to double check that variables have the values you thought they did.
Let me break down the syntax println("xpos =" + xpos, "ypos =" + ypos);
The part that goes "xpos =" says to print the string xpos =
The part that goes + xpos says to print the value assigned to the variable named xpos
The comma tells Processing to put in a white space
The part that goes "ypos =" says to print the string ypos =
The part that goes + ypos says to print the value assigned to the variable named ypos
The commands are executed very fast but that println command gets executed 12 times, and each time the value of xpos and ypos have changed so it prints the new value to the screen. If you look at all the values in the console and cross-check that information with the display window output, you should be able to see the order in which the position of each dough blob was determined, even though they all get plotted at once in the blink of a human eye.
A screenshot of the program and its output:
A program demonstrating a nested for loop. Click for text.
size ( 200 , 200 ); stroke ( 255 ); line ( 10 , 0 , 10 , 100 ); stroke ( 0 ); for ( int i = 10 ; i < 200 ; i = i + 10 ) { println (i); fill (i); ellipse (i, i, 10 , 10 ); } stroke ( 255 ); line ( 30 , 0 , 30 , 100 ); |
This program first draws a white vertical line, then it starts a loop in which it draws a diagonal series of circles, each one lighter in color that the last. Then it leaves the loop and draws another vertical white line. This program demonstrates loops and command order. You can tell that the first white line was set before the loop started because the circles plot on top of it. The second white line came after the loop because it is drawn on top of the circles. The circles go in a diagonal line instead of tiled because I am incrementing both x and y at the same time instead of nesting one loop inside another loop.
2.5 Rewrite the dough blob program so that you place the blobs in the same configuration, but you start from the bottom right corner and work upwards and to the left.
2.6 Use at least one for loop to draw your custom shape from Exercise 2.4 several times.
Turn in Exercise 2.6 to the Lesson 2 dropbox by the due date indicated on this lesson's overview page. Here's what I am looking for in your program: correct use of beginShape(), endShape(), and vertex(), use of variables to set the vertices, and correct use of a for loop to draw the shape multiple times. Name your file lastname2_6.pde
You have reached the end of Lesson 2! In this lesson you learned about variables and for loops, and a little bit about how to troubleshoot a loop with the println command. These will all be useful building blocks for the more complicated programs we'll learn in Lesson 3.
Double-check the to-do list on the Lesson 2 Overview page to make sure you have completed all of the activities listed there before you begin Lesson 3.
2.1 Create a composition out of two or more differently colored overlapping shapes
2.2 Modify Exercise 2.1 above to change the color and thickness of the outlines of the shapes and the background color of the composition.
2.3 Create your own custom shape with vertex(), beginShape(), and endShape().
2.4 Draw your own custom shape using variables to set the vertices.
2.5 Rewrite the dough blob program so that you place the blobs in the same configuration, but you start from the bottom right corner and work upwards and to the left.
2.6 Use at least one for loop to draw your custom shape from Exercise 2.4 several times. [Turn this one in]
Lesson 3 contains our first foray into animation. We'll also have a teaching/learning discussion which does not involve reading a set of published papers, but instead, involves some reflection about the skills we've learned and how you might teach them if you ever wanted to.
By the end of this lesson, you should be able to write a program that simulates the movement of an object on the screen, write an "if statement" correctly, write a program that includes setup() and draw() blocks, and use comments to clarify your code.
This lesson will take us one week to complete, 2 June - 8 June 2021. You will turn in Exercise 3.2, as detailed later on, at the end of the lesson. We will also have a teaching/learning discussion that will span the entire week devoted to Lesson 3.
If you have any questions, please post them to our Questions? discussion forum (not e-mail) in Canvas. I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
This page serves as a placeholder to remind you to go visit the first Teaching/Learning discussion for this course, which can be found linked from the Lesson 3 Module in Canvas. Please visit that discussion board often during the week devoted to Lesson 3 to offer your opinion and to reply to your classmates regarding how/why/what to teach your own students about the topics and skills that have come up so far in this course.
Let's quit messing around and get to what's fun about Processing. Let's make stuff move on the screen. I'll start with my lightning bolt because why not, we already know what that looks like. Here's a program that draws one lightning bolt to the screen and moves it horizontally.
//move the lightning bolt horizontally //declare some variables first int y = 20 ; int x = 10 ; //setup function void setup () { size ( 490 , 200 ); } //draw function where the action happens void draw () { smooth (); background ( 0 ); noStroke (); fill ( 250 , 230 , 5 ); beginShape (); vertex (x, y); vertex (x, y + 50 ); vertex (x + 10 , y + 40 ); vertex (x, y + 100 ); vertex (x + 10 , y + 90 ); vertex (x, y + 100 ); vertex (x + 40 , y + 65 ); vertex (x + 30 , y + 75 ); vertex (x + 40 , y + 25 ); vertex (x + 30 , y + 35 ); vertex (x + 40 , y); endShape ( CLOSE ); //increment the value of x by 1 each time the program loops through draw() x++; } |
This program looks fundamentally different from previous ones we've seen so far. For one thing, there are discrete paragraphs instead of just a list of commands. I'm going to go through what the new syntax does.
Now is a good time to talk about comments, which I haven't done explicitly yet. Notice in all my samples of programs there are some lines that start with two slashes (//). Those are comments. They are like the notes you make in the margin of a cookbook telling you why the recipe is written the way it is. Processing ignores them but they are super useful because they tell somebody else in plain English what your code is doing. This is also important when you go back to a program a few days, weeks, years later and you can't remember what you were thinking or what that program does. Having those comments there helps a lot. If you want to write a longish paragraph of an explanation, you can either start each line with the two slashes or else you can type /* at the beginning, write several lines, and then type */ at the end. Everything between the /* */ will be commented out.
Another good use of comments is when you have a program that is working fine, then you add some things to it, then all of a sudden it's not working anymore. Rats! In that case, I try to isolate the problem by commenting out the new parts one by one and seeing if the program runs without those parts. Then I can usually narrow down how I've screwed up.
In the program above, I first write a comment explaining what the program does. Then I declare some variables. Since these variables are declared at the top of the program before anything else happens, they are defined that way for the entire rest of the program. Then comes a paragraph that looks like:
void setup () { some commands } |
This is the setup() block. setup() is a function whose job is to do things like set the window size or the background color, or to load data files, media, and fonts that might be used in the program. Processing runs through setup() exactly once. There can only be one setup() block per program. If you declare a variable inside setup(), then that variable is not available outside of setup(). The setup() block starts out with the key word void. That tells Processing that setup() is a function that doesn't return an answer. Contrast this to a mathematical function such as f(x) = x + 1. This is a function whose job is to add one to any value of x you give it, and then tell you the result. If we were writing a function like that, we'd have to specify the variable type as the key word instead of void.
After the setup() block there is a paragraph that looks like
void draw () { some commands } |
This is the draw() block. draw() is a function whose job is to loop over the commands contained inside it continuously until you tell it to stop. You can kind of think of it as an infinite for loop in which the time between subsequent runs through the loop is the refresh rate of your computer screen. You need to have a draw() block if you want continual looping in your program. Almost all the rest of the programs we will write from now on will contain a setup() and a draw() block in them. If you declare a variable inside draw(), then that variable is not available outside draw().
The section of the code that goes:
x++; |
Is the line that takes advantage of the continuous looping of the draw() function. All the commands inside the beginShape()/endShape() pair form the lightning bolt just like in earlier programs, in which x = 10 and y = 20 (remember we set those at the top of the program). Then we are telling Processing to take the value of x and add one to it. That's what "x++" means. Next time the lightning bolt will be plotted with the origin of the lightning bolt at x = 11 and y = 20. Each time the program loops through draw(), it plots the lightning bolt, recalculates a new value for x, and plots the lightning bolt again shifted one pixel to the right of where it was before. It looks like the lightning bolt is moving because your eyes can't refresh as fast as your screen.
In pseudo-English, here's what the code example at the top of the page does
that's it!
In the program on the previous page, the lightning bolt traveled off the screen, never to be seen from again, drawing itself in the ether outside of our window. That's not fun! Let's fix it.
An if statement is a way to change a program's behavior depending on some event. It reminds me a lot of the way natives of Pittsburgh use the word "whenever." (As in, "Whenever you are going to the store will you buy me some eggs?" Only western Pennsylvanians have this usage. I think it is weird.) You write an if statement like this:
if (thing you are testing) { some commands that will execute only if (thing you are testing) is true ; } |
The test is enclosed in parentheses, and the commands to execute if the test turns out to be true are enclosed in curly braces. When Processing sees an if statement, it checks whether the test is true and if it is true, it executes the commands between the braces, then goes on with the rest of the program. If the test turns out not to be true, it ignores all the commands inside the curly braces and moves on to the rest of the program. Okay, back to our lightning bolt! In kind-of English, one way to fix the earlier program is to write something like,
if (the lightning bolt gets to the far right edge of the window) { start over again on the left side } |
We can do that! Here's how:
We add some lines of code to our program that say:
if (x == width ) { x = 0 ; } |
That is the code translation of what we wrote in English up above. Now the lightning bolt will move along horizontally and when it gets to the right edge of the display window it will jump back to the left edge and start over.
Simply: One equals sign is telling. Two equals signs are asking.
The == part is a logical operator that means equality. We are testing whether or not two values are equal to each other, in this case x
and the width of window. So x==width is asking the program whether or not x equals width or not.
We already set the width when we specified it using size(200,200), so Processing knows that the value of width is 200 already. Therefore you don't have to write "200" explicitly. It's almost always better to refer to a variable than to write an actual number because then if you change the size of the window, the program will still work the way you intended. Otherwise you'd have to hunt down all the places you wrote 200 and change them to the new width.
Below the line that says if (x==width){, I wrote x=0;
In this case, I am not testing for equality, I am setting x equal to zero. I am telling the program to make x have the value of zero. So remember, that's the difference between one = sign and two of them. One is telling, two is asking. There are other logical operators, such as > (greater than), < (less than), >= (greater than or equal to), <= (less than or equal to), != (not equal to), && (and).
So basically, the if test we wrote won't kick in (Processing will evaluate it as false) until the lightning bolt gets to the far right of the display window. Until then, Processing will check whether or not the value of x is equal to the width of the display window. As long as it is not, then it will not reset the value of x to zero. It will ignore that command. Once the value of x reaches the value that is the width of the display window, Processing will reset the value of x to zero and go on with the program, incrementing x until it reaches the value of width again. It goes forever until you stop the program.
Here's what the whole program looks like now:
// move the lightning bolt horizontally // when it gets to the right edge, put it back where it started //declare some variables first int y = 20 ; int x = 10 ; //setup function void setup () { size ( 200 , 200 ); } //draw function where the action happens void draw () { background ( 0 ); smooth (); noStroke (); fill ( 250 , 230 , 5 ); beginShape (); vertex (x,y); vertex (x,y+ 50 ); vertex (x+ 10 ,y+ 40 ); vertex (x,y+ 100 ); vertex (x+ 10 ,y+ 90 ); vertex (x,y+ 160 ); vertex (x+ 40 ,y+ 65 ); vertex (x+ 30 ,y+ 75 ); vertex (x+ 40 ,y+ 25 ); vertex (x+ 30 ,y+ 35 ); vertex (x+ 40 ,y); endShape ( CLOSE ); //increment the value of x by 1 each time the program loops through draw() x++; if (x> width ){ x= 0 ; } } |
What if we want to make the lightning bolt move faster? Change the line where we are incrementing the value of x to something like:
x = x + 5;What if we want to make the lightning bolt move slower? Change the line where were are incrementing the value of x to something like:
x = x + 0.5;
If you haven't programmed before, then it may take some extra thinking to grasp the meaning of an expression such as x = x + 5;. If this were math class, then you'd quickly realize that there is no such value of x that will make the equation x = x + 5 work. You'd collect like terms and be left with the expression 0 = 5 which is no good. But this isn't math class.
So the expression x = x + 5; is not there to ask the computer to solve for x. What it is saying is: take the value of x that you have in memory right now, add 5 to it, and then take whatever answer you get and reassign that number to x. (Tell x to be the new value you just computed.)
If x were 1, then after performing the command x = x + 5, the value of x will be 6. Any other command or expression performed from here on out will substitute "6" every time x shows up in the program, until x gets reassigned to something else.
There's no rule that says we have to use whole numbers, except for the fact that when we first declared x in our program, we declared it as an integer. That means we cannot give it a fractional part. Therefore if you write x=x+0.5 like I suggested you try out, you will get an error telling you "cannot convert from float to int." What's that about?
If we want to be able to allow x to have a fractional part, we need x to be a floating point number instead of an integer. These are called "floats" in Processing. A float is a floating-point number, which is a number that is not an integer, such as 1.5 or PI (π). Why bother with the difference between ints and floats? Why not just call them all “numbers” or something? Well, one difference between a float and an int is the amount of memory your computer has to allocate to store it. Also, integers are precise and floats are not.
Let’s say I have a cookie. I can declare int numCookie = 1 to tell Processing that I have exactly one cookie. If I’m going to share it with 6 other friends of mine I will break it into seven pieces. If I am really accurate at cookie-breaking we will all get exactly one seventh of the cookie. We can write this precisely as 1/7 on paper. However, if we wanted to express that in a Processing program, we’d probably declare something like
float cookiePortion0 = 0.14 ; float cookiePortion1 = 0.14 ; float cookiePortion2 = 0.14 ; float cookiePortion3 = 0.14 ; float cookiePortion4 = 0.14 ; float cookiePortion5 = 0.14 ; float myCookiePortion = 0.14 ; |
If we add up all the floats we get 0.98, not 1. We could write out each number to more decimal places. 1/7 is actually 0.142857 . . . but it is a repeating decimal. We can’t write an infinite number of digits past the decimal because we don’t want our entire computer’s memory taken up by deciding how much of a cookie each person is getting. The computer has to truncate the number at some point, and it will probably be good enough, but it is not perfect. That's the difference between an int and a float.
In our lightning bolt program, we'll change the declaration of the variable x up at the top to say
float x = 10 ; |
and then we are all set to increment x by a float later on in the program.
We also don't have to make the lightning bolt move from right to left. It can go wherever we want, limited by imagination only.
To make it go down instead of sideways, increment y instead of x, like so:
y++; if (y == height ) { y = 0 ; } |
If you want the lightning bolt to move diagonally, change both x and y with each loop through draw.
If you want the lightning bolt to move back and forth, you have to find a way to use the if test to change the direction of motion in x or y or both. Try it yourself and then check my screencast below to see how I did it.
Simply put, if you place a call to background() inside draw(), then it is the equivalent of erasing the screen with each run through draw(), which makes it look like the object is moving because the human eye can't refresh as fast as your computer display and Processing makes a run through draw() with each new frame your computer draws to its screen. On the other hand, if you put the call to background() inside setup(), you will draw over your previous iterations, without erasing them, essentially leaving a trail of an object's previous positions. Check out the program below. It draws a pink display window on which an orange circle is moving back and forth while a black line moves up and down.
float y = 0.0 ; float x = 0.0 ; float ell_direction = 1 ; float line_direction = 1 ; void setup () { size ( 200 , 200 ); } void draw () { background ( 234 , 128 , 128 ); frameRate ( 60 ); stroke ( 51 , 34 , 45 ); line ( 0 , y, width , y); y = y + ( 1 * line_direction); fill ( 245 , 166 , 47 ); ellipse (x, height / 2 , 10 , 10 ); x = x + ( 0.5 * ell_direction); if (x > width || x < 0 ) { ell_direction = ell_direction * (- 1 ); } if (y > height || y < 0 ) { line_direction = -line_direction; } } |
What would it look like if you cut and pasted the line that says background(234, 128, 128); out of draw() and into setup()?
//draw a circle. Make it move across the screen following the path f(x)=sin(x) float x= 0 ; float y= 100 ; int direction = 1 ; void setup (){ size ( 360 , 200 ); } void draw (){ rectMode ( CENTER ); background ( 200 ); fill ( 0 ); if (x< 0 || x> width ){ direction = direction*- 1 ; } if (direction == 1 ){ ellipse (x,y+ 20 * sin (direction*x* PI / 90 ), 20 , 20 ); x=x+direction; } else if (direction == - 1 ) { rect (x,y+ 20 * sin (direction*x* PI / 90 ), 20 , 20 ); x=x+direction; } else { println ( "I don't know what to do!" ); } } |
What it looks like running:
Let me explain the thought process behind building the program above. I could go through the program from top to bottom, but I didn't really write it in that order. I kind of wrote it from the middle to the outsides. First came the idea: I want to make a shape move across the window according some functional form other than a straight line, and I want it to stay on the screen so I have to make it turn around and go backwards when it gets to the side.
We haven't really talked about this yet, but Processing can make mathematical calculations such addition, subtraction, multiplication, division, exponentiation, logarithms, and trig functions. For my example I decided to draw a shape that traces out a sine wave across the screen. I decided to make it a circle, but I could have made it any shape I wanted. So the meat of the program is going to be something like this:
ellipse (x, sin (x),diameter,diameter); x++; |
This will make an ellipse of some diameter at the initial (x,y) position given by (x, sin(x)). Then we increment x by 1 with each run through draw() and so the circle will trace out a sine wave. So then I'll write some stuff around these lines that will make a runnable program, to see what it looks like so far. I need to write setup block that has the window dimensions in it, and I'll set the values of x and diameter while I'm at it.
float x= 0 ; float y= 100 ; int diameter= 20 ; void setup (){ size ( 200 , 200 ); } void draw (){ background ( 200 ); fill ( 0 ); ellipse (x,y+ sin (x),diameter,diameter); x++; } |
This works, except the circle doesn't go back and forth, and also it looks jittery. We can fix the back-and-forth problem by using an if test that tests where the circle is and what direction it is going and then changes the direction when we exceed the dimensions of our display window. We know how to do that. We just went over that in the previous couple of pages.
The jittery "problem" (I'm calling it a problem because I think it looks ugly. If you like it, then we're all done here!) arises because trig functions in Processing are in radians, not degrees. So the value of sin(x) is sweeping over its entire range very fast. One cycle of sine takes 360 degrees which is 2pi, a number between 6 and 6 and a half. How about if we make this shape sweep out exactly 2 cycles of sin(x) over the whole window? That would look cool. So we'll do a little math inside of where we wrote sin(x). While we're at it let's increase the amplitude so it looks nicer, too. In order to convert radians to degrees you do radians*PI/180. We want 2 cycles so let's multiply by PI/90 instead, and then let's help ourselves out by making the width of the window 360, so that each pixel is like 2 degrees of arc for our sine wave. I basically just trial-and-errored it until I got something that looked nice to me:
ellipse (x,y+ 20 * sin (x* PI / 90 ),diameter,diameter); |
Now let's make it go back and forth. I'll do it with an if test, like this:
if (x< 0 || x> width ){ direction = direction*- 1 ; } |
And then I'll change the line that says x++; to x = x + direction;. That way when direction is negative, x will decrement, and when direction is positive x will increment.
Great, I did it. But now that I got that to work, I realized that it would look even better if the bounceback on the window edges traced sin(-x) instead, so it looks like the ellipse grazes the side of the display window and turns in an arc instead of retracing its exact path. And while I'm at it, I'll change it so it's a square on the way back. The way I'm going to do this is to write another if test. This one in pseudo-English, will do the following:
if(I'm going from left to right){
draw a circle that goes like sin(x)
}
else if (I'm going from right to left){
draw a square that goes like sin(-x)
}
else {do some error handling in case of unexpected results}
Here's how it looks now:
//draw a circle. Make it move across the screen following the path f(x)=sin(x) float x= 0 ; float y= 100 ; int direction = 1 ; void setup (){ size ( 360 , 200 ); } void draw (){ rectMode ( CENTER ); background ( 200 ); fill ( 0 ); if (x< 0 || x> width ){ direction = direction*- 1 ; } if (direction == 1 ){ ellipse (x,y+ 20 * sin (direction*x* PI / 90 ), 20 , 20 ); x=x+direction; } else if (direction == - 1 ) { rect (x,y+ 20 * sin (direction*x* PI / 90 ), 20 , 20 ); x=x+direction; } else { println ( "I don't know what to do!" ); } } |
When I added the square, I changed rectMode to CENTER because otherwise there would be a jump when the shape changed from circle to square. Instead of doing sin(x) and sin(-x) I took advantage of the fact that direction was going from positive to negative so I could just use that variable to set which kind of sine function I wanted, and then I could write the same line both times. I could have just done an if and an else to do the switching between circle and square since direction can only be 1 or -1 in this program. However, it's not a bad idea to get in the habit of putting an else in there to mop up in case you made a mistake somewhere else in your code and there is actually an option that gets triggered that you didn't count on.
Now you can expertly move a shape you drew around your screen at different speeds! Cool!
3.1 Write a program that moves a custom shape around the screen (your choice about what path it follows: straight line or some other function). It should stay on the screen and change direction if it encounters any of the sides of the display window.
3.2 Modify your program from Exercise 3.1 so that the shape also changes color if it hits any of the sides of the display window
Submit Exercise 3.2 to its appropriate assignment in the Lesson 3 module in Canvas. Name your file lastname3_2.pde
Your program should demonstrate correct usage of an if statement to change the direction and fill and/or stroke color of your shape. Your shape should be a custom shape drawn using beginShape() and endShape(). Extra creativity such as using a for loop to set the vertices, or creating a composition with more than one shape is encouraged!
You have reached the end of Lesson 3! Double-check the to-do list on the Lesson 3 Overview page to make sure you have completed all of the activities listed there before you begin Lesson 4.
You should have been participating in the teaching/learning discussion and you should have submitted Exercise 3.2 to the Lesson 3 dropbox.
Our first foray into writing interactive programs in which you can make what's going on in the display window respond to the mouse or keyboard while it is running.
By the end of this lesson, you should be able to:
This lesson will take us one week to complete, 9 - 15 June 2021. This lesson involves two graded items:
If you have any questions, please post them to our Questions? discussion forum (not e-mail) in Canvas. I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
The articles we'll read for this week's discussion are linked from the Lesson 4 discussion board in Canvas.
Coxon, S.V., 2012, Innovative allies: Spatial and creative abilities, Gifted Child Today, 35, p. 277-284.
Mayer, R.E., and V.K. Sims, 1994, For Whom Is a Picture Worth a Thousand Words? Extensions of a Dual-Coding Theory of Multimedia Learning, Journal of Educational Psychology, 86, p. 389-401.
The first paper discusses the fact that contemporary school curricula do not specifically address spatial visualization skills but that innovative teachers could quite easily incorporate activities that speak to these skills while still meeting the various standards of learning. Teaching kids some computer programming is specifically mentioned. If anybody wants to, go to the program called "Scratch [12]" and play around.
The second paper is by educational psychologists who are trying to discover what kind of person benefits from what kind of educational presentation. The idea is that while technology has enabled us to produce whiz-bang animations and graphics, research into whether these techniques actually help people learn hasn't quite caught up yet. So they ran some experiments to try to figure this out.
As you read, consider the following questions, which we will discuss as a class:
Once you have finished the readings, engage in a class discussion that will take place over the entire week devoted to Lesson 4. This discussion will require you to participate multiple times over that period.
You will be graded on the quality of your participation. See the grading rubric [2] for specifics.
Another fun thing to do is write programs that involve interactivity via the keyboard and the mouse. Processing stores the position of the cursor as long as the cursor is inside the display window. It stores that position inside the variables mouseX and mouseY. It also stores the most recent previous position of the cursor in the variables pmouseX and pmouseY. You can take advantage of this by telling your program to do various things depending on where the cursor is.
This program draws a square and circle to the screen. The circle follows the cursor around the display window. Both the circle and square change color depending on the cursor's position.
color bg = color ( 220 , 220 , 200 ); void setup () { background (bg); size ( 200 , 200 ); } void draw () { strokeWeight ( 1 ); fill ( 48 , 107 , 198 , 100 ); if ( mouseX >= 100 && mouseY < 100 ) { fill ( 242 , 99 , 42 , 100 ); } if ( mouseX < 100 && mouseY >= 100 ) { fill ( 146 , 21 , 175 , 100 ); } rect ( 40 , 40 , 40 , 40 ); ellipse ( mouseX , mouseY , 50 , 50 ); } |
Below is another example program that uses mouseClicked(). mouseClicked() is a separate function written in its own block after draw(). In this program, clicking inside one of the squares will make the other square change color.
// Click within a square to change // the color of the other square. color value = color ( 255 , 0 , 0 ); color value2 = color ( 0 , 255 , 0 ); void setup () { size ( 200 , 200 ); background ( 255 ); } void draw () { fill (value); rect ( 25 , 25 , 50 , 50 ); fill (value2); rect ( 75 , 75 , 50 , 50 ); } // The mouseClicked function is called after a mouse button is pressed // and then released. void mouseClicked() { //if the cursor is inside the top square and the color of the bottom //square is green . . . if ( mouseX < 75 && mouseY < 75 && mouseX > 25 && mouseY > 25 && value2 == color ( 0 , 255 , 0 )) { value2 = color ( 0 , 255 , 255 ); } // If the cursor is inside the top square and the color of the bottom // square is aqua . . . else if ( mouseX < 75 && mouseY < 75 && mouseX > 25 && mouseY > 25 && value2 == color ( 0 , 255 , 255 )) { value2 = color ( 0 , 255 , 0 ); } //if the cursor is inside the bottom square and the color of the top //square is red . . . else if ( mouseX < 125 && mouseY < 125 && mouseX > 75 && mouseY > 75 && value == color ( 255 , 0 , 0 )) { value = color ( 255 , 0 , 255 ); } //if the cursor is inside the bottom square and the color of the top //square is pink . . . else if ( mouseX < 125 && mouseY < 125 && mouseX > 75 && mouseY > 75 && value == color ( 255 , 0 , 255 )){ value = color ( 255 , 0 , 0 ); } //tell me if I haven't satisfied any of the above possibilities else { println ( "You have to click inside a square for something to happen!" ); } } |
You already saw an example of the if-else structure back in Lesson 3, but I'm going to repeat the explanation here anyway. Bare bones it looks like:
if else if else if else if else |
inside the mouseClicked() function.
The way it works is like this:
if (test is true ) { do something; } else if (first test was false , then test if this test is true and if it is) { do some other thing; } else if (first two tests were false , test this one) { do some other thing; } else if (first three tests were falst, test this one) { do some other thing; } else { none of the above possibilities were true , so do this by default ; } |
You can write as many else ifs as you want to in between the first if and the final else. The final else does not include a parenthetical test because it is there to mop up. It will execute its commands by default when none of the tests above it have been satisfied.
Screenshot of the text of the program and a still from the program running in which clicking the mouse in certain regions changes the fill color of the squares.
Your turn to play around with mouse inputs:
4.1 Draw a series of points in a trail following the mouse position.
4.2 Draw a shape that follows the mouse but does not leave a trail.
4.3 Draw a shape that follows the mouse but in reverse (reverse x and y or reverse the response to changes in directions of x and y)
4.4 Draw a shape that changes size depending on the mouse position.
4.5 Draw three different shapes of three different colors to the screen and have them move in an interesting relationship to the mouse when the mouse is moved, and have them change color when the mouse is clicked.
Here's another example of user input that involves both the mouse and the keyboard. This program draws a line on the screen that follows the mouse if you also hold down the "r" key or the "k" key. See the demonstration of pmouseX and pmouseY and also the variable keyPressed.
/* Move your mouse across the screen while holding down "r" or "k" key to draw a line that follows the mouse */ // set the background color color bg = color ( 220 , 220 , 200 ); // define a shade of red that I like rd = color ( 242 , 42 , 48 ); void setup () { size ( 200 , 200 ); background (bg); } void draw () { strokeWeight ( 2 ); //keyPressed is a boolean variable. //That means it can either be "true" or false" //Capitals are different from lowercase. //I wrote the if test this way so in case the //user has caps lock on, it will still work if (( keyPressed == true ) && ( key == 'r' || key == 'R' )) { stroke (rd); line ( mouseX , mouseY , pmouseX , pmouseY ); } if (( keyPressed == true ) && ( key == 'k' || key == 'K' )) { stroke ( 0 ); line ( mouseX , mouseY , pmouseX , pmouseY ); } } |
Here's a program that combines keyboard input with moving a shape across the screen, so we are reviewing bits of Lesson 3 while adding Lesson 4 skills here:
//move a shape around the screen according to keyboard inputs int x= 100 ; int y= 100 ; void setup (){ size ( 200 , 200 ); pixelDensity( 2 ); stroke ( 0 ); ellipse (x,y, 10 , 10 ); } void draw (){ //stroke(0); //ellipse(x,y,10,10); if (( keyPressed == true ) && ( key == 'u' || key == 'U' )){ stroke ( 255 , 0 , 0 ); y--; } else if ( keyPressed == true && ( key == 'd' || key == 'D' )){ stroke ( 0 , 255 , 0 ); y++; } else if ( keyPressed == true && ( key == 'r' || key == 'R' )){ stroke ( 0 , 0 , 255 ); x++; } else if ( keyPressed == true && ( key == 'l' || key == 'L' )){ stroke ( 255 , 255 , 0 ); x--; } else { ellipse (x,y, 10 , 10 ); } } |
The beginning position is just a black circle drawn to the middle of the display window. It doesn't do anything unless you type the u, d, l, or r keys, in which case the circle jumps up, down, left, or right, and changes color. If no key is being pressed, the circle just sits still in its most recent position with its most recent color.
4.6 Make a virtual Etch-a-Sketch in which you use the keyboard to draw a black line on a grey background.
The random function is a fun way to produce unpredictable results in a drawing. It works two ways. The first way is that you specify one number inside the parentheses, such as random(100), and a float variable will be created between zero and the number you wrote, in this case 100. The second way is that you specify two numbers inside the parentheses, and a float variable will be created between the two numbers. For example, random(2,5) will output a float between 2 and 5.
Test out the code below to draw circles on the display window, for example:
// draw several circles to the screen in unpredictable places void setup () { size ( 400 , 400 ); } void draw () { float x = random ( width ); // A number between 0 and width float y = random ( height ); // A number between 0 and height float rad = random ( 20 ); // A number between 0 and 20 ellipse (x, y, rad, rad); // Draw the ellipse using the created numbers. // On the next loop through draw, new numbers // will be created. } |
The code above continuously draws circles to the screen in a variety of places because each time it loops through draw(), the x and y values are reset to a random value in between 0 and the number specified, which is width for x and height for y. The radius of each circle is a randomly generated number between 0 and 20.
We can modify that code in a fun and more visually interesting way by also choosing the color of each circle with the random() function, like this:
// Draw several circles to the screen in unpredictable places. // Make their colors unpredictable, too. void setup () { size ( 400 , 400 ); } void draw () { float x = random ( width ); // A number between 0 and width float y = random ( height ); // A number between 0 and height float rad = random ( 20 ); // A number between 0 and 20 fill ( random ( 255 ), random ( 255 ), random ( 255 )); //random color ellipse (x, y, rad, rad); //Draw the ellipse using the created numbers. //On the next loop through draw, new numbers //will be created. } |
The only difference between the two programs is that I added a line that sets the fill() in the second program. Now every time the program loops through draw() each value of red, green, and blue is randomized, too. The default is for random() to produce a float, but you can make it an int instead by enclosing the call to random() inside int(). For example, int x = int(random(4)) will initialize the variable x and set it to an integer between 0 and 4.
Below is a screencast of me walking you through the randomly colored circles program (0:37).
4.7: Modify the program above so that the aspect ratio of each ellipse is also randomized
4.8: Modify the program above so that the top half of the screen gets filled with randomly-placed rectangles and the bottom half gets filled with randomly-placed circles
Submit exercise 4.8 to its assignment dropbox in Canvas. Name your file lastname4_8.pde
In Exercise 4.8 I want to see correct use of the random() function.
We’ve already learned how to write a for loop to make a repetitive sequence of commands more compact to write. Another great utility that can save writing is a function.
You’ve already worked with some functions that are intrinsic to Processing, such as random() that you just learned.
Functions take arguments to control their behavior. For example, the ellipse() function draws an ellipse. ellipse() takes four arguments. They are: 1. the x-coordinate, 2. the y-coordinate, 3. the width of the ellipse, and 4. the height of the ellipse. So, when you want to draw an ellipse, you have to specify these four things in that order.
You can create your own functions and name them yourself. Here are some examples that show you why you’d want to do this and how to do it.
This program draws a figure to the screen in a not-so-smart way by setting each shape explicitly with numbers. I included what the house looks like in the screenshot.
//draw a house in the dumbest possible way void setup () { size ( 200 , 200 ); // We are just drawing a static figure. // Therefore, noLoop tells draw to just run once. noLoop (); } void draw () { fill ( 46 , 136 , 242 ); rect ( 90 , 100 , 40 , 40 ); fill ( 0 ); triangle ( 80 , 100 , 110 , 70 , 140 , 100 ); fill ( 93 , 50 , 10 ); rect ( 100 , 120 , 10 , 20 ); fill ( 242 , 213 , 46 ); rect ( 95 , 105 , 10 , 10 ); rect ( 115 , 105 , 10 , 10 ); line ( 100 , 105 , 100 , 115 ); line ( 95 , 110 , 105 , 110 ); line ( 120 , 105 , 120 , 115 ); line ( 115 , 110 , 125 , 110 ); } |
Now, let’s be a little smarter and use some variables to set the positions of the shapes that make up the figure relative to each other, like this:
// Draw a house. Use variables. int x = 90 ; int y = 100 ; void setup () { size ( 200 , 200 ); // We are just drawing a static figure. // Therefore, noLoop tells draw to just run once. noLoop (); } void draw () { fill ( 46 , 136 , 242 ); rect (x, y, 40 , 40 ); fill ( 0 ); triangle (x - 10 , y, x + 20 , y - 30 , x + 50 , y); fill ( 93 , 50 , 10 ); rect (x + 10 , y + 20 , 10 , 20 ); fill ( 242 , 213 , 46 ) rect (x + 5 , y + 5 , 10 , 10 ); rect (x + 25 , y + 5 , 10 , 10 ); line (x + 10 , y + 5 , x + 10 , y + 15 ); line (x + 5 , y + 10 , x + 15 , y + 10 ); line (x + 30 , y + 5 , x + 30 , y + 15 ); line (x + 25 , y + 10 , x + 35 , y + 10 ); } |
The example above draws the exact same house as before, but now we can control where the house goes just by changing x and y instead of going into each line and recalculating. (Recall this concept from Lesson 2.) There are 15 lines of code inside draw(). What if we wanted to draw two houses? We could declare two more variables and write those same 15 lines again, but then our code inside draw() will be 30 lines long. If we wanted ten houses our code would suddenly be 150 lines long. That would be really annoying. If we write a function that has the 15 lines needed to make the house inside it, then we can just call the function with one line of code, so we only have to write those 15 lines one time.
Here’s an example below in which I define the house() function. You can make up any name for your own functions but don’t make up a name that Processing already uses, such as ellipse() or something like that. My house() function will take two arguments: the x and y position.
// Draw a house using a function. void setup () { size ( 200 , 200 ); // We are just drawing a static figure. // Therefore, noLoop tells draw to just run once. noLoop (); } void draw () { // The house function needs two arguments, x and y. house( 90 , 100 ); } // Here is the house function. // It is written outside of draw. // The two arguments it will take are written inside parentheses. void house( int x, int y) { fill ( 46 , 136 , 242 ); rect (x, y, 40 , 40 ); fill ( 0 ); triangle (x - 10 , y, x + 20 , y - 30 , x + 50 , y); fill ( 93 , 50 , 10 ); rect (x + 10 , y + 20 , 10 , 20 ); fill ( 242 , 213 , 46 ) rect (x + 5 , y + 5 , 10 , 10 ); rect (x + 25 , y + 5 , 10 , 10 ); line (x + 10 , y + 5 , x + 10 , y + 15 ); line (x + 5 , y + 10 , x + 15 , y + 10 ); line (x + 30 , y + 5 , x + 30 , y + 15 ); line (x + 25 , y + 10 , x + 35 , y + 10 ); } |
The program above draws the exact same house as in the previous two programs, and at first glance it doesn’t look like we have gained too much from creating a function. However, now we can make a bunch of houses very easily -- see below:
The program below draws four houses in different places on the screen, but I only had to write the function containing the details of the house one time.
// Draw multiple houses using a function. void setup () { size ( 200 , 200 ); // We are just drawing a static figure. // Therefore, noLoop tells draw to just run once. noLoop (); } void draw () { // I'm calling the house function four times with different x // and y values to make four houses. house( 90 , 100 ); house( 40 , 40 ); house( 100 , 150 ); house( 10 , 60 ); } // But down here I only have to write the house function once. void house( int x, int y) { fill ( 46 , 136 , 242 ); rect (x, y, 40 , 40 ); fill ( 0 ); triangle (x - 10 , y, x + 20 , y - 30 , x + 50 , y); fill ( 93 , 50 , 10 ); rect (x + 10 , y + 20 , 10 , 20 ); fill ( 242 , 213 , 46 ) rect (x + 5 , y + 5 , 10 , 10 ); rect (x + 25 , y + 5 , 10 , 10 ); line (x + 10 , y + 5 , x + 10 , y + 15 ); line (x + 5 , y + 10 , x + 15 , y + 10 ); line (x + 30 , y + 5 , x + 30 , y + 15 ); line (x + 25 , y + 10 , x + 35 , y + 10 ); } |
Now that you’ve got the basic idea, it’s pretty easy to add something to your function. For example, what if we wanted to choose the color of each house, too? We can add that as an argument to the function, like this:
// Draw a small neighborhood with variety in color void setup () { size ( 200 , 200 ); // We are just drawing a static figure. // Therefore, noLoop tells draw to just run once. noLoop (); } void draw () { // The call to house() needs four arguments now. // The arguments are x position, y position, siding color, and roof color. house( 90 , 100 , color ( 46 , 136 , 242 ), color ( 255 )); house( 40 , 40 , color ( 250 , 91 , 221 ), color ( 200 )); house( 100 , 150 , color ( 232 , 192 , 150 ), color ( 100 )); house( 10 , 60 , color ( 148 , 229 , 149 ), color ( 0 )); } // See how I've added the third and fourth arguments at the beginning inside // the parentheses where the arguments are listed? void house( int x, int y, color c, color r) { fill (c); rect (x, y, 40 , 40 ); fill (r); triangle (x - 10 , y, x + 20 , y - 30 , x + 50 , y); fill ( 93 , 50 , 10 ); rect (x + 10 , y + 20 , 10 , 20 ); fill ( 242 , 213 , 46 ) rect (x + 5 , y + 5 , 10 , 10 ); rect (x + 25 , y + 5 , 10 , 10 ); line (x + 10 , y + 5 , x + 10 , y + 15 ); line (x + 5 , y + 10 , x + 15 , y + 10 ); line (x + 30 , y + 5 , x + 30 , y + 15 ); line (x + 25 , y + 10 , x + 35 , y + 10 ); } |
So now that we have made this function, if we ever want to draw some houses in another program, we can just copy and paste this function in without worrying about what each line does. All we have to think about is where to put the house and what color to make it.
4.9 Take your custom shape from previous exercises (or make a new one) and write it as a function.
4.10 Modify your program in 4.9 so that your function is drawn to the screen with a random color scheme.
4.11 Choice!: Modify your program in 4.9 so that your shape is drawn to the display window at the position where you click your mouse. (You’ll have to get rid of noLoop() to make this work)
OR modify your program so that your shape follows your mouse around the display window
OR modify your program so that the display window fills up with your shape in randomized locations. (NOTE: why not do them all? But you only have to submit one. See below)
4.12 Review: Modify your program so that your shape moves back and forth across the screen. (You’ll have to get rid of noLoop() for this one, too.)
Turn in Exercise 4.11 and 4.12 to their assignment dropboxes in Canvas.
In Exercise 4.11 I want to see your shape written as a function and plotted with whichever new skill appeals to you. In Exercise 4.12 I want to see your shape written as a function, and correct use of if to move it back and forth, recalling how to do that from Lesson 3.
We've progressed pretty far at this point. You can make interactive art with your computer!
4.1 Draw a series of points in a trail following the mouse position.
4.2 Draw a shape that follows the mouse but does not leave a trail.
4.3 Draw a shape that follows the mouse but in reverse (reverse x and y or reverse the response to changes in directions of x and y)
4.4 Draw a shape that changes size depending on the mouse position.
4.5 Draw three different shapes of three different colors to the screen and have them move in an interesting relationship to the mouse when the mouse is moved, and have them change color when the mouse is clicked.
4.6 Make a virtual Etch-a-Sketch in which you use the keyboard to draw a black line on a grey background.
4.7 Modify my "random circles" program so that the aspect ratio of each ellipse is also randomized
4.8 Modify my "random circles" program so that the top half of the screen gets filled with randomly-placed rectangles and the bottom half gets filled with randomly-placed circles.
4.9 Take your custom shape from previous exercises (or make a new one) and write it as a function.
4.10 Modify your program in 4.9 so that your function is drawn to the screen with a random color scheme.
4.11 Choice!: Modify your program in 4.9 so that your shape is drawn to the display window at the position where you click your mouse.
OR modify your program so that your shape follows your mouse around the display window
OR modify your program so that the display window fills up with your shape in randomized locations. (NOTE: why not do them all? But you only have to submit one. See below)
4.12 Review: Modify your program so that your shape moves back and forth across the screen.
You have reached the end of Lesson 4! Double-check the to-do list on the Lesson 4 Overview page to make sure you have completed all of the activities listed there before you begin Lesson 5. You should have submitted three programs to the dropbox: exercises 4.8, one of the 4.11 options, and 4.12. Also, you should have been participating all week in the reading discussion.
By the end of this lesson, you should be able to:
This lesson will take us one week to complete. 16-22 June 2021. This lesson involves the following graded items:
Assignment | Description | Submitted for Grading? |
---|---|---|
Discussion | Teaching/Learning Discussion #2 | yes -- participate throughout the week in a Canvas discussion board |
Exercise 5.1 | write your own paragraph in a font you like | no |
Exercise 5.2 | write a program with words that move up and down | no |
Exercise 5.3 | modify the program you wrote in 5.2 so that the words also change color while they move | either submit this OR submit Exercise 5.4 |
Exercise 5.4 | tell a story with moving words | either submit this OR submit Exercise 5.3 |
Exercise 5.5 | experiment with translations to see what happens when you use more than one pushMatrix() popMatrix() pair | no |
Exercise 5.6 | use translate() and rotate() to change a square into a diamond | no |
Exercise 5.7 | use translate() and rotate() to change the coordinate system so that the origin is on the bottom left and increases to the right and upwards | no |
Exercise 5.8 | write a function that uses a combination of scale(), rotate(), or translate() and then do something interactive with it such as having it plot to the display window when the mouse is clicked, or have it move by itself | yes - submit this. |
If you have any questions, please post them to our Questions? discussion forum (not e-mail) in Canvas. I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
For this activity, I want you to reflect on what we've covered so far and consider, not necessarily how you might adapt these materials to your own classroom, but more like how any of this gets at the spatial visualization and various other cognitive skills discussed in some of the papers we read. Is your grey matter changing?! Since this is a discussion activity, you will need to enter the discussion forum more than once to read and respond to others' postings. This discussion will take place over the whole week of this lesson.
You will be graded on the quality of your participation. See the grading rubric [2] for specifics on how this assignment will be graded.
If you want to write text to the screen, Processing has many fonts to choose from. You can pick one by choosing “Create Font” from the Tools drop-down menu. Then in your program, you declare the font variable, load the font, and use the text() function to write text to the screen using the font you created.
Note: Even though Processing itself is platform independent, different operating systems have different fonts. I am a Mac user. If you are a PC user, you might not have the exact fonts I demo in my examples below, but the point is that there are lots to choose from and the way you work them into a program is the same.
Here is an example of a short program I wrote at the end of the 2011 baseball season using text():
// Load a font // Write text to the screen PFont font1; void setup () { size ( 400 , 200 ); font1 = loadFont( "Futura-CondensedExtraBold-36.vlw" ); textFont (font1); textAlign ( CENTER ); } void draw () { background ( 252 , 143 , 143 ); fill ( 160 , 8 , 8 ); text ( "Cards win!" , width / 2 , height / 2 ); } |
Here’s an example with multiple fonts.
// Load more than one font // Write text to the screen PFont font1; PFont font2; PFont font3; void setup () { size ( 600 , 200 ); font1 = loadFont( "BankGothic-Medium-32.vlw" ); font2 = loadFont( "Baskerville-BoldItalic-32.vlw" ); font3 = loadFont( "CenturyGothic-Bold-32.vlw" ); } void draw () { background ( 252 , 143 , 143 ); textFont (font1); textAlign ( CENTER ); fill ( 160 , 8 , 8 ); text ( "Carpenter blanked the Astros" , width / 2 , 50 ); textFont (font2); fill ( 0 ); text ( "and" , width / 2 , 80 ); textFont (font3); fill ( 22 , 8 , 160 ); text ( "The Braves choked again" , width / 2 , 110 ); } |
Notice that in the program in Example 5.1 I placed each line of text by hand because I specified its x and y coordinates. An alternative to doing this is to define all the text you want to display as a String and then use optional parameters in text() to define a box within which the text is written. This is a nice option because you don’t have to mess with the spacing manually. The syntax for setting text in a box is text(string, xTopLeftCoordinate,yTopLeft Coordinate,width,height).
Here is an example of setting the text within a box.
// load a font //write text to the screen in a textbox PFont font1; void setup (){ size ( 400 , 300 ); font1 = loadFont( "BankGothic-Medium-32.vlw" ); } void draw (){ background ( 255 ); textFont (font1); textAlign ( CENTER ); textLeading( 30 ); fill ( 160 , 8 , 8 ); String s = "Carpenter blanked the Astros and the Braves choked again" ; text (s, 50 , 20 , 300 , 200 ); } |
Notice how in the above program, the text was automatically broken up over several lines to fit into a box. Processing is also smart enough to break things up at the blank spaces instead of in the middle of words. I set the vertical spacing between lines of text with textLeading(dist) in which dist sets the number of pixels between each line. That keeps the text from being squished too much. Sometimes you just have to use trial and error to get things to look the way you want.
5.1: Modify any of my examples to write your own paragraph in a font you like.
Nothing yet. Stay tuned.
It’s a little more fun to do something with the text you draw to the screen instead of having it just sit there. Since Processing draws text as an image, you can treat a word just like any other shape.
Here’s an example in which a word follows the mouse around on the screen (code modified from Reas and Fry, 2007).
// The word "follow" follows the mouse // its position is tied to the mouse position. PFont font1; void setup () { size ( 300 , 300 ); font1 = loadFont( "AcademyEngravedLetPlain-48.vlw" ); textFont (font1); textAlign ( RIGHT ); fill ( 0 ); } void draw () { background ( 124 , 194 , 250 ); text ( "follow" , mouseX , mouseY ); } |
Here’s an example of a word changing color frame by frame:
// The word "embarrassed" changes color from neutral to red. PFont font1; int opacity = 0 ; int direction = 1 ; void setup () { size ( 400 , 200 ); font1 = loadFont( "AmericanTypewriter-Bold-48.vlw" ); textFont (font1); textAlign ( CENTER ); } void draw () { background ( 175 , 124 , 124 ); opacity += 1 * direction; if ((opacity < 0 ) || (opacity > 255 )) { direction = -direction; } fill ( 255 , 0 , 0 , opacity); text ( "embarrassed" , width / 2 , height / 2 ); } |
The program in Example 5.4 makes use of the new piece of syntax += which is a compact way to combine addition with assignment. Let's say you have two values: x and y. In Processing,x += y is the same as x = x + y. You are adding the value of y to x and reassigning x the new resulting value all in one step.
Here’s an example of words that move across the screen
PFont font; float x1 = 0 ; float x2 = 200 ; int x1speed = 1 ; float x2speed = 0.73 ; void setup () { size ( 800 , 200 ); font = loadFont( "Trebuchet-BoldItalic-48.vlw" ); textFont (font); fill ( 0 ); } void draw () { background ( 204 ); x1 += x1speed; x2 += x2speed; if (x1 >= 550 ) { x1= 550 ; x2speed= 0 ; strokeWeight ( 3 ); line ( 765 , 0 , 765 , height ); } fill ( 68 , 143 , 232 ); text ( "Braves" ,x2, 100 ); fill ( 160 , 8 , 8 ); text ( "Cardinals" ,x1, 50 ); } |
This is an example of a visual representation of an idea or a time-dependent process, which is something that this programming language is good at doing. My guess is that you can be more creative than I am. See if you can come up with moving, changing, or interactive words that represent some kind of idea or process and illustrate it with your program!
5.2 Write a program with words that move up and down instead of right and left.
5.3 Modify the program in 5.2 with words that also change color as they move.
5.4 Tell a story with moving words
Turn in either 5.3 OR 5.4 to the Lesson 5 dropbox. ** Please zip your whole folder when you turn it in instead of just including your .pde file for this exercise. The reason is that your loaded font is included as a file in your folder where your .pde file also lives. If you don't include the whole thing then I have to go digging around to load the font when I run your program.
In your program I want to see that you can successfully load a font and use it, and I'd ideally like to see some kind of creative thinking about what you want to do with the words or letters that you have decided to draw to the display window.
You know that the default coordinate system has (0,0) in the upper left corner and increases to the right and downwards. You can change this system by calling the function translate(). The translate() function takes two arguments: the number of pixels added to x and the number of pixels added to y. All shapes drawn after translate() will adhere to the new coordinate structure.
The tricky thing to remember about translation is that you aren't really moving the shapes you draw. Instead you are moving the entire coordinate system relative to the display window. Consider the hand-drawn sketch below in which there is a heart whose top cusp is at (3,2), shown by the black dot.
Now if I translate the coordinate system 2 over and 1 down, the display window stays where it is and the heart stays where it is relative to the coordinate mesh, but the coordinate mesh itself has been shifted. So in pseudo-code this would be like:
translate ( 2 , 1 ); heart( 3 , 2 ); |
Translations are cumulative, so the third panel can be achieved either by translating (-5,1) from the second panel, or by translating (-3,2) directly from the first panel. If we had really drawn the third version in Processing, you'd only see the right half of the heart because the other side is out of the display window. Philosophers can tell us whether it exists or not, but in any case we can't see it.
So in pseudo-code this would be:
translate ( 2 , 1 ); translate (- 5 , 1 ); heart( 3 , 2 ); |
Or it could have been
translate (- 3 , 2 ); heart( 3 , 2 ); |
Note how I'm always drawing the heart itself with the same coordinates it had in the first place.
Here is an example in an actual program. Notice in this program that rectangle is always specified by the same coordinates. You can see how translation is cumulative and can be negative, too.
// Demonstrate Translation size ( 200 , 200 ); rect ( 10 , 10 , 50 , 20 ); // Draw a rectangle translate ( 100 , 100 ); // Move the origin to (100, 100) rect ( 10 , 10 , 50 , 20 ); // Draw the same rectangle, but now it is shifted translate ( 50 , 50 ); // Move the origin to (150, 150) rect ( 10 , 10 , 50 , 20 ); // Same rectangle shifted more translate ( 0 , - 75 ); // Translations can be negative fill ( 255 , 0 , 0 ); rect ( 10 , 10 , 50 , 20 ); // Same rectangle shifted up and colored red |
To change the coordinate system temporarily and then have it go back to the way it was before you made a transformation without having to manually make a negative translation, use pushMatrix() and popMatrix(). These functions always have to be used together. The way it works is that you stick in a pushMatrix() before you write the transformation and then you stick in a popMatrix() just before you want to revert back. The coordinate system will revert back to the way it was before pushMatrix().
// Use pushMatrix and popMatrix to revert the translation size ( 200 , 200 ); pushMatrix (); for ( int i = 1 ; i < 200 ; i++) { translate (i, i); rect ( 1 , 1 , 50 , 20 ); // Draw a rectangle } popMatrix (); ellipse ( 100 , 100 , 20 , 20 ); |
Note how in the code in Example 5.7 the circle draws in the center of the screen because the for loop was enclosed by a pushMatrix() popMatrix() pair, meaning that all those accumulated translations didn’t affect the circle.
5.5: Experiment with translations. See what happens when you make multiple translations and use more than one pushMatrix() popMatrix() pair
You can use the function scale() to zoom the coordinate system. It can can take either one argument, or else two arguments if you want to zoom the x and y coordinates differently. In the example below, pay attention to the fact that it's the coordinates that are being stretched, not the shapes. In effect, this means that the position of the shape will change as well as its size.
size ( 200 , 200 ); ellipse ( 20 , 20 , 10 , 10 ); pushMatrix (); scale ( 1.5 ); // Zoom axes by 1.5X ellipse ( 20 , 20 , 10 , 10 ); scale ( 1.5 ); // Scale is cumulative, so now things will be stretched by 2.25X ellipse ( 20 , 20 , 10 , 10 ); popMatrix (); // The ellipse below is the same size as the one inside the push/pop // pair that had been stretched by 2.25X ellipse ( 70 , 70 , 22.5 , 22.5 ); scale ( 1.5 , 1 ); // Only scaling x ellipse ( 70 , 70 , 10 , 10 ); |
In the program in Example 5.8, when the coordinate system got stretched, the strokeWeight got thicker, too. The strokeWeight is also affected by scale(). If you don’t want this to happen, you can get around it by dividing the strokeWeight by the scale factor, like this:
// Demonstrate the uses of scale // Fix the strokeWeight size ( 200 , 200 ); float s = 1.5 ; ellipse ( 20 , 20 , 10 , 10 ); scale (s); // Makes things 150% bigger strokeWeight ( 1 /s); // Divide by scale ellipse ( 30 , 30 , 10 , 10 ); scale (s); // Scale is cumulative strokeWeight ( 1 /( 2 *s)); // Divide by the total scale ellipse ( 40 , 40 , 10 , 10 ); scale (s, 1 ); // Only scaling x strokeWeight ( 1 /( 3 *s)); ellipse ( 50 , 50 , 10 , 10 ); |
You can rotate the coordinate system with the rotate() function. The rotate() function takes one argument, which is an angle in radians. (360 degrees = 2pi radians, so if you don’t like thinking in radians you can specify degrees and then multiply by pi/180 to convert to radians). The rotate() function will by default rotate clockwise around the origin of the coordinate system, so if you want the rotation to occur around a different point, you have to use translate() first to move the origin.
Let's start with the hand-drawn heart from the discussion of translation. Its top cusp is at (3,2).
Now let's rotate by 45 degrees and then draw the heart. In pseudo-code, this would be
rotate ( PI / 4 ); heart( 3 , 2 ); |
Here's what that looks like:
But what if you wanted to rotate the heart itself? The key is to remember that all rotations occur around (0,0). So to make a shape rotate around its own center of gravity, you basically have to draw the shape so that its center is at (0,0). Then you can do whatever rotation you want and it will appear that the shape rotates around itself. But you probably don't want to draw the shape up in the top corner where you can't see most of it! So you translate the coordinate system, thereby putting the origin in the place on the display window that you want, then draw the shape there.
In pseudo-code:
translate ( 3 , 2 ); rotate ( PI / 4 ); heart( 0 , 0 ); |
Here’s an example program that uses translate() and rotate().
// Demonstrate translate and rotate size ( 200 , 200 ) background ( 255 ); stroke ( 247 , 117 , 10 ); translate ( 100 , 100 ); strokeWeight ( 3 ); line ( 0 , 0 , 55 , 0 ); // A horizontal orange line rotate ( 60 * PI / 180 ); // How to write 60 degrees in radians line ( 0 , 0 , 55 , 0 ); // Orange line rotated |
Combining skills!
Here’s an example of a shape I made with translate and rotate. I turned it into a function and I’m having it move across the screen.
//the sun rises and sets float x = 10 ; float y . 190 ; float speed = 1.0 ; int direction = 1 ; int g = 10 ; void setup (){ size ( 400 , 200 ); } void draw (){ background ( 10 , g, 247 ); //I'm going to change the green so it has a variable smooth (); sun(x, y); // calls the sun function x = x + . 5 *speed; //moves to the right y = y - (. 5 *speed*direction); //moves up if (x > width || x < 0 ){ x= 1 ; // if it hits the right side, start over y= 190 ; direction = 1 ; } if (y ‹ 0 ){ direction *= - 1 ; // if it hits the top, go down } if (x== 1 ){ g= 10 ; //resets the green value } if (direction == 1 ){ g=g+ 1 ; //make the sky lighter as the sun goes up } else { g=g- 1 ; //make sky darker as the sun goes down } } // This is my sun function // Notice the use of push, pop, translate, and rotate void sun( float x, float y) ( pushMatrix (); translate (x,y); stroke ( 247 , 117 , 10 , 120 ); fill ( 247 , 200 , 10 ); ellipse ( 0 , 0 , 30 , 30 ); for ( int i = 0 ; i < 24 ; i++) { strokeWeight ( 3 ); rotate ( PI / 12 ); line ( 30 , 30 , 10 , 0 ); } popMatrix (); } |
5.6 Use translate() and rotate() to change a square into a diamond.
5.7: Use a combination of translate() and rotate() to change the coordinate system so that its origin is at the bottom left corner and increases to the right and upwards.
5.8: write a function that uses some combination of scale(), rotate(), or translate(), and then do something with it such as having it plot to the screen with a mouse click, or having it move by itself.
Turn in 5.8 to its assignment dropbox in Canvas.
In 5.8 I want to see you use new skills such as scale, rotate, and translate, combined with previously-learned skills such as function-writing, and/or mouse/keyboard interactivity.
In order to make a shape look like it is continuously rotating we can take advantage of the automatic looping inherent in draw(). The key is that instead of specifying an x or y coordinate that increments with time, we instead specify a rotation angle and have it increment with every run through draw().
For example, let's draw a square and rotate it about its own center of gravity. Remember that angles are in radians, so you have to choose a fairly small increment if you want your shape to spin slowly.
//twirling square float rotAngle= 0 ; void setup (){ size ( 200 , 200 ); } void draw (){ background ( 255 ); translate ( width / 2 , height / 2 ); rotate (rotAngle); rectMode ( CENTER ); fill ( 242 , 190 , 17 ); rect ( 0 , 0 , 50 , 50 ); rotAngle += 0.05 ; } |
Let's draw two squares now and see what that looks like:
//2 twirling squares float rotAngle= 0 ; void setup (){ size ( 200 , 200 ); } void draw (){ background ( 255 ); translate ( width / 2 , height / 2 ); rotate (rotAngle); rectMode ( CENTER ); fill ( 242 , 190 , 17 ); rect ( 0 , 0 , 50 , 50 ); rotAngle += 0.05 ; fill ( 242 , 126 , 17 ); rect ( 0 , 60 , 50 , 50 ); } |
It looks like the orange square is pinned to the yellow one since they are both rotating about the same point at the same speed. Let's say we want to make them rotate independently, with different speeds and directions. We can do that simply by using some more variables to define different rotation speeds and directions. If we want them to rotate about different origin points, then we need to put the calls to translate() and rotate() inside some pushMatrix() / popMatrix() pairs. Let's do that. I am going to change the program so that the orange square spins about its own center, counter-clockwise, and slower than the yellow square.
//2 twirling squares. Yellow goes clockwise. Orange counterclockwise float rotAngle1= 0 ; float rotAngle2= 0 ; void setup (){ size ( 200 , 200 ); } void draw (){ background ( 255 ); rectMode ( CENTER ); pushMatrix (); translate ( width / 2 , height / 2 ); rotate (rotAngle1); fill ( 242 , 190 , 17 ); rect ( 0 , 0 , 50 , 50 ); rotAngle1 += 0.05 ; popMatrix (); pushMatrix (); translate ( width / 2 + 60 , height / 2 ); fill ( 242 , 126 , 17 ); rotate (rotAngle2); rect ( 0 , 0 , 50 , 50 ); rotAngle2 -= 0.025 ; popMatrix (); } |
Probably the most important thing to remember about rotation is the following: First translate, then rotate. I will say it again. First translate, then rotate. When you get unexpected results from a rotate() command, it is usually because you forgot to translate first, so your shape flies off the display window where you can't see it anymore.
Here's another example of a program that uses scale(), translate(), and rotate().
//interlocking gears float x = 100 ; float y = 100 ; float gearAngle1 = 0 ; float gearAngle2 = 0 ; float gearAngle3 = 0 ; void setup (){ size ( 400 , 300 ); } void draw (){ background ( 10 , 10 , 247 ); smooth (); pushMatrix (); translate (x,y); rotate (gearAngle1); gear( 0 , 0 ); popMatrix (); gearAngle1 += . 01 ; pushMatrix (); translate (x+ 125 ,y+ 20 ); rotate (gearAngle2); scale ( 2 ); gear( 0 , 0 ); popMatrix (); gearAngle2 -=. 005 ; pushMatrix (); translate (x+ 133 ,y+ 135 ); rotate (gearAngle3); scale ( 0.75 ); gear( 0 , 0 ); popMatrix (); gearAngle3 +=. 015 ; } void gear( float x, float y) { pushMatrix (); translate (x,y); stroke ( 247 , 117 , 10 , 120 ); fill ( 247 , 200 , 10 ); ellipse ( 0 , 0 , 30 , 30 ); for ( int i = 0 ; i < 12 ; i++) { strokeWeight ( 5 ); rotate ( PI / 6 ); line ( 0 , 0 , 30 , 30 ); } popMatrix (); } |
This is a short program that combines skills from this lesson. I import a font and write a word to the screen. I also translate the coordinate system and rotate continuously so that the word spins around its own center.
// the word "pinwheel" spins around PFont font1; float rotAngle = 0 ; void setup (){ size ( 200 , 200 ); font1 = loadFont( "Herculanum-48.vlw" ); textFont (font1); textAlign ( CENTER ); } void draw (){ background ( 11 , 139 , 1 ); translate ( width / 2 , height / 2 ); rotate (rotAngle); text ( "pinwheel" , 0 , 0 ); rotAngle += 0.01 ; } |
(this is a repeat from the various pages where these were written.)
5.1: Modify any of my examples to write your own paragraph in a font you like.
5.2 Write a program with words that move up and down instead of right and left.
5.3 Modify the program in 5.2 with words that also change color as they move.
5.4 Tell a story with moving words
5.5: Experiment with translations. See what happens when you make multiple translations and use more than one pushMatrix() popMatrix() pair
5.6 Use translate() and rotate() to change a square into a diamond.
5.7: Use a combination of translate() and rotate() to change the coordinate system so that its origin is at the bottom left corner and increases to the right and upwards.
5.8: write a function that uses some combination of scale(), rotate(), or translate(), and then do something with it such as having it plot to the screen with a mouse click, or having it move by itself.
You should have turned in either Exercise 5.3 or Exercise 5.4 to its assignment dropbox and also Exercise 5.8 to its assignment dropbox. You should also have been participating in the Teaching/Learning discussion throughout the week.
Remember that in your Exercise 5.3 or 5.4 where you practiced with typography, you need to zip your whole folder and turn that into the dropbox so that I have the font you used and I can run your program.
This Lesson introduces one of the 3D renderers available in Processing, and we also start to see how external files (in this case image files) can be imported into Processing for use in a composition. We also learn how to save a still shot of our composition directly from our program, obviating the need to take a screen shot of it if you want to save it.
By the end of this lesson, you should be able to:
This lesson will take us one week to complete (23 - 29 Jun 2021). The deliverables for this lesson are programming exercises, detailed on the next pages.
Assignment | description | submitted for grading? |
---|---|---|
Exercise 6.1 | Draw three shapes to the screen all at once. Have each of them rotate about their own centers: one around the x axis, one around the y axis, one around the z axis | submit EITHER this program OR 6.3 to the dropbox. |
Exercise 6.2 | Finish the fourth side of the cube from Example 6.4. Change the program to rotate the cube with the mouse. | No |
Exercise 6.3 | Use beginShape(TRIANGLES) to draw a four-sided pyramid with mouse-controlled rotation in 3D space. | submit EITHER this program OR 6.1 to the dropbox |
Exercise 6.4 | Export one of your programs as an app that can be run outside of the Processing environment. | No |
Exercise 6.5 | Find/create a smallish snapshot so you can play around with it in a program. | No |
Exercise 6.6 | Using a head shot of you or a friend, make an animated person that walks across the screen, or jumps up and down, or says something with a speech bubble, by importing an image of the face and drawing the rest of the body with your programming skills. | Yes - submit this. |
If you have any questions, please post them to our Questions? discussion forum (not e-mail) in Canvas. I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
Last week when we rotated shapes, all the rotations were in the plane of the computer screen, meaning that the rotation was happening around the Z axis, if you think of the Z axis as a line drawn poking straight through your screen. Processing has a 3D renderer to allow you to work in a three-dimensional coordinate system. The default is that the positive Z direction is out of the screen towards you and the negative Z direction is into the screen away from you. In order to let Processing know that you want to have a 3D coordinate system, you have to specify the renderer when you call size(). We are going to work with the P3D renderer but there are other ones, such as OpenGL. You can look up the other ones at the Processing website if you want to.
Here are a series of three simple programs to demonstrate rotations in 3D. First, start with the rotation we know about from last week: rotation around the Z axis.
//rotating orange square float rotAngle = 0 ; void setup (){ size ( 400 , 400 ); } void draw (){ background ( 255 ); translate ( width / 2 , height / 2 ); rotate (rotAngle); rotAngle+= 0.01 ; fill ( 250 , 100 , 13 ); rectMode ( CENTER ); rect ( 0 , 0 , 200 , 200 ); } |
Now let’s take that same square and rotate it around the X axis. That program looks like this:
//rotating orange square //rotates around X axis float rotAngle = 0 ; void setup (){ size ( 400 , 400 , P3D ); } void draw (){ background ( 255 ); translate ( width / 2 , height / 2 , 0 ); rotateX(rotAngle); rotAngle+= 0.01 ; fill ( 250 , 100 , 13 ); strokeWeight ( 4 ); rectMode ( CENTER ); rect ( 0 , 0 , 200 , 200 ); } |
Notice the different things in this program: When I called size(), I specified P3D after I gave the dimensions of the window. When I called translate(), I used three arguments. The third one is for translation in Z. I used the command rotateX instead of just rotate.
Here’s the same program again except with rotation around the Y axis:
//rotating orange square //rotates around Y axis float rotAngle = 0 ; void setup (){ size ( 400 , 400 , P3D ); } void draw (){ background ( 255 ); translate ( width / 2 , height / 2 , 0 ); rotate (rotAngle); rotAngle+= 0.01 ; fill ( 250 , 100 , 13 ); strokeWeight ( 4 ); rectMode ( CENTER ); rect ( 0 , 0 , 200 , 200 ); } |
The only thing different between the program in Example 6.2 and the program in Example 6.1 is the use of rotateY instead of rotateX.
Now let’s modify our rotating square so that we are controlling the rotation with the mouse. You know all the commands to make this work already. Here’s how it is done. Decide how much of a rotation you want to allow (a full rotation is 360 degrees, or 2*PI radians). Then map this angle to the width or height of the screen based on the mouse location.
Here is an example of rotation about the Y axis based on the X location of the mouse. Does this seem counterintuitive? Think about it for a minute. Rotation around Y basically means side-to-side rolling, so to me it seems more intuitive to control this motion with the side-to-side, or X, position of the mouse.
//rotating orange square //rotates around Y axis with mouse void setup (){ size ( 400 , 400 , P3D ); } void draw (){ background ( 255 ); translate ( width / 2 , height / 2 , 0 ); float rotAngle = 2 * PI * mouseX / width ; rotateY(rotAngle); fill ( 250 , 100 , 13 ); strokeWeight ( 4 ); rectMode ( CENTER ); rect ( 0 , 0 , 200 , 200 ); } |
The most important line of this program is the one that says:
float rotAngle = 2*PI*mouseX/width;
That line does a lot for you. It tells Processing that you want one whole rotation to occur over the distance that equals the width of the window and you want the amount of rotation to be dependent on the X position of the mouse. It is a good idea to experiment with putting different numbers into this line to see what they do. For example, try getting rid of the number 2 or changing it to 4 and see what happens. What would you have to change to get one whole rotation about the X axis based on the Y position of the mouse? Here's a screencast of the mouse-controlled rotation program [23] (and a captioned version [24]) so you can see what it looks like when it is running.
Knowing how to rotate a flat shape in space is the first step to drawing an actual shape in three dimensions. There is a little bit of a trick to drawing in three dimensions and that is that the usual shape primitives, such as rect() and ellipse() don’t accept Z coordinates as arguments. If you want to build a three-dimensional object, you have to use beginShape() and endShape() because the vertex() command does accept Z coordinates.
If you have ever taken a drawing class, then you probably have learned how to draw 3D shapes on paper using perspective techniques, like in my sketch of a make believe city street:
Even with no formal training, most people I've ever met can draw a cube in perspective by drawing two overlapping squares and then connecting up all the corners.
Those techniques are great for visualizing a three-dimensional world on a flat canvas, but when we want to use computer graphics, we won't use these techniques. Instead we have to imagine where the vertices of our three dimensional shape really are with respect to each other, set those vertices, and then if we want the shape to be seen in perspective, we let the computer create the viewing angle.
So in the program examples below where we are going to draw a cube that rotates about its own center, we have to imagine where the 8 vertices are with respect to the center of a cube:
Let’s draw a cube that will rotate about its center. We’ll start by drawing one face of the cube, like this:
float theta= 0 ; float d = 100 ; //length of an edge of the cube void setup (){ size ( 600 , 400 , P3D ); } void draw (){ background ( 255 ); stroke ( 0 ); //make the origin the center of the cube translate ( width / 2 , height / 2 , 0 ); rotate (theta); theta+=. 01 ; beginShape (QUADS); //back face of the cube fill ( 255 , 0 , 0 ); vertex (-d/ 2 , -d/ 2 , -d/ 2 ); vertex (d/ 2 , -d/ 2 , -d/ 2 ); vertex (d/ 2 , d/ 2 , -d/ 2 ); vertex (-d/ 2 , d/ 2 , -d/ 2 ); endShape (); } |
Notice that I used the argument QUADS with beginShape(). That lets Processing know that I’m trying to draw something with four sides. This will become useful later because if we want to draw several four-sided shapes in a row, we don’t have to use separate beginShape()/endShape() pairs. You’ll see what I mean with the next step for this project when we add the front face of the cube:
float theta= 0 ; float d = 100 ; //length of an edge of the cube void setup (){ size ( 600 , 400 , P3D ); } void draw (){ background ( 255 ); stroke (s); //make the origin the center of the cube translate ( width / 2 , height / 2 , 0 ); rotateY(theta); theta+=. 01 ; beginShape (QUADS); //back face of the cube is red fill ( 255 , 0 , 0 ); vertex (-d/ 2 , -d/ 2 , -d/ 2 ); vertex (d/ 2 , -d/ 2 , -d/ 2 ); vertex (d/ 2 , d/ 2 , -d/ 2 ); vertex (-d/ 2 , d/ 2 , -d/ 2 ); //front face of the cube is yellow fill ( 255 , 255 , 0 ); vertex (-d/ 2 , -d/ 2 , d/ 2 ); vertex (d/ 2 , -d/ 2 , d/ 2 ); vertex (d/ 2 , d/ 2 , d/ 2 ); vertex (-d/ 2 , d/ 2 , d/ 2 ); endShape (); } |
See how there is only one beginShape() / endShape() pair. That is how putting QUADS as the argument to beginShape() saves us some work. When you put QUADS in as the argument to beginShape(), Processing expects you to write down a list of vertices in groups of 4 and it will make each group of 4 into a closed quadrilateral. That way you only have to do beginShape() / endShape() one time, just enclose all the vertices in there. You can do this in 2D also if you want to make a series of quadrilaterals.
OK, now to add a side face:
float theta= 0 ; float d = 100 ; //length of an edge of the cube void setup (){ size ( 600 , 400 , P3D ); } void draw (){ background ( 255 ); stroke ( 0 ); //make the origin the center of the cube translate ( width / 2 , height / 2 , 0 ); rotateY(theta); theta+=. 01 ; beginShape (QUADS); //back face of the cube is red fill ( 255 , 0 , 0 ); vertex (-d/ 2 , -d/ 2 , -d/ 2 ); vertex (d/ 2 , -d/ 2 , -d/ 2 ); vertex (d/ 2 , d/ 2 , -d/ 2 ); vertex (-d/ 2 , d/ 2 , -d/ 2 ); //front face of the cube is yellow fill ( 255 , 255 , 0 ); vertex (-d/ 2 , -d/ 2 , d/ 2 ); vertex (d/ 2 , -d/ 2 , d/ 2 ); vertex (d/ 2 , d/ 2 , d/ 2 ); vertex (-d/ 2 , d/ 2 , d/ 2 ); //a green side face fill ( 0 , 255 , 0 ); vertex (-d/ 2 , -d/ 2 , -d/ 2 ); vertex (-d/ 2 , -d/ 2 , d/ 2 ); vertex (-d/ 2 , d/ 2 , d/ 2 ); vertex (-d/ 2 , d/ 2 , -d/ 2 ); endShape (); } |
It’s an exercise for you to finish the cube. Note that since we are looking at this cube with a straight-on perspective, you can just draw the four side faces and forget about the top and bottom because you'll never see them. It is a common trick in computer graphics not to draw anything that won't be seen. If we were going to allow rotation around the x axis, then you'd probably want to draw the top and bottom faces.
In fact, Processing does have two intrinsic functions called box() and sphere(). For real, you’d probably want to use the box() function to draw a 3D cube. I made you do it the hard way so you’d see how to draw other less regular shapes.
Wouldn’t it be great to show your pals what you’ve been up to without making them download Processing and compile your program?! I’m sure you’ve been wanting to do this. Well, you can. All you have to do is choose “Export Application . . .” from Processing's File drop-down menu. Processing will pop up a dialog box asking you what kind of platform (mac, windows, or linux) you are intending to run it on. Then it will make a new folder inside your sketch folder with the app in it. Try it out!
6.1 Draw three shapes to the screen all at once. Have each of them rotate about their own centers: one rotates around the z axis, one rotates around the x axis, and the other rotates around the y axis.
6.2 Finish the 4th side of the cube. Change the program to rotate the cube with the mouse.
6.3 Use beginShape(TRIANGLES) to draw a four-sided pyramid with mouse-controlled rotation in 3D space.
6.4 Export an app so you or someone else can run one of your programs.
Turn in either 6.1 OR 6.3 to its assignment dropbox in Canvas
It is also useful to take advantage of the 3D renderer if you want to do the equivalent of "flipping" a shape over the x or y axis, which is easier than trying to recalculate the vertices in your head.
void setup (){ size ( 200 , 200 , P3D ); noStroke (); noLoop (); } void draw (){ fill ( 0 ); capitalE( width / 2 , height / 2 ); fill ( 255 ); pushMatrix (); translate ( width / 2 , height / 2 , 0 ); rotate ( PI ); capitalE( 0 , 0 ); popMatrix (); fill ( 255 , 0 , 0 ); pushMatrix (); translate ( width / 2 , height / 2 , 0 ); rotateY( PI ); capitalE( 0 , 0 ); popMatrix (); } void capitalE( int x, int y) { beginShape (); vertex (x,y); vertex (x,y+ 100 ); vertex (x+ 40 ,y+ 100 ); vertex (x+ 40 ,y+ 90 ); vertex (x+ 10 ,y+ 90 ); vertex (x+ 10 ,y+ 55 ); vertex (x+ 29 ,y+ 55 ); vertex (x+ 29 ,y+ 45 ); vertex (x+ 10 ,y+ 45 ); vertex (x+ 10 ,y+ 10 ); vertex (x+ 33 ,y+ 10 ); vertex (x+ 33 ,y); endShape ( CLOSE ); } |
//twirling squares //they revolve around the center of the window //press the mouse inside the window to make them go float boxDiam = 40 ; float boxAngle1 = 0 ; void setup (){ size ( 400 , 200 ); } void draw (){ rectMode ( CENTER ); background ( 247 ); stroke ( 0 ); translate ( 200 , 100 ); rotate (boxAngle1); fill ( 255 , 0 , 0 ); rect ( 0 , 0 ,boxDiam,boxDiam); fill ( 250 , 108 , 13 ); rect ( 60 , 0 ,boxDiam,boxDiam); fill ( 250 , 203 , 13 ); rect ( 120 , 0 ,boxDiam,boxDiam); fill ( 182 , 250 , 13 ); rect ( 180 , 0 ,boxDiam,boxDiam); if ( mousePressed == true ){ boxAngle1 += . 05 ; } } |
Here is a 13 second video demonstration (video has no sound)
Note the difference between this program and the previous one. In this one I use a series of pushMatrix() / popMatrix() pairs around each rectangle so that they each rotate about their own centers.
//twirling squares //they each rotate about their own centers //press the mouse in the window to make them go float boxDiam = 40 ; float boxAngle1 = 0 ; void setup (){ size ( 400 , 200 ); } void draw (){ rectMode ( CENTER ); background ( 247 ); stroke ( 0 ); pushMatrix (); translate ( 200 , 100 ); rotate (boxAngle1); fill ( 255 , 0 , 0 ); rect ( 0 , 0 ,boxDiam,boxDiam); popMatrix (); pushMatrix (); translate ( 260 , 100 ); rotate (boxAngle1); fill ( 250 , 108 , 13 ); rect ( 0 , 0 ,boxDiam,boxDiam); popMatrix (); pushMatrix (); translate ( 320 , 100 ); rotate (boxAngle1); fill ( 250 , 203 , 13 ); rect ( 0 , 0 ,boxDiam,boxDiam); popMatrix (); pushMatrix (); translate ( 380 , 100 ); rotate (boxAngle1); fill ( 182 , 250 , 13 ); rect ( 0 , 0 ,boxDiam,boxDiam); popMatrix (); if ( mousePressed == true ){ boxAngle1 += . 05 ; } } |
Here is a 13 second video demonstration (video has no sound)
In this one, I translate to (200,100) so that the center of the red box is the origin around which the rotation happens. The important thing to remember with rotations is to translate first, and then rotate.
//twirling squares //they revolve around the center of the window //in 3D! //press the mouse to make them go float boxDiam = 40 ; float boxAngle1 = 0 ; void setup (){ size ( 400 , 200 , P3D ); } void draw (){ rectMode ( CENTER ); background ( 247 ); smooth (); stroke ( 0 ); translate ( 200 , 100 ); rotateY(boxAngle1); fill ( 255 , 0 , 0 ); rect ( 0 , 0 ,boxDiam,boxDiam); fill ( 250 , 108 , 13 ); rect ( 60 , 0 ,boxDiam,boxDiam); fill ( 250 , 203 , 13 ); rect ( 120 , 0 ,boxDiam,boxDiam); fill ( 182 , 250 , 13 ); rect ( 180 , 0 ,boxDiam,boxDiam); if ( mousePressed == true ){ boxAngle1 += . 05 ; } } |
Here is a 13 second video demonstration (video has no sound)
You can use Processing to import an image and display it in the display window. The procedure is somewhat similar to the procedure to load a font and display text. You first need to put the image you want into the sketch’s data folder, or else in your program you need to specify the entire pathname to locate the image in your computer’s file system. There are several ways to put an image in your folder. The easiest is to drag the icon for the image from wherever it lives on your computer and then drop it onto your open Processing editing window. You will get a message in the console saying “1 file added to sketch” or something similar. In order to interact with your image in your program, you declare the variable type PImage, you use loadImage() to get it into the program and then you use image() to display it. Here is a simple example:
// display an image of Massimo's head PImage img; size ( 400 , 400 ); img = loadImage ( "massimo_head.egg_e5137.png" ); image (img, 0 , 0 ); |
Images are treated just like any other object, so using all the skills we know already, we can plot this image to the screen multiple times, or have it move or twirl it around, or plot it to the screen with a mouse click, or change its size, and so forth. Here are a couple of examples of that:
// display three images of Massimo's head // they move across the screen PImage img; float x = 0 ; void setup (){ size ( 800 , 800 , P3D ); img = loadlmage( "massimo_head.egg_e5137.png" ); } void draw (){ background ( 200 ); for ( int i= 0 ;i< 550 ;i=i+ 250 ){ image (img,x,i); } x++; if (x>= width ) { x= 0 ; } } |
In the program above, I use a for loop to draw three of the images to the screen. They move across the screen from left to right and when they get to the right side they start over again on the left side.
You can change the overall color and opacity of an image with tint(). The tint() command takes 3 rgb color arguments -- or one argument for greyscale -- and an optional fourth argument to specify opacity. If you want to change the opacity of an image without changing its colors, use white (255) as the tint color, and use the desired opacity as the second argument, so in that case you’d just have two arguments in tint(). Here is an example of tint():
// display images of Massimo's head with a mouse click // images are tinted green and have some transparency Plmage img; void setup (){ size ( 800 , 800 ); img = loadlmage( "massimo_head.egg_e5137.png" ); } void draw (){ } void mouseClicked(){ tint ( 59 , 98 , 5 , 128 ); scale ( random ( 0.5 , 2.5 )); image (img, mouseX , mouseY ); } |
Notice anything unusual about the program in Example 6.6? Look at the draw() block. There's nothing there! The deal is that if there is a setup() block, then there has to be a draw() block as well. However, it doesn't have to do anything, it can just be a place holder. You want to do all your file input in setup() so that it only has to happen once. This goes for images and fonts and other data files. In the program I wrote, all I wanted was for the image to display when I clicked the mouse inside the display window. So, I used the mouseClicked() function to do all the action in the program. But since I was importing an image, I had to use setup(), and since there was a setup(), there had to be a draw(). I made draw() empty since I didn't need it for anything.
You can save a snapshot of your creation with the save() command. You can specify TIFF, TARGA, JPEG, or PNG files. The default if you don’t specify a file extension is TIFF. The default place your exported image file will go is into the same folder where your sketch lives. Here’s an example in which a key press saves a file called testimage.jpg:
// display an image of Massimo's head // save it with a key press PImage img; void setup (){ size ( 400 , 400 ); img = loadImage ( "massimo_head.egg_e5137.png" ); } void draw (){ image (img, 0 , 0 ); if ( keyPressed == true && key == 's' ){ save( "testimage.jpg" ); println ( "Your image has been saved! Yay!" ); } } |
6.5 Find or create a smallish snapshot to play around with images.
6.6 Using a head shot of you or a friend, make an animated person that walks across the screen (or jumps up and down or says something with a speech bubble) by importing an image of the face and drawing the rest of the body by hand.
Submit 6.6 to its assignment dropbox in Canvas. Please zip your whole folder and submit that, or else make sure you also submit your image file(s) along with your pde file so I can run your program!
In this lesson, you learned how to use the 3D renderer to draw shapes and simulate their movement in 3D space. You also learned how to import an image file into a program and work with it just the same as any shape you would draw.
(These are copied and pasted from the pages in Lesson 6 where they originally appeared)
6.1 Draw three shapes to the screen all at once. Have each of them rotate about their own centers: one rotates around the z axis, one rotates around the x axis, and the other rotates around the y axis.
6.2 Finish the 4th side of the cube. Change the program to rotate the cube with the mouse.
6.3 Use beginShape(TRIANGLES) to draw a four-sided pyramid with mouse-controlled rotation in 3D space.
6.4 Export an app so you or someone else can run one of your programs.
6.5 Find or create a smallish snapshot to play around with images.
6.6 Using a head shot of you or a friend, make an animated person that walks across the screen (or jumps up and down or says something with a speech bubble) by importing an image of the face and drawing the rest of the body by hand.
Turn in either 6.1 or 6.3, plus 6.6 to their dropboxes in Canvas. There is no discussion for this lesson.
Finally we have gotten to the point where we are developing the necessary skills to do some data manipulation! In this lesson we'll learn just a few simple unix-based commands that can make your life easier and faster. We'll learn to read in a data file, and do some computation with its contents.
By the end of this lesson, you should be able to:
This lesson will take us one week to complete, 30 June - 6 July 2021. The deliverable for this lesson is only a reading discussion. The Unix exercises are all in a non-graded form ("Try This!", "Quiz Yourself", etc.) but you should take the time to work through them anyway because knowing some of these simple tricks will save you a lot of headaches in Lesson 8 and anytime you want to import a dataset into Processing to make a plot out of it.
If you have any questions, please post them to our Questions? discussion forum (not e-mail) in Canvas. I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
This week we will read and discuss two papers:
Tversky, B., J.B. Morrison, M. Betrancourt, 2002, Animation: can it facilitate, Journal of Human-Computer Studies, 57, p. 247-262.
Höffler, Tim N., and Detlev Leutner, 2007, Instructional animation versus static pictures: A meta-analysis, Learning and Instruction, 17, p. 722-738.
These papers try to compare learning outcomes from still visuals with those from animations to see if animations are really better, or not.
As you read, consider the following questions, which we will discuss as a class:
Once you have finished the readings, engage in a class discussion that will take place during Lesson 7. This discussion will require you to participate multiple times over that period.
You will be graded on the quality of your participation. See the grading rubric [2] for specifics.
This short tutorial is meant to provide you with familiarity using a small number of unix commands to manipulate big text files of data. It is not meant to substitute for a complete understanding of unix, or programming in general, or even an exhaustive listing of useful commands but I hope that if you follow along, you'll learn enough simple file editing skills to save you some time.
The first thing we'll want to do is open a terminal window. Go to the Utilities folder inside your Applications folder and open Terminal. A window appears with some text that should be similar to the following.
Last login: Tue Apr 20 09:43:13 on console [rockfall:~] eliza% |
The first line "Last login: Tue Apr 20 09:43:13 on console" provides the date, and time from the last time that you logged into the system. On the second line, the word rockfall refers to the machine that you are logged into. In this case, rockfall refers to my machine or hard drive. The text after the : refers to which directory I am in. The ~ means that I am currently in my home directory. So, [rockfall:~] means that I am logged into the home directory on my machine. Next, "eliza" refers to my userid and the % means that is the end of the prompt and is waiting for your input.
An image of the window with the text referenced above is below.
Here are three commands to try:
Now let's take a text file and mess around with it using unix commands in the terminal window. Here is a link to a plain text file of ten days of aftershocks [27] following the 4 April 2010 Baja California earthquake. Put it in the new directory you called earth801/data1. Go to earth801/data1 and type ls to verify the file is there. Type more baja_neic.txt (in which baja_neic.txt is the actual name of the text file). The file should look like the screenshot below. If your terminal window is too small to show the whole file at once, you will get a black bar at the bottom that tells you what percentage of the file you are seeing. Hit the spacebar and you'll see another chunk of the file. Continue to hit the spacebar until you've seen the whole file and you are back at the terminal prompt. Alternatively, if you type cat baja_neic.txt the entire file will scroll by and leave you at the prompt when it's done.
The command head baja_neic.txt shows you exactly the first ten lines of the file. Try it. You can also modify the head command like this:
head -5 baja_neic.txt |
The -5 tells head to show the first 5 lines. Showing ten lines is the default when head has no arguments, so the following two commands are equivalent:
head baja_neic.txt |
head -10 baja_neic.txt |
The command tail is similar to head but works on the end of the file instead of the beginning. The commands more, cat, head, and tail return their output to the screen by default but you can also have them create a new file and put their results in it instead. The way to do this is to redirect the output with the > symbol.
For example, do this:
head -5 baja_neic.txt > newfile |
and you will create a new text file called "newfile" which contains exactly the first five lines of the original file baja_neic.txt. It is important to note here that performing this command has not changed the original file in any way. You can type ls to verify that you now have two files in your data1 directory. One of them is the original baja_neic.txt and the other one is called newfile and it is a copy of the first five lines of baja_neic.txt. Use the more command to look at your newfile file. Did you get what you were expecting? When the "head" command counts lines of a file, blank lines are counted just like lines that have text characters in them, so that's why newfile looks the way it does. At this point, if you have been following along, the following three commands should give you identical output:
head -5 baja_neic.txt |
more newfile |
cat newfile |
Okay, on to the next command of interest. The cp command copies one file to another but instead of using > you just specify the other filename. So, these two commands are equivalent ways of copying the entire file baja_neic.txt to a new file called baja_neic_copy.txt:
cp baja_neic.txt baja_neic_copy.txt |
cat baja_neic.txt > baja_neic_copy.txt |
If you want to rename a file without changing its contents, use mv. Like cp, mv requires two filenames, the previous one and the new one.
mv newfile baja_neic_five.txt |
The above command renames the file "newfile" to "baja_neic_five.txt". You can also use "mv" to change the location of a file. Try typing
mv baja_neic_copy.txt .. /data2/baja .txt |
This command takes the file "baja_neic_copy.txt" and moves it from the folder data1 to the folder data2 and renames it baja.txt. You can go to data2 (remember how?) and verify there is now a file in there called baja.txt and that it is a duplicate of baja_neic.txt.
Another cool use of the cat command is to stick two or more files together and make one file. So,
cat baja_neic.txt newfile > baja2.txt |
will make a file called baja2.txt which is a copy of baja_neic.txt plus a copy of "newfile" stuck together.
This short tutorial is meant to provide you with familiarity using a small number of unix commands to manipulate big text files of data. It is not meant to substitute for a complete understanding of unix, or linux, or even an exhaustive listing of useful commands but I hope that if you follow along, you'll learn enough simple file manipulation skills to save you some time.
If you are on a PC running Windows, you can emulate a unix/linux command window environment by running "cygwin." To reiterate: you aren't running linux, but it looks like you are.
To download it, go to the home of the cygwin project. [28]
I also need to mention the following caveat: I'm not a PC user! When I was in grad school I had a Sun workstation that I used for everything, including typesetting. I didn't even write in Word, I used LaTeX. Now I use a mac. I also had to relax my principles on avoiding Word or face a lifetime of really cranky collaborators, but that's another story. The upshot here is that I am probably less of a complete doofus than your grandparents when it comes to PCs but . . . okay you get the picture. So when I made the screen casts of me attempting to present a tutorial of cygwin, I borrowed a pc and figured it out on the fly. And it basically worked okay; I give cygwin my thumbs up.
The first thing we'll want to do is open a terminal window. Double-click the cygwin icon to open a terminal window. When the window is active there will be a blinking cursor where you can start typing. Unix commands are all typed at the prompt and by default the output of any command you type goes to the screen in the terminal window where you are typing.
Here are some commands to try:
In Part 2, we'll see how to use unix commands to change our location in the computer's file structure. It's analogous to clicking through the various discs and folders from the windows launched when you double-click "my computer" except that it involves no mouse clicks, only typing.
The command of interest here is called cd. The way it works is that you type "cd pathname" at the prompt, and then you will go there (you have to type the actual path, not the word "pathname"). Nested folders have to be separated by forward slashes "/". You can verify that you are where you think you are by typing pwd or by navigating to the same address via windows and noting that the folder contents are the same.
Note that in order to move between the C and D drives, you'll have to start the address with "cygdrive". For example, the command cd /cygdrive/d will take you to the uppermost level of drive D and cd /cygdrive/c takes you to the uppermost level of drive C.
In the rest of this tutorial we'll do some simple things to files using unix commands.
First of all let's take a text file and mess around with it using unix commands in the terminal window. Here is a link to a plain text file of ten days of aftershocks [27] following the 4 April 2010 Baja California earthquake.
The command head baja_neic.txt shows you exactly the first ten lines of the file. Try it. You can also modify the head command like this:
head -5 baja_neic.txt |
The -5 tells head to show the first 5 lines. Showing ten lines is the default when head has no arguments, so the following two commands are equivalent:
head baja_neic.txt head -10 baja_neic.txt |
The command tail is similar to head but works on the end of the file instead of the beginning. The commands less, cat, head, and tail return their output to the screen by default but you can also have them create a new file and put their results in it instead. The way to do this is to redirect the output with the > symbol.
For example, do this:
head -5 baja_neic.txt > newfile.txt |
and you will create a new text file called "newfile.txt" which contains exactly the first five lines of the original file baja_neic.txt. It is important to note here that performing this command has not changed the original file in any way. You can type ls to verify that you now have two files in your data1 directory. One of them is the original baja_neic.txt and the other one is called newfile.txt and it is a copy of the first five lines of baja_neic.txt. Use the less command to look at your newfile.txt file. Did you get what you were expecting? When the head command counts lines of a file, blank lines are counted just like lines that have text characters in them, so that's why newfile.txt looks the way it does. At this point, if you have been following along, the following three commands should give you identical output:
head -5 baja_neic.txt less newfile.txt cat newfile.txt |
Okay, on to the next command of interest. The cp command copies one file to another but instead of using > you just specify the other filename. So, these two commands are equivalent ways of copying the entire file baja_neic.txt to a new file called baja_neic_copy.txt:
cp baja_neic.txt baja_neic_copy.txt cat baja_neic.txt > baja_neic_copy.txt |
If you want to rename a file without changing its contents, use mv. Like cp, mv requires two filenames, the previous one and the new one.
mv newfile.txt baja_neic_five.txt |
The above command renames the file "newfile.txt" to "baja_neic_five.txt". You can also use mv to change the location of a file. Try typing
mv baja_neic_copy.txt .. /data2/baja .txt |
This command takes the file "baja_neic_copy.txt" and moves it from the folder data1 to the folder data2 and renames it baja.txt. You can go to data2 (remember how?) and verify there is now a file in there called baja.txt and that it is a duplicate of baja_neic.txt.
Another cool use of the cat command is to stick two or more files together and make one file. So,
cat baja_neic.txt newfile.txt > baja2.txt |
will make a file called baja2.txt which is a copy of baja_neic.txt with a copy of "newfile.txt" appended to the bottom.
To delete a file or a folder type rm filename. Careful here because the file won't go into a trash folder that you can change your mind about. It is really gone.
All the examples detailed here are accomplished from your terminal window if you are on a Mac, or from your cygwin window if you are on a PC.
The awk command is a powerful way to manipulate the contents of textfiles. We are only going to skim the surface of what awk can do right now. Let's start with a simple example. Say you have a text file that has some columns of numbers in it. With one awk command you can rearrange the columns in a different order, or perform some arithmetic on the columns.
Download the file1.txt [29] textfile in order to follow along with what I am doing. This is accomplished by clicking the link to the filename and then choosing Save As . . . from your browser's file menu. Save it somewhere on your computer, then in the terminal, navigate to that place. Remember how? You'll want to use cd.
This is what "file1.txt" contains. It is simply a plain text three-column six-row arrangement of numbers. The first column is the number 1, the second column is the number 2, the third column is the number 3.
Awk is great for quickly manipulating files that are arranged in columns, so it is a nice way to fiddle around with plain-text data files since those are frequently in columns or tables. It uses some peculiar syntax. Let's say we wanted to display just the first column from "file1.txt." Here's how to do it:
awk '{print $1}' file1.txt
The first thing you type is awk and then put single quotes and curly braces. Inside the curly braces we wrote print $1 which is the command to print column #1. The filename from which we are extracting column #1 goes next.
Let's say we wanted to display just the second column from "file1.txt." In that case we'd type:
awk '{print $2}' file1.txt
You can output any number of columns and put them in whatever order you want. Let's say we want column 3, then column 1 but not column 2.
awk '{print $3, $1}' file1.txt
The comma between $3 and $1 tells awk to put a space between the columns.
Let's say you want to output column 1, substitute 4's in column 2, then output column 3 unchanged. That would be like this:
awk '{print $1, 4, $3}' file1.txt
The 4 inside the the curly braces doesn't have a $ in front of it because it is the actual number 4, it is not referring to a 4th column.
You can do math inside the print statement of awk and you can also deal with columns that aren't numbers. Let's say I want to output the sum of columns 1 and 2 as the first column, my name as the second column, and the product of columns 2 and 3 as the third column, and then make a fourth column that is the number 25:
awk '{print $1+$2, "eliza", $2*$3, 25}' file1.txt
Here's what the output of awk '{print $1+$2, "eliza", $2*$3, 25}' file1.txt looks like.
There are a few special characters. A useful one sometimes is "\t" which tells awk that you want tab spaces in between the columns.
awk '{print $1 "\t" $2 "\t" $3 "\t"}' file1.txt
The command above will output file1.txt unchanged except for tab spaces in between the columns instead of just one space.
All the examples so far have output the results of the awk command to the screen. They have not altered the original file, and they haven't saved the results anywhere. To put the output of awk into a new file instead of showing it on the screen, use >. Let's say I want to make a new file that is the same as file1.txt but with the columns in reverse order:
awk '{print $3, $2, $1}' file1.txt > file2.txt
Now if I look in the folder where file1.txt is, there are two files. file1.txt is still there, but there is a new file called file2.txt as well.
On the left is the original file1.txt.
The command
awk '{print $3, $2, $1}' file1.txt > file2.txt
creates the new file2.txt, seen at right.
vi (also called vim; the two are basically the same) is a text editor that allows you to create and edit text inside a terminal window without popping up another window and without using the mouse. Truthfully, most people would never want to get rid of their mouse if they are used to using it all the time, but if you want to get into a file, do a simple thing to it, such as deleting the first 30 lines or adding one line to the bottom or something like that, then vi is handy. However, the functionality of vi does not lend itself well to making a screen capture "how-to" movie because most of the action takes place on the keyboard and you can't see my hands with a screen capture.
When you are using vi to edit a file, you will either be in "insert" mode or in "moving around" mode. While you are in "insert" mode, whatever you type becomes part of the file. (just like whatever word processor/text editor you are used to). But when you are in "moving around" mode, you use keyboard keys to move the cursor around the file. To get started, type vi filename at the terminal prompt (in which "filename" is your actual filename, not the word "filename" unless that's the name of your file .
Here's an incomplete command list (the man page for vi will do better) but it's a start:
i to insert before the cursor, I to insert at the beginning of the current line
a to insert after the cursor, A to insert at the end of the current line
o to make a new blank line below the cursor and put the cursor at the beginning of it, O to make a new blank line above the cursor and put the cursor at the beginning of it
ESC to get out of insert mode and go into moving around mode.
h moves one space to the left
l moves one space to the right
j moves one line down
k moves one line up
dd deletes the current line
x deletes the current character
typing a number before a command repeats the command that many times, so 10 dd deletes 10 lines beginning with the current line.
:w saves your work
:q quits vi
you can do these together, so :wq saves your work and quits vi all in one step.
Let's put our skills to work. Download this file of a catalog of earthquakes [30] from the USGS. Use vi and awk to make a new file that contains just one column -- the earthquake magnitudes. This is the kind of thing that will be super useful for making a frequency magnitude diagram! Try it on your own and if you get stuck, check to see how I did it. Keep in mind that there is just about always more than one way to accomplish an editing task like this. The point is to get the end result without lots of work and cumbersome steps in a non-mathematical spreadsheet program that was not intended to handle a big dataset.
In this lesson we will build on the Unix skills from Lesson 7 and work with data files inside the Processing environment.
By the end of this lesson, you should be able to:
This lesson will take us one week to complete, 7 - 13 July 2021. The deliverable for this lesson is one programming exercise, detailed on the last page of the lesson.
If you have any questions, please post them to our Questions? discussion forum (not e-mail) in Canvas. I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
Now we are ready to work with a new variable type called an array. An array is like a matrix. It is a variable that holds a bunch of the same type of data instead of just one piece of data. Arrays are useful for storing lists of similar things because it insulates you from having to declare so many variables. Declaring an array is similar to declaring other variables such as ints, floats, and strings, but you have to say what kind of data the array will store and use the square brackets [ ] so Processing knows that you want to make an array of that type of data.
For example, if you want to declare three integer variables called "x1", "x2", and "x3" and assign them the values 0, 25, and 6, here is one way to do it (hopefully this is old hat by now):
int x1 = 0; int x2 = 25; int x3 = 6; |
But you could make an array of integers instead. Let's do that and call the array "x." There are a few different ways to do this. Here they are:
int [] x; //declare it here. I'm telling Processing //to make an array of integers called "x" void setup () { size ( 400 , 400 ); x = new int [ 3 ]; // Creating it with the "new" command. x[ 0 ] = 0 ; // Assigning values to each element. x[ 1 ] = 25 ; x[ 2 ] = 6 ; } void draw () { // The rest of the program } |
In the program above, we first told Processing we wanted to make an array of integers called "x". Then inside the setup block we created the array using new and inside of the square brackets we tell Processing how big x will be, meaning how many integers it will hold. This allows your computer to allocate the right amount of memory to store the array. Then we assign values to each element in x. Remember that you always start counting at zero, not one in Processing! That means the first element of the array is denoted x[0], and the second element of the array is denoted x[1]. The number inside the square brackets tells you which element it is, not the value of that element. For example, x[2] = 6; means that the third element of the integer array x is assigned the value 6.
int [] x = new int [ 3 ]; //declare and create it here void setup () { size ( 400 , 400 ); x[ 0 ] = 0 ; //assigning here x[ 1 ] = 25 ; x[ 2 ] = 6 ; } void draw () { //the rest of the program } |
In the above example, we declared the array of integers up at the top before the setup() block. When we declared it we also told Processing how many elements the array would have in it. So we saved a step compared to the first example. Since we did this up at the top of the program before setup(), the array x is available inside both setup() and draw(). We are filling up the array with values later on the program. It is important to note that there is a difference between an empty element in an array and an element that is assigned the value zero. When we wrote the command
int[] x = new int[3]; |
This told Processing to make an array that has room for three integers but we didn't tell it what those integers would be. Until we do, the array is empty, which means that there are no values assigned to it. When we wrote
x[0] = 0; |
We assigned the value zero to the first spot in the array, which was previously nothing.
int [] x = { 0 , 25 , 6 }; void setup () { size ( 100 , 100 ); } void draw () { //rest of the program } |
In the example above, you don't have to use new if you do the declaration, creation, and assignment all at the same time. And you don't have to tell Processing how big the array is going to be because you are assigning the spots in the array to numbers right away.
The examples above told you how to make an array, but those programs don't really do anything, do they? So here's an example of a program that uses an array to draw a shape:
//use two arrays to draw a star int [] a= { 20 , 40 , 20 , 40 , 50 , 60 , 80 , 60 , 80 , 50 }; int [] b = { 20 , 45 , 60 , 60 , 80 , 60 , 60 , 45 , 20 , 35 }; void setup (){ size ( 100 , 100 ); noLoop (); //just a static shape drawn once } void draw (){ beginShape (); for ( int i = 0 ; i< a. length ; i++){ vertex (a[i], b[i]); } endShape ( CLOSE ); } |
In the program above, I declared, created, and assigned two arrays at the beginning of the program before setup() and draw(). Inside setup() I just set the size of the display window. Inside draw() I made a beginShape()/endShape() pair and in between them I wrote a for loop that loops through the arrays and sets the vertices one by one. This is the first time you have seen the dot operator . in a program. The dot operator in Processing is used to access some attribute of the variable and it is analogous to an apostrophe in English to designate possession. For instance, when I wrote a.length that means in English "the length of the array called a," or "a's length." For an array, its length is the number of elements in it, so a.length == 10 in the star-drawing program.
It is also useful to use a for loop to fill up an array with numbers instead of assigning each element by hand. Here's an example of a program that does this:
//five balls, each ball is faster than the one above it int num = 5 ; float [] xpos = new float [num]; float [] speed = new float [num]; float dy = 60 ; void setup (){ size ( 400 , 400 ); for ( int i = 0 ; i<num; i++){ xpos[i] = i; speed[i] = i+ 0.1 ; } } void draw (){ background ( 0 ); for ( int i = 0 ; i<num; i++){ float y=(i+dy)*i; ellipse (xpos[i],y, 20 , 20 ); xpos[i]+=speed[i]; if (xpos[i]> width - 10 ){ xpos[i]= 0 ; } } |
Here is a 12 second video demonstrating the code above (video is silent).
Another useful thing to do with arrays is to use them store the history of mouseX and mouseY positions.
Here is an example of that:
//snake of circles follows the mouse int num = 50 ; int [] x = new int [num]; int [] y = new int [num]; void setup (){ size ( 400 , 400 ); noStroke (); smooth (); fill (2S5, 100 ); } void draw (){ background ( 0 ); //go backwards through the loop //and shift all values to the right for ( int i = num- 1 ; i> 0 ; i--){ x[i] = x[i- 1 ]; y[i] = y[i- 1 ]; } //put current values of mouseX and mouseY at beginning x[ 0 ] = mousex; y[ 0 ] = mouseY ; //draw circles for ( int i = 0 ; i < num; i++){ ellipse (x[i],y[i], 20 , 20 ); } } |
In the program above, I declare and create two arrays before setup() and draw(). I use setup() for the usual things. Inside draw() there is a for loop that steps backwards through the two arrays and shifts all the array values to the right, then adds the current values of mouseX and mouseY to the beginning of each array. Think of this as a conveyer belt that runs from left to right. You are continuously adding a new value to the left side while the rightmost value falls off the other side and gets thrown away. Then I use another for loop that goes forwards through the array to draw all the circles. The whole effect is that a trail of however many circles assigned to the value "num" (in this case 50) follow the mouse around like a snake.
Three examples of reading in a file, doing something with its contents, plotting the result.
The box below contains the contents of a plain text file named "cards_data.txt" that I created using vi and have dragged and dropped onto my sketch. There are three columns separated by tabs. The first column is a list of last names of 2002 St. Louis Cardinals position players (no pitchers). The second column lists the number of RBIs each of them earned that year and the final column is each of their salaries in millions of dollars. The format of this file looks kind of ugly because some of the names are too long for the tabbing to work out right, but Processing won't care about this! We are going to read this text file into Processing using loadStrings. Then we are going to make a plot out of it.
Cairo 23 0.85 Drew 56 3.6 Edmonds 83 8.33 Marrero 66 1.5 Martinez 75 7.5 Matheny 35 3.25 Palmeiro 31 0.7 Perez 26 0.5 Pujols 127 0.9 Renteria 83 6.5 Robinson 15 0.32 Rolen 110 7.625 Vina 54 5.33
Here's the program, and a screenshot of the plot I made.
//this data is the number of RBIs in 2002 for Cards position players //and their salaries (in millions of $) //we will read the data in from a 3-column plain text file String[] cards; //make the array and fill it with data later void setup () { size ( 200 , 200 ); background ( 255 ); PFont font1; font1 = loadFont( "AbadiMT-CondensedLight-14.vlw" ); textFont (font1); smooth (); cards = loadStrings( "cards_data.txt" ); //this is how we read in the file contents noLoop (); //just drawing a static plot once } void draw () { //make a grid for plotting. use translate to leave some blank space for labels translate ( 50 , - 50 ); stroke ( 200 ); for ( int i = 0 ; i< 100 ; i=i+ 20 ) { line (i, 60 , i, height ); //vertical gridlines line ( 0 , height -i, 140 , height -i); //horizontal gridlines } //plot the data stroke ( 0 ); fill ( 75 ); println ( "number of lines in data file is " +cards. length ); //go through the array called "cards" line by line for ( int i = 0 ; i<cards. length ; i++) { //split each line where there is a tab //create a new array of strings called "data" to hold this info String[] data = split(cards[i], '\t' ); String Name = (data[ 0 ]); //player name in first column int Rbi = int (data[ 1 ]); //Rbi in the second column float Salary = float (data[ 2 ]); //Salary in third column //make a scatter plot of Rbi v. salary ellipse (Rbi, height -Salary* 10 , 10 , 10 ); //want the axes origin at lower left, so do (height - y data) } //label the axes //I did these by trial-and-error until I got them to look right fill ( 0 ); text ( "RBIs" , ( width / 2 )- 50 , height + 30 ); text ( "Salary $ mil" , - 50 , 100 , 30 , 100 ); text ( "20" , 15 , height + 15 ); text ( "60" , 55 , height + 15 ); text ( "100" , 95 , height + 15 ); text ( "2" , - 10 , height - 15 ); text ( "6" , - 10 , height - 55 ); text ( "10" , - 15 , height - 95 ); } |
The secondary purpose of this plot is to demonstrate a few new commands and how to deal with an external data file. Inside setup() we used loadStrings to read the file into the program. You want to do all the reading-in of external files in setup() because that block just runs once and you don't want your cpu hogged by re-loading your files every time you run through draw(). The file will be loaded in as lines of String variables. The first thing we want to do is tell Processing that we actually want three columns, not 13 lines. So, we go through the data file line by line and split each line where there are tabs. The syntax '\t' tells Processing to look for a tab.
The fact that the data comes in as strings works out great for the player names because they are words. But if we want to do some arithmetic with the numbers, or otherwise treat them as numbers, then we have to convert them to other variable types. Inside the for loop where we run through the data file, we first split each line into three pieces, making a three-element array named data. Then we rename each element in the data array and convert it to another variable type if we want to. For example, we made an integer array out of the RBI data, and we made a float array out of the salary data. The chunk of code that does all that is here:
String[] data = split(cards[i], '\t' ); String Name = (data[ 0 ]); int Rbi = int (data[ 1 ]); float Salary = float (data[ 2 ]); |
Then we plot Rbi v. Salary.
ellipse (Rbi, height -Salary* 10 , 10 , 10 ); |
The rest of the program is devoted to doing the background work that spreadsheet and other canned plotting programs do for you. Here's where we make some gridlines:
//make a grid for plotting. use translate to leave some blank space for labels translate ( 50 , - 50 ); stroke ( 200 ); for ( int i = 0 ; i< 100 ; i=i+ 20 ) { line (i, 60 , i, height ); //vertical gridlines line ( 0 , height -i, 140 , height -i); //horizontal gridlines } |
Here's where we label the axes:
//label the axes //I did these by trial-and-error until I got them to look right fill ( 0 ); text ( "RBIs" , ( width / 2 )- 50 , height + 30 ); text ( "Salary $ mil" , - 50 , 100 , 30 , 100 ); text ( "20" , 15 , height + 15 ); text ( "60" , 55 , height + 15 ); text ( "100" , 95 , height + 15 ); text ( "2" , - 10 , height - 15 ); text ( "6" , - 10 , height - 55 ); text ( "10" , - 15 , height - 95 ); |
Of course this is a slightly more tedious way to make a simple plot -- you would probably rather just paste this little datafile into your favorite program and not spend time tinkering with the way the plotting grid looks, right? Sure, but the point is that you can do it this way and you have complete control over the way it looks, which is cool!!
I got this data file from the NOAA coastline extractor, which is now obsolete but you can find a similar version of it at the CIA World Data Bank II [31]. I'm not giving you a screenshot of the datafile this time because it is a 1.2 Mb file with over 62,000 lines. And that's the low-res version! Try pasting that one into Excel! However the program that makes this plot is quite simple:
//plotting a map of the world String[] coast; void setup () { size ( 600 , 300 ); coast = loadStrings( "coastText.txt" ); noLoop (); } void draw () { background ( 255 ); float [] coastLon = new float [coast. length ]; float [] coastLat = new float [coast. length ]; float [] newCoastLon = new float [coast. length ]; float [] newCoastLat = new float [coast. length ]; for ( int i= 0 ; i<coast. length ; i++){ String[] data = split(coast[i], ' ' ); coastLon[i] = float (data[ 0 ]); coastLat[i] = float (data[ 1 ]); } for ( int i= 0 ; i<coastLon. length ; i++){ newCoastLon[i] = map (coastLon[i],- 180 , 180 , 0 , width ); newCoastLat[i] = map (coostLat[i],- 90 , 90 , height , 0 ); } stroke ( 50 ); for ( int i= 0 ; i<coastLon. length ; i++){ point (newCoastLon[i],newCoastLat[i]); } |
I should point out here that it is just a coincidence that I used map to make an actual map. In fact, map is handy anytime you have a variable with a natural range to it but you want it to be expanded or contracted proportionally to a different range. For example, here is a program where map is used to expand the greyscale [32], which normally goes from 0 to 255, to a range that goes from 0 to 400, the width of the screen:
// demo use of "map" float x; float y; void setup () { size ( 500 , 200 ); } void draw (){ x= random ( width ); y= random ( height ); int a= int (x); color colr = int ( map (a, 0 , width , 0 , 255 )); fill (colr); ellipse (x,y, 20 , 20 ); } |
Further explanation: This is a for loop. The loop variable goes from zero to five by ones. Look at the two lines of code inside the for loop. One of them is the command to draw a line. Drawing a line has four arguments and they are x1, y1, x2, y2. In this program, the x1 is always 35 and the x2 is always 50. The y1 and y2 look like a mess but they are the same as each other, so this code draws six horizontal lines.
The lines are evenly spaced between the top and bottom of the display window. That is what map does for us. We do not have to calculate where each line will be. We just map the values onto the range we want in the display window. I mapped the six lines from height-1 to 1 instead of height to 0 because lines plotted right on the border of the display window would not have shown up.
The other command inside the for loop puts a text label next to each horizontal line. In fact it writes the value of i, which is a number. You can see that text is placed with its origin at the bottom left, so that's why the number 5 is cut off.
The lines are black and the text is white because we didn't set fill or stroke. Processing therefore uses the defaults:fill(255) and stroke(0).
The program on this page demonstrates a lot of the skills we have learned this semester so I'm going to go through it piece by piece. First of all, here is the plot that it draws and the whole code.
//plot some data from an array //this data is the number of RBIs in 2002 for Cards position players //and their salary (in millions of $) //we will read the data in from a 3-column plain text file String[] cards; //make the array and fill it with data later int rbiMax= 140 ; //actual maximum in the datafile is 127, this gives some room int salMax= 10 ; //actual maximum in the datafile is 8.33, this gives some room int nudge = 30 ; //gives some border room for the plot void setup () { size ( 500 , 500 ); background ( 255 ); PFont font1; font1 = loadFont( "AbadiMT-CondensedLight-14.vlw" ); textFont (font1); smooth (); //this is how we read in the file contents cards = loadStrings( "cards_data.txt" ); noLoop (); } void draw () { //make a grid for plotting. use map to put the grid where I want. //horizontal gridlines and labels //SALARY data on y axis. data range is 0.5-8.33, make it 0 to 10 stroke ( 200 ); for ( int i = 0 ; i< salMax; i++) { line ( map ( 0 , 0 ,rbiMax,nudge* 2 , width ), map (i, 0 ,salMax, height -( 2 *nudge), 0 ), map (rbiMax, 0 ,rbiMax,nudge* 2 , width ), map (i, 0 ,salMax, height -( 2 *nudge), 0 )); text (i, map (- 5 , 0 ,rbiMax,nudge* 2 , width ), map (i, 0 ,salMax, height -( 2 *nudge), 0 )); } //vertical gridlines and labels //RBI data on y axis. data range is 15-127, make it 0-140. for ( int i = 0 ; i<rbiMax; i=i+ 10 ){ line ( map (i, 0 ,rbiMax,nudge* 2 , width ), map ( 0 , 0 ,salMax, height -( 2 *nudge), 0 ), map (i, 0 ,rbiMax,nudge* 2 , width ), map (salMax, 0 ,salMax, height -( 2 *nudge), 0 )); text (i, map (i, 0 ,rbiMax,nudge* 2 , width ), map (- 0.5 , 0 ,salMax, height -( 2 *nudge), 0 )); } //plot the data //go through the array called "cards" line by line //split each line where there is a tab //create a new array of strings called "data" to hold this info //player name in first column //Rbi in the second column //Salary in third column for ( int i = 0 ; i<cards. length ; i++) { String[] data = split(cards[i], '\t' ); String Name = (data[ 0 ]); float Rbi = map ( float (data[ 1 ]), 0 ,rbiMax,nudge* 2 , width ); float Salary = map ( float (data[ 2 ]), 0 ,salMax, height -( 2 *nudge), 0 ); //make a scatter plot of Rbi v. salary ellipse (Rbi,Salary, 2 , 2 ); text (Name,Rbi,Salary); } //label the axes fill ( 0 ); textAlign ( CENTER ); text ( "Runs Batted In, 2002" , ( width / 2 ), height - 20 ); pushMatrix (); translate ( 30 , height / 2 ); rotate (- PI / 2 ); text ( "Salary ($ millions)" , 0 , 0 ); popMatrix (); } void mousePressed (){ save( "cardsRbiData3.png" ); } |
Let's break down this program into chunks instead of trying to understand the whole thing at once. Think of this as how you would organize a paper, or a lab report. For example, in a scientific paper, you have to start with an introduction and some background knowledge or literature review, then explain your methods, then display your results, then interpret your results, and finally make some general conclusions. You can't tinker with this order too much or else your paper will not flow logically. You wouldn't want to jump right in with the interpretation of your results before you even explain what you were trying to find out and what measurements you made, right?
Similarly, there are some parts of a data-plotting program that have to go in order, as you already know. For example, if you want a shape to be outlined in blue, you have to set that color first and then draw the shape. If you want to plot some data from an external file, you first have to read the data into the program, then you can plot it. If you are making a scatter plot by hand on graph paper you first have to figure out where your origin will be, then figure out the range of the axes before you start plotting the points. Otherwise you won't know where your points should go.
If you use a software plotting application, the whole exercise of figuring out the range of the axes is done for you by the app. You can modify the axes after the fact, but you don't usually have to spend any time up front on that task. This is beneficial for saving time, but not beneficial if you want to teach your students the art of plotmaking. You want your students to look carefully at their data before just tossing it into a plotting program and hoping for the best.
I usually use the first few lines of a program to write a note to my future self about what the program is supposed to do, and where I got the data. This is also the place to declare global variables. Here's the preamble from the RBI plotter:
//plot some data from an array //this data is the number of RBIs in 2002 for Cards position players //and their salary (in millions of $) //we will read the data in from a 3-column plain text file String[] cards; //make the array and fill it with data later int rbiMax= 140 ; //actual maximum in the datafile is 127, this gives some room int salMax= 10 ; //actual maximum in the datafile is 8.33, this gives some room int nudge = 30 ; //gives some border room for the plot |
The first four lines are just notes. Then I declare an array of strings which is going to hold the data that I read in later. I set three global integers. I already looked at my data and I know that the maximum number of RBIs in my file is 127 and the maximum salary is 8.33 million. This tells me approximately what range I ought to use for my x and y axes. It's useful to use a variable here instead of an actual number because what if I write a really long program that refers to the x or y axis range a bunch of times? If I go back and want to change the range for aesthetic reasons or whatever then I'll have to go back and find each place where that number appears. If instead I set that number to what it represents up at the top then if I want to change it I can just change it one time.
If there is a draw(), there has to be a setup(). setup() runs exactly once and the commands are run in order. Variables declared in setup() are not available outside of setup(). Anything that does not need to be changed while the program is running can be put in setup() to save computation time. Here’s a list of things that are commonly in setup().
The setup() block for this program is:
void setup () { size ( 500 , 500 ); background ( 255 ); PFont font1; font1 = loadFont( "AbadiMT-CondensedLight-14.vlw" ); textFont (font1); smooth (); //this is how we read in the file contents cards = loadStrings( "cards_data.txt" ); noLoop (); } |
I set the size and background, I load a font, and I read in the data from a plain text file. It's important that I already declared the array cards before setup() because now I can use that array to hold the information I'm reading in and I can also use it later in draw() when I want to do something with it.
If there is a setup() there has to be a draw(). draw() runs immediately after setup() and continues to run over and over again until you stop the program. You can tell draw() to go looking for other functions that come after it but you can’t tell it to look in setup() for something. Here’s a list of what’s usually in draw():
The draw() block for this program is:
void draw () { //make a grid for plotting. use map to put the grid where I want. //horizontal gridlines and labels //SALARY data on y axis. data range is 0.5-8.33, make it 0 to 10 stroke ( 200 ); for ( int i = 0 ; i< salMax; i++) { line ( map ( 0 , 0 ,rbiMax,nudge* 2 , width ), map (i, 0 ,salMax, height -( 2 *nudge), 0 ), map (rbiMax, 0 ,rbiMax,nudge* 2 , width ), map (i, 0 ,salMax, height -( 2 *nudge), 0 )); text (i, map (- 5 , 0 ,rbiMax,nudge* 2 , width ), map (i, 0 ,salMax, height -( 2 *nudge), 0 )); } //vertical gridlines and labels //RBI data on y axis. data range is 15-127, make it 0-140. for ( int i = 0 ; i<rbiMax; i=i+ 10 ){ line ( map (i, 0 ,rbiMax,nudge* 2 , width ), map ( 0 , 0 ,salMax, height -( 2 *nudge), 0 ), map (i, 0 ,rbiMax,nudge* 2 , width ), map (salMax, 0 ,salMax, height -( 2 *nudge), 0 )); text (i, map (i, 0 ,rbiMax,nudge* 2 , width ), map (- 0.5 , 0 ,salMax, height -( 2 *nudge), 0 )); } //plot the data //go through the array called "cards" line by line //split each line where there is a tab //create a new array of strings called "data" to hold this info //player name in first column //Rbi in the second column //Salary in third column for ( int i = 0 ; i<cards. length ; i++) { String[] data = split(cards[i], '\t' ); String Name = (data[ 0 ]); float Rbi = map ( float (data[ 1 ]), 0 ,rbiMax,nudge* 2 , width ); float Salary = map ( float (data[ 2 ]), 0 ,salMax, height -( 2 *nudge), 0 ); //make a scatter plot of Rbi v. salary ellipse (Rbi,Salary, 2 , 2 ); text (Name,Rbi,Salary); } //label the axes fill ( 0 ); textAlign ( CENTER ); text ( "Runs Batted In, 2002" , ( width / 2 ), height - 20 ); pushMatrix (); translate ( 30 , height / 2 ); rotate (- PI / 2 ); text ( "Salary ($ millions)" , 0 , 0 ); popMatrix (); } |
First I use a for loop to make horizontal gridlines and label them with numbers. Then I use a for loop to make vertical gridlines and label them with numbers. Note use of map() and the global variables salMax and rbiMax to create the ranges for the axes. Next I use a for loop to go through the cards array. This array is holding the information from the external file I read in setup(). Note that I use map() to put the data inside the ranges that I set with salMax and rbiMax. I make a scatter plot with ellipse() and I also label each plotted point with the corresponding player's name using the text() command. At the end I give the axes titles. Notice the use of pushMatrix(), popMatrix(), translate(), and rotate() to make the title of the y axis appear sideways.
This is where you put functions that are called in draw(). This is also where you put commands to save the results of computations or save the contents of the display window.
Here's what comes after draw() in this program:
void mousePressed (){ save( "cardsRbiData3.png" ); } |
When I press the mouse inside the display window, an image file is saved into the data folder of this program called cardsRbiData3.png. That's it! The whole program!
Remember in Earth 501 when I made you create a frequency-magnitude diagram of a year's worth of earthquakes around the world? I know you were all hating me as you wrestled with the huge dataset and how to do all the sorting and counting by hand. The example program below is a better way to make that plot. First I went to the USGS earthquake catalog search page and made a catalog of all earthquakes for the year 2012. I used vi to get rid of the header info cluttering up the top and bottom of the file, then I used awk to extract the 9th column where the magnitudes are. Since that's the only data I care about for frequency-magnitude diagram purposes, why waste cpu reading in a bigger file? I'll just read in a 1-column file that contains the magnitudes.
//plot some data from an array //this data comes from a usgs catalog file of global earthquakes in 2012. //we will read the data in from a 1-column plain text file String[] mags; //make the array and fill it with data later void setup () { size ( 400 , 400 ); background ( 255 ); mags = loadStrings( "mags.txt" ); //this is how we read in the file contents noLoop (); //just drawing a static plot once } void draw () { //working with the data follows from here println ( "number of earthquakes in the data file is " +mags. length ); //we want to draw a cumulative frequency-magnitude diagram //we will plot magnitude on the x axis and number of eq's >= mag on the y axis. //so we have to make an array to hold this information, then go through the data file and count. //I know my smallest value is 1.1 and my biggest is 8.6 but let's go from 0 to 9 by tenths. That's a 90-element array. float [] xValue = new float [ 90 ]; float [] cumHist = new float [xValue. length ]; float [] newCumHist = new float [xValue. length ]; //cumulative histogram //I nested the for loops so that we go through the whole data //file each time we populate one spot in the cumHist array for ( int i= 0 ;i<cumHist. length ;i++){ for ( int j= 0 ;j<mags. length ; j++){ float magvalue = float (mags[j]); //convert string to float int newMagvalue = int (magvalue* 10 ); // turns a number like 3.4 into 34 for example if (newMagvalue>=i){ cumHist[i]++; } } } for ( int i= 0 ; i<cumHist. length ; i++){ newCumHist[i]= map (log10(cumHist[i]), 0 ,log10(max(cumHist)), height , 0 ); xValue[i]= map (i, 0 ,xValue. length , 0 , width ); line (xValue[i],newCumHist[i],xValue[i], height ); } } // Calculates the base-10 logarithm of a number float log10 ( float x) { return (log(x) / log( 10 )); } //save a plot when mouse clicked in display window void mousePressed (){ save( "fmplot.jpg" ); } |
So this program is more complicated than the cardinals rbi plotter or the world map plotter because we are actually doing some calculations with the data we are reading in. In order to make a histogram, I created an array to hold the histogram values, then I use a for loop to populate each entry in the histogram array. Each time I go to the next value of the histogram array I loop through the entire datafile of values to see if each one is greater than or equal to the place where I am in the histogram. If it is, I add one. At the end, I've populated the whole histogram. Remember that we need to plot logarithm of the cumulative number of earthquakes to make the plot have the slope of -1. log() in Processing is natural log (base e), so to get base 10, you have to take the natural log of the value and divide that by the natural log of 10. I do that in a function that comes after draw().
// Calculates the base-10 logarithm of a number float log10 ( float x) { return (log(x) / log( 10 )); } |
Note that instead of writing the log10 function with void at the beginning, I wrote float at the beginning instead. That's because this function is designed to output a number. It's much more like a function the way you learned it in your math classes. This function takes a number, calculates the log base 10 of it, and spits it out.
The plotting action of this program happens when I make a line at each place in the array whose height is log10 of the number of earthquakes greater than or equal to that magnitude. I could have made a scatter plot or whatever, but I thought a bar-graph-looking figure would be fun. That happens in this line:
line (xValue[i],newCumHist[i],xValue[i], height ); |
Okay, okay! I know this plot violates all my own rules for good plot-making, such as, it doesn't have any labels anywhere, no title, etc. But the content is correct. The next step is to fix up the plot so that it has all of those things. Guess what! That's an exercise for you!
Go back to the New Madrid frequency-magnitude problem set [33] from Earth 501, and pick one plot to recreate using Processing skills to make a plot. You can pick one year of earthquakes from New Madrid, or one year from the southern California catalog, or one year from the world (similar to what I did, above), or some combination of those.
In your program, I'd like to see correct use of loading a text file and working with array data. In your finished plot I'd like to see a correct cumulative frequency-magnitude diagram with nice-looking labels and a title.
Submit your program and all ancillary files needed to run your program to the Exercise 8.0 dropbox. Remember to zip your folder and submit that so that I have all the extra files needed.
Time to put the skills you've been learning to work while designing two small projects. Ideally one of the projects will be something for fun and the other one will be something that has science and/or data in it. I don't want to proscribe either one of them too narrowly because I'd like you to have control over what you want to do. If you created a program in an earlier lesson but didn't quite have the time or the know-how to make it work just the way you wanted, this is the perfect time to revisit that program and make it better. There is also a reading discussion featuring two chapters from The Visual Display of Quantitative Information by Edward Tufte.
By the end of this lesson, you should be able to:
This lesson will take us two weeks to complete. The book chapter discussion nominally takes place over the first week, 14-20 July 2021. Turn in your two projects by 27 July 2021.
If you have any questions, please post them to our Questions? discussion forum (not e-mail) in Canvas. I will check that discussion forum daily to respond. While you are there, feel free to post your own responses if you, too, are able to help out a classmate.
The .pdf copies of the articles we'll read for this week's discussion are in Canvas.
This week we will read and discuss two chapters from Edward Tufte's book The Visual Display of Quantitative Information:
The first chapter discusses why data maps, time series plots, and "narrative data graphics" are so valuable and why it pays to think carefully about how to construct them. There are some beautiful examples presented (and a couple of examples of what not to do) along with some how-to points regarding graphic construction best practices.
Chapter 8 I really like because of the baseball sparklines showing how different teams fared throughout a season. There are some other relevant points for us to consider in this chapter, too, because as we have discovered during this semester and in other courses in this master's degree program, Earth science produces really big datasets and usually you have to display all the data for the viewer to get the point.
As you read, consider the following questions, which we will discuss as a class:
Once you have finished reading these two chapters, engage in a class discussion in Canvas that will take place over a week. This discussion will require you to participate multiple times over that period.
You will be graded on the quality of your participation. See the grading rubric [2] for specifics.
My suggestion is to create a cool animation, game, or drawing. Here's a video of my feeble example of a game. It would be better if it were relevant to Earth science concepts in some way.
Watch this video (1:25).
When it's running, you have to click inside the circle to make the circle go up. It will bounce off the sides and the top. Each time it bounces off the top border, it goes a little faster. If it hits the bottom, the game is over. A better version of this game would involve some kind of screen with directions at the beginning and a button to make it start, and a restart button. Maybe one of you can beef this up or create something a lot better.
Processing code for my example of a game:
// Bouncing ball game. // Don't let the ball hit the bottom. // Click inside the ball to make it go up. color bg = color ( 220 , 220 , 200 ); float ballsize = 40.0 ; float speed = 1.0 ; float y = random (ballsize, ( height - ballsize)); float x = random (ballsize, ( width - ballsize)); float ball_xdirection = 1 ; float ball_ydirection = 1 ; int points = 0 ; PFont font1; PFont font2; void setup () P size ( 200 , 200 ); font1 = loadFont( "BradleyHandITCTT-Bold-48.vlw" ); font2 = loadFont( "Garamond-14.vlw" ); } void draw () { background (bg); strokeWeight ( 4 ); ellipse (x, y, ballsize, ballsize); x = x + ((. 5 * speed) * ball_xdirection); y = y + (speed * ball_ydirection); if (x > width - (ballsize/ 2 ) || x < ballsize/ 2 )) { ball_xdirection *= - 1 ; int r = int ( random ( 255 )); int g = int ( random ( 255 )); int b = int ( random ( 255 )); stroke (r, g, b); } if (y < (ballsize/ 2 )) { ball_ydirection *= - 1 ; int r = int ( random ( 255 )); int g = int ( random ( 255 )); int b = int ( random ( 255 )); fill (r, g, b); points=points+ 1 ; println ( "Good work! " + points + " points for you" ); } if (y > height - (ballsize/ 2 )) { println ( "GAME OVER" ); textFont (font1); textAlign ( CENTER ); text ( "SPLAT!" , width / 2 , height / 2 ); textFont (font2); textAlign ( RIGHT ); fill ( 0 ); text ( "Points: " + points, width - 20 , 20 ); strokeWeight ( 8 ); stroke (bg); fill (bg); ellipse (x, y, ballsize, ballsize); fill ( 255 ); stroke ( 255 ); ellipse (x, y + ballsize/ 2 , ballsize* 2 , ballsize/ 2 ); noLoop (); } if ( mousePressed == true && mouseX <= x + ballsize/ 2 && mouseX >= x - ballsize/ 2 && mouseY <= y + ballsize/ 2 && mouseY >= ballsize) { speed = speed + 0.05 ; ball_ydirection = - 1 ; } } |
A still shot from the end of the game:
Submit your program to the "small project 1" dropbox. Remember that if your program requires extra files such as fonts or external files then zip the whole thing and submit the zipped file to the dropbox.
I suggest that this project do something visually interesting with an Earth science data set of some sort. If you are stumped or can't find anything good, let me know and I can help brainstorm.
Below is a video of the example program of an an animation of some foreshocks and aftershocks from the March 2011 Tohoku-Oki earthquake that happened off the coast of Japan. There's a time-elapsed clock up in the corner so you can tell how far along the animation is. I made the circle sizes go as a log scale instead of a linear scale like most people do because this way is actually a more accurate representation of the difference in earthquake magnitudes compared to each other. The fact that they are all represented as circular sources is not accurate, though. One thing you can see that falls naturally with my way of plotting is that you can really see the fact that aftershock locations basically fill up the area swept out by the mainshock. Remember from Earth 520 that one way to tell the areal extent of the rupture of a large earthquake is to see where the aftershocks are.
Watch this video (1:25).
Here is the code that was shown in the video:
/** * tohokumap * * Loads a text file that contains three columns (lat, long, mag) of seismic locations * from NEIC Feb 10 - Apr 5 of 2011, in a box from 32N - 46N and 14E - 148E (Japan). * * * The magnitude of each event is shown by its radius (exp(mag)/20 = radius in pixels). * * The "clock" in the corner shows time elapsing as the events happen **/ String[] lines; String[] coast; int index = 0 ; float magscale = 1.1 ; void setup () { size ( 700 , 700 ); // Each degree of lon/lat is 50 pixels background ( 200 ); smooth (); //frameRate(30); PFont font; font = loadFont( "AppleSymbols-18.vlw" ); textFont (font); lines = loadStrings( "tohoku_latlonmag.txt" ); coast = loadStrings( "coast30062.txt" ); fill ( 0 ); text ( "Animation begins on 10 Feb 2011, ends on 5 Apr 2011" , 100 , 50 ); // Draw the coastline for ( int i = 0 ; i < coast. length ; i++) { String[] pieces = split(coast[i], ' ' ); float lon = float (pieces[ 0 ]); float lonMap = map (lon, 134 , 148 , 0 , width ); float lat = float (pieces[ 1 ]); float latMap = map (lat, 32 , 46 , heiht, 0 ); pushMatrix (); translate (lonMap, latMap); stroke ( 0 , 0 , 255 ); point ( 0 , 0 ); popMatrix (); } } void draw () { // Plot the events stroke ( 150 ); if (index < lines. length ) { String[] pieces = split(lines[index], ' ' ); float y = float (pieces[ 0 ]); // latitude float yMap = map (y, 32 , 46 , height , 0 ); float x = float (pieces[ 1 ]); // longitude float xMap = map (x, 134 , 148 , 0 , width ); float m = float (pieces[ 2 ]); //magnitude if (m < 4.0 ) { fill ( 192 , 250 , 58 ); } else if (m >= 4.0 && m <= 4.9 ) { fill ( 243 , 250 , 58 ); } else if (m >= 5.0 && m <= 5.9 ) { fill ( 250 , 109 , 58 ); } else if (m >= 6.0 && m <= 6.9 ) { fill ( 250 , 186 , 58 ); } else if (m >= 7.0 && m <= 7.9 ) { fill ( 150 , 129 , 58 ); } else if (m >= 8.0 && m <= 8.9 ) { fill ( 250 , 100 , 58 ); } else if (m > 8.9 ) { fill ( 250 , 87 , 58 ); } else { println ( "bummer! I don't know what to do with m = " +m); } ellipse (xMap, yMap, exp(m) / 20 , exp(m) / 20 ); // Make a time elapsed clock up in the corner. noFill (); ellipse ( 50 , 50 , 80 , 80 ); // Angles start at 3 o'clock // Subtract HALF_PI to make the clock start at the top. float t = map (index, 0 , lines. length , 0 , TWO_PI ) - HALF_PI ; line ( 50 , 50 , cos (t) * 38 + 50 , sin (t) * 38 + 50 ); index = index + 1 ; } } |
Another point made by this animation is that a magnitude 9 earthquake is really, really, really, mindbogglingly big!
If you are prepared to be watching closely at the beginning, you can see that this earthquake had a big foreshock, too, and that foreshock had a cascade of its own aftershocks before the main event happened.
Here is a video showing just the Tohoku aftershocks animation by itself (0:25). (This video lets you watch the animation without hearing me talk about the details of the program)
Here's a still shot from the end of the animation:
Turn in your project to the "small project 2" dropbox. Remember to zip your whole folder and turn the zipped file in if your program requires external files such as fonts, images, or data in order to run.
The capstone project for this course is a venue for you to synthesize your programming skills with your Earth science education skills. Create a small lesson that you would teach to an audience of your choice that involves some aspect of programming. Note that you do NOT have to teach programming in your lesson (but you could). Your lesson needs to make use of your programming skills, though, so for example you could develop one of your two small projects into a lesson in which students would have to interact with an animation or plot you made with Processing.
By the end of this project you should be able to integrate programming and Earth science education into a teachable lesson.
Your capstone project should be lesson plan that has:
Turn in your project to the capstone dropbox by 11 August 2021.
Enter the Canvas discussion board for our last Teaching/Learning discussion!
This last discussion is a good opportunity to upload a favorite program that you wrote this semester so everyone else can see it. Also this is a good place to see if anybody else did something cool that you'd like to figure out how to do, too.
Links
[1] http://www.processing.org
[2] https://www.e-education.psu.edu/earth801/node/537
[3] http://processing.org/reference/rectMode_.html
[4] http://processing.org/reference/ellipseMode_.html
[5] http://processing.org/reference/arc_.html
[6] http://processing.org/reference/bezier_.html
[7] http://processing.org/reference/triangle_.html
[8] http://processing.org/reference/curve_.html
[9] http://www.yesteryearsports.com/New-York-Knights-1939-Home-Jersey-Roy-Hobbs-NYK39H.htm
[10] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/forLoop.mp4
[11] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/forLoopCC.mov
[12] https://scratch.mit.edu/
[13] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/cardsText0.mp4
[14] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/cardsText0CC.mov
[15] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/cardsBraves.mp4
[16] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/cardsBravesCC.mov
[17] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/rotate0.mp4
[18] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/rotate0CC.mov
[19] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/sunriseSunset.mp4
[20] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/sunriseSunsetCC.mov
[21] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/rotatingSquare.mp4
[22] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/rotatingSquareCC.mov
[23] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/rotatingSquareMouse.mp4
[24] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/rotatingSquareMouseCC.mov
[25] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/massimoDisplaySave.mp4
[26] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/massimoDisplaySaveCC.mov
[27] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/images/lesson7/baja_neic.txt
[28] http://www.cygwin.com
[29] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/text/lesson7/file1.txt
[30] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/text/lesson7/baja_neic.txt
[31] https://www.evl.uic.edu/pape/data/WDB/
[32] https://www.e-education.psu.edu/earth801/sites/www.e-education.psu.edu.earth801/files/screencasts/mapDemo.mp4
[33] https://www.e-education.psu.edu/earth501/content/p4_p7.html