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:
Jürg Lehni 2011-03-20 11:38:06 +00:00
parent a794816097
commit 417d015eab
2 changed files with 41 additions and 29 deletions

View file

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

View file

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