Second attempt at simplifying boolean code.

This time without endless loops.
This commit is contained in:
Jürg Lehni 2015-09-15 16:31:05 +02:00
parent 089738478c
commit 3ce7d88347

View file

@ -148,7 +148,7 @@ PathItem.inject(new function() {
tracePaths(segments, monoCurves, operation), path1, path2); tracePaths(segments, monoCurves, operation), path1, path2);
} }
var scaleFactor = 1 / 3000; // 0.5; // 1 / 3000; var scaleFactor = 1; // 1 / 3000;
var textAngle = 60; var textAngle = 60;
var fontSize = 5; var fontSize = 5;
@ -448,7 +448,7 @@ PathItem.inject(new function() {
*/ */
function tracePaths(segments, monoCurves, operation) { function tracePaths(segments, monoCurves, operation) {
var segmentCount = 0; var segmentCount = 0;
var pathCount = 0; var pathCount = 1;
function labelSegment(seg, text, color) { function labelSegment(seg, text, color) {
var point = seg.point; var point = seg.point;
@ -481,7 +481,7 @@ PathItem.inject(new function() {
strokeScaling: false strokeScaling: false
}); });
var inter = seg._intersection; var inter = seg._intersection;
labelSegment(seg, '#' + (pathCount + 1) + '.' labelSegment(seg, '#' + pathCount + '.'
+ (path ? path._segments.length + 1 : 1) + (path ? path._segments.length + 1 : 1)
+ ' ' + (segmentCount++) + '/' + index + ': ' + text + ' ' + (segmentCount++) + '/' + index + ': ' + text
+ ' v: ' + !!seg._visited + ' v: ' + !!seg._visited
@ -534,107 +534,74 @@ PathItem.inject(new function() {
inter = seg._intersection, inter = seg._intersection,
otherSeg = inter && inter._segment, otherSeg = inter && inter._segment,
otherStartSeg = otherSeg, otherStartSeg = otherSeg,
added = false, // Whether a first segment as added already added = false; // Whether a first segment as added already
dir = 1;
do { do {
var handleIn = dir > 0 ? seg._handleIn : seg._handleOut; var handleIn = added && seg._handleIn;
// If the intersection segment is valid, try switching to
// it, with an appropriate direction to continue traversal.
// Else, stay on the same contour.
if (added && otherSeg && otherSeg !== startSeg) { if (added && otherSeg && otherSeg !== startSeg) {
// There is an intersection. If the intersecting segment is
// valid, try switching to it to continue traversal.
// Otherwise stay on the same contour.
if (otherSeg._path === seg._path) { // Self-intersection if (otherSeg._path === seg._path) { // Self-intersection
drawSegment(seg, 'self-int ' + dir, i, 'red'); drawSegment(seg, 'self-int', i, 'red');
// Switch to the intersection segment, as we need to // Switch to the intersecting segment, as we need to
// resolving self-Intersections. // resolving self-Intersections.
seg = otherSeg; seg = otherSeg;
dir = 1; } else if (operation !== 'exclude'
} else if (operation !== 'exclude' && operator(seg._winding)) { && operator(seg._winding)) {
// Do not switch to the intersection as the segment is // Do not switch to the intersecting segment as it is
// part of the boolean result. // contained inside the boolean result.
drawSegment(seg, 'keep', i, 'black'); drawSegment(seg, 'ignore-keep', i, 'black');
} else if (operation !== 'intersect' && inter._overlap) { } else if (operation !== 'intersect' && inter._overlap) {
// Switch to the overlapping intersection segment // Switch to the overlapping intersection segment
// if its winding number along the curve is 1, to // if its winding number along the curve is 1, to
// leave the overlapping area. // leave the overlapping area.
// NOTE: We cannot check the next (overlapping) // NOTE: We cannot check the next (overlapping)
// segment since its winding number will always be 2 // segment since its winding number will always be 2
drawSegment(seg, 'overlap ' + dir, i, 'orange'); drawSegment(seg, 'overlap', i, 'orange');
var curve = otherSeg.getCurve(); var curve = otherSeg.getCurve();
if (getWinding(curve.getPointAt(0.5, true), if (getWinding(curve.getPointAt(0.5, true),
monoCurves, curve.isHorizontal()) === 1) { monoCurves, curve.isHorizontal()) === 1) {
seg = otherSeg; seg = otherSeg;
dir = 1;
} }
} else { } else if (operation === 'exclude'
var c1 = seg.getCurve(); || operator(otherSeg._winding)) {
if (dir > 0) // Look at both tangents at the intersection to
c1 = c1.getPrevious(); // determine if this intersection is actually a crossing
var t1 = c1.getTangentAt(dir < 0 ? tMin : tMax, true), var t1 = seg.getPrevious().getCurve().getTangentAt(tMax, true),
// Get both curves at the intersection t2 = otherSeg.getCurve().getTangentAt(tMin, true);
// (except the entry curves). if (t1.isCollinear(t2)) {
c4 = otherSeg.getCurve(),
c3 = c4.getPrevious(),
// Calculate their winding values and tangents.
t3 = c3.getTangentAt(tMax, true),
t4 = c4.getTangentAt(tMin, true),
// Cross product of the entry and exit tangent
// vectors at the intersection, will let us select
// the correct contour to traverse next.
w3 = t1.cross(t3),
w4 = t1.cross(t4);
if (Math.abs(w3 * w4) < /*#=*/Numerical.EPSILON) {
drawSegment(seg, 'no cross', i, 'blue');
dir = 1; // TODO: Why? Is this correct?
} else if (operation === 'exclude') {
// Do not attempt to switch contours if we aren't // Do not attempt to switch contours if we aren't
// sure that there is a possible candidate. // sure that there is a possible candidate.
// If we didn't find a suitable direction for next drawSegment(seg, 'no cross', i, 'blue');
// contour to traverse, stay on the same contour. } else {
// Switch to the intersection segment. // Switch to the intersection segment.
seg = otherSeg; seg = otherSeg;
drawSegment(seg, 'exclude:switch', i, 'green'); drawSegment(seg, 'switch', i, 'green');
dir = 1;
} else {
// Do not attempt to switch contours if we aren't
// sure that there is a possible candidate.
var curve = w3 < w4 ? c3 : c4;
if (!operator(curve._segment1._winding))
curve = curve === c3 ? c4 : c3;
dir = curve === c3 ? -1 : 1;
var next = curve._segment1;
// If we didn't find a suitable direction for next
// contour to traverse, stay on the same contour.
if (next._visited && seg._path !== next._path
|| !operator(next._winding)) {
drawSegment(next, 'not suitable', i, 'orange');
dir = 1;
} else {
// Switch to the intersection segment.
seg = otherSeg;
if (next._visited)
dir = -dir;
drawSegment(seg, 'switch', i, 'green');
}
} }
} else {
drawSegment(otherSeg, 'not suitable', i, 'orange');
} }
} else { } else {
drawSegment(seg, 'keep', i, 'black'); drawSegment(seg, 'keep', i, 'black');
} }
if (seg._visited) {
// We didn't manage to switch, so stop right here.
console.error('Unable to switch to intersecting segment, '
+ 'aborting #' + pathCount + '.' +
(path ? path._segments.length + 1 : 1));
break;
}
// Add the current segment to the path, and mark the added // Add the current segment to the path, and mark the added
// segment as visited. // segment as visited.
path.add(new Segment(seg._point, added && handleIn, path.add(new Segment(seg._point, handleIn, seg._handleOut));
dir > 0 ? seg._handleOut : seg._handleIn)); seg._visited = added = true;
seg._visited = true; seg = seg.getNext();
added = true;
// Move to the next segment according to the traversal direction
seg = dir > 0 ? seg.getNext() : seg.getPrevious();
inter = seg && seg._intersection; inter = seg && seg._intersection;
otherSeg = inter && inter._segment; otherSeg = inter && inter._segment;
} while (seg && seg !== startSeg && seg !== otherStartSeg } while (seg && seg !== startSeg && seg !== otherStartSeg
// Exclusion switches on each intersection, we need to look // If we're about to switch, try to see if we can carry on
// ahead & carry on if the other segment wasn't visited yet. // if the other segment wasn't visited yet.
&& (!seg._visited || operation === 'exclude' && (!seg._visited || otherSeg && !otherSeg._visited)
&& otherSeg && !otherSeg._visited)
&& (inter || operator(seg._winding))); && (inter || operator(seg._winding)));
// Finish with closing the paths if necessary, correctly linking up // Finish with closing the paths if necessary, correctly linking up
// curves etc. // curves etc.
@ -643,14 +610,14 @@ PathItem.inject(new function() {
path.setClosed(true); path.setClosed(true);
if (window.reportSegments) { if (window.reportSegments) {
console.log('Boolean operation completed', console.log('Boolean operation completed',
'#' + (pathCount + 1) + '.' + '#' + pathCount + '.' +
(path ? path._segments.length + 1 : 1)); (path ? path._segments.length + 1 : 1));
} }
} else { } else {
// path.lastSegment._handleOut.set(0, 0); // path.lastSegment._handleOut.set(0, 0);
console.error('Boolean operation results in open path, segs =', console.error('Boolean operation results in open path, segs =',
path._segments.length, 'length = ', path.getLength(), path._segments.length, 'length = ', path.getLength(),
'#' + (pathCount + 1) + '.' + '#' + pathCount + '.' +
(path ? path._segments.length + 1 : 1)); (path ? path._segments.length + 1 : 1));
path = null; path = null;
} }
@ -661,9 +628,7 @@ PathItem.inject(new function() {
if (path && (path._segments.length > 4 if (path && (path._segments.length > 4
|| !Numerical.isZero(path.getArea()))) || !Numerical.isZero(path.getArea())))
paths.push(path); paths.push(path);
if (window.reportSegments) { pathCount++;
pathCount++;
}
} }
return paths; return paths;
} }