mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-20 22:39:50 -05:00
Improve curve time parametrization precision by iteratively adding up sub-range lengths, and optimise speed by determining integration precision based on range size.
This commit is contained in:
parent
a794816097
commit
417d015eab
2 changed files with 41 additions and 29 deletions
|
@ -302,7 +302,12 @@ var Curve = this.Curve = Base.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Amount of integral evaluations
|
// Amount of integral evaluations
|
||||||
var numEval = 16;
|
function getIterations(a, b) {
|
||||||
|
// Guess required precision based and size of range...
|
||||||
|
// TODO: There should be much better educated guesses for
|
||||||
|
// this. Also, what does this depend on? Required precision?
|
||||||
|
return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32)));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getPoint: function(parameter) {
|
getPoint: function(parameter) {
|
||||||
|
@ -331,7 +336,7 @@ var Curve = this.Curve = Base.extend({
|
||||||
}
|
}
|
||||||
var ds = getLengthIntegrand(
|
var ds = getLengthIntegrand(
|
||||||
p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
|
p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
|
||||||
return Numerical.integrate(ds, a, b, numEval);
|
return Numerical.integrate(ds, a, b, getIterations(a, b));
|
||||||
},
|
},
|
||||||
|
|
||||||
getParameter: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
|
getParameter: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
|
||||||
|
@ -347,39 +352,39 @@ var Curve = this.Curve = Base.extend({
|
||||||
return Math.max(Math.min(start
|
return Math.max(Math.min(start
|
||||||
+ length / Math.sqrt(dx * dx + dy * dy), 0, 1));
|
+ length / Math.sqrt(dx * dx + dy * dy), 0, 1));
|
||||||
}
|
}
|
||||||
// Let's use the Van Wijngaarden–Dekker–Brent Method to find
|
// See if we're going forward or backward, and handle cases
|
||||||
// solutions more reliably than with False Position Method.
|
// differently
|
||||||
// The precision of 5 iterations seems enough for this
|
|
||||||
var forward = length > 0,
|
var forward = length > 0,
|
||||||
|
a = forward ? start : 0,
|
||||||
|
b = forward ? 1 : start,
|
||||||
|
length = Math.abs(length),
|
||||||
// Use integrand to calculate both range length and part
|
// Use integrand to calculate both range length and part
|
||||||
// lengths in f(t) below.
|
// lengths in f(t) below.
|
||||||
ds = getLengthIntegrand(
|
ds = getLengthIntegrand(
|
||||||
p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y),
|
p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y),
|
||||||
a, b, f;
|
// Get length of total range
|
||||||
// See if we're going forward or backward, and handle cases
|
rangeLength = Numerical.integrate(ds, a, b,
|
||||||
// differently
|
getIterations(a, b)),
|
||||||
if (forward) { // Normal way
|
// Use length / rangeLength for an initial guess for t, to
|
||||||
a = start;
|
// bring us closer:
|
||||||
b = 1;
|
guess = length / rangeLength,
|
||||||
// We're moving b to the right to find root for length
|
len = 0;
|
||||||
f = function(t) {
|
|
||||||
return Numerical.integrate(ds, a, t, numEval) - length;
|
|
||||||
}
|
|
||||||
} else { // Going backwards
|
|
||||||
a = 0;
|
|
||||||
b = start;
|
|
||||||
length = -length;
|
|
||||||
// We're moving a to the left to find root for length
|
|
||||||
f = function(t) {
|
|
||||||
return Numerical.integrate(ds, t, b, numEval) - length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var rangeLength = Numerical.integrate(ds, a, b, numEval);
|
|
||||||
if (length >= rangeLength)
|
if (length >= rangeLength)
|
||||||
return forward ? b : a;
|
return forward ? b : a;
|
||||||
// Use length / rangeLength for an initial guess for t, to
|
// Iteratively calculate curve range lengths, and add them up,
|
||||||
// bring us closer:
|
// using integration precision depending on the size of the
|
||||||
var guess = length / rangeLength;
|
// range. This is much faster and also more precise than not
|
||||||
|
// modifing start and calculating total length each time.
|
||||||
|
function f(t) {
|
||||||
|
var count = getIterations(start, t);
|
||||||
|
if (start < t) {
|
||||||
|
len += Numerical.integrate(ds, start, t, count);
|
||||||
|
} else {
|
||||||
|
len -= Numerical.integrate(ds, t, start, count);
|
||||||
|
}
|
||||||
|
start = t;
|
||||||
|
return len - length;
|
||||||
|
}
|
||||||
return Numerical.findRootNewton(f, ds,
|
return Numerical.findRootNewton(f, ds,
|
||||||
forward ? a : b - guess, // a
|
forward ? a : b - guess, // a
|
||||||
forward ? a + guess : b, // b
|
forward ? a + guess : b, // b
|
||||||
|
|
|
@ -74,13 +74,20 @@ var Numerical = new function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Newton-Raphson Method Using Derivative
|
* Newton-Raphson Method Using Derivative. This is a special version
|
||||||
|
* that clips results to 0 .. 1, as required by Paper.js iterative
|
||||||
|
* approach for curve time parametrization: Ending up far outside
|
||||||
|
* the bezier curve boundaries resulted in inprecision of added up
|
||||||
|
* curve lengths.
|
||||||
*/
|
*/
|
||||||
findRootNewton: function(f, fd, a, b, n, tol) {
|
findRootNewton: function(f, fd, a, b, n, tol) {
|
||||||
var x = 0.5 * (a + b);
|
var x = 0.5 * (a + b);
|
||||||
for (var i = 0; i < n; i++) {
|
for (var i = 0; i < n; i++) {
|
||||||
var dx = f(x) / fd(x);
|
var dx = f(x) / fd(x);
|
||||||
x -= dx;
|
x -= dx;
|
||||||
|
// Clip to 0 .. t .. 1. See comment above
|
||||||
|
if (x < 0) x = 0;
|
||||||
|
else if (x > 1) x = 1;
|
||||||
if (Math.abs(dx) < tol)
|
if (Math.abs(dx) < tol)
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue