paperjs / paper.js

The Swiss Army Knife of Vector Graphics Scripting – Scriptographer ported to JavaScript and the browser, using HTML5 Canvas. Created by @lehni & @puckey
http://paperjs.org
Other
14.45k stars 1.22k forks source link

Problem with multiple paths exclude #541

Closed tomashanacek closed 9 years ago

tomashanacek commented 9 years ago

Actual result:

http://sketch.paperjs.org/#S/lZTLboMwEEV/xWKRgIQIGAoNVVf5gapdhiwcMykojh3ZkFaN8u81D+epJpQFYHNnPPfMiL3FyQas1PpYQ0ULy7WoyJv1ZIJUQbbgZ3xHZP+OXhGHL/RGqsJ7B1oR/snA3mcc6WsrSl6laB76kYswjhdut6/KH2i28bOL9M1sl1yB1PoVYQoyfnBeMp5xc2yQ8e55dqS9v4hpIzqRR/LcbmVNDTYOQu9J1xBOnT81URK3muSOJgniJotOdidPPHVREp0LKBMKcl14JWswruCbsjoHA3J0tClBGZMzsdmKmued2Q4TLUqWS+CaYBfqqXpZSUIru8vguH2qqw++Y0ivSsZmggmZovGSEboe3+DGffF4UIeDRHc4OLXSdNjXKPBT8LDDhkVrfXSsQC/xMBJN4JVdbDjg04dG9phCm0zCRuzAds6hhD2UcNjYB3q+g+gWCtZj7+P/QMEGiq5gGA18RSM0NMILGnjITLTpTjz0L2Epgaxbn8pK54vDLw==

Expected result:

rectangle 1

lehni commented 9 years ago

I found one other case where this above change would cause issues in the winding code, where we apparently need higher precision:

https://dl.dropboxusercontent.com/u/85606/paper.js/sketch/index.html#S/XVXLbhsxDPwVw5ekgCBIpJ4peih6dYEucmvTQx7bJohjB46TICj6753hrtukBwuUSA2p4XD9a7k5vxuXJ8vT23F/eb10y8vtFfdP57vF/fn+Oi4+LDbj8+IL7OOz5WeNzcVefBqkqy+T7aRmL05CHiQX35xE+iQlrtJ8GUSFccnOxe4U9W0QCbQbfmZpqF7mU1XzIbo4TZ3RQGlOS2e2lJ02ZXTGHaedKytJLoXEaKswRWbWSK/Zg1qN83ntROwRNaZg6DXQG5pVgJiVVuKYPdCL81zmVayaMmiC1ex+JLrZg4ZwsPFGVq3VuKuViDWg3gmv8xSMICI1RmerS7O9z7JJJHeTPbFktvTpvHOtFSiWrRjfQAcnOTLackpCzo6fkjkNMjNMfNZqNl/z7zxbb5UZtSAikSOcFOCtUjDWaYM/i0x97gayoX6wwHwysd0YQXUAy6pnp5Ajwgo8xY2Dqv5q7evZ8t37s81Bk/KfJqWLL9VF6T72QRrfPG1QGGgBHnqXKc7kOzD5FjUx9uRiKr5PAu0ugtKCa4IqoouUAhBj86outuxRZky+oljxCk5DZ2YSJeYhava92p1OYrKvGTxEHyiB4Ct0icTCxoEcAJboa0VLsctWveLl2ICWoD5E60wBNYBs0LxQl+ozHWiV9r+nlhqmem3GL15J2Wg0DSZSKl4A1RtpIT6GIhiCNOMhIT6oKQdNTiACdCQ0U4ekyQc0FaPROD+YkAxWBE+bZgdkUizdWW/RhBTB34rqRnkRo9nzwEoCutUS8yh6Z5h4YjKUgFI4rCgPvWdhQOnQHqY5Y4dhz3gkrvdZd5wpyc4EjicWtAEupEGyjBWvadUrR7gddg0EqRXJXacP1w67QU2uB1fEjPIaPi387kEGxfCF8dEgUQJYYD09muqgLMlgCOooaClUhLkIPAwkit9GhkCKCtd0hsZBCfbZ5GRAOmW61o2uojbW4DdWFIt7YFRBLF4cs+kpyLyzMsHhK1+eNmXFqa/FRqRGBBqhh4F5PUvz3Nn/gP9xs15/2q63O0zf0W68Opo98tbzczeOG/qmed2NDzidEG42+3H3MF7uj+0eoeH2d9un8eMFlrfHb1BfxvV6+3x0cD2u1y+n4xpQ4xXd+93jSCf+vy524/nt/Ra5HpYn377//gM=

My solution will be to use some preconditioning in Curve.solveCubic(), if both a and b are near 0 (abs(a) <= 1e-12), and then use the current code unmodified.

BrownBear2 commented 9 years ago

Not sure if it is, but this seems related. I'm trying to do something simple like this: http://sketch.paperjs.org/#S/jVFBasMwEPyKUA92wBgn6cmlp3wgNMc4B1ne1sbbVZDWLU3I37uyQaG0h0og0MxoZwZdNZl30LU+jMC214W2rov3D+NV6M0ZKvWsCD7V3nBfvoBlQ28I+bUhJevsBuJaHTdVVSg5TsWCh+ECAm8jvL3DrwPizqHztco8dFlDt9VTQw0lu/W/7Oa5j7/s5hTrv+1anOCnX+scxnZLzTJMLXtjOV9yRN2sKNMU0WYP1tosUYG9GyGRsdL92YT4dQCUDtAJy36CRIoTBTQMubQoqlXMFLd8QOvBjHPRoOvj6fYN

The substracted path is a compundpath instead of a hexagon.

hkrish commented 9 years ago

@BrownBear2 This is because paperjs boolean operations does not detect overlapping paths and try to avoid them. Try removing the smaller rectangle from the compoundpath after the boolean operation, the intersections are found correctly. I am still looking for a nice way to solve those class of issues.

lehni commented 9 years ago

Yeah, that bug is tracked in #450.

iconexperience commented 9 years ago

@lehni Isn't part of this whole issue simply that CompoundPath only accepts Path objects as children? If you try to add a CompoundPath as a child (which is done in the original example) the child does not get added at all. See my new example at #614.

I am not sure if this is the intended behaviour, but it certainly explains a lot of the weirdness in the original example.

Forget about this, I just noticed that I only found a fix for the old code base with the old Sketch version. Sorry

iconexperience commented 9 years ago

Alright, here is the solution: I added a function that creates an array of Path objects from the provided arguments, which can be Path or CompoundPath objects. This is used to ensure that all children added to a CompoundPath are Path objects. And voilà:

http://sketch.paperjs.org/#S/nVRNc9owEP0rGh+CPfE4tkyggXDI5N7ptEfgIOwFexCSR5LTUib/vSvb4nNIaHRA1urt231PEjtPsA14I+/XGkxWeKGXydyu35gimQJm4AczxYtSbEsmZFmLzJRS+EytdEB2M0FwWGyFKI2I6XzcBpdSEd/ulBiNxzg9E8yqNyCMjjiIlSkwen+/p3FUpYEN5uzB09Jx2lEuiW8RJ2nHG1HGmdbfURaZTEjvVW4qWYvcyuhdJNnRiIsqJY002wqiqtZFxKqKb/1GVUha2qLkuQIRjE8p3glwDdfKXy/bcDfFWj3ntIdl99lNCkytRJuOORidiYcHogtWQTwT1sH2Gz0U8JvYDqKfkBkmVhz8rpNKlsKMyDSN+yGhdDAP27gu/4IN028hwR8XLoUGhfglQ61Y1HZ7KJvMRDsflfR3JzlNRguKWJ77Dcz24NMkjR6xh/QpuIrpDwcNZvgBZpgMLAuSfcAzeArJsH8MyLjUkGPjRtXgVMGfjNc5OCPv9jIVaCfy+GI5V90dGZ2/Hb9linS9MIplpl0nQdgxn23EQdAZvyw5f5VcqhHpLTjL1r0L92mnhd504MkQDzw5nKw78BidoY/JpwfurGmcuNt3gEv6JWMsz5l66myhhw0L+9yUhkzBRr6BHxx7lHYepbc9igRvf9K/9Ijio4jp/3hEnUfYwZfMoWfmpM6c9MQcesuNaeicPRjBv/sFVlw3urU3ms7f/wE=

