Improve new getWinding() direction flipping code.

This commit is contained in:
Jürg Lehni 2016-07-22 23:30:36 +02:00
parent eab0b9db5e
commit 27af304414

View file

@ -358,6 +358,8 @@ PathItem.inject(new function() {
* {@link CompoundPath#getCurves()} * {@link CompoundPath#getCurves()}
* @param {Number} [dir=0] the direction in which to determine the * @param {Number} [dir=0] the direction in which to determine the
* winding contribution, `0`: in x-direction, `1`: in y-direction * winding contribution, `0`: in x-direction, `1`: in y-direction
* @param {Boolean} [dontFlip=false] controls whether the algorithm is
* allowed to flip direction if it is deemed to produce better results
* @return {Object} an object containing the calculated winding number, as * @return {Object} an object containing the calculated winding number, as
* well as an indication whether the point was situated on the contour * well as an indication whether the point was situated on the contour
* @private * @private
@ -382,16 +384,15 @@ PathItem.inject(new function() {
onPath = false, onPath = false,
roots = [], roots = [],
vPrev, vPrev,
vClose, vClose;
result;
function addWinding(v) { function addWinding(v) {
var o0 = v[io], var o0 = v[io],
o3 = v[io + 6]; o3 = v[io + 6];
if (o0 > po && o3 > po || o0 < po && o3 < po) { if (po < min(o0, o3) || po > max(o0, o3)) {
// If curve is outside the ordinates' range, no intersection // If the curve is outside the ordinates' range, no intersection
// with the ray is possible. // with the ray is possible.
return v; return;
} }
var a0 = v[ia], var a0 = v[ia],
a1 = v[ia + 2], a1 = v[ia + 2],
@ -409,16 +410,19 @@ PathItem.inject(new function() {
} }
// 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.
return vPrev; // Bail out without updating vPrev at the end of the call.
return;
} }
var t = null, var t = po === o0 ? 0
a = po === o0 ? a0 : po === o3 ? 1
: po === o3 ? a3
: paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3) : paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3)
? (a0 + a3) / 2 ? 0.5
: Curve.solveCubic(v, io, po, roots, 0, 1) === 1 : Curve.solveCubic(v, io, po, roots, 0, 1) === 1
? Curve.getPoint(v, t = roots[0])[dir ? 'y' : 'x'] ? roots[0]
: (a0 + a3) / 2, : 0.5,
a = t === 0 ? a0
: t === 1 ? a3
: Curve.getPoint(v, t)[dir ? 'y' : 'x'],
winding = o0 > o3 ? 1 : -1, winding = o0 > o3 ? 1 : -1,
windingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1, windingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1,
a3Prev = vPrev[ia + 6]; a3Prev = vPrev[ia + 6];
@ -435,7 +439,7 @@ PathItem.inject(new function() {
} }
} else if (winding !== windingPrev) { } else if (winding !== windingPrev) {
// Curve is crossed at starting point and winding changes from // Curve is crossed at starting point and winding changes from
// previous. Cancel winding contribution from previous curve. // previous curve. Cancel the winding from previous curve.
if (a3Prev < paR) { if (a3Prev < paR) {
pathWindingL += winding; pathWindingL += winding;
} }
@ -454,15 +458,14 @@ PathItem.inject(new function() {
pathWindingL += winding; pathWindingL += winding;
} }
} }
if (onPath && !dontFlip) { vPrev = v;
// If we're on the path, look at the tangent to determine if we // If we're on the path, look at the tangent to decide whether to
// should flip direction to determine a reliable winding number. // flip direction to determine a reliable winding number:
t = po === o0 ? 0 : po === o3 ? 1 : t; // If the tangent is parallel to the direction, call getWinding()
result = t !== null // again with flipped direction and return the result.
&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0 return onPath && !dontFlip
&& getWinding(point, curves, dir ? 0 : 1, true); && Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
} && getWinding(point, curves, dir ? 0 : 1, true);
return v;
} }
function handleCurve(v) { function handleCurve(v) {
@ -482,17 +485,22 @@ PathItem.inject(new function() {
// abscissa, it can be treated as a monotone curve: // abscissa, it can be treated as a monotone curve:
monoCurves = paL > max(a0, a1, a2, a3) || monoCurves = paL > max(a0, a1, a2, a3) ||
paR < min(a0, a1, a2, a3) paR < min(a0, a1, a2, a3)
? [v] : Curve.getMonoCurves(v, dir); ? [v] : Curve.getMonoCurves(v, dir),
for (var i = 0, l = monoCurves.length; !result && i < l; i++) { res;
vPrev = addWinding(monoCurves[i]); for (var i = 0, l = monoCurves.length; i < l; i++) {
// Calling addWinding() my lead to direction flipping, in
// which case we already have the result and can return it.
if (res = addWinding(monoCurves[i]))
return res;
} }
} }
} }
for (var i = 0, l = curves.length; !result && i < l; i++) { for (var i = 0, l = curves.length; i < l; i++) {
var curve = curves[i], var curve = curves[i],
path = curve._path, path = curve._path,
v = curve.getValues(); v = curve.getValues(),
res;
if (!i || curves[i - 1]._path !== path) { if (!i || curves[i - 1]._path !== path) {
// We're on a new (sub-)path, so we need to determine values of // We're on a new (sub-)path, so we need to determine values of
// the last non-horizontal curve on this path. // the last non-horizontal curve on this path.
@ -529,16 +537,17 @@ PathItem.inject(new function() {
} }
} }
handleCurve(v); // Calling handleCurve() my lead to direction flipping, in which
// case we already have the result and can return it.
if (res = handleCurve(v))
return res;
if (!result && (i + 1 === l || curves[i + 1]._path !== path)) { if (i + 1 === l || curves[i + 1]._path !== path) {
// We're at the last curve of the current (sub-)path. If a // We're at the last curve of the current (sub-)path. If a
// closing curve was calculated at the beginning of it, handle // closing curve was calculated at the beginning of it, handle
// it now to treat the path as closed: // it now to treat the path as closed:
if (vClose) { if (vClose && (res = handleCurve(vClose)))
handleCurve(vClose); return res;
vClose = null;
}
if (!pathWindingL && !pathWindingR && onPath) { 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
@ -558,27 +567,25 @@ PathItem.inject(new function() {
if (onPath) if (onPath)
onPathCount++; onPathCount++;
onPath = false; onPath = false;
vClose = null;
} }
} }
if (!result) { if (!windingL && !windingR) {
if (!windingL && !windingR) { windingL = windingR = onPathWinding;
windingL = windingR = onPathWinding;
}
windingL = windingL && (2 - abs(windingL) % 2);
windingR = windingR && (2 - abs(windingR) % 2);
// Return the calculated winding contribution and detect if we are
// on the contour of the area by comparing windingL and windingR.
// This is required when handling unite operations, where a winding
// number of 2 is not part of the result unless it's the contour:
result = {
winding: max(windingL, windingR),
windingL: windingL,
windingR: windingR,
onContour: !windingL ^ !windingR,
onPathCount: onPathCount
};
} }
return result; windingL = windingL && (2 - abs(windingL) % 2);
windingR = windingR && (2 - abs(windingR) % 2);
// Return the calculated winding contribution and detect if we are
// on the contour of the area by comparing windingL and windingR.
// This is required when handling unite operations, where a winding
// number of 2 is not part of the result unless it's the contour:
return {
winding: max(windingL, windingR),
windingL: windingL,
windingR: windingR,
onContour: !windingL ^ !windingR,
onPathCount: onPathCount
};
} }
function propagateWinding(segment, path1, path2, curves, operator) { function propagateWinding(segment, path1, path2, curves, operator) {