mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-05 20:32:00 -05:00
Implement and brush up @iconexperience's new version of getWinding()
Along with optimizations and simplifications to _getMonoCurves() Closes #890
This commit is contained in:
parent
9c1aa47162
commit
eba9601601
1 changed files with 44 additions and 111 deletions
|
@ -303,8 +303,6 @@ PathItem.inject(new function() {
|
||||||
*/
|
*/
|
||||||
function getWinding(point, curves, horizontal) {
|
function getWinding(point, curves, horizontal) {
|
||||||
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
||||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
|
||||||
tMax = 1 - tMin,
|
|
||||||
px = point.x,
|
px = point.x,
|
||||||
py = point.y,
|
py = point.y,
|
||||||
windLeft = 0,
|
windLeft = 0,
|
||||||
|
@ -350,103 +348,57 @@ PathItem.inject(new function() {
|
||||||
while (end < length) {
|
while (end < length) {
|
||||||
start = end;
|
start = end;
|
||||||
// The first curve of a loop holds information about its length
|
// The first curve of a loop holds information about its length
|
||||||
// and the first / last curve with non-zero winding.
|
// and the last curve with non-zero winding.
|
||||||
// Retrieve and use it here (See _getMonoCurve()).
|
// Retrieve and use it here (See _getMonoCurve()).
|
||||||
var curve = curves[start],
|
var curve = curves[start],
|
||||||
firstWinding = curve.firstWinding,
|
last = curve.last,
|
||||||
lastWinding = curve.lastWinding;
|
// Get the values of to the end x coordinate and winding of
|
||||||
|
// the last non-horizontal curve, which will be the previous
|
||||||
|
// non-horizontal curve for the first curve of the loop.
|
||||||
|
prevWinding = last.winding,
|
||||||
|
prevEnd = last.values[6];
|
||||||
end = start + curve.length;
|
end = start + curve.length;
|
||||||
// Walk through one single loop of curves.
|
|
||||||
var startCounted = false,
|
|
||||||
prevWinding, // The previous non-horizontal curve.
|
|
||||||
nextWinding, // The next non-horizontal curve (with winding)
|
|
||||||
prevT = null,
|
|
||||||
curve = null;
|
|
||||||
for (var i = start; i < end; i++) {
|
for (var i = start; i < end; i++) {
|
||||||
if (!curve) {
|
var curve = curves[i],
|
||||||
prevWinding = lastWinding;
|
winding = curve.winding,
|
||||||
nextWinding = firstWinding;
|
values = curve.values,
|
||||||
} else if (curve.winding) {
|
yStart = values[1],
|
||||||
prevWinding = curve;
|
yEnd = values[7];
|
||||||
}
|
|
||||||
curve = curves[i];
|
|
||||||
if (curve === nextWinding) {
|
|
||||||
// Each time we've reached the next curve with winding,
|
|
||||||
// search for the next one after.
|
|
||||||
nextWinding = curve.next;
|
|
||||||
while (nextWinding && !nextWinding.winding) {
|
|
||||||
nextWinding = nextWinding.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var values = curve.values,
|
|
||||||
winding = curve.winding;
|
|
||||||
// Since the curves are monotone in y direction, we can just
|
// Since the curves are monotone in y direction, we can just
|
||||||
// compare the endpoints of the curve to determine if the
|
// compare the endpoints of the curve to determine if the
|
||||||
// ray from query point along +-x direction will intersect
|
// ray from query point along +-x direction will intersect
|
||||||
// the monotone curve. Results in quite significant speedup.
|
// the monotone curve.
|
||||||
if (winding && (winding === 1
|
if (winding && (py >= yStart && py <= yEnd
|
||||||
&& py >= values[1] && py <= values[7]
|
|| py >= yEnd && py <= yStart)) {
|
||||||
|| py >= values[7] && py <= values[1])
|
if (py === yStart) {
|
||||||
&& Curve.solveCubic(values, 1, py, roots, 0, 1) === 1) {
|
var x = values[0];
|
||||||
var t = roots[0];
|
if (winding * prevWinding < 0) {
|
||||||
// Due to numerical precision issues, two consecutive
|
|
||||||
// curves may register an intercept twice, at t = 1 and
|
|
||||||
// 0, if y is almost equal to one of the endpoints of
|
|
||||||
// the curves. But since curves may contain more than
|
|
||||||
// one loop of curves and the end point on the last
|
|
||||||
// curve of a loop would not be registered as a double,
|
|
||||||
// we need to filter these cases:
|
|
||||||
if (!( // = the following conditions will be excluded:
|
|
||||||
// Detect and exclude intercepts at 'end' of loops
|
|
||||||
// if the start of the loop was already counted.
|
|
||||||
t > tMax && startCounted && curve === lastWinding
|
|
||||||
// Detect 2nd case of a consecutive intercept
|
|
||||||
|| t < tMin && prevT > tMax)) {
|
|
||||||
var x = Curve.getPoint(values, t).x,
|
|
||||||
counted = false;
|
|
||||||
// Take care of cases where the ray merely touches
|
|
||||||
// the connecting point between two neighboring mono
|
|
||||||
// curves, but does not cross either of them.
|
|
||||||
if (t < tMin && prevWinding
|
|
||||||
&& winding * prevWinding.winding < 0
|
|
||||||
|| t > tMax && nextWinding
|
|
||||||
&& winding * nextWinding.winding < 0) {
|
|
||||||
if (x > xBefore && x < xAfter) {
|
if (x > xBefore && x < xAfter) {
|
||||||
++windLeft;
|
++windLeft;
|
||||||
++windRight;
|
++windRight;
|
||||||
counted = true;
|
} else if (x < xBefore) {
|
||||||
|
windLeft += winding;
|
||||||
|
} else if (x > xAfter) {
|
||||||
|
windRight += winding;
|
||||||
}
|
}
|
||||||
} else if (x <= xBefore) {
|
} else if (x < xBefore && prevEnd > xBefore) {
|
||||||
windLeft += winding;
|
windLeft += winding;
|
||||||
counted = true;
|
} else if (x > xAfter && prevEnd < xAfter) {
|
||||||
} else if (x >= xAfter) {
|
|
||||||
windRight += winding;
|
windRight += winding;
|
||||||
counted = true;
|
|
||||||
}
|
}
|
||||||
// Mark the start of the path as counted.
|
} else if (Curve.solveCubic(values, 1, py, roots, 0, 1)
|
||||||
if (curve === firstWinding) {
|
=== 1) {
|
||||||
startCounted = t < tMin && counted;
|
var x = Curve.getPoint(values, roots[0]).x;
|
||||||
|
if (x < xBefore) {
|
||||||
|
windLeft += winding;
|
||||||
|
} else if (x > xAfter) {
|
||||||
|
windRight += winding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prevT = t;
|
// Update previous winding and end coordinate whenever
|
||||||
} else if (!winding) {
|
// we encountered a non-horizontal curve.
|
||||||
// If the point is on a horizontal curve and winding
|
prevWinding = winding;
|
||||||
// changes between before and after the curve, we treat
|
prevEnd = values[6];
|
||||||
// this as a 'touch point'.
|
|
||||||
if (prevWinding && nextWinding
|
|
||||||
&& abs(py - values[1]) < epsilon
|
|
||||||
&& (values[0] < xAfter && values[6] > xBefore
|
|
||||||
|| values[6] < xAfter && values[0] > xBefore)
|
|
||||||
&& prevWinding.winding * nextWinding.winding < 0) {
|
|
||||||
++windLeft;
|
|
||||||
++windRight;
|
|
||||||
}
|
|
||||||
// We keep the value for prevT to avoid double counting
|
|
||||||
// of intersections at the end of a curve and the start
|
|
||||||
// of the next curve, even if any number of horizontal
|
|
||||||
// curves is between both curves.
|
|
||||||
} else {
|
|
||||||
prevT = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -929,9 +881,7 @@ Path.inject(/** @lends Path# */{
|
||||||
*/
|
*/
|
||||||
_getMonoCurves: function() {
|
_getMonoCurves: function() {
|
||||||
var monoCurves = this._monoCurves,
|
var monoCurves = this._monoCurves,
|
||||||
prevCurve,
|
last;
|
||||||
firstWinding = null,
|
|
||||||
lastWinding;
|
|
||||||
|
|
||||||
// Insert curve values into a cached array
|
// Insert curve values into a cached array
|
||||||
function insertCurve(v) {
|
function insertCurve(v) {
|
||||||
|
@ -945,23 +895,11 @@ Path.inject(/** @lends Path# */{
|
||||||
: y0 > y1
|
: y0 > y1
|
||||||
? -1 // Decreasing
|
? -1 // Decreasing
|
||||||
: 1, // Increasing
|
: 1, // Increasing
|
||||||
curve = {
|
curve = { values: v, winding: winding };
|
||||||
values: v,
|
|
||||||
winding: winding,
|
|
||||||
// Add a reference to neighboring curves.
|
|
||||||
previous: prevCurve,
|
|
||||||
next: null // Always set it for hidden class optimization.
|
|
||||||
};
|
|
||||||
if (prevCurve)
|
|
||||||
prevCurve.next = curve;
|
|
||||||
monoCurves.push(curve);
|
monoCurves.push(curve);
|
||||||
prevCurve = curve;
|
// Keep track of the last non-horizontal curve (with winding).
|
||||||
// Keep track of the first and last curves with winding numbers.
|
if (winding)
|
||||||
if (winding) {
|
last = curve;
|
||||||
if (!firstWinding)
|
|
||||||
firstWinding = curve;
|
|
||||||
lastWinding = curve;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle bezier curves. We need to chop them into smaller curves with
|
// Handle bezier curves. We need to chop them into smaller curves with
|
||||||
|
@ -1028,16 +966,11 @@ Path.inject(/** @lends Path# */{
|
||||||
handleCurve([p1x, p1y, p1x, p1y, p2x, p2y, p2x, p2y]);
|
handleCurve([p1x, p1y, p1x, p1y, p2x, p2y, p2x, p2y]);
|
||||||
}
|
}
|
||||||
if (monoCurves.length > 0) {
|
if (monoCurves.length > 0) {
|
||||||
// Link first and last curves
|
// Add information about the loop length and the last curve with
|
||||||
var first = monoCurves[0],
|
// non-zero winding, as required in getWinding().
|
||||||
last = monoCurves[monoCurves.length - 1];
|
var first = monoCurves[0];
|
||||||
first.previous = last;
|
|
||||||
last.next = first;
|
|
||||||
// Add information about the loop length and the first / last
|
|
||||||
// curve with non-zero winding (Used in getWinding()).
|
|
||||||
first.length = monoCurves.length;
|
first.length = monoCurves.length;
|
||||||
first.firstWinding = firstWinding;
|
first.last = last;
|
||||||
first.lastWinding = lastWinding;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return monoCurves;
|
return monoCurves;
|
||||||
|
|
Loading…
Reference in a new issue