Hit Detection and Functions

In these notes you will learn:

  • What boolean functions are.
  • How to test if a point is in a circle.
  • How to test if a point is in a rectangle.

Introduction

In this note we will see how to do basic hit detection. The general problem is to determine when two shapes touch, or intersect, each other. For example, in a graphical user interface you need to know which window the mouse pointer is in when it is clicked. Or in a first-person shooter video game you want to know when a bullet has hit another player.

Hit detection turns out to be a surprisingly complex topic if you are dealing with arbitrary shapes. Thus, we will restrict ourselves to a couple of the most important cases involving circles and rectangles.

Boolean Functions

Suppose you want to test if a number, x, is negative. You could do that with this function:

boolean isNegative(float x) {
    if (x < 0) {
        return true;
    } else {
        return false;
    }
}

isNegative returns either the value true, or the value false. These two values are the boolean values, and so we call a function that returns booleans boolean a boolean function.

Note

Sometimes boolean functions are called predicates, or test functions. In this course, we will stick with the term boolean function.

You can use it like this:

if (isNegative(x)) {
    println("Can't take the square root of a negative number.");
} else {
    println(sqrt(x));
}

The body of the isNegative function consists of an if/else structure. However, some programmers prefer writing it is this way:

boolean isNegative(float x) {
    return x < 0;
}

This is much shorter! The expression x < 0 is a boolean expression that evaluates to either true or false, depending upon the value of x. If x < 0 is true, then x must be negative. If x < 0 is false, then x is either 0 or positive.

Testing if a Point is in a Circle

Testing if a point is in a circle.

Suppose you are given a point (a, b), and a circle with center (x, y) and radius r. How can you tell if (a, b) is inside the circle, or outside the circle?

As shown in 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(x, y, a, b) returns the distance between (x, y) and (a, b). We can use it like this:

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

Note

dist(x, y, a, b) calculates the distance using this formula:

\[\sqrt{(x-a)^2 + (x-b)^2}\]

If dist didn’t already exist, you could could calculate the distance between (x, y) and (a, b) like this:

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

Let’s write it as a boolean function to make it easy to re-use:

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

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

class Sprite {
  float x;
  float y;
  float dx;
  float dy;
}

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

color ball_color;

Sprite ball = new Sprite();
float diam;

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

  // set the ball's initial position and velocity
  ball.x = 250;
  ball.y = 250;
  ball.dx = 1.3;
  ball.dy = -2.77;
  diam = 50;
  ball_color = color(0, 255, 0);
}

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

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

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

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

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

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

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

  // set the ball's color to red if the mouse
  // pointer is over the ball, and green otherwise
  if (pointInCircle(mouseX, mouseY, ball.x, ball.y, diam / 2)) {
    ball_color = color(255, 0, 0);
  } else {
    ball_color = color(0, 255, 0);
  }
}

Notice that we don’t need to click the mouse pointer for the ball to change color. All that matters in this program is that the pointer “hover” over the ball.

Clicking the Ball

Lets modify our program so that the ball changes color just when we click 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 in its body. In this case the ball will become red whenever you click the mouse button.

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.

What if we want the color to change just when we click inside the ball? The solution is to move the if-statement that we commented-out in draw() into mousePressed():

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

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

Testing if a Point is in a Rectangle

Testing if a point is in a rectangle.

Suppose you want to test if a point (a, b) is inside a rectangle of width Width, height Height, and with upper-left corner (x, y).

In the diagram, (x, y) is the upper-left corner of a rectangle with dimensions Width-by-Height. The green point (a, b), it is inside the rectangle because it is both 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).

The trick for testing if (a, b) is inside the rectangle is to do two separate “between” tests: first, test if (a, b) is between the left/right edges, and then test if it is between 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 point (a, b), is inside this rectangle? From the discussion above we need to check that:

  • a is between the left and right edges
  • b is between the top and bottom edges

To test if a is between the left and right edges, we can use this expression:

x <= a && a <= x + Width

Recall that && means “and” in Processing, and so this expression is true just when both x <= a and a <= x + Width are true.

Similarly, to test if b is between the top and bottom edges of the rectangle, we can do this:

y <= b && b <= y + Height

Now if the (a, b) 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. This helps us format the code in whatever is easiest for humans to read.

Now lets put this test into a boolean function to make it easier to re-use:

//
// 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,   // any 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.

Many experienced programmers would write pointInRect like this:

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);
}

You can write it whichever way you prefer, but you should be able to read and understand both.

Lets write a demonstration program that makes a rectangle change color when we click on it. This program is very similar to the one with the ball:

class Sprite {
  float x;
  float y;
  float dx;
  float dy;
}

Sprite ball = new Sprite();
float Width, Height;

color rect_color = color(255, 0, 0);

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

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

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

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

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

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

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

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

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

void mousePressed() {
  if (pointInRect(mouseX, mouseY, ball.x, ball.y, Width, Height)) {
    if (rect_color == color(0, 255, 0)) {
      rect_color = color(255, 0, 0);
    }
    else {
      rect_color = color(0, 255, 0);
    }
  }
}

Questions

  1. What is a boolean function?

  2. For each of the following English descriptions, write an equivalent boolean expression in Processing. You don’t need to write a function, just an expression.

    1. Test if a number x is positive:

      boolean isPositive(float x)
      
    2. Test if a number x is 0:

      boolean isZero(float x)
      
    3. Test if a number x is both greater than, or equal to, 0, and less than, or equal to, 100:

      boolean inRange(float x)
      
    4. Test if a number xx is the square of another number x. For example, if xx is 25 and x is 5, then your function should return true.

      boolean isSquareOf(float xx, float x)
      
  3. Explain the dist function. Give an example of how to call it.

  4. Consider the following statement:

    a = (x == 1);
    

    Assuming a has been defined, is this a legal statement in Processing? If so, what type of variable is a, and what is it’s value?

  5. Consider a rectangle that is 180 pixels wide and 265 pixels high with upper-left corner at (100, 205). Write a boolean expression that is true just when the mouse pointer is inside this rectangle.

Programming Questions

  1. 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.