diff --git a/src/path/PathItem.Boolean.js b/src/path/PathItem.Boolean.js index c3b65bb4..3160105a 100644 --- a/src/path/PathItem.Boolean.js +++ b/src/path/PathItem.Boolean.js @@ -401,8 +401,8 @@ PathItem.inject(new function() { */ function getWinding(point, curves, dir, closed, dontFlip) { var epsilon = /*#=*/Numerical.WINDING_EPSILON, - // Determine the index of the abscissa and ordinate values in the - // curve values arrays, based on the direction: + // Determine the index of the abscissa and ordinate values in the + // curve values arrays, based on the direction: ia = dir ? 1 : 0, // the abscissa index io = dir ? 0 : 1, // the ordinate index pv = [point.x, point.y], @@ -412,11 +412,8 @@ PathItem.inject(new function() { paR = pa + epsilon, windingL = 0, windingR = 0, - pathWindingL = 0, - pathWindingR = 0, onPath = false, - onPathWinding = 0, - onPathCount = 0, + quality = 1, roots = [], vPrev, vClose; @@ -440,7 +437,7 @@ PathItem.inject(new function() { // +-----+ // +----+ | // +-----+ - if (a1 < paR && a3 > paL || a3 < paR && a1 > paL) { + if (a0 < paR && a3 > paL || a3 < paR && a0 > paL) { onPath = true; } // If curve does not change in ordinate direction, windings will @@ -453,33 +450,30 @@ PathItem.inject(new function() { : paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3) ? 0.5 : Curve.solveCubic(v, io, po, roots, 0, 1) === 1 - ? roots[0] - : 0.5, + ? roots[0] + : 0.5, a = t === 0 ? a0 : t === 1 ? a3 : Curve.getPoint(v, t)[dir ? 'y' : 'x'], winding = o0 > o3 ? 1 : -1, windingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1, a3Prev = vPrev[ia + 6]; + if (a >= paL && a <= paR) onPath = true; if (po !== o0) { // Standard case, curve is not crossed at its starting point. if (a < paL) { - pathWindingL += winding; + windingL += winding; } else if (a > paR) { - pathWindingR += winding; - } else { - onPath = true; - pathWindingL += winding; - pathWindingR += winding; + windingR += winding; } } else if (winding !== windingPrev) { // Curve is crossed at starting point and winding changes from // previous curve. Cancel the winding from previous curve. if (a3Prev < paR) { - pathWindingL += winding; + windingL += winding; } if (a3Prev > paL) { - pathWindingR += winding; + windingR += winding; } } else if (a3Prev < paL && a > paL || a3Prev > paR && a < paR) { // Point is on a horizontal curve between the previous non- @@ -487,20 +481,34 @@ PathItem.inject(new function() { onPath = true; if (a3Prev < paL) { // left winding was added before, now add right winding. - pathWindingR += winding; + windingR += winding; } else if (a3Prev > paR) { - // right winding was added before, not add left winding. - pathWindingL += winding; + // right winding was added before, now add left winding. + windingL += winding; } } + // Determine the quality of the winding calculation. Currently the + // quality is reduced with every crossing of the ray very close + // to the path. This means that if the point is on or near multiple + // curves, the quality becomes less than 0.5 + //ToDo Set quality depending on distance + if (po !== o0) { + if (a > pa - 100 * epsilon && a < pa + 100 * epsilon) { + //quality *= Math.min(1, (100 * epsilon * Math.abs(a - pa) + 0.5)); + quality /= 2; + } + } else { + //ToDo: + quality = 0; + } vPrev = v; // If we're on the curve, look at the tangent to decide whether to // flip direction to better determine a reliable winding number: // If the tangent is parallel to the direction, call getWinding() // again with flipped direction and return that result instead. return !dontFlip && a > paL && a < paR - && Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0 - && getWinding(point, curves, dir ? 0 : 1, closed, true); + && Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0 + && getWinding(point, curves, dir ? 0 : 1, closed, true); } function handleCurve(v) { @@ -516,11 +524,11 @@ PathItem.inject(new function() { a1 = v[ia + 2], a2 = v[ia + 4], a3 = v[ia + 6], - // Get monotone curves. If the curve is outside the point's - // abscissa, it can be treated as a monotone curve: + // Get monotone curves. If the curve is outside the point's + // abscissa, it can be treated as a monotone curve: monoCurves = paL > max(a0, a1, a2, a3) || - paR < min(a0, a1, a2, a3) - ? [v] : Curve.getMonoCurves(v, dir), + paR < min(a0, a1, a2, a3) + ? [v] : Curve.getMonoCurves(v, dir), res; for (var i = 0, l = monoCurves.length; i < l; i++) { // Calling addWinding() my lead to direction flipping, in @@ -548,9 +556,9 @@ PathItem.inject(new function() { // into account, just like how closed paths behave. if (!path._closed) { vClose = Curve.getValues( - path.getLastCurve().getSegment2(), - curve.getSegment1(), - null, !closed); + path.getLastCurve().getSegment2(), + curve.getSegment1(), + null, !closed); // This closing curve is a potential candidate for the last // non-horizontal curve. if (vClose[io] !== vClose[io + 6]) { @@ -586,31 +594,10 @@ PathItem.inject(new function() { // it now to treat the path as closed: if (vClose && (res = handleCurve(vClose))) return res; - if (onPath && !pathWindingL && !pathWindingR) { - // If the point is on the path and the windings canceled - // each other, we treat the point as if it was inside the - // path. A point inside a path has a winding of [+1,-1] - // for clockwise and [-1,+1] for counter-clockwise paths. - // If the ray is cast in y direction (dir == 1), the - // windings always have opposite sign. - var add = path.isClockwise(closed) ^ dir ? 1 : -1; - windingL += add; - windingR -= add; - onPathWinding += add; - } else { - windingL += pathWindingL; - windingR += pathWindingR; - pathWindingL = pathWindingR = 0; - } - if (onPath) - onPathCount++; - onPath = false; vClose = null; } } - if (!windingL && !windingR) { - windingL = windingR = onPathWinding; - } + // If the winding one windingL = windingL && (2 - abs(windingL) % 2); windingR = windingR && (2 - abs(windingR) % 2); // Return the calculated winding contribution and detect if we are @@ -622,7 +609,8 @@ PathItem.inject(new function() { windingL: windingL, windingR: windingR, onContour: !windingL ^ !windingR, - onPathCount: onPathCount + onPath: onPath, + quality: quality }; } @@ -642,38 +630,47 @@ PathItem.inject(new function() { totalLength += length; segment = segment.getNext(); } while (segment && !segment._intersection && segment !== start); - // Sample the point at a middle of the chain to get its winding: - var length = totalLength / 2; - for (var j = 0, l = chain.length; j < l; j++) { - var entry = chain[j], - curveLength = entry.length; - if (length <= curveLength) { - var curve = entry.curve, - path = curve._path, - parent = path._parent, - t = curve.getTimeAt(length), - pt = curve.getPointAtTime(t), + // Determine winding at three points in the chain. If a winding with + // sufficient quality is found, use it. Otherwise use the winding with + // the best quality. + var offsets = [0.48, 0.1, 0.9]; + for (var i = 0; (!winding || winding.quality < 0.5) && i < offsets.length; i++) { + var length = totalLength * offsets[i]; + for (var j = 0, l = chain.length; j < l; j++) { + var entry = chain[j], + curveLength = entry.length; + if (length <= curveLength) { + var curve = entry.curve, + path = curve._path, + parent = path._parent, + t = curve.getTimeAt(length), + pt = curve.getPointAtTime(t), // Determine the direction in which to check the winding // from the point (horizontal or vertical), based on the // curve's direction at that point. If the tangent is less // than 45°, cast the ray vertically, else horizontally. - dir = abs(curve.getTangentAtTime(t).normalize().y) - < Math.SQRT1_2 ? 1 : 0; - if (parent instanceof CompoundPath) - path = parent; - // While subtracting, we need to omit this curve if it is - // contributing to the second operand and is outside the - // first operand. - winding = !(operator.subtract && path2 && ( - path === path1 && - path2._getWinding(pt, dir, true).winding || - path === path2 && - !path1._getWinding(pt, dir, true).winding)) + dir = abs(curve.getTangentAtTime(t).normalize().y) + < Math.SQRT1_2 ? 1 : 0; + if (parent instanceof CompoundPath) + path = parent; + // While subtracting, we need to omit this curve if it is + // contributing to the second operand and is outside the + // first operand. + var windingNew = !(operator.subtract && path2 && ( + path === path1 && + path2._getWinding(pt, dir, true).winding || + path === path2 && + !path1._getWinding(pt, dir, true).winding)) ? getWinding(pt, curves, dir, true) - : { winding: 0 }; - break; + : {winding: 0}; + if (windingNew.winding != 0 && + (!winding || winding.quality < windingNew.quality)) { + winding = windingNew; + } + break; + } + length -= curveLength; } - length -= curveLength; } // Now assign the winding to the entire curve chain. for (var j = chain.length - 1; j >= 0; j--) { diff --git a/src/path/PathItem.js b/src/path/PathItem.js index 28936d21..58c0cf80 100644 --- a/src/path/PathItem.js +++ b/src/path/PathItem.js @@ -256,26 +256,26 @@ var PathItem = Item.extend(/** @lends PathItem# */{ _contains: function(point) { // NOTE: point is reverse transformed by _matrix, so we don't need to // apply the matrix here. -/*#*/ if (__options.nativeContains || !__options.booleanOperations) { - // To compare with native canvas approach: - var ctx = CanvasProvider.getContext(1, 1); - // Use dontFinish to tell _draw to only produce geometries for hit-test. - this._draw(ctx, new Base({ dontFinish: true })); - var res = ctx.isPointInPath(point.x, point.y, this.getFillRule()); - CanvasProvider.release(ctx); - return res; -/*#*/ } else { // !__options.nativeContains && __options.booleanOperations - // Check the transformed point against the untransformed (internal) - // handle bounds, which is the fastest rough bounding box to calculate - // for a quick check before calculating the actual winding. - var winding = point.isInside( + /*#*/ if (__options.nativeContains || !__options.booleanOperations) { + // To compare with native canvas approach: + var ctx = CanvasProvider.getContext(1, 1); + // Use dontFinish to tell _draw to only produce geometries for hit-test. + this._draw(ctx, new Base({ dontFinish: true })); + var res = ctx.isPointInPath(point.x, point.y, this.getFillRule()); + CanvasProvider.release(ctx); + return res; + /*#*/ } else { // !__options.nativeContains && __options.booleanOperations + // Check the transformed point against the untransformed (internal) + // handle bounds, which is the fastest rough bounding box to calculate + // for a quick check before calculating the actual winding. + var winding = point.isInside( this.getBounds({ internal: true, handle: true })) - ? this._getWinding(point) - : {}; - return !!(this.getFillRule() === 'evenodd' - ? winding.windingL & 1 || winding.windingR & 1 - : winding.winding); -/*#*/ } // !__options.nativeContains && __options.booleanOperations + ? this._getWinding(point) + : {}; + return winding.onPath || !!(this.getFillRule() === 'evenodd' + ? winding.windingL & 1 || winding.windingR & 1 + : winding.winding); + /*#*/ } // !__options.nativeContains && __options.booleanOperations }, /**