Closed lehni closed 8 years ago
@iconexperience I am going to compare it with the old #reorient() now to spot the differences.
The change happened here: a3a3a2ec1ba4f3e77a7ad56d777c72df8f141469
Interesting!
Your code does this:
path.setClockwise((counter % 2 === 0) === clockwise);
The old code was doing this:
path.setClockwise((counter % 2 === 0) && clockwise);
And that works! So the comparison is different based on the rule.
Yes, that seems to be a problem. My thought behind the change was that the rule will not work if the first child is anti-clockwise. I still think that this is a prolem, but it looks like my solution is not working well.
Yeah if I switch to the old way, then your example isn't working anymore. :/
But why isn't this example working? it's only two children, already alternating orientation, the first one is clockwise, nothing special at all. It should work.
I think the outer path is not clockwise, that's why...
Here some debug logging for the child:
counter % 2 === 0: false
clockwise: false
Wow, the interior point of the second child is not inside the first one:
Watch out though, you're running on an old library... But I do get false
too there. Odd!
Oh, the inner path is child zero, the outer child one.
yes, just realized the same : )
Here's what I propose: After sorting the children, we set the first one to clockwise always. We do the same already for normal paths anyway:
// TODO: Is this really required? We don't do the same for
// compound-paths:
// Paths that are not part of compound paths should never be
// counter- clockwise for boolean operations.
This works like a charm, with your example as well as all my other tests: Sketch
Hmm, what if we use the original orientation from before resolveCrossings()
? Currently we sort the children, and then take the orientation from the first child. Should we take it before sorting?
I don't think that will improve the situation. With the example above, the first child is the inside of the path, which is completely strange. And we've always set #clockwise
to true
for simple paths.
If we allow the main path to be counter clockwise, then subtracting it will actually add it...
Or maybe I'm wrong about that last assumption.
It works with this example:smile: I see a problem if for two overlapping compound paths one of them changes orientation. I do not know how realistic this scenario is in a real world scenario.
I'm happy to preserve orientation if there's a way to actually make it work with this example. I don't think your suggestion would fix it.
Oh wait, yes the first child is clockwise... Hmmm.
Ok, I have it: Updated Sketch
I need to start with the winding number for the first child as the starting counter, and then just use counter % 2
in the end. Bingo!
Here is another sketch where the larger circles are stacked on top of the smaller ones. Your change works, but give me ten minutes to see if I find a way to keep the original orientation.
And actually, counter = clockwise ? 1 : -1
is wrong, since counter
!= winding
. But counter = 1
works just as well : D
I'm sorry, I'm tired. I forgot to remove that line... Now here's the up-to-date version, with counter = 1
and no changing of the original orientation. And it works: Updated Sketch
There are some cases where it fails. Just press "run" several times and watch the circles at the bottom. This is tricky.
Yeah I just saw that too... I'm giving up for the day, brain fried. Setting clockwise to true
on the main path would be the easy way out I guess.
Yes, but give me time until tomorrow afternoon to see if there is an easy way to keep orientation. You did some amazing work today.
Here is a version that keeps the orientation of the original first child. But does it make sense at all to keep the orientation of the first child? Or should we keep the orientation of the bottom child(ren)? I will think about it after some sleep.
And here is a version that keeps the original orientation of the bottom children (the ones that have no containing, larger child). Currently I like this one best, but let's sleep first.
Note that in the last two examples we iterate through all children (including the largest).
What is the advantage of always calling setClockwise()
?
Oh, and be careful. You forgot to remove the line // The first, largest child can be skipped.
:wink:
I think mine calls it less often :smile:, please note the break
statement. It only gets called once per child, and only if there is a containing, non-excluded child.
But yours is probably better in the long run, because if clockwise then will be defined for all children and does not need to be caculated again the next time getClockwise()
is called. And it is better to read.
Hmm but doesn't the break
mean that the windings code isn't executed properly for all children then? I don't fully understand it all yet...
After a little sleep I am quite confident that we have found a nice solution here. And performance of the function should be considerable better that before, because for each child we only iterate up until we find the first containing, non-excluded child.
The break
statement works, because we run through the children from largest to smallest. This means that for every child, all potentially containing childs have already been processed and their orientation has been fixed. Since we want to achieve alternating orientation, all we have to do is to find one non-excluded child that contains the current child, and we can simply set the orientation as the opposite of the containing child. (@lehni I know that you already understood this, but this is for future reference).
@lehni I noticed that we make a lot of calls to contains()
, which always calculated the winding without checking if the point is inside the handle bounds. Adding bounds checking somewhere should yield in a performance improvement, because calculating the winding is quite an expensive operation (at least if the point is within the path's vertical bounds). Let's keep this in mind if we think that performance of the boolean code needs better performance.
I think the difference between mine and your versions is as you say simply that mine always calls setCockwise()
, and this is currently a must, since insertChildren()
on CompoundPath is trying to do something clever with the winding itself (first child = clockwise, all others = counter clockwise). That strategy is of course terrible, and should be fixed as well.
Would you like to help implement a strategy that's fast in CompoundPath#insertChildren()
that is better than the current state of affairs?
And I believe insertChildren()
has a hidden parameter that allows the deactivation of this process, but the used setChildren()
doesn't. That's easy to fix though.
Regarding the optimizations, I think we should implement this straight away. There already are getters for handle bounds. I will look into it.
I'm going to add this comment to the code: https://github.com/paperjs/paper.js/issues/869#issuecomment-167509150
Great. I will try to help with the strategy, but give me some time today for the fat line clipping. I hope that I have some initial sketch finished tonight.
Super! No rush at all with the other stuff. Meanwhile I shall look into this one, a bit frightened: #870
I am sorry to bring this up again, but I just noticed that we can go back to start the iteration at index 1 again. This will save us one call to getInteriorPoint()
, which can be quite an expensive operation.
Don't be sorry! So the same code works with index 1?
PS @iconexperience you'll be pleased by this: 3abffab5ebb47c1a328361c0382994e2dd161dab
Hmm the current implementation is still making trouble. Here an example
Used to look like this:
Now looks like this:
Going back to the version that always sets clockwise solves this issue though, so I guess that solves it! But I can't seem to start iteration at index 1, that doesn't work at all for me...
If you start from index 1, you certainly have to add the first child to the items previously. Does it still not work?
And the difference now is that all the bottom children (the ones that are not contained in another child) will have the same orientation. Maybe that's the right way to go.
@lehni There is still a bug in our algorithm. Look at this example:
var cp = new CompoundPath();
cp.addChild(new Path.Circle(100, 100, 50));
cp.addChild(new Path.Circle(100, 100, 30));
cp.addChild(new Path.Circle(200, 100, 20));
cp.fullySelected = true;
It looks like this (I have added the index that the children have after sorting in resolveCrossings()
:
What is happening is this:
child 0: no containing child, orientation will stay clockwise
child 1: child 0 is containing child, orientation will stay counter-clockwise
child2: no containing child found, but clockwise
is still set to false
from child 1, therefore it will be set to counter-clockwise.
To solve this, we have to set `clockwise = first.isClockwise()
for each child, not only once at the beginning.
Solving this should also solve issue #877.
Yes that makes sense! But I discovered some other issues that are linked to this change, mainly: Sketch
Both paths have counter-clockwise orientation and now they both remain counter-clockwise. I wonder if that's the reason why the unite()
command fails, before they used to be forced to be clockwise. What do you think?
Hmm but are you sure that solves it? Doesn't seem to a my end...
It works on my side. I pull the clockwise = first.isClockwise()
into the for(i)
loop, and my issue #877 works.
Example:
Result: