8. Hit Detection and Functions

In these notes you will learn:

  • How to test if a point is in a circle.
  • The basic usage and form of if-statements.
  • How to detect and respond to mouse-clicks.
  • How to test if a point is in a rectangle.
  • How to write simple functions.
  • The difference between local and global variables.

8.1. Introduction

In many interactive graphics programs, such as games or user interfaces, it’s important to determine if one object has hit another. For instance, in a first-person shooter game you need to know when a bullet has hit a player. Or, in a graphical interface you need to know what window the mouse pointer is in when it has been clicked.

Hit detection turns out to be a surprisingly tricky topic if you are dealing with arbitrary shapes. Efficiently determining if, say, a give point is inside even a polygon is non-trivial. Thus, we will restrict ourselves to a few important cases that are not too hard to test: circles and rectangles. Also, we are going to stick to 2 dimensions: hit detection in 3D is significantly more complicated.

Another important goal of this section is to introduce the idea of writing our own functions. A function is essentially a named block of code that stores code that we want to re-use.

8.2. Point in a Circle

Testing if a point is in a circle.

For our first test, lets see how to determine if a point is in a circle. Lets assume that the point has coordinates (a, b), and the circle has center (x, y) and radius r.

How can we test, mathematically, if (a, b) is in the circle?

Take a look at the diagram on the right. The trick is to measure the distance between (x, y) and (a, b). If it’s r, or less, then (a, b) is in the circle; otherwise it’s outside the circle.

The Processing function dist calculates the distance between two points: dist(x, y, a, b) returns the distance between (x, y) and (a, b). We can write our test with an if-statement:

if (dist(x, y, a, b) <= r) {
   // (a, b) is in the circle
   // do something!
}

Note

dist(x, y, a, b) calculates the following value:

\begin{equation*} \sqrt{(x-a)^2 + (x-b)^2} \end{equation*}

You could write this in Processing like this:

sqrt((x - a) * (x - a) + (x - b) * (x - b))

For example, here’s a program that changes a bouncing ball’s color to red when the mouse pointer is on it:

color orange = color(255, 165, 0);
color white = color(255, 255, 255);

color ball_color = orange;

float x;
float y;

float dy;
float dx;

float diam;

void setup() {
  size(500, 500);
  smooth();

  // set the ball's initial position and velocity
  x = 250;
  y = 250;
  dx = 1.3;
  dy = -2.77;
  diam = 50;
}

void draw() {
  // re-draw the background
  background(white);

  // draw the ball
  noStroke();
  fill(ball_color);
  ellipse(x, y, diam, diam);

  // move the ball to its next position
  x += dx;
  y += dy;

  // hit the top edge?
  if (y <= 0) {
    dy = -dy;
  }

  // hit the bottom edge?
  if (y >= 499) {
    dy = -dy;
  }

  // hit the left edge?
  if (x <= 0) {
    dx = -dx;
  }

  // hit the right edge?
  if (x >= 499) {
    dx = -dx;
  }

  // change the ball's fill color to red if the mouse
  // pointer is over the ball
  if (dist(mouseX, mouseY, x, y) <= diam / 2) {
    ball_color = color(255, 0, 0);
  }

}

Note the following:

  • The diameter of the ball is stored in a global variable called diam.

  • The fill color of the ball is stored in a global variable called ball_color. We need a variable for fill color because we are going to change it.

  • At the end of the draw() function, we check to see if the location of the mouse pointer is inside the ball. If it is, then the ball’s color is set to red.

    Notice that we don’t need to click the mouse pointer for the ball to turn red. All that matters in this program is that the pointer be on the ball, i.e. that it “hover” over the ball.

8.3. If-else Statements

When you try this program, you’ll pretty quickly see that once the ball turns red, it stays red forever. Lets change it so that the ball is red just when the mouse pointer is hovering over it, and orange when it’s not. The only change we need to make is to the if-statement in draw():

