Use quality factor for better winding calculation and propagation

This commit is contained in:
iconexperience 2017-01-05 14:56:36 +01:00
parent ed38634a80
commit 3c2588fdec
2 changed files with 94 additions and 97 deletions

View file

@ -412,11 +412,8 @@ PathItem.inject(new function() {
paR = pa + epsilon, paR = pa + epsilon,
windingL = 0, windingL = 0,
windingR = 0, windingR = 0,
pathWindingL = 0,
pathWindingR = 0,
onPath = false, onPath = false,
onPathWinding = 0, quality = 1,
onPathCount = 0,
roots = [], roots = [],
vPrev, vPrev,
vClose; 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; onPath = true;
} }
// If curve does not change in ordinate direction, windings will // If curve does not change in ordinate direction, windings will
@ -461,25 +458,22 @@ PathItem.inject(new function() {
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];
if (a >= paL && a <= paR) onPath = true;
if (po !== o0) { if (po !== o0) {
// Standard case, curve is not crossed at its starting point. // Standard case, curve is not crossed at its starting point.
if (a < paL) { if (a < paL) {
pathWindingL += winding; windingL += winding;
} else if (a > paR) { } else if (a > paR) {
pathWindingR += winding; windingR += winding;
} else {
onPath = true;
pathWindingL += winding;
pathWindingR += winding;
} }
} 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 curve. Cancel the winding from previous curve. // previous curve. Cancel the winding from previous curve.
if (a3Prev < paR) { if (a3Prev < paR) {
pathWindingL += winding; windingL += winding;
} }
if (a3Prev > paL) { if (a3Prev > paL) {
pathWindingR += winding; windingR += winding;
} }
} 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-
@ -487,12 +481,26 @@ PathItem.inject(new function() {
onPath = 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; windingR += winding;
} else if (a3Prev > paR) { } else if (a3Prev > paR) {
// right winding was added before, not add left winding. // right winding was added before, now add left winding.
pathWindingL += 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; vPrev = v;
// If we're on the curve, look at the tangent to decide whether to // If we're on the curve, look at the tangent to decide whether to
// flip direction to better determine a reliable winding number: // flip direction to better determine a reliable winding number:
@ -586,31 +594,10 @@ PathItem.inject(new function() {
// it now to treat the path as closed: // it now to treat the path as closed:
if (vClose && (res = handleCurve(vClose))) if (vClose && (res = handleCurve(vClose)))
return res; 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; vClose = null;
} }
} }
if (!windingL && !windingR) { // If the winding one
windingL = windingR = onPathWinding;
}
windingL = windingL && (2 - abs(windingL) % 2); windingL = windingL && (2 - abs(windingL) % 2);
windingR = windingR && (2 - abs(windingR) % 2); windingR = windingR && (2 - abs(windingR) % 2);
// Return the calculated winding contribution and detect if we are // Return the calculated winding contribution and detect if we are
@ -622,7 +609,8 @@ PathItem.inject(new function() {
windingL: windingL, windingL: windingL,
windingR: windingR, windingR: windingR,
onContour: !windingL ^ !windingR, onContour: !windingL ^ !windingR,
onPathCount: onPathCount onPath: onPath,
quality: quality
}; };
} }
@ -642,8 +630,12 @@ PathItem.inject(new function() {
totalLength += length; totalLength += length;
segment = segment.getNext(); segment = segment.getNext();
} while (segment && !segment._intersection && segment !== start); } while (segment && !segment._intersection && segment !== start);
// Sample the point at a middle of the chain to get its winding: // Determine winding at three points in the chain. If a winding with
var length = totalLength / 2; // 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++) { for (var j = 0, l = chain.length; j < l; j++) {
var entry = chain[j], var entry = chain[j],
curveLength = entry.length; curveLength = entry.length;
@ -664,17 +656,22 @@ PathItem.inject(new function() {
// While subtracting, we need to omit this curve if it is // While subtracting, we need to omit this curve if it is
// 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 && ( var windingNew = !(operator.subtract && path2 && (
path === path1 && path === path1 &&
path2._getWinding(pt, dir, true).winding || path2._getWinding(pt, dir, true).winding ||
path === path2 && path === path2 &&
!path1._getWinding(pt, dir, true).winding)) !path1._getWinding(pt, dir, true).winding))
? getWinding(pt, curves, dir, true) ? getWinding(pt, curves, dir, true)
: { winding: 0 }; : {winding: 0};
if (windingNew.winding != 0 &&
(!winding || winding.quality < windingNew.quality)) {
winding = windingNew;
}
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; chain[j].segment._winding = winding;

View file

@ -256,7 +256,7 @@ var PathItem = Item.extend(/** @lends PathItem# */{
_contains: function(point) { _contains: function(point) {
// NOTE: point is reverse transformed by _matrix, so we don't need to // NOTE: point is reverse transformed by _matrix, so we don't need to
// apply the matrix here. // apply the matrix here.
/*#*/ if (__options.nativeContains || !__options.booleanOperations) { /*#*/ if (__options.nativeContains || !__options.booleanOperations) {
// To compare with native canvas approach: // To compare with native canvas approach:
var ctx = CanvasProvider.getContext(1, 1); var ctx = CanvasProvider.getContext(1, 1);
// Use dontFinish to tell _draw to only produce geometries for hit-test. // Use dontFinish to tell _draw to only produce geometries for hit-test.
@ -264,7 +264,7 @@ var PathItem = Item.extend(/** @lends PathItem# */{
var res = ctx.isPointInPath(point.x, point.y, this.getFillRule()); var res = ctx.isPointInPath(point.x, point.y, this.getFillRule());
CanvasProvider.release(ctx); CanvasProvider.release(ctx);
return res; return res;
/*#*/ } else { // !__options.nativeContains && __options.booleanOperations /*#*/ } else { // !__options.nativeContains && __options.booleanOperations
// Check the transformed point against the untransformed (internal) // Check the transformed point against the untransformed (internal)
// handle bounds, which is the fastest rough bounding box to calculate // handle bounds, which is the fastest rough bounding box to calculate
// for a quick check before calculating the actual winding. // for a quick check before calculating the actual winding.
@ -272,10 +272,10 @@ var PathItem = Item.extend(/** @lends PathItem# */{
this.getBounds({ internal: true, handle: true })) this.getBounds({ internal: true, handle: true }))
? this._getWinding(point) ? this._getWinding(point)
: {}; : {};
return !!(this.getFillRule() === 'evenodd' return winding.onPath || !!(this.getFillRule() === 'evenodd'
? winding.windingL & 1 || winding.windingR & 1 ? winding.windingL & 1 || winding.windingR & 1
: winding.winding); : winding.winding);
/*#*/ } // !__options.nativeContains && __options.booleanOperations /*#*/ } // !__options.nativeContains && __options.booleanOperations
}, },
/** /**