2

I want the user to be able to drag the edges of a square around the canvas. With my current solution it works but has glitches, sometimes an edge cannot be selected. Is there a clean way to tell if a line has been clicked (e.g. passes through a coordinate)? This is how I'm currently testing:

// check edge pressed, edge is the line between to
// coords e.g. (i) & (i = 1)
for (int i = 0; i < coords.size(); i++) {
    p1 = coords.get(i);
    if ((i + 1) > (coords.size() - 1)) p2 = coords.get(0);
    else p2 = coords.get(i + 1);

    // is this the line pressed
    if (p1.x <= event.getX() + 5 && event.getX() - 5 <= p2.x && p1.y <= event.getY() + 5 && event.getY() - 5 <= p2.y) {
        // points found, set to non temp
        // variable for use in ACTION_MOVE
        point1 = p1;
        point2 = p2;
        break;
    } else if (p1.x >= event.getX() + 5 && event.getX() - 5 >= p2.x && p1.y >= event.getY() + 5 && event.getY() - 5 >= p2.y) {
        // points found, set to non temp
        // variable for use in ACTION_MOVE
        point1 = p1;
        point2 = p2;
        break;
    }
}

The code bellow //is this the line pressed is the most important and also most likely the issue. The +5 and -5 are used to give the use a larger area to click on.

Here is the whole on click event:

public void EditEdge() {

    //TODO this works like shit             
    // Detect the two coordinates along the edge pressed and drag
    // them
    scene.setOnTouchListener(new View.OnTouchListener() {
        private int startX;
        private int startY;
        private Point point1 = new Point(0, 0);
        private Point point2 = new Point(0, 0);

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    startX = (int) event.getX();
                    startY = (int) event.getY();

                    Point p1;
                    Point p2;

                    // check edge pressed, edge is the line between to
                    // coords e.g. (i) & (i = 1)
                    for (int i = 0; i < coords.size(); i++) {
                        p1 = coords.get(i);
                        if ((i + 1) > (coords.size() - 1)) p2 = coords.get(0);
                        else p2 = coords.get(i + 1);

                        // is this the line pressed
                        if (p1.x <= event.getX() + 5 && event.getX() - 5 <= p2.x && p1.y <= event.getY() + 5 && event.getY() - 5 <= p2.y) {
                            // points found, set to non temp
                            // variable for use in ACTION_MOVE
                            point1 = p1;
                            point2 = p2;
                            break;
                        } else if (p1.x >= event.getX() + 5 && event.getX() - 5 >= p2.x && p1.y >= event.getY() + 5 && event.getY() - 5 >= p2.y) {
                            // points found, set to non temp
                            // variable for use in ACTION_MOVE
                            point1 = p1;
                            point2 = p2;
                            break;
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    point1 = new Point(0, 0);
                    point2 = new Point(0, 0);
                    // scene.setOnTouchListener(scene.editModeOnTouchListener);
                    break;
                case MotionEvent.ACTION_MOVE:

                    for (Point p: new Point[] {
                        point1, point2
                    }) {
                        int modX = (int)(p.x + (event.getX() - startX));
                        int modY = (int)(p.y + (event.getY() - startY));
                        p.set(modX, modY);
                    }

                    SetCoords(coords);
                    startX = (int) event.getX();
                    startY = (int) event.getY();

                    break;
                default:
                    return false;
            }
            return true;
        }
    });
}

So is there a easier way to tell is a line is clicked/ passes through a point or is that not the issue?

Thanks

Kara
  • 6,115
  • 16
  • 50
  • 57
user1056798
  • 245
  • 6
  • 20

3 Answers3

9

use the line equation y = mx + b to find out if the point is on a line

float EPSILON = 0.001f;

public boolean isPointOnLine(Point linePointA, Point linePointB, Point point) {
    float m = (linePointB.y - linePointA.y) / (linePointB.x - linePointA.x);
    float b = linePointA.y - m * linePointA.x;
    return Math.abs(point.y - (m*point.x+b)) < EPSILON);
}
Mapsy
  • 4,192
  • 1
  • 37
  • 43
tyczj
  • 71,600
  • 54
  • 194
  • 296
1

Great piece of code by @tyczj !
I added a use-case to handle vertical lines, which gives me the following code fragment:

public boolean isPointOnLine(PointF lineStaPt, PointF lineEndPt, PointF point) {
    final float EPSILON = 0.001f;
    if (Math.abs(staPt.x - endPt.x) < EPSILON) {
        // We've a vertical line, thus check only the x-value of the point.
        return (Math.abs(point.x - lineStaPt.x) < EPSILON);
    } else {
        float m = (lineEndPt.y - lineStaPt.y) / (lineEndPt.x - lineStaPt.x);
        float b = lineStaPt.y - m * lineStaPt.x;
        return (Math.abs(point.y - (m * point.x + b)) < EPSILON);
    }
}

Also a piece of code to check if a point lies on a line-segment:

public boolean isPointOnLineSegment(PointF staPt, PointF endPt, PointF point) {
    final float EPSILON = 0.001f;
    if (isPointOnLine(staPt, endPt, point)) {
        // Create lineSegment bounding-box.
        RectF lb = new RectF(staPt.x, staPt.y, endPt.x, endPt.y);
        // Extend bounds with epsilon.
        RectF bounds = new RectF(lb.left - EPSILON, lb.top - EPSILON, lb.right + EPSILON, lb.bottom + EPSILON);
        // Check if point is contained within lineSegment-bounds.
        return bounds.contains(point.x, point.y);
    }
    return false;
}
iOS-Coder
  • 1,221
  • 1
  • 16
  • 28
  • vertical line handling is not correct. Consider staPt(1, 3), endPt(1,5) and our touch point(1,8). .....i this case the touch point is not in the between staPt & endPt, but your method will return true – Jovin Sep 23 '17 at 03:43
  • If you call isPointOnLine(staPt,endPt,touchPt) it returns true since the touchPt lies on the same line passing through staPt,endPt. If however you call isPointOnLineSegment(staPt,endPt,touchPt) the touchPt does NOT lie on the line segment 'between' staPt - endPt! So IMHO my method returns the correct true/false results given your examples. Note that there is a difference in lying on a line or line-segment! – iOS-Coder Sep 24 '17 at 10:22
0

You could define 8 Rect to check against - the 4 sides and 4 corners (so you can move 2 edges at once). The lines of the edge should have a width for the touchable area.

Define a Point centred on your touch event, there are then methods for checking if a rect contains a point.

FunkTheMonk
  • 10,908
  • 1
  • 31
  • 37