Boolean: Implement branch structure to be able to go back on traces and try different crossings.

Relates to #1073
This commit is contained in:
Jürg Lehni 2016-07-27 21:40:53 +02:00
parent bcd02129a3
commit 445d8ae22f

View file

@ -676,20 +676,22 @@ PathItem.inject(new function() {
// If there are multiple possible intersections, find the one that's // If there are multiple possible intersections, find the one that's
// either connecting back to start or is not visited yet, and will be // either connecting back to start or is not visited yet, and will be
// part of the boolean result: // part of the boolean result:
function findBestIntersection(inter, exclude) { function findBestIntersection(segment) {
if (!inter._next) var inter = segment._intersection,
start = inter;
if (!inter || !inter._next)
return inter; return inter;
while (inter) { while (inter && inter !== start) {
var seg = inter._segment, var other = inter._segment,
nextSeg = seg.getNext(), next = other.getNext(),
nextInter = nextSeg && nextSeg._intersection; nextInter = next && next._intersection;
// See if this segment and the next are both not visited yet, or // See if this segment and the next are both not visited yet, or
// are bringing us back to the beginning, and are both valid, // are bringing us back to the beginning, and are both valid,
// meaning they are part of the boolean result. // meaning they are part of the boolean result.
if (seg !== exclude && (isStart(seg) || isStart(nextSeg) if (other !== segment && (isStart(other) || isStart(next)
|| nextSeg && !seg._visited && !nextSeg._visited || next && !other._visited && !next._visited
// Self-intersections (!operator) don't need isValid() calls // Self-intersections (!operator) don't need isValid() calls
&& (!operator || isValid(seg) && (isValid(nextSeg) && (!operator || isValid(other) && (isValid(next)
// If the next segment isn't valid, its intersection // If the next segment isn't valid, its intersection
// to which we may switch might be, so check that. // to which we may switch might be, so check that.
|| nextInter && isValid(nextInter._segment))) || nextInter && isValid(nextInter._segment)))
@ -698,7 +700,7 @@ PathItem.inject(new function() {
// If it's no match, continue with the next linked intersection. // If it's no match, continue with the next linked intersection.
inter = inter._next; inter = inter._next;
} }
return null; return start;
} }
// Sort segments to give non-ambiguous segments the preference as // Sort segments to give non-ambiguous segments the preference as
@ -730,6 +732,9 @@ PathItem.inject(new function() {
var path = null, var path = null,
finished = false, finished = false,
closed = true, closed = true,
branches = [],
branch,
visited,
seg = segments[i], seg = segments[i],
inter = seg._intersection, inter = seg._intersection,
handleIn; handleIn;
@ -763,24 +768,25 @@ PathItem.inject(new function() {
while (true) { while (true) {
// For each segment we encounter, see if there are multiple // For each segment we encounter, see if there are multiple
// intersections, and if so, pick the best one: // intersections, and if so, pick the best one:
inter = inter && findBestIntersection(inter, seg) || inter; var inter = findBestIntersection(seg),
// Get the reference to the other segment on the intersection. // Get the other segment on the intersection.
var other = inter && inter._segment; other = inter && inter._segment,
if (isStart(seg)) { first = !path,
finished = true; cross = false;
} else if (other) { if (first) {
if (isStart(other)) { path = new Path(Item.NO_INSERT);
finished = true; start = seg;
// Switch the segment, but do not update handleIn otherStart = other;
seg = other; }
} else if (isValid(other, isValid(seg, true))) { finished = !first && isStart(seg);
// Note that we pass `true` for excludeContour here if if (!finished && other) {
// the current segment is valid and not a contour finished = !first && isStart(other);
// segment. See isValid()/getWinding() for explanations. // Are we at the end or at a crossing and the other segment
// We are at a crossing and the other segment is part of // is part of the boolean result? If so, switch over.
// the boolean result, switch over. cross = finished || isValid(other, isValid(seg, true));
seg = other; // NOTE: We pass `true` for excludeContour here if the
} // current segment is valid and not a contour segment.
// See isValid()/getWinding() for explanations.
} }
if (finished) { if (finished) {
seg._visited = true; seg._visited = true;
@ -791,13 +797,45 @@ PathItem.inject(new function() {
closed = seg._path._closed; closed = seg._path._closed;
break; break;
} }
// If a invalid segment is encountered, bail out immediately. if (cross && branch) {
if (!isValid(seg)) // If we're about to cross, start a new branch and add the
break; // current one to the list of branches.
if (!path) { branches.push(branch);
path = new Path(Item.NO_INSERT); branch = null;
start = seg; }
otherStart = other; if (!branch) {
visited = [];
branch = {
start: path._segments.length,
segment: seg,
handleIn: handleIn,
visited: visited
};
}
if (cross)
seg = other;
// If an invalid segment is encountered, go back to the last
// crossing and try the other direction by not crossing at the
// intersection.
if (!isValid(seg)) {
// Remove the already added segments, and mark them as no
// visited so they become available again as options.
path.removeSegments(branch.start);
for (var j = 0, k = visited.length; j < k; j++) {
visited[j]._visited = false;
}
// Go back to the segment at which the crossing happened,
// but don't cross this time.
seg = branch.segment;
handleIn = branch.handleIn;
visited = branch.visited;
// Now restore the previous branch and keep adding to it,
// since we don't cross here anymore.
branch = branches.pop();
if (!branch) {
console.log('run out of branches, breaking.');
break;
}
} }
// Add the segment to the path, and mark it as visited. // Add the segment to the path, and mark it as visited.
// But first we need to look ahead. If we encounter the end of // But first we need to look ahead. If we encounter the end of
@ -808,11 +846,11 @@ PathItem.inject(new function() {
path.add(new Segment(seg._point, handleIn, path.add(new Segment(seg._point, handleIn,
next && seg._handleOut)); next && seg._handleOut));
seg._visited = true; seg._visited = true;
visited.push(seg);
// If this is the end of an open path, go back to its first // If this is the end of an open path, go back to its first
// segment but ignore its handleIn (see above for handleOut). // segment but ignore its handleIn (see above for handleOut).
seg = next || seg._path.getFirstSegment(); seg = next || seg._path.getFirstSegment();
handleIn = next && next._handleIn; handleIn = next && next._handleIn;
inter = seg._intersection;
} }
if (finished) { if (finished) {
// Finish with closing the paths, and carrying over the last // Finish with closing the paths, and carrying over the last