if (dist(mouseX, mouseY, x, y) <= diam / 2) {
  ball_color = color(255, 0, 0);
} else {
  ball_color = orange;
}

This is an example of an if-else statement: if the condition is true, then the ball’s color is set to red; otherwise, it is set to orange.

We’ll be using if-else statements quite a bit, so it’s useful to write them out in their most general form:

// ... previous statements ...

if (cond) {
   // ... statements to do when cond is true ...
} else {
   // ... statements to do when cond is false ...
}

// ... following statements ...

Here cond is a boolean expression, i.e. cond must evaluate to either true or false.

If-else flow chart.

8.4. Clicking the Ball

Lets modify our program once more: how can we get the ball to change color just when we click on it?

To do this we need some way to recognize mouse-clicks. One way is to use the special mousePressed() function:

// comment-out the if-statement at the end of draw() before adding
// this function
void mousePressed() {
   ball_color = color(255, 0, 0);
}

As the comment says, when you add this to the program above you should comment-out the if-statement at the end of draw().

Every time you click a mouse button, Processing automatically calls mousePressed() and runs whatever code is inside of it. Thus, in this particular example the ball will become red whenever you click the mouse button.

Notice that it doesn’t (yet) matter where the mouse pointer is when you click it: mousePressed() only takes mouse-clicks into account, and does not care about the location of the pointer.

So how can we change the color of the ball just when we click inside of it? The solution is to put the if-statement that we commented-out in the draw() function into mousePressed():

void mousePressed() {
  if (dist(mouseX, mouseY, x, y) <= diam / 2) {
    ball_color = color(255, 0, 0);
  }
}

Now, when the user clicks a mouse button, mousePressed() is called. The first thing it does is check if the mouse pointer is inside the ball. If it is, the ball’s color is set to red. Otherwise, it does nothing.

Finally, lets make the ball alternate between red and orange when we click on it. To do this, we’ll use an if-statement to check the color of the ball inside of mousePressed():

void mousePressed() {
  if (dist(mouseX, mouseY, x, y) <= diam / 2) {
    if (ball_color == orange) {
      ball_color = color(255, 0, 0);
    } else {
      ball_color = orange;
    }
  }
}

Here we have an if-statement within an if-statement (and lots of braces!). The inner-most if-statement checks the current color of the ball. If the ball is currently orange, then it sets it to red. Otherwise, if the ball is not currently orange, then it must be red (because we are only using two colors), and so we change it to orange.

Notice the inner-most if-statement condition:

