Boolean: Return the full winding object from _getWinding() and use it to improve PathItem#contains() with even-odd full-rule.

Also store the full winding object on the processed segments, to have more information available in boolean operations.
This commit is contained in:
Jürg Lehni 2016-07-22 15:46:42 +02:00
parent 23f3097f84
commit cbe41c536e
3 changed files with 27 additions and 19 deletions

View file

@ -176,7 +176,7 @@ PathItem.inject(new function() {
path._overlapsOnly = false; path._overlapsOnly = false;
// This is no overlap. If it is valid, take note that this // This is no overlap. If it is valid, take note that this
// path contains valid intersections other than overlaps. // path contains valid intersections other than overlaps.
if (operator[segment._winding]) if (operator[segment._winding.winding])
path._validOverlapsOnly = false; path._validOverlapsOnly = false;
} }
} }
@ -378,7 +378,8 @@ PathItem.inject(new function() {
pathWindingL = 0, pathWindingL = 0,
pathWindingR = 0, pathWindingR = 0,
onPathWinding = 0, onPathWinding = 0,
isOnPath = false, onPathCount = 0,
onPath = false,
vPrev, vPrev,
vClose; vClose;
@ -402,7 +403,7 @@ PathItem.inject(new function() {
// +----+ | // +----+ |
// +-----+ // +-----+
if (a1 < paR && a3 > paL || a3 < paR && a1 > paL) { if (a1 < paR && a3 > paL || a3 < paR && a1 > paL) {
isOnPath = true; onPath = true;
} }
// If curve does not change in ordinate direction, windings will // If curve does not change in ordinate direction, windings will
// be added by adjacent curves. // be added by adjacent curves.
@ -426,7 +427,7 @@ PathItem.inject(new function() {
} else if (a > paR) { } else if (a > paR) {
pathWindingR += winding; pathWindingR += winding;
} else { } else {
isOnPath = true; onPath = true;
pathWindingL += winding; pathWindingL += winding;
pathWindingR += winding; pathWindingR += winding;
} }
@ -442,7 +443,7 @@ PathItem.inject(new function() {
} else if (a3Prev < paL && a > paL || a3Prev > paR && a < paR) { } else if (a3Prev < paL && a > paL || a3Prev > paR && a < paR) {
// Point is on a horizontal curve between the previous non- // Point is on a horizontal curve between the previous non-
// horizontal and the current curve. // horizontal and the current curve.
isOnPath = true; onPath = true;
if (a3Prev < paL) { if (a3Prev < paL) {
// left winding was added before, now add right winding. // left winding was added before, now add right winding.
pathWindingR += winding; pathWindingR += winding;
@ -528,7 +529,7 @@ PathItem.inject(new function() {
handleCurve(vClose); handleCurve(vClose);
vClose = null; vClose = null;
} }
if (!pathWindingL && !pathWindingR && isOnPath) { if (!pathWindingL && !pathWindingR && onPath) {
// If the point is on the path and the windings canceled // If the point is on the path and the windings canceled
// each other, we treat the point as if it was inside the // each other, we treat the point as if it was inside the
// path. A point inside a path has a winding of [+1,-1] // path. A point inside a path has a winding of [+1,-1]
@ -544,7 +545,9 @@ PathItem.inject(new function() {
windingR += pathWindingR; windingR += pathWindingR;
pathWindingL = pathWindingR = 0; pathWindingL = pathWindingR = 0;
} }
isOnPath = false; if (onPath)
onPathCount++;
onPath = false;
} }
} }
if (!windingL && !windingR) { if (!windingL && !windingR) {
@ -558,7 +561,10 @@ PathItem.inject(new function() {
// contribution of 2 is not part of the result unless it's the contour: // contribution of 2 is not part of the result unless it's the contour:
return { return {
winding: max(windingL, windingR), winding: max(windingL, windingR),
contour: !windingL ^ !windingR windingL: windingL,
windingR: windingR,
onContour: !windingL ^ !windingR,
onPathCount: onPathCount
}; };
} }
@ -600,8 +606,8 @@ PathItem.inject(new function() {
// contributing to the second operand and is outside the // contributing to the second operand and is outside the
// first operand. // first operand.
winding = !(operator.subtract && path2 && ( winding = !(operator.subtract && path2 && (
path === path1 && path2._getWinding(pt, dir) || path === path1 && path2._getWinding(pt, dir).winding ||
path === path2 && !path1._getWinding(pt, dir))) path === path2 && !path1._getWinding(pt, dir).winding))
? getWinding(pt, curves, dir) ? getWinding(pt, curves, dir)
: { winding: 0 }; : { winding: 0 };
break; break;
@ -610,9 +616,7 @@ PathItem.inject(new function() {
} }
// Now assign the winding to the entire curve chain. // Now assign the winding to the entire curve chain.
for (var j = chain.length - 1; j >= 0; j--) { for (var j = chain.length - 1; j >= 0; j--) {
var seg = chain[j].segment; chain[j].segment._winding = winding;
seg._winding = winding.winding;
seg._contour = winding.contour;
} }
} }
@ -637,9 +641,10 @@ PathItem.inject(new function() {
// also part of the contour of the result. Such segments are not // also part of the contour of the result. Such segments are not
// chosen as the start of new paths and are not always counted as a // chosen as the start of new paths and are not always counted as a
// valid next step, as controlled by the excludeContour parameter. // valid next step, as controlled by the excludeContour parameter.
var winding;
return !!(seg && !seg._visited && (!operator return !!(seg && !seg._visited && (!operator
|| operator[seg._winding] || operator[(winding = seg._winding).winding]
|| !excludeContour && operator.unite && seg._contour)); || !excludeContour && operator.unite && winding.onContour));
} }
function isStart(seg) { function isStart(seg) {
@ -849,7 +854,7 @@ PathItem.inject(new function() {
* @return {Number} the winding number * @return {Number} the winding number
*/ */
_getWinding: function(point, dir) { _getWinding: function(point, dir) {
return getWinding(point, this.getCurves(), dir).winding; return getWinding(point, this.getCurves(), dir);
}, },
/** /**

View file

@ -265,8 +265,11 @@ var PathItem = Item.extend(/** @lends PathItem# */{
// for a quick check before calculating the actual winding. // for a quick check before calculating the actual winding.
var winding = point.isInside( var winding = point.isInside(
this.getBounds({ internal: true, handle: true })) this.getBounds({ internal: true, handle: true }))
&& this._getWinding(point); ? this._getWinding(point)
return !!(this.getFillRule() === 'evenodd' ? winding & 1 : winding); : {};
return !!(this.getFillRule() === 'evenodd'
? winding.windingL & 1 || winding.windingR & 1
: winding.winding);
/*#*/ } // !__options.nativeContains && __options.booleanOperations /*#*/ } // !__options.nativeContains && __options.booleanOperations
}, },

View file

@ -135,7 +135,7 @@ test('CompoundPath#contains() (donut)', function() {
} }
path.fillRule = 'evenodd'; path.fillRule = 'evenodd';
// testDonut(path, '\'evenodd\''); testDonut(path, '\'evenodd\'');
path.reorient(); path.reorient();
testDonut(path, '\'evenodd\' + reorient()'); testDonut(path, '\'evenodd\' + reorient()');
path.fillRule = 'nonzero'; path.fillRule = 'nonzero';