Introduce CURVETIME_EPSILON, to be used when handling curve time parameters.

Relates to #777
This commit is contained in:
Jürg Lehni 2015-09-12 22:55:58 +02:00
parent 4f04dae20f
commit d62caf6faa
6 changed files with 55 additions and 51 deletions

View file

@ -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),

View file

@ -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,

View file

@ -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 ++

View file

@ -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

View file

@ -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

View file

@ -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,