mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-05 20:32:00 -05:00
Implement a more refined strategy to handle 'contour' segment in unite boolean operations.
Improvement for the fix in 648beb33e9
for #1054, solves the issues described in https://github.com/paperjs/paper.js/issues/1054#issuecomment-225356983
This commit is contained in:
parent
34c35e26bd
commit
7241edd98a
1 changed files with 44 additions and 28 deletions
|
@ -299,7 +299,7 @@ PathItem.inject(new function() {
|
||||||
* Private method that returns the winding contribution of the given point
|
* Private method that returns the winding contribution of the given point
|
||||||
* with respect to a given set of monotonic curves.
|
* with respect to a given set of monotonic curves.
|
||||||
*/
|
*/
|
||||||
function getWinding(point, curves, operator, horizontal) {
|
function getWinding(point, curves, horizontal) {
|
||||||
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
||||||
px = point.x,
|
px = point.x,
|
||||||
py = point.y,
|
py = point.y,
|
||||||
|
@ -333,9 +333,9 @@ PathItem.inject(new function() {
|
||||||
yTop = (yTop + py) / 2;
|
yTop = (yTop + py) / 2;
|
||||||
yBottom = (yBottom + py) / 2;
|
yBottom = (yBottom + py) / 2;
|
||||||
if (yTop > -Infinity)
|
if (yTop > -Infinity)
|
||||||
windLeft = getWinding(new Point(px, yTop), curves, operator);
|
windLeft = getWinding(new Point(px, yTop), curves).winding;
|
||||||
if (yBottom < Infinity)
|
if (yBottom < Infinity)
|
||||||
windRight = getWinding(new Point(px, yBottom), curves, operator);
|
windRight = getWinding(new Point(px, yBottom), curves).winding;
|
||||||
} else {
|
} else {
|
||||||
var xBefore = px - epsilon,
|
var xBefore = px - epsilon,
|
||||||
xAfter = px + epsilon,
|
xAfter = px + epsilon,
|
||||||
|
@ -422,12 +422,14 @@ PathItem.inject(new function() {
|
||||||
windRight = windRightOnCurve;
|
windRight = windRightOnCurve;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We need to handle the winding contribution differently when dealing
|
// Return both the calculated winding contribution, and also detect if
|
||||||
// with unite operations, so that it will be 1 for any point on the
|
// we are on the contour of the area by comparing windLeft & windRight.
|
||||||
// outside path, since we are not considering a contribution of 2 a part
|
// This is required when handling unite operations, where a winding
|
||||||
// of the result, but would have to for outside points. See #1054
|
// contribution of 2 is not part of the result unless it's the contour:
|
||||||
return operator && operator.unite && !windLeft ^ !windRight ? 1
|
return {
|
||||||
: Math.max(abs(windLeft), abs(windRight));
|
winding: Math.max(abs(windLeft), abs(windRight)),
|
||||||
|
contour: !windLeft ^ !windRight
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function propagateWinding(segment, path1, path2, monoCurves, operator) {
|
function propagateWinding(segment, path1, path2, monoCurves, operator) {
|
||||||
|
@ -438,7 +440,7 @@ PathItem.inject(new function() {
|
||||||
var chain = [],
|
var chain = [],
|
||||||
start = segment,
|
start = segment,
|
||||||
totalLength = 0,
|
totalLength = 0,
|
||||||
winding = 0;
|
winding;
|
||||||
do {
|
do {
|
||||||
var curve = segment.getCurve(),
|
var curve = segment.getCurve(),
|
||||||
length = curve.getLength();
|
length = curve.getLength();
|
||||||
|
@ -465,17 +467,19 @@ 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, operator, hor) ||
|
path === path1 && path2._getWinding(pt, hor) ||
|
||||||
path === path2 && !path1._getWinding(pt, operator, hor)))
|
path === path2 && !path1._getWinding(pt, hor)))
|
||||||
? getWinding(pt, monoCurves, operator, hor)
|
? getWinding(pt, monoCurves, hor)
|
||||||
: 0;
|
: { winding: 0 };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
length -= curveLength;
|
length -= curveLength;
|
||||||
}
|
}
|
||||||
// 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--) {
|
||||||
chain[j].segment._winding = winding;
|
var seg = chain[j].segment;
|
||||||
|
seg._winding = winding.winding;
|
||||||
|
seg._contour = winding.contour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,8 +498,14 @@ PathItem.inject(new function() {
|
||||||
start,
|
start,
|
||||||
otherStart;
|
otherStart;
|
||||||
|
|
||||||
function isValid(seg) {
|
function isValid(seg, excludeContour) {
|
||||||
return !!(!seg._visited && (!operator || operator[seg._winding]));
|
// Unite operations need special handling of segments with a winding
|
||||||
|
// contribution of two (part of both involved areas) but which are
|
||||||
|
// 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
|
||||||
|
// valid next step, as controlled by the excludeContour parameter.
|
||||||
|
return !!(!seg._visited && (!operator || operator[seg._winding]
|
||||||
|
|| !excludeContour && operator.unite && seg._contour));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStart(seg) {
|
function isStart(seg) {
|
||||||
|
@ -572,12 +582,15 @@ PathItem.inject(new function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Do not start paths with invalid segments (segments that were
|
// Exclude three cases of invalid starting segments:
|
||||||
// already visited, or that are not going to be part of the result).
|
// - Do not start with invalid segments (segments that were already
|
||||||
// Also don't start in overlaps, unless all segments are part of
|
// visited, or that are not going to be part of the result).
|
||||||
|
// - Do not start in segments that have an invalid winding
|
||||||
|
// contribution but are part of the contour (excludeContour=true).
|
||||||
|
// - Do not start in overlaps, unless all segments are part of
|
||||||
// overlaps, in which case we have no other choice.
|
// overlaps, in which case we have no other choice.
|
||||||
if (!isValid(seg) || !seg._path._validOverlapsOnly
|
if (!isValid(seg, true)
|
||||||
&& inter && seg._winding && inter._overlap)
|
|| !seg._path._validOverlapsOnly && inter && inter._overlap)
|
||||||
continue;
|
continue;
|
||||||
start = otherStart = null;
|
start = otherStart = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -594,7 +607,10 @@ PathItem.inject(new function() {
|
||||||
finished = true;
|
finished = true;
|
||||||
// Switch the segment, but do not update handleIn
|
// Switch the segment, but do not update handleIn
|
||||||
seg = other;
|
seg = other;
|
||||||
} else if (isValid(other)) {
|
} else if (isValid(other, isValid(seg, true))) {
|
||||||
|
// Note that we pass `true` for excludeContour here if
|
||||||
|
// the current segment is valid and not a contour
|
||||||
|
// segment. See isValid()/getWinding() for explanations.
|
||||||
// We are at a crossing and the other segment is part of
|
// We are at a crossing and the other segment is part of
|
||||||
// the boolean result, switch over.
|
// the boolean result, switch over.
|
||||||
// We need to mark overlap segments as visited when
|
// We need to mark overlap segments as visited when
|
||||||
|
@ -615,7 +631,8 @@ PathItem.inject(new function() {
|
||||||
}
|
}
|
||||||
// If there are only valid overlaps and we encounter and invalid
|
// If there are only valid overlaps and we encounter and invalid
|
||||||
// segment, bail out immediately. Otherwise we need to be more
|
// segment, bail out immediately. Otherwise we need to be more
|
||||||
// tolerant due to complex situations of crossing.
|
// tolerant due to complex situations of crossing,
|
||||||
|
// see findBestIntersection()
|
||||||
if (seg._path._validOverlapsOnly && !isValid(seg))
|
if (seg._path._validOverlapsOnly && !isValid(seg))
|
||||||
break;
|
break;
|
||||||
if (!path) {
|
if (!path) {
|
||||||
|
@ -679,9 +696,8 @@ PathItem.inject(new function() {
|
||||||
* part of a horizontal curve
|
* part of a horizontal curve
|
||||||
* @return {Number} the winding number
|
* @return {Number} the winding number
|
||||||
*/
|
*/
|
||||||
_getWinding: function(point, operator, horizontal) {
|
_getWinding: function(point, horizontal) {
|
||||||
return getWinding(point, this._getMonoCurves(), operator,
|
return getWinding(point, this._getMonoCurves(), horizontal).winding;
|
||||||
horizontal);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue