-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
path.unite() fails on rather simple case #936
Comments
The first call to |
Hmm! I wonder why it works on 0.9.25? Are you looking into it? Welcome back! ; ) |
Sorry for the silence recently, I was (and am) really busy.
As far as I can see in So it seems like the problem is in |
One point on which I tried to find the correct winding number for this point and came up with this sketch, which shows the intersections if all horizonal lines are absolutely hozizontal: The blue dot is our point, the red dot are intersections that will be counted to calculate the winding number. Since the point is one the vertical line, the intersection at the point is not counted. To the left there is only one intersection, setting the left winding to +1. To the right there are two intersections which contribute -1 and +1, which sets the right winding to 0. The overall winding is calculated as
In this case the overall winding number should be 1. |
Oh gee. I have no clue! : ) That part was always a bit unclear to me. What's your guess? 2? |
No, 2 means outside the path for |
Yes, 1 sounds right! So why do we get 3? |
The point is at
|
And this is howt our horizontal ray through the point crosses the path
|
I suspect that the problem is that we alway increment the left and right winding at an extremum in PathItem.Boolean.js line 404 Actually the for the right case
If we replace PathItem.Boolean.js lines 401/402 with
things seem to work. |
My brain's not at all in boolean / winding mode at the moment, and it's hard to switch back into it. Are you comfortable working on this one? |
I will try. Unfortunately there are cases where the fix above does not work. I will have to investigate further. Maybe we have a combination of flaws here. But I think that I found a way to simplify |
Here is a case that still fails, but it fails with the 0.9.25 release, too. So it may have a different cause, but I am posting it here to make sure it does not get lost.
And here is a very similar example that does not fail:
|
The first one also still fails even with your PR, right? |
Oh, I see, it still fails with the fix posted above. |
My PR is not supposed to fix anything, this was just a simplification. |
Wow, I think I finally found a really robust implementation of |
That's just the bed-time story I wanted to hear! Very curious : ) |
Let's start with a simple test case: If the result is correct (as it is in the image above), all points in the green area or on the grey stroke are green, all other points are red. This is the code: var points = [[250, 230], [200, 230], [200, 280], [190, 280], [175, 270], [175, 220], [175, 270], [160, 280], [150, 280], [150, 220], [150, 200], [100, 200], [100, 180], [150, 180], [150, 160], [150, 100], [160, 100], [175, 110], [175, 160], [175, 110], [190, 100], [200, 100], [200, 150], [250, 150], [270, 120], [270, 90], [270, 120], [290, 150], [290, 230], [270, 260], [270, 290], [270, 260], [250, 230]];
var path = new Path({segments: points, strokeColor: 'tan', fillColor: 'palegreen', closed: true});
path.setWindingRule("evenodd");
var showPoint = function(p, xOffs, yOffs) {
var pOffs = new Point(p.x + xOffs, p.y + yOffs);
new Path.Circle({center: pOffs, radius: 3, fillColor: path.contains(pOffs) ? 'green' : 'red'});
};
for (var i = 0; i < points.length; i++) {
var p = new Point(points[i]);
showPoint(p, 0, 0);
showPoint(p, 10, 0);
showPoint(p, -10, 0);
showPoint(p, 0, 10);
showPoint(p, 0, -10);
} Now let's look what the current develop branch and the 0.9.25 do with this test: The blue arrows show some examples where |
My new idea is to count only intersections of the horizontal ray with a monocurve if the point is not on the curve or an adjacent horizontal curve. If the point is on a monocurve, we set a flag. At the end we make sure that the winding is odd if we previously found that the point is on a monocurve. Here is an example of three curves and some points, showing for which points the intersection of the horizontal ray with the path is counted. As before we have to treat intersections at curve ends/beginnings in a special way if the winding does not change. In this case we must prevent that the intersection gets counted twice, once for the curve end and once for the start of the next curve. |
So I found a fix for the original example, but the other example still fails. I suspect the secons example fails for an entirely different reason. If this is the case, I will open a new issue for the second example. |
Very impressive, and so quick, @iconexperience! I am trying to have a look at the changes as we speak. Fingers crossed! |
Even if everything looks good from your side, maybe you should wait with accepting the PR until we know what causes the other example to fail, |
Let's accept it. All tests pass, and we test a whole lot of boolean edge cases... |
We can always revert a commit. |
OK, it's the develop branch after all. |
I found what causes the still failing example to fail. The problem is that The crossing of the horizontal ray and the y-monotone curve happens right of the point, but before To solve this it would be nice to have something like One way to achieve this without API changes is to replace
with
which is a nice hack, but not really a clean solution. |
@iconexperience yes this makes a lot of sense. And so you verified already that using the hack, the issue goes away? We might only require the snapping for tangents and normals anyway, not points... I shall investigate. |
Yes, the issue goes away and I am not able to create similar issues after implementing the workaround. Here is an image of how the situation actually looks in the example above (at the bottom right self intersection). You can see that the horizontal line through I will switch off snapping to the end points in evaluate for getting points (not for tangents and normals) and test if I can find any side effects. |
Unfortunately there seem to be side effects, like in this example from the test cases: var path1 = new paper.Path.Circle(new paper.Point(50, 50), 50);
var path2 = new paper.Path.Circle(new paper.Point(100, 100), 50);
console.log(path1.getIntersections(path2).length); If I disable snapping to the end points, this finds 5 intersections, while there should be two. If I only disable snapping to the end points in |
I have added some more points to my new test for The visual result looks like this (red circles indicate the test for the point has failed): As the next step I will change this into unit test format (all that needs to be done is to remove the |
Regarding #936 (comment): I have looked into this a bit. The snapping is just masking an imprecision of the results from the fat-line clipping code, which are so far apart that they don't get merged (much more than |
@iconexperience I can't see any negative side-effects of decreasing |
I do not think there is a measurable performance impact from this. The real problematic curve pairs are extremely rare (I still have not come across one in the wild), and I am not even sure if the CLIPPING_EPSILON makes a diffence for these cases. So let's just do it. In the worst case we will have to revert the change and use the workaround mentioned above until we have a better solution. |
@iconexperience this is quite interesting: I tried to further reduce var path = new Path({segments:[[349.31714952528665, 176.97192499841802, 2.0667935742653185, -10.850610233997372, 0, 0], [308.23418424120047, 394.39737504104323, 0, 0, -0.3852007263769224, 2.0386172397749647], [273.60284923271973, 415.7138856106701, 9.194683617471242, 1.7373535056759692, 0, 0], [340.6967045975081, 152.3115389894092, -4.006231967752797, -3.6687868382265094, 5.694026309937783, 5.214418174126138], [349.72123243229134, 170.85618880187394, -0.36146393999291604, -6.612268801346318, 0.1110858449889065, 2.0320961120115726], [349.31714952528654, 176.97192499841861, 0.3852007263769224, -2.038617239775249, -2.0667935742656027, 10.850610233997315], [333.4126425432937, 153.58289639999975, -10.850610233997315, -2.0667935742653754, 10.850610233997372, 2.0667935742653754]], closed:true});
path.strokeColor = 'red';
var inters = path.getIntersections();
console.log(inters.length);
inters.forEach(function(inter) {
console.log(inter, inter.time);
}); |
Oh, and I've played with decreasing all the epsilons, and I think we can increase precisions quite substantially all across now with the new code-base. |
@lehni Maybe the intersection is not found anymore because we reach the recursion limit first? Just a guess, but it would make sense. I hope to fine some time to look at that tomorrow and also find out why my test suite suddently developed artistic skills... |
Commits not specifically documented because they were: internal, already documented, of fixed a bug introduced in the develop branch (wasn't present in 0.9.25) in chronological order of commits (starting with 45595b2 - I haven't gone back): - Fix unit test error on Node.js 45595b2 - Implement unit tests for SVG Importing, based on visual comparison. … a12e99e - Fix wrongly copied attributes in Item#reduce() … 8e25327 - Events: paper namespace may not be initialized when key evens are emi… … b2f3b58 - SVG: Some renaming omitted in previous commit. 1c4ff31 - SVG: Rename 'SVG' prefix to 'Svg' … af59847 - Implement tests for paperjs#697 … 21dce1a - Move Path_Bounds tests to Item_Bounds. … a02d724 - Fix accidentally leaked global variable. 74d1889 - Simplify getWindings(), The first curve of a loop always has the 'las… … 0716ebb - Merge pull request paperjs#938 from iconexperience/getWinding-simplification … 53269ab - Optimize Emitter._installEvents() … 0f084ea - JSON: Prevent `name: undefined` exports. 7888d1d - Change the way we determine the winding in getWinding(). Now the wind… … aed9d05 (paperjs#936) - Clean-up changes from paperjs#939 41aca10 - Define unit test for #internalBounds regression. 336460b - Fix internalBounds regression caused by 1ac8e46 0152439 - Revert "Change the way we determine the winding in getWinding()." f7b1aca - Use correct SVG namespace again. … fc4bdf4 - Replace the "straight curves with zero-winding" test with a more comp… … e03c8cd - Change the implementation of getWinding() again, so we pass all tests… … 5b31aee - Merge pull request paperjs#944 from iconexperience/fix-getWinding … ec75985 - Merge pull request paperjs#943 from iconexperience/replace-path-contains-test … 23045bb - Do not snap curve points to t = 0 / 1 with epsilon … 0371f66 (in boolean ops) - Clean-up unit test for paperjs#943 and add edge case from paperjs#944 … a59a535 - Some comment cleanup. ffe42a0 - Merge branch 'winding-fix' into develop … 5a46620 - Some code cleanup for winding-fix. 55909b8
Could you share your current version of the test suite with me? I'm not sure what's wrong with mine... |
Which one do you mean? The test suite or paper.js? |
I simply sent you everything, including the paper.js version that I am using. |
Cool : ) |
Ok... I converted all calls with the old notation (passing |
Closing this again. |
Commits not documented listed here, starting with 45595b2. They are not included in the changelog because the appeared to be internal or already documented or they were a fix for a bug that was introduced in "develop" branch. There were commits before 45595b2 but that's when I started recording what I didn't put in the changelog. Not sure whether the curveAt/CurveAtTime functions are deprecated or removed. Format: commit message, commit ID. In chronological order of commits: - Fix unit test error on Node.js 45595b2 - Implement unit tests for SVG Importing, based on visual comparison. … a12e99e - Fix wrongly copied attributes in Item#reduce() … 8e25327 - Events: paper namespace may not be initialized when key evens are emi… … b2f3b58 - SVG: Some renaming omitted in previous commit. 1c4ff31 - SVG: Rename 'SVG' prefix to 'Svg' … af59847 - Implement tests for paperjs#697 … 21dce1a - Move Path_Bounds tests to Item_Bounds. … a02d724 - Fix accidentally leaked global variable. 74d1889 - Simplify getWindings(), The first curve of a loop always has the 'las… … 0716ebb - Merge pull request paperjs#938 from iconexperience/getWinding-simplification … 53269ab - Optimize Emitter._installEvents() … 0f084ea - JSON: Prevent `name: undefined` exports. 7888d1d - Change the way we determine the winding in getWinding(). Now the wind… … aed9d05 (paperjs#936) - Clean-up changes from paperjs#939 41aca10 - Define unit test for #internalBounds regression. 336460b - Fix internalBounds regression caused by 1ac8e46 0152439 - Revert "Change the way we determine the winding in getWinding()." f7b1aca - Use correct SVG namespace again. … fc4bdf4 - Replace the "straight curves with zero-winding" test with a more comp… … e03c8cd - Change the implementation of getWinding() again, so we pass all tests… … 5b31aee - Merge pull request paperjs#944 from iconexperience/fix-getWinding … ec75985 - Merge pull request paperjs#943 from iconexperience/replace-path-contains-test … 23045bb - Do not snap curve points to t = 0 / 1 with epsilon … 0371f66 (in boolean ops) - Clean-up unit test for paperjs#943 and add edge case from paperjs#944 … a59a535 - Some comment cleanup. ffe42a0 - Merge branch 'winding-fix' into develop … 5a46620 - Some code cleanup for winding-fix. 55909b8 - Include NPM and Bower badges. 80e6246 - Improve handling of view updates and detection of invisible documents. … da216aa - Fix new exception in unit tests. de9653a - Rearrange method sequence in Path. d1b11c6 - Clean-up PathFitter code. e5d139c - No need to pass normalized tangents to PathFitter#fitCubic() … c793538 - Fix JSDoc warning on Style class. a48d138 - Implement PathItem#getNearestLocation() / #getNearestPoint() … 717bc4b - Implement Item#_hitTestChildren() … 740c94e - Some CurveLocation cleanup. … 00d2e2a - Remove duplicate unit tests. ed43477 - SVGExport: Remove unnecessary calls to Point#transform() in exportGra… … adc5b86 - Remove unnecessary double-spaces. 98fc513 - Improve fix for paperjs#650 … c1b7366 - SVGImport: Further improve handling of gradients … d9e09b9 - Gulp: Add test:browser task, to solve CORS issues on Chrome. 8542eb6 - SVGImport: Improve consistency of style handling. df57c4a - SVGImport: Inherit default styles on Node.js too. e38a33f - SvgImport: Always create a clip-item when viewBox is specified. 68c4541 - Tests: Implement additional tests for SvgImport. c0b39c4 - Travis CI: Use Arial in all tests. cb79232 - Shortcut Curve.evaluate() for t === 1 to avoid imprecision. aa1f219 (paperjs#960) - Part 1 of large refactoring of bounds handling. 55c5f42 - Update straps.js 892e567 - Part 2 of large refactoring of bounds handling. 12f829c - Travis CI: Switch to g++ 4.8 to see if this solves strange new buildi… … 5ec5c26 - Introduce Base.filter(), to copy and filter object properties. 6d5d1ce - Implement unit tests for Item#getItems() with overlapping / inside pr… … 80c8aae - Merge pull request paperjs#962 from iconexperience/fix-issue-960 … 7c24fc9 - Add test for paperjs#960 and improve fix a bit. … e2bc83a - Remove unnecessary edge-case handling in CurveLocation#isCrossing() … 84a75e3 (paperjs#951, paperjs#959 - but falls into boolean fixes in general). - Implement consistent checks for fill / stroke / shadow styles in test… … c6bcf43 - Clean-up previous commit. 0a196da - Boolean: Implement proper handling of fully overlapping (identical) p… … 3348fb7 (paperjs#923, paperjs#958 - but falls into boolean fixes in general). - Boolean: Only compare segments when determining if paths are identitcal. 009761d (boolean fixes in general). - Switch from new Base() to Base.set({}) where possible. c3fff9f - Docs: Fix warning about isFlatEnough() 27197bd - Better detect code that requires a tool object. … dbd7a90 (in example fixes) - Matrix: Switch to a better implementation of #decompose() … 3ee46ff - Implement failing test for paperjs#968 9c9f43d - Hit-Test: Pass viewMatrix as argument instead of in options object. fa6c1f4 - Update Curve.js … fb76065 - Update Path.js … add2866 - Clean up PR paperjs#931 … 79d4461 - Improve handling of points on paths in getWinding() e2eaf87 - Adjust comments to match new implementation 406e6c9 - Fix regression introduced in 4e7fa2f 55e7689 - Implement more unit tests for PaperScope#settings.insertItems 01fade8 - Merge pull request paperjs#971 from iconexperience/fix-issue-968 … 4c72d98 (boolean fixes in general) - Simplify code from paperjs#971 and activate unit test for it again. 8d5c922 - Document options.insert in #importSVG() 3c3c8d9 - List all supported events in event methods on View. 9f9222f - Switch to PathItem.create() in unit tests. 0e2498b - Fix failing SVG unit test. 08e51b5 - Fix failing unit tests. 3d330da - SvgImport: Fix issues introduced in 6f4890c 16a7baa - Merge pull request paperjs#976 from iconexperience/patch-2 … 7f48486 - SVG Export: Do not filter out empty paths. 6975690 - Fix paperjs#977: Apply hit-testing tolerance to fills in Shape. 4081afb - Fix paperjs#977: Implement unit-tests. 6df4602 - Fix paperjs#982: Make sure `self` points to the global scope on Webpack. b5c837b - Extend mapping of attribute names to required namespaces a4757b3 - Add trailing slashes to svg related namespaces (xmlns, xlink) 49104c5 - Merge pull request paperjs#984 from aschmi/fix-namespaces-of-exported-svg … 623ec73 - SVG Import: Fix namespacing issues introduced by paperjs#984. acb1e40 - SVG: Add comments explaining IE related changes in paperjs#984. 50bd5be - Implement unit tests for paperjs#991 … 1cb2916 - fix paperjs#994: Revert commit b5af47a … 69c3470 - Rename SegmentSelection related internal objects and properties. … 1db419a - Simplify Path#getArea() … 7dd110f - jsdom v8.0.0 equires Node.js v4.0.0 or newer. 7300260 - Implement Path#splitAt(offset) … da7d0d8 - Add more unit tests for SvgImport. … 7a4794d - SVG Import: Add more tests. … d52a6f3 - Refactor GradientStop: Improve handling of optionally defined color a… … d93aca6 - Travis CI: See if using Arial solves the failing test. 17555b1 - Travis CI: Use Arial in all SVG tests and reduce tolerance. d6ce470 - Travis CI: Adjust SVG test tolerances. beabd6b - Travis CI: More SVG test adjustments. bb19fad - Replace Item#_boundsSelected with #_selectBounds … 336bc10 - More clean-up of selection handling refactoring. 00b2102
I have found a relatively simple example where
path.unite()
fails on the current build. The bug does not occur in version 0.9.25.The path consists only of lines and all intersections are found correctly and are recognized as crossings.
Also, if we simply round the coordinates to ensure that all lines are exactly horizontal, vertical or diagonal, the result is fine:
The text was updated successfully, but these errors were encountered: