mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Boolean: Implement branch structure to be able to go back on traces and try different crossings.
Relates to #1073
This commit is contained in:
parent
bcd02129a3
commit
445d8ae22f
1 changed files with 74 additions and 36 deletions
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue