Programming for Interactive Digital Arts
CS 2, Fall 2023

4. State

No magic numbers

Last time we saw that Processing uses variables to keep track of some aspects of the state of the world, namely the mouse position. Two additional variables that it provides are width and height, which tell us how big the window is. They'll both be 100 unless we call createCanvas() in setup(), in which case they'll keep track of that number for us. The question you might have is why this matters. One reason is that if we just use the number 100 all over our sketch, it looks a little "magical" when we see it -- 'why was it we used 100 for that call?' Then if we decide to make the window 400x400, we have to find all the 100s that had to do with the window size (but not other ones) and change them to 400. By using width and height, we don't have a mysterious number, and we don't have to remember to change it if we make the screen bigger.

As with the mouse position variables, these variables hold numbers, so we can treat them exactly the same. Thus we can add (+), subtract (-), multiply (*), and divide (/) them, among other things. Here's an example where we "mirror" the mouse position, by subtracting it from the width and/or height of the window:

[sketch1]
function setup()
{
  createCanvas(400,400);
  noStroke();
  frameRate(30);
  background(0);
}

function draw()
{
  // fade away
  background(0,20);
  
  // draw the mouse
  fill(255);
  ellipse(mouseX,mouseY,30,30);
  
  // mirror it
  ellipse(width-mouseX,mouseY,30,30);
  ellipse(mouseX,height-mouseY,30,30);
  ellipse(width-mouseX,height-mouseY,30,30);
}

Notice also that we do the fade-away with a transparent background

Here's an example that sets the red component of the background color based on the x coordinate, and the blue component based on the y component. The formula calculates the position as a fraction of the width (or height), from 0 to 1, and scales that to be from 0 to 255. It doesn't matter how big we set the window -- the sketch will still work correctly. Also take a look at the Processing example Basics | Input | Mouse2D, which sets sizes according to mouse position.

[sketch2]
function draw()
{
  background(255*mouseX/width,0,255*mouseY/height);
}

One more example: drawing lines from the corners to the mouse position.

[sketch3]
function setup()
{
  createCanvas(400,400);
  background(0);
}

function draw()
{
  background(0);
  // Guide
  stroke(255);
  line(0,0,mouseX,mouseY);
  line(0,height,mouseX,mouseY);
  line(width,0,mouseX,mouseY);
  line(width,height,mouseX,mouseY);
  // Mouse position
  fill(0,0,255);
  noStroke();
  ellipse(mouseX,mouseY,15,15);
}

Our own variables

Suppose we want to draw a bunch of same-sized ellipses, one at (100,100), one at (150,300), one at (300,150), etc. To make sure they're all of the same size, we put the same values for their width and height, say 100 and 100. But what if we decide we want them all to be bigger? We're in the same situation as with the width and height of the window -- we've got magic numbers, and we need to remember which 100s we need to change. To help us out, Processing let us create our own variables to keep track of information. Let's start with the ellipse example.

[sketch4]
createCanvas(400,400);

var diameter = 100;  // declare our variable and give it an initial value

ellipse(100,100, diameter,diameter);
ellipse(150,300, diameter,diameter);
ellipse(300,150, diameter,diameter);

All the syntax stuff for the example is discussed down in the Programming notes section. What we've done is declared a variable called "diameter" that holds a number that could have a decimal part, though we don't give one here, and set it to be 100. We've then used that variable exactly like a number (as we have done with mouseX and so forth) to specify the ellipse's width and height. If we ever decide we want bigger ellipses, we just change the 100 up where "diameter" is declared; try it.

As the name suggests, a variable can actually vary. For example, we could set the value of "diameter" to a different random number each frame; then all ellipses change diameter in synch.

[sketch5]
var diameter = 100;  // declare our variable and give it an initial value

function setup() {
  createCanvas(400,400);
  frameRate(10);
  background(0);
}

function draw() {
  background(0);
  diameter = random(50,150);
  ellipse(100,100, diameter,diameter);
  ellipse(150,300, diameter,diameter);
  ellipse(300,150, diameter,diameter);
}

The assignment diameter = random(25,75); gives a new value to the diameter variable. Note that when we update the value, we don't specify the type again; if we did, it would actually be seen as the declaration of a new variable of the same name.

We can use this same idea to create motion, as in the following example (Shiffman 4-3, 4-4, and 4-8 provide some more examples). In this case, we declare variables named "x" and "y", with initial values both 200. The assignment then updates the values of the variables, setting x to its old value plus some random amount, and similarly with y.

[sketch6]
// The current coordinates of the ball, starting at the center
var x=200, y=200;

function setup()
{
  createCanvas(400,400);
  noStroke();
  background(0);
}

function draw()
{
  background(0,20);
  fill(255);
  ellipse(x,y,20,20);
  // Move the position by random steps in x and y
  x = x+random(-10,10);
  y = y+random(-10,10);
}

Events

The draw() function is called every frame. Similarly, we can define functions that are called every time the mouse or a key is pressed. These are called events. For example, we can draw an ellipse when a mouse button is pressed using the mousePressed(). A similar example could check for a key pressed, byt defining the keyPressed() function.

[sketch7]
function setup()
{
  createCanvas(400,400);
  noStroke();
  background(0);
  frameRate(15);
}

function draw()
{
  background(0,20);
}

function mousePressed()
{
  fill(255);
  ellipse(mouseX,mouseY,50,50);
}

Two additional functions exists that check when a mouse or keyboard button is released: mouseReleased() and keyReleased(). I suggest to use the "pressed" functions since they are more intuitive, unless a specific need arise. In my case, I used the keyReleased function to "hide" a button that capture screenshots for these examples, writing something like:

function keyReleased()
{
  save("sketch.png");
}

You can see the actual code I use if you download the 'pde' file from the applet link. My version is a bit more complex to make things work well inside a broswer. Please ignore these details for now, since we will cover them later. The actual saving is done by the save() function, that can output different image formats depending on the file extension.

By using our own variables, we can make the mouse control aspects of the sketch. For example, we can make the random wanderer teleport to the current mouse location and continue wandering from there. Just add the following to the above sketch.

function mousePressed()
{
  x = mouseX;
  y = mouseY;
}

Programming notes

Math
We can do calculator-like things with numbers, including add (+), subtract (-), multiply (*), and divide (/) them. Other mathematical operations include abs() (absolute value) and min() (minimum). The results of these operations are themselves numbers. There are rules about the order in which the parts of complex expressions are evaluated (e.g., multiplication before addition). Parentheses can help make it clear.
Declaration
To create our own variable, we declare it with "var" and its name, e.g., "var x;". In many cases, it is apropriate to give it an initial value, too, e.g., "var x=50;". Multiple variables of the same type can be declared together, separated by commas, e.g., "var x=50, y=50;".
Local vs. global
Where and how a variable can be used depends on where it is declared. A global variable is declared before the setup function. It can be used anywhere, and its value carries over from one function call to the next (e.g., from frame to frame). A local variable is declared inside a function definition (e.g., inside the draw function). It can only be used in that function, and its value is reset each time the function is called. So far we're only using global variables, but local variables are quite useful to hold temporary calculations, as we'll see in the next few lectures.
Identifier
Choose meaningful names for variables, so that when you read the code, you can tell what they stand for. There are restrictions on the names, though -- stick with alphanumeric (beginning with a letter), and afunction names already used by Processing (e.g., ellipse). They are case sensitive; unless you want your code to look like it's from the 70s, use mostly lower-case. A common practice for multi-word variables is to capitalize the first letter of the second (third, fourth, ...) word, e.g., myName.
Assignment
Assignment updates the value of a variable. It is written "variable = expression", where before the equals sign is the name of the variable, and after it is an expression of the appropriate type. We can combine math operators with assignment (e.g., +=, -=, *=), e.g., "variable += expression" is shorthand for "variable = variable + expression". Even shorter-hand expressions are available for incrementing by one, "x++", and decrementing by one, "x--".