This again is only a fix for the old code base with the old Sketch version. Sorry, I should have read all the comments.

iconexperience commented 9 years ago

I am confused. Why is my example above working with the current version (v0.9.22)? Has anything changed? And since the original example works with the current version when we add only Path objects to a CompoundPath, can this issue be closed now? After all, the original example created an illegal object hierarchy, so formally the bug was in the example (even though IMHO something should be done for cases when a CompoundPath is added as a child of another CompoundPath).

lehni commented 9 years ago

The first sketch still doesn't work correctly at my end... Are you sure you're looking at the right code? @hkrish had a suggestion what needs to change for that to work as well, see https://github.com/paperjs/paper.js/issues/541#issuecomment-68764206 I am going to keep this open until that issue is resolved.

lehni commented 9 years ago

Argh, we have had a regression here, the code on the 'develop' branch appears to be back to the initial behavior. I'm investigating.

lehni commented 9 years ago

This appears to be caused by the fix for #708...

lehni commented 9 years ago

Who would have thought! I managed to address these issues in the current code base, and the work in the boolean-fix branch has landed on develop! Time to test thoroughly, but I'm closing these bugs. Feel free to reopen if similar problems arise.

petrbrzek commented 9 years ago

For me the initial issue comment is still buggy. (I've tested it in boolean-fix branch)

lehni commented 9 years ago

Yes we're dealing with some regressions right now. If you roll back a couple of commits, to about 17 days ago, this particular issue should work : ) We'll get there, bear with us. It's complex.

lehni commented 9 years ago

The new problems started appearing here: dae8bb630b5966e5d14426d070e60e1319c00ccb Revert to before that, and you should be good.

iconexperience commented 9 years ago

@lehni At the bounds checking we are not using any tolerances at all at the moment. This seems to be inconsistent with the rest of the code.

lehni commented 9 years ago

Yes but even if I take it out fully again, it doesn't work in the newest code... Tricky one to investigate as there were many changes.

lehni commented 9 years ago

And I don't think not using tolerances is inconsistent, since we're comparing actual path / curve data that is linked, not values that resulted from calculations.

lehni commented 9 years ago

I'm not worried about this one. We'll fix it, but I want to get the open questions about tolerances right first, to avoid changing behavior again. And I should add unit tests for all these edge cases so we're protected against regressions.

lehni commented 9 years ago

Alright, this is easier than I thought. Since we're now resolving self intersections, the boolean operations sometimes produce compound-paths with sub-paths free of self-intersections, where previously they produced self-intersecting paths in those situations. That's the expected behavior.

But the code above creates new compound-paths with compound-paths as children, and that's not supported. Hence the different outcome.

We can consider supporting that case in the constructor, in which case the children of the compound-paths would just be passed over and their parent removed. Would that be useful?

lehni commented 9 years ago

I just implemented my suggestion and the first code example above finally works as it should without further modifications and fixes. :+1:

petrbrzek commented 9 years ago

Good job. :+1:

lehni commented 9 years ago

Since b532c9cce2c064a237b2f392b3d8ae4df3e73a12 (#781), it is now possible to use the exclude() operation directly on compound-paths, to achieve the same result with much less code and faster execution time (one boolean pass instead of two).

No sketch-link yet, since this will only work after the next release:

// shape0
var shape0 = new Path.Rectangle({
    point: [304, 226],
    size: [328, 328],
    fillColor: 'black'
});

// shape1
shape1 = new Path();
shape1.add(new Point(213.5, 239));
shape1.add(new Point(476.5, 279));
shape1.add(new Point(716, 233.5));
shape1.add(new Point(469, 74));
shape1.closed = true;

res1 = shape0.exclude(shape1);
shape0.remove();
shape1.remove();

// shape2
shape2 = new Path.Rectangle({
    point: [174, 128],
    size: [309, 251]
});

res2 = res1.exclude(shape2);
res1.remove();
shape2.remove();

// shape3
shape3 = new Path.Rectangle({
    point: [318, 148],
    size: [302, 302]
});

var res3 = res2.exclude(shape3);
res2.remove();
shape3.remove();