In these notes you will learn -
We’ve seen in previous notes how to draw dots on the screen whenever the user clicks the mouse button, and add lines between the previous dot and the most recent dot.
Let’s modify our program’s functionality so that instead, the user gets to choose where to have the lines drawn.
In the planning stage we should make the program’s description more precise. The user should be able to make new points, or click on two points in succession to make an edge between the two points.
Our program will work as follows:
The user will click on the screen.
If there wasn’t a point already drawn on the screen where the user clicked, make a new point.
Otherwise, there was a point there, so the user must want an edge.
the start point.
Otherwise, make an edge between the start point and the clicked point.
A flow chart always helps to decipher descriptions such as this:
Since all of this code occurs after the user clicks the mouse, the natural place for it is in the mouse mouseClicked().
Now that we know what we want to achieve, we need to figure out what else we need in order to have this functionality. We need to be able to detect if the are where the user clicked already had point there. Another way to look at it, is that each point needs to determine whether or not it has been clicked. So we will have to add this to our Point class.
We will add a boolean function wasClicked(float a, float b) to the Point class. This function is a variation of a function we’ve seen in previous notes:
boolean wasClicked() { return dist(mouseX, mouseY, x, y) <= diam / 2; }
Here is the code for the entire class:
class Point { // (x, y) position of the point float x, y; float dx, dy; float diam = 10; color pointColor = color(255, 0, 0); Point(float a, float b) { x = a; y = b; dx = 0; dy = 0; } boolean wasClicked(float a, float b) { return dist(a, b, x, y) <= diam / 2; } void render() { fill(pointColor); noStroke(); ellipse(x, y, diam, diam); x += dx; y += dy; dy += gravity; if (y + diam / 2 >= height - 1) { dy = -(dy * elasticity); y = height - 1 - diam / 2; } } }
This function allows us to transfer the responsibility of determining who was clicked to the Points. When the user clicks, we will ask each Points whether it was hit.
For the previous program, we had a simple rule for drawing edges: we draw lines between consecutive points. In this instance it’s a little trickier since the user gets to decide which lines to draw.
We will make a simple Edge class to help us deal with this problem. Each edge will know where it begins and where it ends, and be able do draw itself to the screen:
class Edge { }
How should the edge represent its endpoints? One way to do this is to have four float variables:
class Edge { float startX, startY; float endX, endY; }
But since we already have a Point class, we can use that instead:
class Edge { Point start; Point end; }
This brings up an important point: any object we’ve defined can be used as a type in our program. If the data you deal with is cumbersome, consider wrapping it in an object!
Using Point objects in our Edge class makes the class definition very simple:
class Edge { Point start; Point end; // Constructor Edge (Point s, Point e) { start = s; end = e; } // Draw myself. void render() { stroke(0); line(start.x, start.y, end.x, end.y); } }
We will need global variables for the list of points, as well as for the list of edges. Further, since we might need to keep track of the start point of an edge:
ArrayList<Point> points; ArrayList<Point> edges; Point start; float gravity; float elasticity;
Our setup() function doesn’t need to do much, simply initialize all the screen and the two lists. Further, draw() only needs to go draw all of the edges, followed by all of the points:
void setup() { size(500, 500); points = new ArrayList<Point>(); // empty points list edges = new ArrayList<Point>(); // empty edges list. gravity = 0; elasticity = 0 } void draw() { background(255); for (Edge e : edges) { e.render(); } for (Point p : points) { p.render(); } }
All that is left is to write mouseClicked(). In order to follow the chart above, the first thing we need to do is to determine whether a Point has been clicked or not. We will use the null value for this. Recall that each Point object has a wasClicked() method, which returns true only when the mouse position is inside of it.
To find out which point was clicked, if any, we must go through all of the points asking each one if it was clicked:
void mouseClicked() { for (Point p : points) { if (p.wasClicked()) { // ? } } }
Now we need to keep track the Point that was actually clicked. We could use a boolean, but if a Point was clicked, we’ll have to use it later to either store in the start variable, or make an edge with. So instead we’ll make use of the null value:
void mouseClicked() { Point clickedPoint = null; for (Point p : points) { if (p.wasClicked()) { clickedPoint = p; } } }
Here we are essentially searching for the point that was clicked, through the entire list. We also allow for the possibility that no point was clicked: if that is the case, clickedPoint will stil be null after the for-each loop terminates, so we can just make a new Point. If clickedPoint is not null, we have to look at start:
void mouseClicked() { // Search for the clicked point. Point clickedPoint = null; for (Point p : points) { if (p.wasClicked()) { clickedPoint = p; } } if (clickedPoint == null) { // no Point clicked, make new Point points.add(new Point(mouseX, mouseY)); } else if (start == null) { // clicked a first Point. start = clickedPoint start.pointColor = color(0, 0, 255); } else { // clicked a second Point. Make new edge. edges.add(new Edge(souce, clickedPoint)); start.pointColor = color(255, 0, 0); start = null; } }
You should compare this if-else-if-statement with the flow chart at the beginning of these notes to see that they correspond.