if (ball_color == orange) {
 // ...

The symbol == is used to test if two primitive values (such as Processing colors) are equal. We can’t use = here, because in Processing = is the assignment operator that is used to give values to variables.

8.5. Point in a Rectangle

Testing if a point is in a rectangle.

Suppose you want to test if a point is inside or outside a rectangle. In a graphical windowing interface, where all the windows are rectangles, this is an important and common test.

In the diagram, (x, y) is the upper-left corner of a rectangle with dimensions Width-by-Height. If you look at the green point (a, b), it is inside the rectangle because it is between the two vertical edges, and it is also between the two horizontal edges. In contrast, the point (c, d) is not inside the rectangle because it is not between the vertical edges (although it is between the horizontal edges).

So to test if a point is inside a rectangle, we need to test if the point is between the left/right edges, and also the top/bottom edges. Suppose we’ve drawn a rectangle on the screen with this statement:

rect(x, y, Width, Height);

How can we tell if the mouse pointer, which is at (mouseX, mouseY), is inside this rectangle? From the discussion above we need to check that:

  • mouseX is between the left and right edges
  • mouseY is between the top and bottom edges

To test if mouseX is between the left and right edges, we need to know where, exactly, these edges are situated. Since (x, y) is the upper-left corner of the rectangle, the left edge is x pixels along the x-axis, while the right edge is x + Width pixels along the x-axis. Thus, mouseX is between these edges when both x <= mouseX and mouseX <= x + Width are true at the same time.

In Processing, && means “and”, and so we can combine these two expressions into one:

(x <= mouseX) && (mouseX <= x + Width)

This boolean expression is true just when mouseX is between the left and right edges of the rectangle.

Testing if mouseY is between the top and bottom edges is done with a similar expression:

(y <= mouseY) && (mouseY <= y + Height)

Now if the mouse pointer is inside the rectangle, then both expressions must be true, i.e. this big boolean expression must be true:

(x <= mouseX) && (mouseX <= x + Width)
&&
(y <= mouseY) && (mouseY <= y + Height)

Note

We’ve written the && on its own line to emphasize it. In Processing extra spaces, or blank lines, around symbols usually don’t matter. Processing allows this because it helps us format the code in whatever is easiest for humans to read.

Now lets write a program that makes a rectangle bounce around the screen and change color when we click on it. This program is very similar to the one with the ball:

color orange = color(255, 165, 0);
color white = color(255, 255, 255);

float x, y;
float Width, Height;
float dx, dy;

color rect_color = orange;

void setup() {
  size(500, 500);
  smooth();

  // set the rectangle's initial position and velocity
  x = 250;
  y = 250;
  Width = 75;
  Height = 50;
  dx = 1.3;
  dy = -2.77;
}

void draw() {
  // re-draw the background
  background(white);

  // draw the rectangle
  noStroke();
  fill(rect_color);
  rect(x, y, Width, Height);

  // move the rectangle to its next position
  x += dx;
  y += dy;

  // hit the top edge?
  if (y <= 0) {
    dy = -dy;
  }

  // hit the bottom edge?
  if (y >= 499) {
    dy = -dy;
  }

  // hit the left edge?
  if (x <= 0) {
    dx = -dx;
  }

  // hit the right edge?
  if (x >= 499) {
    dx = -dx;
  }
}

void mousePressed() {
  if ((x <= mouseX) && (mouseX <= x + Width) && (y <= mouseY)
    && (mouseY <= y + Height)) {
    if (rect_color == orange) {
      rect_color = color(255, 0, 0);
    }
    else {
      rect_color = orange;
    }
  }
}

The main difference between this program and the one with the ball is that we have to use a different test to check if a point is in a rectangle. You can see that the if-statement condition in mousePressed is bigger than the one for the ball. Such long expressions make our program harder to read, so in the next section we’ll see how to simplify this code using a function.

8.6. Writing Functions

For any code we think we might want use more than once, it’s usually a good idea to write it as a function. Essentially, a function is a named block of code that accepts input values and returns an output value.

For instance here’s a function we might write named pointInCircle that tests if a point (x, y) is inside a circle with center (a, b) and radius r:

// Returns true if (x, y) is in the circle with center (a, b) and with
// radius r; otherwise it returns false

boolean pointInCircle(float x, float y,  // (x, y) is any point
                      float a, float b,  // (a, b) is the circle's center
                      float r            // r is the circle's radius
                     )
{
   if (dist(x, y, a, b) <= r) {
      return true;
   } else {
      return false;
   }
}

Let’s look at the details of this function:

  • The top two lines, the ones beginning with //, are source code comments that explain what the function does. Such comments document the function, i.e. they tell people reading the code what the function does.

    Clear and precise documentation pays off, especially for functions that will be used again and again. It’s fair to say that most programmers don’t like writing documentation, and often leave it to the end, or ignore it altogether. While you might save time now by not writing documentation, you end up losing more time later when you need to use a function that you don’t really understand. Overall, it’s worth it to take a little bit of extra time to document your functions.

  • After the function documentation comes the function header:

    boolean pointInCircle(float x, float y, float a, float b, float r)
    

    The name of this function is pointInCircle, and it promises to return either true or false, i.e. it has a return value of type boolean. By return we mean that when you call the pointInCircle function somewhere in your program, it will evaluate to either true or false, depending upon the input you give it.

    The function header also tells us that pointInCircle takes 5 float values as input: x, y, a, b, and r. These are called the function’s parameters, and they specify the input the function needs to work correctly.

    In general, function headers in Processing always have this form:

    return_type functionName(parameter_list)
    

    Five parameters for one function is a lot of parameters: it’s easy to mix-up their order. Later we will see how object-oriented programming can help drastically cut down on the number of parameters functions like this need to take.

  • Next comes the function body:

    {
       if (dist(x, y, a, b) <= r) {
          return true;
       } else {
          return false;
       }
    }
    

    This is the code that will be run when you call the function. The variables x, y, a, b, and r are the ones specified in the function header (and so we know they are all of type float).

    The return statements indicates the points where the function ends and returns a value. Since the return type of pointInCircle is boolean, we must make sure that the function returns only true or false (otherwise you will get an error when you try to run the program).

    Note

    Once a return statement is executed, no other statements in the function are run: Processing immediately exits the function after it executes a return.

    Lastly, notice that the function body is its own block of code: it begins with an open brace {, and ends with a close brace }. All function bodies are wrapped in braces.

    pointInCircle contains an if-else statement, but, in general, a function body can contain almost any Processing code at all.

Here’s how we can use pointInCircle in the bouncing ball program:

boolean pointInCircle(float x, float y, float a, float b, float r) {
  if (dist(x, y, a, b) <= r) {
    return true;
  } else {
    return false;
  }
}

void mousePressed() {
  if (pointInCircle(mouseX, mouseY, x, y, diam / 2)) {
    if (ball_color == orange) {
      ball_color = color(255, 0, 0);
    } else {
      ball_color = orange;
    }
  }
}

mousePressed is now a little bit easier to read because the name pointInCircle makes it clear what the outer if-statement is doing.

Note

Another way to write the pointInCircle function is like this:

boolean pointInCircle(float x, float y, float a, float b, float r)
{
  return dist(x, y, a, b) <= r;
}

Here we’ve removed the if-statement, and instead directly returned the value of the boolean expression dist(x, y, a, b) <= r.

Most experienced programmers prefer this way of writing the function: it is shorter and, when you get used to it, more readable than the version in the notes.

8.7. Testing if a Point is in a Rectangle

Now lets see how we can write a function to test if a point (a, b) is in a rectangle with upper-left corner (x, y) and width w and height h:

// returns true if point (a, b) is the rectangle with upper-left
// corner (x, y), and width w and height h

boolean pointInRect(float a, float b,   // point
                    float x, float y,   // rectangle's upper-left corner
                    float w, float h)   // width and height
{
   if ((a >= x && a <= x + w) && (b >= y && b <= y + h)) {
      return true;
   } else {
      return false;
   }
}

This function requires 6 input values, which is a lot. We’ve grouped the related variables on lines an added comments to help clarify what they are for. While there is no way to get rid of any of these variables (all are needed to determine if a given point is inside a given rectangle), later we will see that object-oriented programming will greatly simplify this function.

Note

As with pointInCircle, experienced programmers would probably write pointInRect in this shorter form:

boolean pointInRect(float a, float b, float x, float y, float w, float h)
{
   return (a >= x && a <= x + w) && (b >= y && b <= y + h);
}

8.8. Global and Local Variables

An important detail in this program is that the variables x and y occur twice each: x and y are defined as global variables that store the center of the ball, and they are also defined as the parameters for the pointInCircle function. So a problem arises: when x and y are used inside pointInCircle, do they refer to the global x and y, or to the local variables x and y declared in the header of pointInCircle?

It turns out that Processing uses the local x and y declared in the function header. The global x and y are not directly accessible inside the function.

So you must be careful when you read the body of functions inside another program: the local variables in a function need not have any relation to global variables with the same name.

8.9. Computer Functions and Mathematical Functions

It’s useful to compare Processing‘s functions with traditional mathematical functions. They have many similarities, but also some important differences.

In mathematics, a function is a rule that pairs an input value with an output value. For example, in math we might define a function \(f\) like this:

\begin{equation*} f(x) = x^2, x \geq 0 \end{equation*}

The name of this function is f, and it takes a single input called \(x\) . The value of \(x\) must be 0 or greater. It’s understood that both \(x\) and \(x^2\) are numbers.

We can use \(f\) in calculations. For example, \(f(5) = 5 \times 5 = 25\) , and we would say that \(f(5)\) evaluates to 25. We can also use it within expressions, such as \(2 + 3f(2)\) , which evaluates to 14.

Now lets see how we would write the equivalent of \(f\) in Processing:

float f(float x) {
   return x * x;
}

The most obvious difference is that the Processing function is a lot bigger: it contains more information than the mathematical one.

For example, the Processing function explicitly indicates the type of the input variable x. That is, we must tell Processing that f expects an input value of type float. We didn’t bother doing that for the mathematical \(f\) because it is understood by the reader that \(x^2\) works for numbers.

Similarly, we also have to explicitly indicate the return-type of the Processing function. Again, this is obvious in the mathematical version, but here we must be precise. It is more typing, but one benefit is that giving an explicit return-type catches errors where we try to use f in an expression that doesn’t accept a float.

The body of the Processing function is a single line that begins with return. This indicates that when f(x) is evaluated in the program, it should evaluate to x * x. We don’t bother writing return in the mathematical \(f\) function because it is understood that it evaluates to \(x^2\) . Plus, who wants to write out the word “return” every time you define a function?

The essential difference between a mathematical function and Processing is formality: Processing is much stricter than regular mathematics, and insists that you define all functions in a completely precise and formal way. In mathematics, things are not so strict: it avoids much of the verbosity of Processing by relying on common shared understanding among the humans who read and write mathematics.

Finally, we should point out that Processing functions are more general and more powerful than mathematical functions. For example, there is no mathematical equivalent of this:

float g(float x) {
   println("debug: x = " + x);
   return x * x;
}

This is an ordinary Processing function, but there is no way to write it mathematically: it makes no sense for a mathematical function to print something (where would it print it?).

8.10. Question

  1. Explain what the dist function does. Give an example of how to call it.

  2. Explain, in precise English, what the following code does (the println function prints a message on the console window, i.e. on the little window under the source code in the Processing editor):

    // ... assume x has been defined and given some value ...
    
    if (x == 1) {
       println("Hello!");
    } else {
       println("Goodbye!");
    }
    
  3. Does the following code behave the same as the code in the previous question? Explain your answer.

    if (x == 1) {
       println("Hello!");
    }
    if (x != 1) {
       println("Goodbye!");
    }
    
  4. Explain the difference between = and ==. Give examples of how to use each.

  5. Consider the following statement:

    a = (x == 1);
    

    Assuming a as been defined, is this a legal statement in Processing? If so, what is the type of a?

  6. In the sample programs, color variables were created for orange and white, but not for red. Why did these programs use color(255, 0, 0) everywhere instead of defining a variable called red?

  7. What, in general, does the mousePressed function do?

  8. Write a small function (both the header and the body) named add5 that takes one float as input, and returns, as a float, that input incremented by 5. For example, add5(3) returns 8, and add5(-2) returns 3.

8.11. Programming Questions

  1. Write the body of a function with the following header (do not change the header!!):

    // Returns true if point (x, y) is in the square with upper-left
    // corner (a, b) and side length s
    boolean pointInSquare(float x, float y, float a, float b, float s)
    

    Test pointInSquare to make sure it works by using it in the square-bouncing program in the notes: replace the edge hit-detection code in that program with a call to pointInSquare. That is, on each call to draw() use pointInSquare to check if the upper-left corner of the bouncing square is in the inside the 500-by-500 square formed by the screen.

  2. Modify the bouncing rectangle program so that it draws two rectangles that touch just at one corner, e.g.:

    Two rectangles touch at a corner.

    Further, change the program so that when the user clicks inside either rectangle, then both rectangles change color.

    Use the pointInRect function to test when the mouse pointer is inside a rectangle.