views:

213

answers:

4

So I'm trying to figure out how to implement a method of selecting lines or edges in a drawing area but my math is a bit lacking. This is what I got so far:

  • A collection of lines, each line has two end points (one to start and one to end the line)
  • The lines are drawn correctly on a canvas
  • Mouse clicks events are received when clicking the canvas, so I can get the x and y coordinate of the mouse pointer

I know I can iterate through the list of lines, but I have no idea how to construct an algorithm to select a line by a given coordinate (i.e. the mouse click). Anyone got any ideas or point me to the right direction?

// import java.awt.Point

public Line selectLine(Point mousePoint) {
    for (Line l : getLines()) {
        Point start = l.getStart();
        Point end = l.getEnd();
        if (canSelect(start, end, mousePoint)) {
            return l; // found line!
        }
    }
    return null; // could not find line at mousePoint
}

public boolean canSelect(Point start, Point end, Point selectAt) {
    // How do I do this?
    return false;
}
+3  A: 

If you use the 2D api then this is already taken care of.

You can use Line2D.Double class to represent the lines. The Line2D.Double class has a contains() method that tells you if a Point is onthe line or not.

Vincent Ramdhanie
Wow, didn't know about the 2D api. That api will take care of a bunch of other things I had problems with before… if I just knew about it before. (>_<);
Spoike
.. are you sure? As I read from the API, `contains()` always returns false because `Line2D` has no area
Andreas_D
I think you can still use the intersect() method if you use a small rectangle as a selection area
Spoike
Thats right. By definition, contains() does not work with Line2D but deciding if the line intersects a very small rectangle around the click will work.
Vincent Ramdhanie
+4  A: 

Well, first off, since a mathematical line has no width it's going to be very difficult for a user to click exactly ON the line. As such, your best bet is to come up with some reasonable buffer (like 1 or 2 pixels or if your line graphically has a width use that) and calculate the distance from the point of the mouse click to the line. If the distance falls within your buffer then select the line. If you fall within that buffer for multiple lines, select the one that came closest.

Line maths here:

http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html

http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment

Toji
A: 

Sorry, mathematics are still required... This is from java.awt.geom.Line2D:

public boolean contains(double x, double y)

Tests if a specified coordinate is inside the boundary of this Line2D. This method is required to implement the Shape interface, but in the case of Line2D objects it always returns false since a line contains no area.

Specified by: contains in interface Shape

Parameters: x - the X coordinate of the specified point to be tested y - the Y coordinate of the specified point to be tested

Returns: false because a Line2D contains no area.

Since: 1.2

I recommend Tojis answer

Andreas_D
+3  A: 

Best way to do this is to use the intersects method of the line. Like another user mentioned, you need to have a buffer area around where they clicked. So create a rectangle centered around your mouse coordinate, then test that rectangle for intersection with your line. Here's some code that should work (don't have a compiler or anything, but should be easily modifiable)

// Width and height of rectangular region around mouse
// pointer to use for hit detection on lines
private static final int HIT_BOX_SIZE = 2;



public void mousePressed(MouseEvent e) {
    int x = e.getX();
    int y = e.getY();

    Line2D clickedLine = getClickedLine(x, y);
}


/**
* Returns the first line in the collection of lines that
* is close enough to where the user clicked, or null if
* no such line exists
*
*/

public Line2D getClickedLine(int x, int y) {
int boxX = x - HIT_BOX_SIZE / 2;
int boxY = y - HIT_BOX_SIZE / 2;

int width = HIT_BOX_SIZE;
int height = HIT_BOX_SIZE;

for (Line2D line : getLines()) {
 if (line.intersects(boxX, boxY, width, height) {
  return line;
 }  
}
return null;

}

I82Much
Or just see if ptLineDist() is less than a certain threshold. You'll get less strange results that way for larger "buffers sizes".
PSpeed
Nice solution - I hadn't heard of the ptLineDist function before.
I82Much