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

View file

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