mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-29 09:22:22 -05:00
Introduce CURVETIME_EPSILON, to be used when handling curve time parameters.
Relates to #777
This commit is contained in:
parent
4f04dae20f
commit
d62caf6faa
6 changed files with 55 additions and 51 deletions
|
@ -442,10 +442,11 @@ var Curve = Base.extend(/** @lends Curve# */{
|
|||
// TODO: Rename to divideAt()?
|
||||
divide: function(offset, isParameter, ignoreStraight) {
|
||||
var parameter = this._getParameter(offset, isParameter),
|
||||
tolerance = /*#=*/Numerical.TOLERANCE,
|
||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin,
|
||||
res = null;
|
||||
// Only divide if not at the beginning or end.
|
||||
if (parameter >= tolerance && parameter <= 1 - tolerance) {
|
||||
if (parameter >= tMin && parameter <= tMax) {
|
||||
var parts = Curve.subdivide(this.getValues(), parameter),
|
||||
setHandles = ignoreStraight || this.hasHandles(),
|
||||
left = parts[0],
|
||||
|
@ -618,8 +619,8 @@ statics: {
|
|||
} else if (sy === -1) {
|
||||
ty = tx;
|
||||
}
|
||||
// Use average if we're within tolerance
|
||||
if (abs(tx - ty) < /*#=*/Numerical.TOLERANCE)
|
||||
// Use average if we're within epsilon
|
||||
if (abs(tx - ty) < /*#=*/Numerical.CURVETIME_EPSILON)
|
||||
return (tx + ty) * 0.5;
|
||||
}
|
||||
}
|
||||
|
@ -726,7 +727,7 @@ statics: {
|
|||
// Add some tolerance for good roots, as t = 0, 1 are added
|
||||
// separately anyhow, and we don't want joins to be added with radii
|
||||
// in getStrokeBounds()
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin;
|
||||
// Only add strokeWidth to bounds for points which lie within 0 < t < 1
|
||||
// The corner cases for cap and join are handled in getStrokeBounds()
|
||||
|
@ -995,7 +996,7 @@ statics: {
|
|||
|
||||
// Now iteratively refine solution until we reach desired precision.
|
||||
var step = 1 / (count * 2);
|
||||
while (step > /*#=*/Numerical.TOLERANCE) {
|
||||
while (step > /*#=*/Numerical.CURVETIME_EPSILON) {
|
||||
if (!refine(minT - step) && !refine(minT + step))
|
||||
step /= 2;
|
||||
}
|
||||
|
@ -1155,12 +1156,13 @@ new function() { // Scope for methods that require private functions
|
|||
c1x = v[2], c1y = v[3],
|
||||
c2x = v[4], c2y = v[5],
|
||||
p2x = v[6], p2y = v[7],
|
||||
tolerance = /*#=*/Numerical.TOLERANCE,
|
||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin,
|
||||
x, y;
|
||||
|
||||
// Handle special case at beginning / end of curve
|
||||
if (type === 0 && (t < tolerance || t > 1 - tolerance)) {
|
||||
var isZero = t < tolerance;
|
||||
if (type === 0 && (t < tMin || t > tMax)) {
|
||||
var isZero = t < tMin;
|
||||
x = isZero ? p1x : p2x;
|
||||
y = isZero ? p1y : p2y;
|
||||
} else {
|
||||
|
@ -1184,10 +1186,10 @@ new function() { // Scope for methods that require private functions
|
|||
// the x and y coordinates:
|
||||
// Prevent tangents and normals of length 0:
|
||||
// http://stackoverflow.com/questions/10506868/
|
||||
if (t < tolerance) {
|
||||
if (t < tMin) {
|
||||
x = cx;
|
||||
y = cy;
|
||||
} else if (t > 1 - tolerance) {
|
||||
} else if (t > tMax) {
|
||||
x = 3 * (p2x - c2x);
|
||||
y = 3 * (p2y - c2y);
|
||||
} else {
|
||||
|
@ -1198,8 +1200,7 @@ new function() { // Scope for methods that require private functions
|
|||
// When the tangent at t is zero and we're at the beginning
|
||||
// or the end, we can use the vector between the handles,
|
||||
// but only when normalizing as its weighted length is 0.
|
||||
if (x === 0 && y === 0
|
||||
&& (t < tolerance || t > 1 - tolerance)) {
|
||||
if (x === 0 && y === 0 && (t < tMin || t > tMax)) {
|
||||
x = c2x - c1x;
|
||||
y = c2y - c1y;
|
||||
}
|
||||
|
@ -1250,8 +1251,7 @@ new function() { // Scope for methods that require private functions
|
|||
return start;
|
||||
// See if we're going forward or backward, and handle cases
|
||||
// differently
|
||||
var tolerance = /*#=*/Numerical.TOLERANCE,
|
||||
abs = Math.abs,
|
||||
var abs = Math.abs,
|
||||
forward = offset > 0,
|
||||
a = forward ? start : 0,
|
||||
b = forward ? 1 : start,
|
||||
|
@ -1261,7 +1261,7 @@ new function() { // Scope for methods that require private functions
|
|||
// Get length of total range
|
||||
rangeLength = Numerical.integrate(ds, a, b,
|
||||
getIterations(a, b));
|
||||
if (abs(offset - rangeLength) < tolerance) {
|
||||
if (abs(offset - rangeLength) < /*#=*/Numerical.GEOMETRIC_EPSILON) {
|
||||
// Matched the end:
|
||||
return forward ? b : a;
|
||||
} else if (abs(offset) > rangeLength) {
|
||||
|
@ -1286,7 +1286,7 @@ new function() { // Scope for methods that require private functions
|
|||
// Start with out initial guess for x.
|
||||
// NOTE: guess is a negative value when not looking forward.
|
||||
return Numerical.findRoot(f, ds, start + guess, a, b, 16,
|
||||
tolerance);
|
||||
/*#=*/Numerical.CURVETIME_EPSILON);
|
||||
},
|
||||
|
||||
getPoint: function(v, t) {
|
||||
|
@ -1319,7 +1319,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
|||
function addLocation(locations, param, v1, c1, t1, p1, v2, c2, t2, p2,
|
||||
overlap) {
|
||||
var loc = null,
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin;
|
||||
if (t1 == null)
|
||||
t1 = Curve.getParameterOf(v1, p1.x, p1.y);
|
||||
|
@ -1353,7 +1353,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
|||
return;
|
||||
// Let P be the first curve and Q be the second
|
||||
var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7],
|
||||
tolerance = /*#=*/Numerical.TOLERANCE,
|
||||
epsilon = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
getSignedDistance = Line.getSignedDistance,
|
||||
// Calculate the fat-line L for Q is the baseline l and two
|
||||
// offsets which completely encloses the curve P.
|
||||
|
@ -1371,7 +1371,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
|||
dp3 = getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]),
|
||||
tMinNew, tMaxNew,
|
||||
tDiff;
|
||||
if (q0x === q3x && uMax - uMin < tolerance && recursion >= 3) {
|
||||
if (q0x === q3x && uMax - uMin < epsilon && recursion >= 3) {
|
||||
// The fat-line of Q has converged to a point, the clipping is not
|
||||
// reliable. Return the value we have even though we will miss the
|
||||
// precision.
|
||||
|
@ -1419,7 +1419,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
|||
parts[1], v1, c2, c1, locations, param,
|
||||
t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
|
||||
}
|
||||
} else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) {
|
||||
} else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < epsilon) {
|
||||
// We have isolated the intersection with sufficient precision
|
||||
var t1 = tMinNew + (tMaxNew - tMinNew) / 2,
|
||||
t2 = uMin + (uMax - uMin) / 2;
|
||||
|
@ -1602,8 +1602,8 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
|||
*/
|
||||
function addOverlap(v1, v2, c1, c2, locations, param) {
|
||||
var abs = Math.abs,
|
||||
tolerance = /*#=*/Numerical.TOLERANCE,
|
||||
epsilon = /*#=*/Numerical.EPSILON,
|
||||
timeEpsilon = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
geomEpsilon = /*#=*/Numerical.GEOMETRIC_EPSILON,
|
||||
straight1 = Curve.isStraight(v1),
|
||||
straight2 = Curve.isStraight(v2),
|
||||
straight = straight1 && straight2;
|
||||
|
@ -1614,7 +1614,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
|||
var line1 = new Line(v1[0], v1[1], v1[6], v1[7]),
|
||||
line2 = new Line(v2[0], v2[1], v2[6], v2[7]);
|
||||
if (!line1.isCollinear(line2) || line1.getDistance(line2.getPoint())
|
||||
> /*#=*/Numerical.GEOMETRIC_EPSILON)
|
||||
> geomEpsilon)
|
||||
return false;
|
||||
} else if (straight1 ^ straight2) {
|
||||
// If one curve is straight, the other curve must be straight, too,
|
||||
|
@ -1636,8 +1636,8 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
|||
if (pairs.length === 1 && pair[0] < pairs[0][0]) {
|
||||
pairs.unshift(pair);
|
||||
} else if (pairs.length === 0
|
||||
|| abs(pair[0] - pairs[0][0]) > tolerance
|
||||
|| abs(pair[1] - pairs[0][1]) > tolerance) {
|
||||
|| abs(pair[0] - pairs[0][0]) > timeEpsilon
|
||||
|| abs(pair[1] - pairs[0][1]) > timeEpsilon) {
|
||||
pairs.push(pair);
|
||||
}
|
||||
}
|
||||
|
@ -1660,10 +1660,10 @@ new function() { // Scope for intersection using bezier fat-line clipping
|
|||
// We could do another check for curve identity here if we find a
|
||||
// better criteria.
|
||||
if (straight ||
|
||||
abs(p2[2] - p1[2]) < epsilon &&
|
||||
abs(p2[3] - p1[3]) < epsilon &&
|
||||
abs(p2[4] - p1[4]) < epsilon &&
|
||||
abs(p2[5] - p1[5]) < epsilon) {
|
||||
abs(p2[2] - p1[2]) < geomEpsilon &&
|
||||
abs(p2[3] - p1[3]) < geomEpsilon &&
|
||||
abs(p2[4] - p1[4]) < geomEpsilon &&
|
||||
abs(p2[5] - p1[5]) < geomEpsilon) {
|
||||
// Overlapping parts are identical
|
||||
addLocation(locations, param, v1, c1, pairs[0][0], null,
|
||||
v2, c2, pairs[0][1], null, true),
|
||||
|
|
|
@ -45,7 +45,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
_distance, _overlap, _intersection) {
|
||||
// Merge intersections very close to the end of a curve to the
|
||||
// beginning of the next curve.
|
||||
if (parameter >= 1 - /*#=*/Numerical.TOLERANCE) {
|
||||
if (parameter >= 1 - /*#=*/Numerical.CURVETIME_EPSILON) {
|
||||
var next = curve.getNext();
|
||||
if (next) {
|
||||
parameter = 0;
|
||||
|
@ -286,7 +286,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
// Use the same tolerance for curve time parameter
|
||||
// comparisons as in Curve.js
|
||||
&& Math.abs(this.getParameter() - loc.getParameter())
|
||||
< /*#=*/Numerical.TOLERANCE
|
||||
< /*#=*/Numerical.CURVETIME_EPSILON
|
||||
&& (_ignoreIntersection
|
||||
|| (!this._intersection && !loc._intersection
|
||||
|| this._intersection && this._intersection.equals(
|
||||
|
@ -316,7 +316,6 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
|
||||
statics: {
|
||||
sort: function(locations) {
|
||||
var tolerance = /*#=*/Numerical.TOLERANCE;
|
||||
locations.sort(function compare(l1, l2) {
|
||||
var curve1 = l1._curve,
|
||||
curve2 = l2._curve,
|
||||
|
@ -331,7 +330,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
if (path1 === path2) {
|
||||
if (curve1 === curve2) {
|
||||
var diff = l1._parameter - l2._parameter;
|
||||
if (Math.abs(diff) < tolerance) {
|
||||
if (Math.abs(diff) < /*#=*/Numerical.CURVETIME_EPSILON){
|
||||
var i1 = l1._intersection,
|
||||
i2 = l2._intersection,
|
||||
curve21 = i1 && i1._curve,
|
||||
|
|
|
@ -1182,7 +1182,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
index = arg.index;
|
||||
parameter = arg.parameter;
|
||||
}
|
||||
var tMin = /*#=*/Numerical.TOLERANCE,
|
||||
var tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin;
|
||||
if (parameter >= tMax) {
|
||||
// t == 1 is the same as t == 0 and index ++
|
||||
|
|
|
@ -100,7 +100,7 @@ PathItem.inject(new function() {
|
|||
segments = [],
|
||||
// Aggregate of all curves in both operands, monotonic in y
|
||||
monoCurves = [],
|
||||
tolerance = /*#=*/Numerical.TOLERANCE;
|
||||
epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON;
|
||||
|
||||
function collect(paths) {
|
||||
for (var i = 0, l = paths.length; i < l; i++) {
|
||||
|
@ -154,8 +154,7 @@ PathItem.inject(new function() {
|
|||
if (length <= curveLength) {
|
||||
// If the selected location on the curve falls onto its
|
||||
// beginning or end, use the curve's center instead.
|
||||
if (length < tolerance
|
||||
|| curveLength - length < tolerance)
|
||||
if (length < epsilon || curveLength - length < epsilon)
|
||||
length = curveLength / 2;
|
||||
var curve = node.segment.getCurve(),
|
||||
pt = curve.getPointAt(length),
|
||||
|
@ -227,15 +226,20 @@ PathItem.inject(new function() {
|
|||
if (false) {
|
||||
console.log('Intersections', intersections.length);
|
||||
intersections.forEach(function(inter) {
|
||||
if (inter._other)
|
||||
return;
|
||||
var other = inter._intersection;
|
||||
var log = ['CurveLocation', inter._id, 'p', inter.getPath()._id,
|
||||
'i', inter.getIndex(), 't', inter._parameter,
|
||||
'o', !!inter._overlap];
|
||||
if (inter._other) {
|
||||
inter = inter._intersection;
|
||||
log.push('Other', inter._id, 'p', inter.getPath()._id,
|
||||
'i', inter.getIndex(), 't', inter._parameter,
|
||||
'o', !!inter._overlap);
|
||||
}
|
||||
'o', !!inter._overlap,
|
||||
'Other', other._id, 'p', other.getPath()._id,
|
||||
'i', other.getIndex(), 't', other._parameter,
|
||||
'o', !!other._overlap];
|
||||
new Path.Circle({
|
||||
center: inter.point,
|
||||
radius: 3,
|
||||
strokeColor: 'green'
|
||||
});
|
||||
console.log(log.map(function(v) {
|
||||
return v == null ? '-' : v
|
||||
}).join(' '));
|
||||
|
@ -243,7 +247,7 @@ PathItem.inject(new function() {
|
|||
}
|
||||
|
||||
// TODO: Make public in API, since useful!
|
||||
var tMin = /*#=*/Numerical.TOLERANCE,
|
||||
var tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin,
|
||||
noHandles = false,
|
||||
clearSegments = [];
|
||||
|
@ -298,7 +302,7 @@ PathItem.inject(new function() {
|
|||
// Determine if the curve is a horizontal straight curve by checking the
|
||||
// slope of it's tangent.
|
||||
return curve.isStraight() && Math.abs(curve.getTangentAt(0.5, true).y)
|
||||
< /*#=*/Numerical.TOLERANCE;
|
||||
< /*#=*/Numerical.GEOMETRIC_EPSILON;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -307,7 +311,7 @@ PathItem.inject(new function() {
|
|||
*/
|
||||
function getWinding(point, curves, horizontal, testContains) {
|
||||
var epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON,
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin,
|
||||
px = point.x,
|
||||
py = point.y,
|
||||
|
@ -514,7 +518,7 @@ PathItem.inject(new function() {
|
|||
// NOTE: Even though getTangentAt() supports 0 and 1 instead of
|
||||
// tMin and tMax, we still need to use this instead, as other issues
|
||||
// emerge from switching to 0 and 1 in edge cases.
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin;
|
||||
for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) {
|
||||
seg = startSeg = segments[i];
|
||||
|
@ -801,7 +805,7 @@ Path.inject(/** @lends Path# */{
|
|||
var a = 3 * (y1 - y2) - y0 + y3,
|
||||
b = 2 * (y0 + y2) - 4 * y1,
|
||||
c = y1 - y0,
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin,
|
||||
roots = [],
|
||||
// Keep then range to 0 .. 1 (excluding) in the search for y
|
||||
|
|
|
@ -81,6 +81,7 @@ var Numerical = new function() {
|
|||
* range (see MACHINE_EPSILON).
|
||||
*/
|
||||
EPSILON: EPSILON,
|
||||
CURVETIME_EPSILON: 1e-6,
|
||||
GEOMETRIC_EPSILON: 1e-9,
|
||||
/**
|
||||
* MACHINE_EPSILON for a double precision (Javascript Number) is
|
||||
|
|
|
@ -161,7 +161,7 @@ test('Curve#getParameterAt()', function() {
|
|||
var t2 = curve.getParameterAt(o2);
|
||||
equals(t1, t2, 'Curve parameter at offset ' + o1
|
||||
+ ' should be the same value as at offset' + o2,
|
||||
Numerical.TOLERANCE);
|
||||
Numerical.CURVETIME_EPSILON);
|
||||
}
|
||||
|
||||
equals(curve.getParameterAt(curve.length + 1), null,
|
||||
|
|
Loading…
Reference in a new issue