Rewrite method for linking and choosing multiple intersections in the same location.

The special handling of overlaps reduces the amount of remaining glitches substantially.

Relates to #784.
This commit is contained in:
Jürg Lehni 2015-10-06 21:09:35 +02:00
parent adabe9126a
commit 3ac3df8d32
3 changed files with 105 additions and 77 deletions

View file

@ -1733,21 +1733,21 @@ new function() { // Scope for intersection using bezier fat-line clipping
// We only have to check if the handles are the same, too.
if (pairs.length === 2) {
// create values for overlapping part of each curve
var p1 = Curve.getPart(v[0], pairs[0][0], pairs[1][0]),
p2 = Curve.getPart(v[1], pairs[0][1], pairs[1][1]);
var o1 = Curve.getPart(v[0], pairs[0][0], pairs[1][0]),
o2 = Curve.getPart(v[1], pairs[0][1], pairs[1][1]);
// Check if handles of overlapping paths are similar enough.
// We could do another check for curve identity here if we find a
// better criteria.
if (straight ||
abs(p2[2] - p1[2]) < geomEpsilon &&
abs(p2[3] - p1[3]) < geomEpsilon &&
abs(p2[4] - p1[4]) < geomEpsilon &&
abs(p2[5] - p1[5]) < geomEpsilon) {
abs(o2[2] - o1[2]) < geomEpsilon &&
abs(o2[3] - o1[3]) < geomEpsilon &&
abs(o2[4] - o1[4]) < geomEpsilon &&
abs(o2[5] - o1[5]) < geomEpsilon) {
// Overlapping parts are identical
addLocation(locations, param, v1, c1, pairs[0][0], null,
v2, c2, pairs[0][1], null, true),
v2, c2, pairs[0][1], null, o1),
addLocation(locations, param, v1, c1, pairs[1][0], null,
v2, c2, pairs[1][1], null, true);
v2, c2, pairs[1][1], null, o2);
return true;
}
}

View file

