In these notes you will learn:
In this note we’ll write a program that lets you “throw” a ball using the mouse. To do this we will combine two things we’ve already seen: moving a ball and drag-and-drop.
Our goal is to make a small demo program the lets a user graph and throw a ball (in 2 dimensions). The ball moves in the thrown direction at a constant speed that is proportional the speed the ball was thrown at. That is, the faster the user flicks the mouse, the faster the ball will move.
As before, we use the variables dx and dy to represent the speed of the object. When we throw the ball, we will need to set the values of dx and dy in a way that depends on the speed and direction of the mouse pointer.
We wish to find out the speed of the mouse motion. Since we measure distance in pixels, we want to measure the number of pixels travelled per some unit of time. The “unit of time” we will choose is the amount of time to call the draw() function. This is reasonable since that is usually when we wish to know this value.
One way to determine the speed of the mouse motion is to examine the difference between the current mouse position and the previous mouse position. To see why, consider mouseX and pmouseX, and suppose that mouseX is 5 and pmouseX is 10, so that the mouse travelled 5 pixels to the left in the time it took to call draw again. In other words, the ball has travelled mouseX - pmouseY = 10 - 5 = 5 pixels per call to draw(). Similar reasoning works for mouseY and pmouseY.
We also need to think about where to actually put these lines of code? Where is the best place to update the speed of the ball. Let’s break down the action of throwing the ball. It consists of three discrete steps: picking up by hold-clicking, throwing the ball by flicking the mouse, and most important for our current question, letting the ball go by releasing the mouse button. So as soon as we release the mouse button we have enough information to update the speed of the ball. Thus, the mouseReleased() function is the placed to hold our code:
void mouseReleased() {
dx = mouseX - pmouseY;
dy = mouseY - pmouseY;
}
The rest of the program is a combination of ball-dragging and ball bouncing code that we are all familiar with
float x, y; // center of the ball float diam; float dx, dy; boolean shouldMoveBall; void setup() { size(500, 500); smooth(); x = 250; y = 250; diam = 60; dx = 0; dx = 0; dy = 0; shouldMoveBall = false; } void draw() { background(255); noStroke(); fill(200, 0, 0); ellipse(x, y, diam, diam); x += dx; y += dy; // top edge? if (y - diam / 2 <= 0) { y = diam / 2; // no interpenetration dy = -dy; } // bottom edge? if (y + diam / 2 >= height) { y = height - diam / 2; dy = -dy; } // left edge? if (x - diam / 2 <= 0 ) { x = diam / 2; dx = -dx; } // right edge? if (x + diam / 2 >= width) { x = height - diam / 2; dx = - dx; } } void mousePressed() { shouldMoveBall = pointInCircle(mouseX, mouseY, x, y, diam / 2); } void mouseDragged() { if (shouldMoveBall) { 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 a, float b, float x, float y, float r) { return dist(a, b, x, y) <= r; }
This works. Playing with this program will give you better idea of how mouseDragged() function works. Notice that if you hold the button and move the mouse over the ball while it is in motion, it will catch the ball. This is because we’ve set dx and dy inside mouseDragged().
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 (shouldMoveBall) {
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.
float x, y, diam;
float dx, dy;
boolean shouldMoveBall;
boolean dragging;
void setup() {
size(500, 500);
smooth();
x = 250;
y = 250;
diam = 60;
dx = 0;
dx = 0;
dy = 0;
shouldMoveBall = false;
dragging = false;
}
void draw() {
background(255);
noStroke();
fill(200, 0, 0);
ellipse(x, y, diam, diam);
x += dx;
y += dy;
// top edge?
if (y - diam / 2 <= 0) {
y = diam / 2; // no interpenetration
dy = -dy;
}
// bottom edge?
if (y + diam / 2 >= height) {
y = height - diam / 2;
dy = -dy;
}
// left edge?
if (x - diam / 2 <= 0 ) {
x = diam / 2;
dx = -dx;
}
// right edge?
if (x + diam / 2 >= width) {
x = height - diam / 2;
dx = - dx;
}
}
void mousePressed() {
shouldMoveBall = pointInCircle(mouseX, mouseY, x, y, diam);
}
void mouseDragged() {
if (shouldMoveBall) {
boolean dragging = false;
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 a, float b, float x, float y, float r) {
return dist(a, b, x, y) <= r;
}