mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-07 13:22:07 -05:00
Fix issues in Numerical.solveQuadratic(), solveCubic() and Path#contains().
Closes #71.
This commit is contained in:
parent
63640cad03
commit
f73717a7e7
3 changed files with 48 additions and 43 deletions
|
@ -319,7 +319,7 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
|
||||||
crossings = 0;
|
crossings = 0;
|
||||||
for (var i = 0; i < num; 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
|
||||||
// previous curve, do not count this root, as we're merely
|
// previous curve, do not count this root, as we're merely
|
||||||
// touching a tip. Passing 1 for Curve.evaluate()'s type means
|
// touching a tip. Passing 1 for Curve.evaluate()'s type means
|
||||||
|
@ -665,7 +665,7 @@ var Curve = this.Curve = Base.extend(/** @lends Curve# */{
|
||||||
*/
|
*/
|
||||||
function toBezierForm(v, point) {
|
function toBezierForm(v, point) {
|
||||||
var n = 3, // degree of B(t)
|
var n = 3, // degree of B(t)
|
||||||
degree = 5, // degree of B(t) . P
|
degree = 5, // degree of B(t) . P
|
||||||
c = [],
|
c = [],
|
||||||
d = [],
|
d = [],
|
||||||
cd = [],
|
cd = [],
|
||||||
|
|
|
@ -558,7 +558,7 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
||||||
*/
|
*/
|
||||||
removeSegments: function(from, to) {
|
removeSegments: function(from, to) {
|
||||||
from = from || 0;
|
from = from || 0;
|
||||||
to = Base.pick(to, this._segments.length);
|
to = Base.pick(to, this._segments.length);
|
||||||
var segments = this._segments,
|
var segments = this._segments,
|
||||||
curves = this._curves,
|
curves = this._curves,
|
||||||
last = to >= segments.length,
|
last = to >= segments.length,
|
||||||
|
|
|
@ -58,9 +58,15 @@ var Numerical = new function() {
|
||||||
// Math short-cuts for often used methods and values
|
// Math short-cuts for often used methods and values
|
||||||
var abs = Math.abs,
|
var abs = Math.abs,
|
||||||
sqrt = Math.sqrt,
|
sqrt = Math.sqrt,
|
||||||
|
pow = Math.pow,
|
||||||
cos = Math.cos,
|
cos = Math.cos,
|
||||||
PI = Math.PI;
|
PI = Math.PI;
|
||||||
|
|
||||||
|
// Define the missing Math.cbrt()
|
||||||
|
function cbrt(x) {
|
||||||
|
return x > 0 ? pow(x, 1 / 3) : x < 0 ? -pow(-x, 1 / 3) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
TOLERANCE: 10e-6,
|
TOLERANCE: 10e-6,
|
||||||
// Precision when comparing against 0
|
// Precision when comparing against 0
|
||||||
|
@ -121,33 +127,26 @@ var Numerical = new function() {
|
||||||
* a*x^2 + b*x + c = 0
|
* a*x^2 + b*x + c = 0
|
||||||
*/
|
*/
|
||||||
solveQuadratic: function(a, b, c, roots, tolerance) {
|
solveQuadratic: function(a, b, c, roots, tolerance) {
|
||||||
// After Numerical Recipes in C, 2nd edition, Press et al.,
|
// Code ported over and adapted from Uintah library (MIT license).
|
||||||
// 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) {
|
||||||
roots[0] = -c / b;
|
roots[0] = -c / b;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// If all the coefficients are 0, infinite values are
|
// If all the coefficients are 0, we have infinite solutions!
|
||||||
// possible!
|
return abs(c) < tolerance ? -1 : 0; // Infinite or 0 solutions
|
||||||
if (abs(c) < tolerance)
|
|
||||||
return -1; // Infinite 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; // 0 solutions
|
return 0; // 0 solutions
|
||||||
q = sqrt(q);
|
q = sqrt(q);
|
||||||
if (b < 0)
|
a *= 2; // Prepare division by (2 * a)
|
||||||
q = -q;
|
|
||||||
q = (b + q) * -0.5;
|
|
||||||
var n = 0;
|
var n = 0;
|
||||||
if (abs(q) >= tolerance)
|
roots[n++] = (-b - q) / a;
|
||||||
roots[n++] = c / q;
|
if (q > 0)
|
||||||
if (abs(a) >= tolerance)
|
roots[n++] = (-b + q) / a;
|
||||||
roots[n++] = q / a;
|
return n; // 1 or 2 solutions
|
||||||
return n; // 0, 1 or 2 solutions
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,37 +156,43 @@ 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, roots, tolerance) {
|
solveCubic: function(a, b, c, d, roots, tolerance) {
|
||||||
// After Numerical Recipes in C, 2nd edition, Press et al.,
|
// Code ported over and adapted from Uintah library (MIT license).
|
||||||
// 5.6, Quadratic and Cubic Equations
|
|
||||||
if (abs(a) < tolerance)
|
if (abs(a) < tolerance)
|
||||||
return Numerical.solveQuadratic(b, c, d, roots, tolerance);
|
return Numerical.solveQuadratic(b, c, d, roots, tolerance);
|
||||||
// Normalize
|
// Normalize to form: x^3 + b x^2 + c x + d = 0:
|
||||||
b /= a;
|
b /= a;
|
||||||
c /= a;
|
c /= a;
|
||||||
d /= a;
|
d /= a;
|
||||||
// Compute discriminants
|
// Compute discriminants
|
||||||
var Q = (b * b - 3 * c) / 9,
|
var bb = b * b,
|
||||||
R = (2 * b * b * b - 9 * b * c + 27 * d) / 54,
|
p = 1 / 3 * (-1 / 3 * bb + c),
|
||||||
Q3 = Q * Q * Q,
|
q = 1 / 2 * (2 / 27 * b * bb - 1 / 3 * b * c + d),
|
||||||
R2 = R * R;
|
// Use Cardano's formula
|
||||||
b /= 3; // Divide by 3 as that's required below
|
ppp = p * p * p,
|
||||||
if (R2 < Q3) { // Three real roots
|
D = q * q + ppp;
|
||||||
// This sqrt and division is safe, since R2 >= 0, so Q3 > R2,
|
// Substitute x = y - b/3 to eliminate quadric term: x^3 +px + q = 0
|
||||||
// so Q3 > 0. The acos is also safe, since R2/Q3 < 1, and
|
b /= 3;
|
||||||
// thus R/sqrt(Q3) < 1.
|
if (abs(D) < tolerance) {
|
||||||
var theta = Math.acos(R / sqrt(Q3)),
|
if (abs(q) < tolerance) { // One triple solution.
|
||||||
// This sqrt is safe, since Q3 >= 0, and thus Q >= 0
|
roots[0] = - b;
|
||||||
q = -2 * sqrt(Q);
|
return 1;
|
||||||
roots[0] = q * cos(theta / 3) - b;
|
} else { // One single and one double solution.
|
||||||
roots[1] = q * cos((theta + 2 * PI) / 3) - b;
|
var u = cbrt(-q);
|
||||||
roots[2] = q * cos((theta - 2 * PI) / 3) - b;
|
roots[0] = 2 * u - b;
|
||||||
return 3;
|
roots[1] = - u - b;
|
||||||
} else { // One real root
|
return 2;
|
||||||
var A = -Math.pow(abs(R) + sqrt(R2 - Q3), 1 / 3);
|
}
|
||||||
if (R < 0) A = -A;
|
} else if (D < 0) { // Casus irreducibilis: three real solutions
|
||||||
var B = (abs(A) < tolerance) ? 0 : Q / A;
|
var phi = 1 / 3 * Math.acos(-q / sqrt(-ppp));
|
||||||
roots[0] = (A + B) - b;
|
var t = 2 * sqrt(-p);
|
||||||
return 1;
|
roots[0] = t * cos(phi) - b;
|
||||||
|
roots[1] = - t * cos(phi + PI / 3) - b;
|
||||||
|
roots[2] = - t * cos(phi - PI / 3) - b;
|
||||||
|
return 3;
|
||||||
|
} else { // One real solution
|
||||||
|
D = sqrt(D);
|
||||||
|
roots[0] = cbrt(D - q) - cbrt(D + q) - b;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue