mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Use quality factor for better winding calculation and propagation
This commit is contained in:
parent
ed38634a80
commit
3c2588fdec
2 changed files with 94 additions and 97 deletions
|
@ -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--) {
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue