Variations on Dots and Lines¶
In these notes you will learn:
- Simple techniques for simulating gravity and elasticity with an array of points.
- How to drag and drop points stored in an array.
Introduction¶
The dots and lines notes showed how to write a program that made a trail of dots and lines wherever the user clicked on the screen. Here we will see a couple of neat variations on that program.
Throughout these notes we will be modifying the OOP version of the dots and lines program.
Variation 1: Bouncing Points¶
Here’s what we would like to have happen: after the user has clicked as many points on the screen as they like, they press any key and instantly the points start falling downwards. When they hit the bottom of the screen they bounce up like they were rubber balls. The lines stay connected to the moving balls.
We’ve already seen how to do this with a single ball in the gravity notes. The program we will write here is essentially a combination of those notes and the dots and lines program
Here’s the code:
class Point {
float x, y;
float dx, dy;
Point() { // default constructor
x = 0;
y = 0;
dx = 0;
dy = 0;
}
Point(int a, int b) { // constructor
x = a;
y = b;
dx = 0;
dy = 0;
}
}
ArrayList<Point> points;
float gravity;
float elasticity;
void setup() {
size(500, 500);
smooth();
points = new ArrayList<Point>();
gravity = 0;
elasticity = 0;
}
void draw() {
background(255);
int n = points.size(); // n is the number of points
// draw the lines
if (n > 1) {
stroke(0);
int i = 1;
while (i < n) {
Point p = points.get(i - 1);
Point q = points.get(i);
line(p.x, p.y, q.x, q.y);
++i;
}
}
// draw the dots
if (n > 0) {
fill(255, 0, 0);
noStroke();
for(Point p : points) {
ellipse(p.x, p.y, 10, 10);
}
}
// update the dot positions
for(Point p : points) {
p.x += p.dx;
p.y += p.dy;
p.dy += gravity;
if (p.y >= 500) {
p.dy = -(p.dy * elasticity);
p.y = 499;
}
}
} // draw
void mousePressed() {
Point p = new Point(mouseX, mouseY);
points.add(p);
}
void keyPressed() {
gravity = 0.1;
elasticity = 0.8;
}
Be sure to try this program: the collapsing effect is quite neat!
Note the following:
- We’ve added
dx
anddy
variables to the points to allow them move. - The gravity and elasticity variables are used in the same way as in the earlier notes on gravity. Gravity controls how fast the balls fall down, and elasticity controls how far they bounce up after hitting the ground.
- Gravity only comes into effect after the
keyPressed()
function is called, i.e. after the user presses any key. Before that,gravity
is 0 and so has no effect on the points.
Variation 2: Dragging and Dropping the Points (Optional)¶
In this variation, we want to allow the user to drag-and-drop any of the points they have placed on the screen. The idea is to combine the previous program with the ideas from the drag and drop notes from earlier in the course.
One of the problems we will face is handling overlapping points. When
two, or more, points overlap, we must be careful to drag only one of
them. The way we will handle this is to use a new variable called
pointBeingDragged
that records the point currently being
dragged. If no point is being dragged, then we will set
pointBeingDragged
to null
.
Using pointBeingDragged
, we can then easily ensure we are always
dragging the correct point.
Another detail we need to think about is what to do when the user clicks on a point (when no point is being dragged). In the previous program, a new point was drawn even if it was on top of an old one. However, in this program if the user clicks on an existing point, we will assume they want to drag it and so will not create a new point.
With those details in mind, here is the re-written code:
class Point {
float x, y;
float dx, dy;
float diam;
// default constructor
Point() {
x = 0;
y = 0;
diam = 20;
dx = 0;
dy = 0;
}
Point(int a, int b) {
x = a;
y = b;
diam = 20;
dx = 0;
dy = 0;
}
}
ArrayList<Point> points;
Point pointBeingDragged; // initiall null
float gravity;
float elasticity;
void setup() {
size(500, 500);
smooth();
points = new ArrayList<Point>();
gravity = 0;
elasticity = 0;
}
void draw() {
background(255);
int n = points.size(); // n is the number of points
// draw the lines
if (n > 1) {
stroke(0);
int i = 1;
while (i < n) {
Point p = points.get(i - 1);
Point q = points.get(i);
line(p.x, p.y, q.x, q.y);
++i;
}
}
// draw the dots
if (n > 0) {
fill(255, 0, 0);
noStroke();
for(Point p : points) {
ellipse(p.x, p.y, p.diam, p.diam);
}
}
// update the dot positions
for(Point p : points) {
p.x += p.dx;
p.y += p.dy;
p.dy += gravity;
if (p.y >= 500) {
p.dy = -(p.dy * elasticity);
p.y = 499;
}
}
} // draw
void mousePressed() {
// first check to see if the user has clicked inside an existing point;
// if so, then we assume they want to drag it and not create a new point
for(Point p : points) {
if (pointInCircle(mouseX, mouseY, p.x, p.y, p.diam / 2)) {
pointBeingDragged = p;
return; // ends the mousePressed() function
}
}
// only reach here if the user has not clicked in an existing point
Point p = new Point(mouseX, mouseY);
points.add(p);
}
void keyPressed() {
gravity = 0.1;
elasticity = 0.8;
}
void mouseDragged() {
if (pointBeingDragged != null) {
Point p = pointBeingDragged;
p.x = mouseX;
p.y = mouseY;
}
}
void mouseReleased() {
pointBeingDragged = null;
}
boolean pointInCircle(float x, float y, float a, float b, float r) {
if (dist(x, y, a, b) <= r) {
return true;
}
else {
return false;
}
}
Notice the following:
When the user clicks the mouse, there are two possibilities: they either want to create a new point, or drag an existing point. We distinguish between these two choices by checking if the user clicks on an existing point. If they do, we assume they want to drag it:
void mousePressed() { // first check to see if the user has clicked inside an existing point; // if so, then we assume they want to drag it and not create a new point for(Point p : points) { if (pointInCircle(mouseX, mouseY, p.x, p.y, p.diam / 2)) { pointBeingDragged = p; return; // ends the mousePressed() function } } // only reach here if the user has not clicked in an existing point Point p = new Point(mouseX, mouseY); points.add(p); }
This first step is to check to see if the click is inside an existing point. We do that using a for-each to call
pointInCircle
on all the objects inpoints
. If the mouse is within a point, then we record that point usingpointBeingDragged
. Thereturn
statement on its own line causes the function to end immediately.If the click is not inside an existing point, then we assume the user wants to create a brand new point. The code for this is the same as in the previous program.
The
mouseDragged
function is called automatically when the mouse is being dragged (i.e. when the mouse is moving an a button is being pressed):void mouseDragged() { if (pointBeingDragged != null) { Point p = pointBeingDragged; p.x = mouseX; p.y = mouseY; } }
We can only drag one point at a time, and
pointBeingDragged
stores the point currently being dragged.However, it is possible that the user is dragging the mouse without having first selected a point, e.g. they could start dragging on some place on the screen that doesn’t have a point. In that case
pointBeingDragged
will benull
, which means there is nothing to move. So the if-statement inmouseDragged
is needed to check for this possibility.Dragging ends when the mouse is released:
void mouseReleased() { pointBeingDragged = null; }
mouseReleased
is automatically called whenever a mouse button is released, and for us that means we wantpointBeingDragged
to be set -1.
Questions¶
Describe what a
return
statement on its own line does:return;
Is the if-statement in
mouseDragged
necessary? What goes wrong if it (and its matching}
) is removed?The variable
pointBeingDragged
is quite a long name. Why is that a good thing in this program? Why would a short variable name, liked
, not be as good?
Programming Questions¶
Modify the final variation 2 program so that the point being dragged is green (and the rest of the points are red).
When the balls drop in the sample programs above, they bounce lower and lower until they settle on the bottom edge. However, they do not lie still: they constantly jiggle around.
Fix this: modify the second sample program (the one with drag-and-drop) so that after the balls drop they eventually become completely still at the bottom of the screen.