Change root solvers to not produce new arrays each time but fill a passed one that can be reused. Yields io impressive performance improvements.

This commit is contained in:
Jürg Lehni 2011-07-09 10:50:47 +02:00
parent 839107d341
commit 833d4968ce
3 changed files with 36 additions and 31 deletions

View file

@ -309,15 +309,15 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
return Curve.getParameter(this.getValues(), point.x, point.y);
},
getCrossings: function(point, matrix) {
getCrossings: function(point, matrix, roots) {
// Implement the crossing number algorithm:
// http://en.wikipedia.org/wiki/Point_in_polygon
// Solve the y-axis cubic polynominal for point.y and count all
// solutions to the right of point.x as crossings.
var vals = this.getValues(matrix),
roots = Curve.solveCubic(vals, 1, point.y),
num = Curve.solveCubic(vals, 1, point.y, roots),
crossings = 0;
for (var i = 0, l = roots != Infinity && roots.length; i < l; i++) {
for (var i = 0; i < num; i++) {
var t = roots[i];
if (t >= 0 && t < 1 && Curve.evaluate(vals, t, 0).x > point.x) {
// If we're close to 0 and are not changing y-direction from the
@ -470,7 +470,7 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
// Converts from the point coordinates (p1, c1, c2, p2) for one axis to
// the polynomial coefficients and solves the polynomial for val
solveCubic: function (v, coord, val) {
solveCubic: function (v, coord, val, roots) {
var p1 = v[coord],
c1 = v[coord + 2],
c2 = v[coord + 4],
@ -478,14 +478,15 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
c = 3 * (c1 - p1),
b = 3 * (c2 - c1) - c,
a = p2 - p1 - c - b;
return Numerical.solveCubic(a, b, c, p1 - val, Numerical.TOLERANCE);
return Numerical.solveCubic(a, b, c, p1 - val, roots,
Numerical.TOLERANCE);
},
getParameter: function(v, x, y) {
var txs = Curve.solveCubic(v, 0, x),
tys = Curve.solveCubic(v, 1, y),
sx = txs === Infinity ? -1 : txs.length,
sy = tys === Infinity ? -1 : tys.length,
var txs = [],
tys = [],
sx = Curve.solveCubic(v, 0, x, txs),
sy = Curve.solveCubic(v, 1, y, tys),
tx, ty;
// sx, sy == -1 means infinite solutions:
// Loop through all solutions for x and match with solutions for y,

View file

@ -1191,9 +1191,11 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
// number, meaning the starting point is inside the shape.
// http://en.wikipedia.org/wiki/Point_in_polygon
var curves = this.getCurves(),
crossings = 0;
crossings = 0,
// Reuse one array for root-finding, give garbage collector a break
roots = [];
for (var i = 0, l = curves.length; i < l; i++)
crossings += curves[i].getCrossings(point, matrix);
crossings += curves[i].getCrossings(point, matrix, roots);
return (crossings & 1) == 1;
},

View file

@ -117,32 +117,34 @@ var Numerical = new function() {
*
* a*x^2 + b*x + c = 0
*/
solveQuadratic: function(a, b, c, tolerance) {
solveQuadratic: function(a, b, c, roots, tolerance) {
// After Numerical Recipes in C, 2nd edition, Press et al.,
// 5.6, Quadratic and Cubic Equations
// If problem is actually linear, return 0 or 1 easy roots
if (abs(a) < tolerance) {
if (abs(b) >= tolerance)
return [ -c / b ];
if (abs(b) >= tolerance) {
roots[0] = -c / b;
return 1;
}
// If all the coefficients are 0, infinite values are
// possible!
if (abs(c) < tolerance)
return Infinity; // Infinite solutions
return []; // 0 solutions
return -1; // Infinite solutions
return 0; // 0 solutions
}
var q = b * b - 4 * a * c;
if (q < 0)
return []; // 0 solutions
return 0; // 0 solutions
q = sqrt(q);
if (b < 0)
q = -q;
q = (b + q) * -0.5;
var roots = [];
var n = 0;
if (abs(q) >= tolerance)
roots.push(c / q);
roots[n++] = c / q;
if (abs(a) >= tolerance)
roots.push(q / a);
return roots; // 0, 1 or 2 solutions
roots[n++] = q / a;
return n; // 0, 1 or 2 solutions
},
/**
@ -151,11 +153,11 @@ var Numerical = new function() {
*
* a*x^3 + b*x^2 + c*x + d = 0
*/
solveCubic: function(a, b, c, d, tolerance) {
solveCubic: function(a, b, c, d, roots, tolerance) {
// After Numerical Recipes in C, 2nd edition, Press et al.,
// 5.6, Quadratic and Cubic Equations
if (abs(a) < tolerance)
return Numerical.solveQuadratic(b, c, d, tolerance);
return Numerical.solveQuadratic(b, c, d, roots, tolerance);
// Normalize
b /= a;
c /= a;
@ -173,18 +175,18 @@ var Numerical = new function() {
var theta = Math.acos(R / sqrt(Q3)),
// This sqrt is safe, since Q3 >= 0, and thus Q >= 0
q = -2 * sqrt(Q);
return [
q * cos(theta / 3) - b,
q * cos((theta + 2 * PI) / 3) - b,
q * cos((theta - 2 * PI) / 3) - b
];
roots[0] = q * cos(theta / 3) - b;
roots[1] = q * cos((theta + 2 * PI) / 3) - b;
roots[2] = q * cos((theta - 2 * PI) / 3) - b;
return 3;
} else { // One real root
var A = -Math.pow(abs(R) + sqrt(R2 - Q3), 1 / 3);
if (R < 0) A = -A;
var B = (abs(A) < tolerance) ? 0 : Q / A;
return [ (A + B) - b ];
var B = (abs(A) < tolerance) ? 0 : Q / A;
roots[0] = (A + B) - b;
return 1;
}
return [];
return 0;
}
};
};