12. Special Effect: Throwing a Ball

In these notes you will learn:

  • How to “throw” a ball by flicking it with the mouse.

12.1. Introduction

In this note we’ll write a program that lets you “throw” a ball using the mouse. It is an instructive example that combines two things we’ve already seen: moving a ball and drag-and-drop.

12.2. Planning the Program

Our goal is to make a small demo program that lets a user grab and throw a ball (in 2 dimensions). The ball moves in the thrown direction at a constant speed proportional to the speed the ball was thrown at. That is, the faster you throw the ball, the faster it should move.

As we’ve seen, we can represent the speed of an object using dx and dy variables. When we throw the ball, we will need to set the values of dx and dy automatically and in a way that depends upon the speed and direction the mouse pointer is moving during the throwing motion. The simplest way is to subtract the previous mouse pointer position from the current one, e.g.:

dx = mouseX - pmouseX;
dy = mouseY - pmouseY;
calculating dx and dy for a moving ball

Recall that pmouseX and pmouseY are special Processing variables that store the position of the mouse pointer on the previous call to draw.

We also need to think about where to put these lines of code. Throwing the ball consists of three distinct phases: picking up the ball, throwing it by moving the pointer, and then letting it go by releasing the mouse button. So the moment we release the mouse button is when we have enough information to calculate dx and dy. Fortunately, Processing has a special function named mouseReleased() that is called just when a mouse button is released, and so that’s where we put the code to calculate the ball’s velocity:

void mouseReleased() {
   dx = mouseX - pmouseX;
   dy = mouseY - pmouseY;
}

12.3. The Program

The rest of the program is essentially a combination of ball-bouncing and ball-dragging code that we’ve already seen:

float x, y;  // center of the ball
float diam;

float dx, dy;  // velocity of the ball

void setup() {
  size(500, 500);
  smooth();
  x = 250;
  y = 250;
  diam = 60;
  dx = 0;
  dy = 0;
}

void draw() {
  background(255);
  noStroke();
  fill(200, 0, 0);
  ellipse(x, y, diam, diam);
  x += dx;
  y += dy;

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

 // has ball hit the right edge?
 if (x + diam / 2 >= 500) {
   x = 500 - diam / 2;
   dx = -dx;
 }

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

 // has ball hit the bottom edge?
 if (y + diam / 2 >= 500) {
   y = 500 - diam / 2;
   dy = -dy;
 }
}

void mouseDragged() {
  if (pointInCircle(mouseX, mouseY, x, y, diam / 2)) {
    dx = 0;
    dy = 0;
    x += mouseX - pmouseX;
    y += mouseY - pmouseY;
  }
}

void mouseReleased() {
   dx = mouseX - pmouseX;
   dy = mouseY - pmouseY;
   println("mouse thrown: dx = " + dx + ", dy = " + dy);
}

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

This works! Playing with this program will give you a better idea of how the mouseDragged() function works. For instance, notice that if you hold the mouse button down while the ball is bouncing, the pointer will “catch” the ball when it hits it. Also, you can only grab the ball by clicking and moving; just clicking on the ball is not enough to grab it in this program.

12.4. A Small Problem

It’s possible to play with this program for a while not notice it has a major flaw.

The problem is that you can throw the ball without holding it! Try it and see: click on the screen but not on the ball, and then move and release the mouse as if you were throwing the ball. Magically the ball starts moving in your throwing direction. While this is may be what you want in some programs, for us it’s a serious bug that we need to fix. You should only be able to throw the ball while you are holding it.

Note

This is a good example of how a major bug can go undetected. It’s only noticeable if you perform the unnatural action of throwing the ball without actually clicking on it. There’s no real reason why you would ever do this, and so it could stay hidden for a long time.

How can we fix this? If you think about it, we only want the code in mouseReleased() to be called when the mouse is being dragged. For any other click we should not set dx and dy. So we need to be able to tell when the ball is being dragged.

One way of doing this is to add a boolean flag variable called dragging that we will set to true just when the ball is being dragged. Then in mouseReleased() we will check that dragging is true before running any code.

Here are the changes to the program:

boolean dragging;  // global variable

void setup() {
   // ... same as before ...

   dragging = false;   // initially the ball is not being dragged
}

void draw() {
   // ... same as before ...
}

void mouseDragged() {
  if (pointInCircle(mouseX, mouseY, x, y, diam / 2)) {
    dragging = true;
    dx = 0;
    dy = 0;
    x += mouseX - pmouseX;
    y += mouseY - pmouseY;
  }
}

void mouseReleased() {
  if (dragging) {
    dragging = false;
    dx = mouseX - pmouseX;
    dy = mouseY - pmouseY;
  }
}

This fixes the problem: now you can only throw the ball when you are dragging it.

12.5. Questions

  1. Explain the difference between mouseX and pmouseX.
  2. When is mouseReleased() called?

12.6. Programming Questions

  1. Modify the ball-throwing program so that the ball stops moving if you right-click the mouse on the ball. Hint: Read about the mouseClicked() function and the mouseButton variable.

12.7. The Ball Throwing Program

float x, y;  // center of the ball
float diam;

float dx, dy; // velocity of the ball

boolean dragging;

void setup() {
  size(500, 500);
  smooth();
  x = 250;
  y = 250;
  diam = 60;
  dx = 0;
  dy = 0;
  dragging = false;
}

void draw() {
  background(255);

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

  // update the ball's position
  x += dx;
  y += dy;

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

  // has ball hit the right edge?
  if (x + diam / 2 >= 500) {
    x = 500 - diam / 2;
    dx = -dx;
  }

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

  // has ball hit the bottom edge?
  if (y + diam / 2 >= 500) {
    y = 500 - diam / 2;
    dy = -dy;
  }
}

void mouseDragged() {
  if (pointInCircle(mouseX, mouseY, x, y, diam/2)) {
    dragging = true;
    dx = 0;
    dy = 0;
    x += mouseX - pmouseX;
    y += mouseY - pmouseY;
  }
}

void mouseReleased() {
  if (dragging) {
    dragging = false;
    dx = mouseX - pmouseX;
    dy = mouseY - pmouseY;
  }
}

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