Skip to content

Commit

Permalink
Do not snap curve points to t = 0 / 1 with epsilon
Browse files Browse the repository at this point in the history
Closes paperjs#936. Also:
- Reduce CLIPPING_EPSILON to 1e-9 to fix errors that were masked by the snapping
- Implement unit tests for edge cases in paperjs#936
  • Loading branch information
lehni committed Feb 6, 2016
1 parent a59a535 commit 0371f66
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 65 deletions.
122 changes: 59 additions & 63 deletions src/path/Curve.js
Original file line number Diff line number Diff line change
Expand Up @@ -1306,10 +1306,7 @@ 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],
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
tMax = 1 - tMin,
isZero = Numerical.isZero,
x, y;
isZero = Numerical.isZero;
// If the curve handles are almost zero, reset the control points to the
// anchors.
if (isZero(c1x - p1x) && isZero(c1y - p1y)) {
Expand All @@ -1320,70 +1317,69 @@ new function() { // Scope for methods that require private functions
c2x = p2x;
c2y = p2y;
}
// Handle special case at beginning / end of curve
if (type === 0 && (t < tMin || t > tMax)) {
var zero = t < tMin;
x = zero ? p1x : p2x;
y = zero ? p1y : p2y;
// Calculate the polynomial coefficients.
var cx = 3 * (c1x - p1x),
bx = 3 * (c2x - c1x) - cx,
ax = p2x - p1x - cx - bx,
cy = 3 * (c1y - p1y),
by = 3 * (c2y - c1y) - cy,
ay = p2y - p1y - cy - by,
x, y;
if (type === 0) {
// type === 0: getPoint()
// Calculate the curve point at parameter value t
x = ((ax * t + bx) * t + cx) * t + p1x;
y = ((ay * t + by) * t + cy) * t + p1y;
} else {
// Calculate the polynomial coefficients.
var cx = 3 * (c1x - p1x),
bx = 3 * (c2x - c1x) - cx,
ax = p2x - p1x - cx - bx,

cy = 3 * (c1y - p1y),
by = 3 * (c2y - c1y) - cy,
ay = p2y - p1y - cy - by;
if (type === 0) {
// Calculate the curve point at parameter value t
x = ((ax * t + bx) * t + cx) * t + p1x;
y = ((ay * t + by) * t + cy) * t + p1y;
// type === 1: getTangent()
// type === 2: getNormal()
// type === 3: getCurvature()
var tMin = /*#=*/Numerical.CURVETIME_EPSILON,
tMax = 1 - tMin;
// 1: tangent, 1st derivative
// 2: normal, 1st derivative
// 3: curvature, 1st derivative & 2nd derivative
// Simply use the derivation of the bezier function for both
// the x and y coordinates:
// Prevent tangents and normals of length 0:
// http://stackoverflow.com/questions/10506868/
if (t < tMin) {
x = cx;
y = cy;
} else if (t > tMax) {
x = 3 * (p2x - c2x);
y = 3 * (p2y - c2y);
} else {
// 1: tangent, 1st derivative
// 2: normal, 1st derivative
// 3: curvature, 1st derivative & 2nd derivative
// Simply use the derivation of the bezier function for both
// the x and y coordinates:
// Prevent tangents and normals of length 0:
// http://stackoverflow.com/questions/10506868/
if (t < tMin) {
x = cx;
y = cy;
} else if (t > tMax) {
x = 3 * (p2x - c2x);
y = 3 * (p2y - c2y);
} else {
x = (3 * ax * t + 2 * bx) * t + cx;
y = (3 * ay * t + 2 * by) * t + cy;
}
if (normalized) {
// 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 < tMin || t > tMax)) {
x = c2x - c1x;
y = c2y - c1y;
}
// Now normalize x & y
var len = Math.sqrt(x * x + y * y);
if (len) {
x /= len;
y /= len;
}
x = (3 * ax * t + 2 * bx) * t + cx;
y = (3 * ay * t + 2 * by) * t + cy;
}
if (normalized) {
// 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 < tMin || t > tMax)) {
x = c2x - c1x;
y = c2y - c1y;
}
if (type === 3) {
// Calculate 2nd derivative, and curvature from there:
// http://cagd.cs.byu.edu/~557/text/ch2.pdf page#31
// k = |dx * d2y - dy * d2x| / (( dx^2 + dy^2 )^(3/2))
var x2 = 6 * ax * t + 2 * bx,
y2 = 6 * ay * t + 2 * by,
d = Math.pow(x * x + y * y, 3 / 2);
// For JS optimizations we always return a Point, although
// curvature is just a numeric value, stored in x:
x = d !== 0 ? (x * y2 - y * x2) / d : 0;
y = 0;
// Now normalize x & y
var len = Math.sqrt(x * x + y * y);
if (len) {
x /= len;
y /= len;
}
}
if (type === 3) {
// Calculate 2nd derivative, and curvature from there:
// http://cagd.cs.byu.edu/~557/text/ch2.pdf page#31
// k = |dx * d2y - dy * d2x| / (( dx^2 + dy^2 )^(3/2))
var x2 = 6 * ax * t + 2 * bx,
y2 = 6 * ay * t + 2 * by,
d = Math.pow(x * x + y * y, 3 / 2);
// For JS optimizations we always return a Point, although
// curvature is just a numeric value, stored in x:
x = d !== 0 ? (x * y2 - y * x2) / d : 0;
y = 0;
}
}
// The normal is simply the rotated tangent:
return type === 2 ? new Point(y, -x) : new Point(x, y);
Expand Down
5 changes: 3 additions & 2 deletions src/util/Numerical.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ var Numerical = new function() {
*/
TRIGONOMETRIC_EPSILON: 1e-7,
/**
* The epsilon to be used in the fat-line clipping code.
* The epsilon to be used when comparing curve-time parameters in the
* fat-line clipping code.
*/
CLIPPING_EPSILON: 1e-7,
CLIPPING_EPSILON: 1e-9,
/**
* Kappa is the value which which to scale the curve handles when
* drawing a circle with bezier curves.
Expand Down
34 changes: 34 additions & 0 deletions test/tests/Path_Boolean.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,40 @@ test('#899', function() {
'M349.31715,176.97192l-41.08297,217.42545c-0.3852,2.03862 -25.43665,23.05386 -34.63134,21.31651c0,0 45.72613,-193.57408 61.79225,-248.85827c-4.42287,-6.99195 -6.99542,-14.22758 -1.98246,-13.27272c1.9865,0.37838 3.84798,1.03847 5.55095,1.93102c0.85247,-2.33202 1.44527,-3.46596 1.73311,-3.20238c5.69403,5.21442 8.66306,11.93238 9.02453,18.54465c0.10579,1.93513 -0.00703,3.88198 -0.35068,5.82386c-0.01706,0.09726 -0.03486,0.19455 -0.0534,0.29188z');
});

test('#936', function() {
var p1 = new Path({
segments:[
[437.02857142857147, 121.64285714285714],
[390.3571428571429, 74.97142857142856],
[390.3571428571429, 437.02857142857147],
[437.02857142857147, 390.3571428571429],
[74.97142857142862, 390.3571428571429],
[121.6428571428572, 437.02857142857147],
[121.64285714285714, 74.97142857142858],
[74.97142857142859, 121.64285714285714]
],
closed:true
});
compareBoolean(function() { return p1.unite(); },
'M390.35714,121.64286l0,268.71429l-268.71429,0l0,-268.71429z M121.64286,437.02857l-46.67143,-46.67143l46.67143,0z M390.35714,121.64286l0,-46.67143l46.67143,46.67143z M437.02857,390.35714l-46.67143,46.67143l0,-46.67143z M74.97143,121.64286l46.67143,-46.67143l0,46.67143z');

var p2 = new Path({
segments:[
[384.0884708724184, 194.49096034834812, -66.07592496222571, -39.741588860234515],
[364.2895784370851, 164.00333956484477, 0, 0, 9.430248075502885, 76.52772155228502],
[317.5090396516519, 384.0884708724184, 39.74158886023454, -66.07592496222564],
[347.9966604351552, 364.2895784370851, 0, 0, -76.52772155228439, 9.430248075502845],
[127.91152912758163, 317.50903965165196, 66.0759249622259, 39.74158886023463],
[147.71042156291495, 347.9966604351553, 0, 0, -9.43024807550288, -76.52772155228509],
[194.49096034834812, 127.91152912758156, -39.74158886023451, 66.07592496222576],
[164.00333956484474, 147.7104215629149, 0, 0, 76.52772155228499, -9.430248075502845]
],
closed:true
});
compareBoolean(function() { return p2.unite(); },
'M366.1932,184.4397c3.98163,62.97187 -9.38253,125.8453 -38.6329,181.75349c-62.97187,3.98163 -125.8453,-9.38253 -181.75349,-38.6329c-3.98163,-62.97187 9.38253,-125.8453 38.6329,-181.75349c62.97187,-3.98163 125.8453,9.38253 181.75349,38.6329z M164.00334,147.71042l30.48762,-19.79889c-3.53519,5.87774 -6.88622,11.84573 -10.05126,17.89528c-6.81387,0.43083 -13.62889,1.06475 -20.43636,1.90362z M147.71042,347.99666l-19.79889,-30.48762c5.87774,3.53519 11.84573,6.88622 17.89528,10.05126c0.43083,6.81387 1.06475,13.62889 1.90362,20.43636z M366.1932,184.4397c-0.43083,-6.81387 -1.06475,-13.62889 -1.90362,-20.43636l19.79889,30.48762c-5.87774,-3.53519 -11.84573,-6.88622 -17.89528,-10.05126z M347.99666,364.28958l-30.48762,19.79889c3.53519,-5.87774 6.88622,-11.84573 10.05126,-17.89528c6.81387,-0.43083 13.62889,-1.06475 20.43636,-1.90362z');
});

test('frame.intersect(rect);', function() {
var frame = new CompoundPath();
frame.addChild(new Path.Rectangle(new Point(140, 10), [100, 300]));
Expand Down

0 comments on commit 0371f66

Please sign in to comment.