Closed iconexperience closed 8 years ago
Uh oh, here we go again : )
Looks like it does find all the intersections... Next I'm looking at resolveCrossings()
Ok, so this intersections get wrongly filtered out as not a crossing:
The problem is in isTouching()
: https://github.com/paperjs/paper.js/blob/develop/src/path/CurveLocation.js#L384
Or better, the way it gets used in isCrossing()
currently to detect the case where two touchings curves are not an intersection. This is of course wrong...: https://github.com/paperjs/paper.js/blob/develop/src/path/CurveLocation.js#L421
But even when I I deactivate this check, (which doesn't seem to add any issues btw), it still gets wrongly flagged as not a crossing by the code further down. The reason being all the tangents pointing in the same direction. This is where this comment here becomes important:
https://github.com/paperjs/paper.js/blob/develop/src/path/CurveLocation.js#L476
// NOTE: VectorBoolean has code that slowly shifts these points inwards
// until the resulting tangents are not ambiguous. Do we need this too?
The question then also is: Is there a better way to do this? E.g. use a solver to find the first location where the slope changes enough?
Isn't this exactly what Cary Clark talks about here?
https://www.youtube.com/watch?v=OmfliNQsk88&feature=youtu.be&t=1372
Yes it appears to be. And it's quite interesting... It sounds like he's calculating the winding contributions differently to what we're doing, based on just the found intersections and the curves going through it, while we're calling getWinding()
. I guess his approach is faster.
Oh but after he discussed about propagation, which is what we are doing. I should listen to this properly again.
Unfortunately his solution for ordering the curves is for quadratic curves, and he says at the end: "There may be a better one [solution]". So we cannot use this.
Yeah we shouldn't switch now. = ) I also don't feel that our approach is too slow, so that's fine.
But if the two things would happen together (finding intersections and calculating winding contributions) we could probably use the winding numbers to figure out the answer. I think what @kuribas is doing here is smarter in that respect and would help with that: https://github.com/paperjs/paper.js/issues/761#issuecomment-137562021
But we're too far down our current road probably.
Uuuh, I do not want to think about this now :) Just for your information, I found this issue only because I made a mistake when playing with offsetting, which accidentially caused a loop. So this edge case should have never happened (but it did).
hehe, as they always do!
So here is the code mentioned above: https://github.com/adamwulf/vectorboolean/blob/master/VectorBoolean/FBContourOverlap.m#L287
I think we can use the isTouching()
check to figure out initially if we need to do this at all or not, nothing else. Then if we need to find non-ambiguous tangents, I wonder if there's a better approach than simply shifting the offsets on the curves (in curve length) away from the intersection point until we find a unambiguous pair of tangents? Do you have a suggestions here?
Ha, interesting commit message here. It looks like we're not the only ones out there : ) https://github.com/adamwulf/vectorboolean/commit/582e65dab9e40066a39daaaf743426906fba7f80
I am not sure if I fully understand this. So far I tried to stay away from that part of the code. But is the use of getCurvatureAt()
of any help here?
Well, it's simple: In this case, both involved curves have collinear tangents at the intersection. The intersection happens to be in existing segments, and the segments are smooth, meaning their in- & out-handles are collinear, too.
So I was wrong about isTouching()
being involved, since that only gets called when the intersection is within curves, not in segments at their beginnings / ends.
isCrossing()
then gets all the involved tangents and their angles, and compares these to figure out if it is a crossing or not. But since in this example, all the tangents are identical, they are ambiguous.
The only solution that I can think of right now is the same as in VectorBoolean
: Slowly moving away until we find unambiguous tangents, meaning they are not collinear anymore, the curves unveil in which direction they are turning after they touch in the intersection. The offset could be a fraction of the total curve length of the shorter curve perhaps, rather than a fixed amount as in their code.
I was wondering if there could be a better solution, at the same time, this happens so rarely that I guess I shouldn't care?
I initially forgot the example in my previous post. Wouldn't this be better than slowly moving away from the point?
Oh wow, yes! Let me look into this a bit! : )
Hmm not really... There is no distinction between this sketch and that sketch in terms of curvature values.
I wouldn't expect there to be a difference, because in your sketches you only look at the second curve (the one to the right), which is the same.
Look at this example. To be honest, it surprises me a bit that the curvature is different at the same point, depending on which curve you look at.
Yes, you're of course right! I shall give this a go, good thinking.
And I guess that's just how curvature works?
But what about this situation?
They both change signs, and they are actually crossing. Does the amount of curvature then decide? Can we be certain?
Doesn't the curvature indicate how fast the curve is crawling away from the tangent and to which direction? I think it should work in your example, but I cannot give you the correct algorithm at the moment.
Yea I was thinking the same. It's one for another day, with a fresher head : ) But definitely solvable.
As a first step I have created This little sketch that shows the relation of the curvature at t=0 and the direction in which two curves with identical tangent separate.
The curve with the larger (signed) curvature will always curl away to the right, from the curve with the smaller curvature value. In the example below, the green curve has the larger curvature value.
And just as a reminder, the angle in getAngle()
is calculated clockwise:
Yes, because: radius = 1 / curvature
for the circle that can be fit tightly into the location that the curvature was calculated for: updated sketch
But you're aware that you're comparing signed curvatures, yes?
You are correct, the console output is wrong. It should read "red/green curve is to the right". I updated the sketch in my comment above.
So if you have three curves c0, c1 and c2 starting at the same point, then c0 will be between c1 and c2 if
(angle0 > angle1 || (angle0 == angle1 && curvature0 > curvature1)) && (angle0 < angle2 || (angle0 == angle2 && curvature0 < curvature2))
where angle
is the angle at t=0
and curvature
is the curvature at t=0
. For comparing angles, negative angles have to be taken into account (as it is done in isInRange()
.
Do you think this will work?
I don't think that will cover it yet: We have four curves in this case, two of one path and two of the other, both pairs connected in one segment, the location of the intersection.
I have modified the relevant part in CurveIntersection
, but this does not improve anything. Probably because the angles are not exactly the same, just very close.
function isInRange(angle, angleMin, angleMax, curv, curvMin, curvMax) {
var gtMin = angle > angleMin || (angle === angleMin && curv > curvMin),
ltMax = angle < angleMax || (angle === angleMax && curv < curvMax);
return angleMin < angleMax
? gtMin && ltMax
: gtMin && angle <= 180 || angle >= -180 && ltMax;
}
var v2 = c2.getTangentAt(t1Inside ? t1 : tMin, true),
v1 = (t1Inside ? v2 : c1.getTangentAt(tMax, true)).negate(),
v4 = c4.getTangentAt(t2Inside ? t2 : tMin, true),
v3 = (t2Inside ? v4 : c3.getTangentAt(tMax, true)).negate(),
a1 = v1.getAngle(),
a2 = v2.getAngle(),
a3 = v3.getAngle(),
a4 = v4.getAngle(),
r2 = c2.getCurvatureAt(t1Inside ? t1 : tMin, true),
r1 = t1Inside ? -r2 : -c1.getCurvatureAt(tMax, true),
r4 = c4.getCurvatureAt(t2Inside ? t2 : tMin, true),
r3 = t2Inside ? -r4 : -c3.getCurvatureAt(tMax, true);
console.log(a1 + ", " + a2 + ", " + a3 + ", " + a4);
return !!(t1Inside
? (isInRange(a1, a3, a4, r1, r3, r4) ^ isInRange(a2, a3, a4, r2, r3, r4)) &&
(isInRange(a1, a4, a3, r1, r4, r3) ^ isInRange(a2, a4, a3, r2, r4, r3))
: (isInRange(a3, a1, a2, r3, r1, r2) ^ isInRange(a4, a1, a2, r4, r1, r2)) &&
(isInRange(a3, a2, a1, r3, r2, r1) ^ isInRange(a4, a2, a1, r4, r2, r1)));
Well, that would have been too easy.
That's a great start though! Shall I take it from here?
If you want, to, you are welcome. I am a bit stuck at the moment.
Yeah the angles aren't quite the same, even when I go from tmin
/ tmax
to 0
/ 1
(which we now should, as this was a sort of cheap look-ahead before)... This is strange...
When I zoom in (using Firefox, Chrome will not do in this case), I get the feeling that there are two intersections, but of course this is difficult to see. I was thinking that maybe we do not find the real intersection here, but this is only a wild guess.
Here is a simplified sketch that has only the erroneous intersection and allows for zooming in (use Firefox instead of Chrome). Could be helpful for further investigations.
Here the same sketch with drawing of the tangents and pre-zoomed onto the action. It's really hard to see, but if you use the zoom tool button, and then press the space-bar for panning / scrolling, you can go down and up along the tangents and you will see that on either side, the red one is to the right of the blue one, so they indeed do not seem to cross. but they do. What's happening here?
Regarding panning / scrolling, it's a bit buggy right now: If your cursor is blinking in the code editor, you have to click into the console first to loose that focus, then you can pan with the space bar on the canvas. It works exactly like Illustrator then. I shall fix this properly.
Apologies for sending you on a wild goose chase without realizing that this tangents weren't actually collinear to begin with : /
No problem, was still interesting and maybe its useful for further development.
Thanks for the hint on panning, I was looking for that (btw, zoomin out does not seem to work often).
If you look at my last sketch, zoom into the intersection and then scroll up, you will see how the curves diverge and then converge again. I have the feeling that there is the real crossing, but we do not find it.
Can you post a screenshot of what I shall be looking for? And yes, I am aware that things are a bit broken on sketch. One of too many construction sites, and I'e learned to work around it without understanding what causes it...
BTW, this:
new Path({segments: [[point.x, point.y], [point.x, point.y]], fullySelected: true});
Can become that:
new Path({segments: [point], fullySelected: true});
And it still does what you expect it to : )
Here are some screenshots, all with the same zoom factor. This is the found intersection:
If you move up, the curves look like this:
Moving further up, the curves look like this again:
When you're on the right spot and zoom level, could you open the development console, and then enter this into the prompt, press enter, and paste the results here:
paper.view._matrix.getValues()
From that I can then recreated the zoom, e.g.:
view._matrix = new Matrix([2489206.111144464, 0, 0, 2489206.111144464, -869521971.2587767, -440519283.81372666]);
This may be a really stupid question, but how do I open the development console?
Haha! You are on IE / Edge, correct? This hopefully gets you started: https://msdn.microsoft.com/en-us/library/dd565628(v=vs.85).aspx
I think you press F12 to get there? Otherwise, Google should be full of pointers. Just search whatever bowser you are using + JavaScript console or development console / tools.
Here you go (pixelated non-HiDPI Windows 10 screenshots from Parallels running on a HiDPI MacBook):
And then:
To run it, I had to press the green play button int he lower right corner of the console window.
Aehm, no, IE is only if I really need to test something. Usually it's Chrome, but here I am using Firefox. But I found it.
Here we go. I think the real intersection is here (does not work on Chrome).
Oh wow, if I set the max recursions in addCurveIntersections()
to 25, everything works. :open_mouth:
Fantastic test case for fat line clipping!!!
Ah yeah, those zooms are also a great way to find the limits of the canvas renderers. At great zoom levels, strange things start to happen, and it becomes quite clear that we're not the only ones struggling with edge cases. The best is when you then export these as SVGs and try to open them in Illustrator. I usually get a crash right away. But why am I still surprised?
In this example calling
unite()
on a path fails to resolve the self intersections of the path. The result is a path that still has self intersections.