raphael icon indicating copy to clipboard operation
raphael copied to clipboard

isPointInside y coordinate intersection of polygon vertex

Open iomdesign opened this issue 9 years ago • 4 comments

I've noticed that the isPointInside function returns an incorrect value if the given y coordinate intersects with the y coordinate of a vertex. I've managed to fix this on my implementation of the library by adding the following check to the beginning of function n.

function n(a, b, d) {
    if(a.indexOf(b[0][2])!== -1){
        b[0][2] = b[0][2]+0.1;
    }

On my testing this is providing a much more accurate result for hit testing however it does seem somewhat rough and I imagine that there is a much more elegant solution. This however does fix the issue for our particular needs.

iomdesign avatar Feb 10 '15 11:02 iomdesign

Dunno whether it's more elegant. But here's some alternative styles for the same check: :-D

function n(a, b, d) {
  if (~a.indexOf(b[0][2]))  b[0][2] += .1;
}
function n(a, b, d) {
  b[0][2] += ~a.indexOf(b[0][2])? .1 : 0;
}

GoToLoop avatar Feb 10 '15 11:02 GoToLoop

Mind to elaborate on the .1 or why that's a fix? Thanks

tomasAlabes avatar Jul 03 '16 05:07 tomasAlabes

@tomasAlabes - I genuinely wish I could remember!! It's been a while since looking at this. I created a pixel hit test comparing the original code vs the code adjustment. The test basically coloured all the pixels that were within the polygon. With the original code it highlighted the gaps at the x axis of vertices, and resolved with the code. I seem to remember that when it had a 0 value there was an issue testing it however this was fixed, with no issues (in our scenario), by adding 0.1. If I remember the why and how I'll be sure to let you know.

iomdesign avatar Jul 10 '16 21:07 iomdesign

Simplest reproduction of this problem is:

k = paper.path("M0 0L1 1L0 2Z") // simple triangle k.isPointInside(0.5, 0.999) // --> true k.isPointInside(0.5, 1.000) // --> false (WRONG) k.isPointInside(0.5, 1.001) // --> true

The problem is he's using interPathHelper between a horizontal line from the tested xy point and each of the segments of the contour, and it double counts the end intersections.

Raphael.pathIntersectionNumber("M0 0L1 1L0 2Z", "M0.5 0.999H10") // --> 1 (odd number) Raphael.pathIntersectionNumber("M0 0L1 1L0 2Z", "M0.5 1H10") // --> 2 (WRONG)

After a long look round this efficiently coded (but very inefficient implementation code, though it doesn't seem to matter), I've found the quickest fix:

In the function on line 1468, insert two new lines: https://github.com/DmitryBaranovskiy/raphael/blob/master/raphael.js#L1468

function interHelper(bez1, bez2, justCount) {
      var bbox1 = R.bezierBBox(bez1),
            bbox2 = R.bezierBBox(bez2);
++         if (bbox2.height == 0 && bbox1.y >= bbox2.y)
++               return justCount ? 0 : [];
        if (!R.isBBoxIntersect(bbox1, bbox2)) {
            return justCount ? 0 : [];
}

The (bbox2.height == 0) detects that we are intersecting against a pure horizontal line (generated by the isPointInside function), and the (bbox1.y >= bbox2.y) discards segments that don't actually cross this horizontal line from the lower side.

Result: the double counted intersections on the nodes (or on horizontal lines) are filtered out.

We should have a more direct implementation of this that doesn't do a mass n^2 intersection of cubic curves.

goatchurchprime avatar Mar 03 '18 14:03 goatchurchprime