@ -454,7 +454,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
* @see #isTouching()
*/
isOverlap: function() {
return this._overlap;
return !!this._overlap;
},
statics: {
@ -505,9 +505,9 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
// candidates, so check them too (see #search() for details)
if (loc2 = loc.equals(loc2) ? loc2
: search(m, -1) || search(m, 1)) {
// Carry over overlap setting!
// Carry over overlap!
if (loc._overlap) {
loc2._overlap = loc2._intersection._overlap = true;
loc2._overlap = loc2._intersection._overlap = loc._overlap;
}
// We're done, don't insert, merge with the found
// location instead:

View file

@ -160,10 +160,32 @@ PathItem.inject(new function() {
}).join(' '));
}
/**
* Private method for splitting a PathItem at the given locations.
/*
* Creates linked lists between intersections through their _next property.
*
* @param {CurveLocation[]} locations Array of CurveLocation objects
* @private
*/
function linkIntersections(from, to) {
// Only create links if they are not the same, to avoid endless
// recursions.
if (from !== to) {
// Loop through the existing linked list until we find an
// empty spot, but stop if we find `to`, to avoid adding it
// again.
while (from._next && from._next !== to)
from = from._next;
// If we're reached the end of the list, we can add it.
if (!from._next)
from._next = to;
}
}
/**
* Splits a path-item at the given locations.
*
* @param {CurveLocation[]} locations an array of the locations to split the
* path-item at.
* @private
*/
function splitPath(locations) {
if (window.reportIntersections) {
@ -218,32 +240,24 @@ PathItem.inject(new function() {
clearSegments.push(segment);
}
loc._setSegment(segment);
// Link the new segment with the intersection on the other curve
var inter = segment._intersection;
// Create links from the new segment to the intersection on the
// other curve, as well as from there back. If there are multiple
// intersections on the same segment, we create linked lists between
// the intersections through linkIntersections(), linking both ways.
var inter = segment._intersection,
dest = loc._intersection;
if (inter) {
// Prevent circular references that would cause infinite loops
// in getIntersection():
// See if the location already links back to this intersection,
// and do not create another connection if it does.
var other = inter._intersection,
next = loc._next;
while (next && next !== other)
next = next._next;
if (!next) {
if (window.reportIntersections) {
console.log('Link: '
+ segment._path._id + '.' + segment._index
+ ' -> ' + inter._curve._path._id);
}
// Create a chain of possible intersections linked through
// _next First find the last intersection in the chain, then
// link it.
while (inter._next)
inter = inter._next;
inter._next = loc._intersection;
linkIntersections(inter, dest);
// Each time we add a new link to the linked list, we need to
// add links from all the other entries to the new entry.
var other = inter;
while (other) {
linkIntersections(other._intersection, inter);
other = other._next;
}
} else {
segment._intersection = loc._intersection;
segment._intersection = dest;
}
prevCurve = curve;
prevT = origT;
@ -513,21 +527,33 @@ PathItem.inject(new function() {
id = path._id,
point = seg.point,
inter = seg._intersection,
ix = inter && inter._segment,
nx = inter && inter._next && inter._next._segment,
style = path._parent instanceof CompoundPath ? path._parent : path;
ix = inter,
ixs = ix && ix._segment,
n1x = inter && inter._next,
n1xs = n1x && n1x._segment,
n2x = n1x && n1x._next,
n2xs = n2x && n2x._segment,
n3x = n2x && n2x._next,
n3xs = n3x && n3x._segment,
item = path instanceof Path ? path : path._parent;
if (!(id in pathIndices)) {
pathIndices[id] = ++pathIndex;
j = 0;
}
labelSegment(seg, '#' + pathIndex + '.' + (j + 1)
+ ' id: ' + seg._path._id + '.' + seg._index
+ ' ix: ' + (ix && ix._path._id + '.' + ix._index || '--')
+ ' nx: ' + (nx && nx._path._id + '.' + nx._index || '--')
+ ' ix: ' + (ixs && ixs._path._id + '.' + ixs._index
+ '(' + ix._id + ')' || '--')
+ ' n1x: ' + (n1xs && n1xs._path._id + '.' + n1xs._index
+ '(' + n1x._id + ')' || '--')
+ ' n2x: ' + (n2xs && n2xs._path._id + '.' + n2xs._index
+ '(' + n2x._id + ')' || '--')
+ ' n3x: ' + (n3xs && n3xs._path._id + '.' + n3xs._index
+ '(' + n3x._id + ')' || '--')
+ ' pt: ' + seg._point
+ ' ov: ' + !!(inter && inter._overlap)
+ ' wi: ' + seg._winding
, style.strokeColor || style.fillColor || 'black');
, item.strokeColor || item.fillColor || 'black');
}
var paths = [],
@ -550,17 +576,26 @@ PathItem.inject(new function() {
return operator(winding);
}
/**
* Checks if the curve from seg1 to seg2 is part of an overlap, by
* getting a curve-point somewhere along the curve (t = 0.5), and
* checking if it is part of the overlap curve.
*/
function isOverlap(seg1, seg2) {
var inter = seg2._intersection,
overlap = inter && inter._overlap;
if (overlap) {
var pt = Curve.getPoint(Curve.getValues(seg1, seg2), 0.5);
if (Curve.getParameterOf(overlap, pt.x, pt.y) !== null)
return true;
}
return false;
}
// If there are multiple possible intersections, find the one
// that's either connecting back to start or is not visited yet,
// and will be part of the boolean result:
var count = 0;
function getIntersection(strict, inter, prev, ignoreOther) {
/*
if (!prev)
count = 0;
if (count++ >= 16)
return null;
*/
function getIntersection(inter, strict) {
if (!inter)
return null;
var seg = inter._segment,
@ -577,13 +612,13 @@ PathItem.inject(new function() {
+ ', seg wi:' + seg._winding
+ ', next wi:' + nextSeg._winding
+ ', seg op:' + isValid(seg, true)
+ ', next op:' + (isValid(nextSeg,
!strict && inter._overlap)
|| nextInter && isValid(nextInter._segment,
!strict && nextInter._overlap))
+ ', seg ov: ' + (seg._intersection
+ ', next op:' + ((!strict || !isOverlap(seg, nextSeg))
&& isValid(nextSeg, true)
|| !strict && nextInter
&& isValid(nextInter._segment, true))
+ ', seg ov: ' + !!(seg._intersection
&& seg._intersection._overlap)
+ ', next ov: ' + (nextSeg._intersection
+ ', next ov: ' + !!(nextSeg._intersection
&& nextSeg._intersection._overlap)
+ ', more: ' + (!!inter._next));
}
@ -607,26 +642,19 @@ PathItem.inject(new function() {
// since an overlap crossing might have brought us here,
// in which case isValid(seg, false) might be false.
|| (!strict || isValid(seg, true))
// Even if next segment is not valid, its intersection
// to which we may switch might be, so count that too!
&& (isValid(nextSeg, !strict && inter._overlap)
|| nextInter && isValid(nextInter._segment,
!strict && nextInter._overlap))
// Do not consider the nextSeg in strict mode if it is
// part of an overlap, in order to give non-overlapping
// options that might follow the priority over overlaps.
&& (!(strict && isOverlap(seg, nextSeg))
&& isValid(nextSeg, true)
// If next segment is not valid, its intersection to
// which we may switch might be, so allow that too!
|| !strict && nextInter
&& isValid(nextInter._segment, true))
)
? inter
// If it's no match, check the next linked intersection first,
// otherwise carry on with the 'other' intersection location.
: inter._next !== prev // Prevent circular loops
&& getIntersection(strict, inter._next, inter, false)
// We need to get the intersection on the segment, not
// on inter, since multiple solutions are only linked up
// as a chain through _next there. But do not check that
// intersection in the first call to getIntersection()
// (prev == null), since we'd go straight back to the
// originating segment.
|| !ignoreOther
&& (prev || seg._intersection !== inter._intersection)
&& getIntersection(strict, seg._intersection, inter, true);
// If it's no match, continue with the next linked intersection.
: getIntersection(inter._next, strict)
}
for (var i = 0, l = segments.length; i < l; i++) {
var seg = segments[i],
@ -649,8 +677,8 @@ PathItem.inject(new function() {
+ ', other: ' + inter._segment._path._id + '.'
+ inter._segment._index);
}
inter = inter && (getIntersection(true, inter)
|| getIntersection(false, inter)) || inter;
inter = inter && (getIntersection(inter, true)
|| getIntersection(inter, false)) || inter;
var other = inter && inter._segment;
// A switched intersection means we may have changed the segment
// Point to the other segment in the selected intersection.