mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
parent
1914e64e4b
commit
02658c9e74
3 changed files with 96 additions and 44 deletions
|
@ -60,6 +60,7 @@ var Numerical = new function() {
|
||||||
var abs = Math.abs,
|
var abs = Math.abs,
|
||||||
sqrt = Math.sqrt,
|
sqrt = Math.sqrt,
|
||||||
pow = Math.pow,
|
pow = Math.pow,
|
||||||
|
// Constants
|
||||||
EPSILON = 1e-12,
|
EPSILON = 1e-12,
|
||||||
MACHINE_EPSILON = 1.12e-16;
|
MACHINE_EPSILON = 1.12e-16;
|
||||||
|
|
||||||
|
@ -67,23 +68,37 @@ var Numerical = new function() {
|
||||||
return value < min ? min : value > max ? max : value;
|
return value < min ? min : value > max ? max : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitDouble(X) {
|
function getDiscriminant(a, b, c) {
|
||||||
var bigX = X * 134217729, // X*(2^27 + 1)
|
// Ported from @hkrish's polysolve.c
|
||||||
Y = X - bigX,
|
function split(a) {
|
||||||
Xh = Y + bigX; // Don't optimize Y away!
|
var x = a * 134217729,
|
||||||
return [Xh, X - Xh];
|
y = a - x,
|
||||||
|
hi = y + x, // Don't optimize y away!
|
||||||
|
lo = a - hi;
|
||||||
|
return [hi, lo];
|
||||||
|
}
|
||||||
|
|
||||||
|
var D = b * b - a * c,
|
||||||
|
E = b * b + a * c;
|
||||||
|
if (abs(D) * 3 < E) {
|
||||||
|
var ad = split(a),
|
||||||
|
bd = split(b),
|
||||||
|
cd = split(c),
|
||||||
|
p = b * b,
|
||||||
|
dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1],
|
||||||
|
q = a * c,
|
||||||
|
dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0])
|
||||||
|
+ ad[1] * cd[1];
|
||||||
|
D = (p - q) + (dp - dq); // Don’t omit parentheses!
|
||||||
|
}
|
||||||
|
return D;
|
||||||
}
|
}
|
||||||
|
|
||||||
function higherPrecisionDiscriminant(a, b, c) {
|
function getNormalizationFactor(x) {
|
||||||
var ad = splitDouble(a),
|
// Normalization is done by scaling coefficients with a power of 2, so
|
||||||
bd = splitDouble(b),
|
// that all the bits in the mantissa remain unchanged.
|
||||||
cd = splitDouble(c),
|
return pow(2, -Math.floor(
|
||||||
p = b * b,
|
Math.log(x || MACHINE_EPSILON) * Math.LOG2E + 0.5));
|
||||||
dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1],
|
|
||||||
q = a * c,
|
|
||||||
dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0])
|
|
||||||
+ ad[1] * cd[1];
|
|
||||||
return (p - q) + (dp - dq);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return /** @lends Numerical */{
|
return /** @lends Numerical */{
|
||||||
|
@ -241,31 +256,21 @@ var Numerical = new function() {
|
||||||
eMax = max + EPSILON,
|
eMax = max + EPSILON,
|
||||||
x1, x2 = Infinity,
|
x1, x2 = Infinity,
|
||||||
B = b,
|
B = b,
|
||||||
D, E, pi = 3;
|
D, E;
|
||||||
// a, b, c are expected to be the coefficients of the equation:
|
// a, b, c are expected to be the coefficients of the equation:
|
||||||
// Ax² - 2Bx + C == 0, so we take b = -B/2:
|
// Ax² - 2Bx + C == 0, so we take b = -B/2:
|
||||||
b /= -2;
|
b *= -0.5;
|
||||||
D = b * b - a * c; // Discriminant
|
D = getDiscriminant(a, b, c);
|
||||||
E = b * b + a * c;
|
|
||||||
if (pi * abs(D) < E) {
|
|
||||||
D = higherPrecisionDiscriminant(a, b, c);
|
|
||||||
}
|
|
||||||
// If the discriminant is very small, we can try to pre-condition
|
// If the discriminant is very small, we can try to pre-condition
|
||||||
// the coefficients, so that we may get better accuracy
|
// the coefficients, so that we may get better accuracy
|
||||||
if (D !== 0 && abs(D) < MACHINE_EPSILON) {
|
if (D && abs(D) < MACHINE_EPSILON) {
|
||||||
// If the geometric mean of the coefficients is small enough
|
// Normalize coefficients.
|
||||||
var sc = (abs(a) + abs(b) + abs(c)) || MACHINE_EPSILON;
|
var f = getNormalizationFactor(abs(a) + abs(b) + abs(c));
|
||||||
sc = pow(2, -Math.floor(Math.log(sc) * Math.LOG2E + 0.5));
|
a *= f;
|
||||||
a *= sc;
|
b *= f;
|
||||||
b *= sc;
|
c *= f;
|
||||||
c *= sc;
|
B *= f;
|
||||||
// Recalculate the discriminant
|
D = getDiscriminant(a, b, c);
|
||||||
D = b * b - a * c;
|
|
||||||
E = b * b + a * c;
|
|
||||||
B = - 2.0 * b;
|
|
||||||
if (pi * abs(D) < E) {
|
|
||||||
D = higherPrecisionDiscriminant(a, b, c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (abs(a) < EPSILON) {
|
if (abs(a) < EPSILON) {
|
||||||
// This could just be a linear equation
|
// This could just be a linear equation
|
||||||
|
@ -326,18 +331,18 @@ var Numerical = new function() {
|
||||||
* @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com>
|
* @author Harikrishnan Gopalakrishnan <hari.exeption@gmail.com>
|
||||||
*/
|
*/
|
||||||
solveCubic: function(a, b, c, d, roots, min, max) {
|
solveCubic: function(a, b, c, d, roots, min, max) {
|
||||||
var count = 0, x, b1, c2,
|
var x, b1, c2,
|
||||||
s = Math.max(abs(a), abs(b), abs(c), abs(d));
|
s = Math.max(abs(a), abs(b), abs(c), abs(d));
|
||||||
// Normalise coefficients a la Jenkins & Traub's RPOLY
|
// Normalize coefficients à la Jenkins & Traub's RPOLY
|
||||||
if ((s < 1e-7 && s > 0) || s > 1e7) {
|
if (s < 1e-8 || s > 1e8) {
|
||||||
// Scale the coefficients by a multiple of the exponent of
|
// Scale the coefficients by a multiple of the exponent of
|
||||||
// coefficients so that all the bits in the mantissa are
|
// coefficients so that all the bits in the mantissa are
|
||||||
// preserved.
|
// preserved.
|
||||||
var p = pow(2, -Math.floor(Math.log(s) * Math.LOG2E));
|
var f = getNormalizationFactor(s);
|
||||||
a *= p;
|
a *= f;
|
||||||
b *= p;
|
b *= f;
|
||||||
c *= p;
|
c *= f;
|
||||||
d *= p;
|
d *= f;
|
||||||
}
|
}
|
||||||
// If a or d is zero, we only need to solve a quadratic, so we set
|
// If a or d is zero, we only need to solve a quadratic, so we set
|
||||||
// the coefficients appropriately.
|
// the coefficients appropriately.
|
||||||
|
|
45
test/tests/Numerical.js
Normal file
45
test/tests/Numerical.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
||||||
|
* http://paperjs.org/
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
|
||||||
|
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
||||||
|
*
|
||||||
|
* Distributed under the MIT license. See LICENSE file for details.
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QUnit.module('Numerical');
|
||||||
|
|
||||||
|
test('Numerical.solveQuadratic()', function() {
|
||||||
|
function solve(s) {
|
||||||
|
var roots = [],
|
||||||
|
count = Numerical.solveQuadratic(s, 0, -s, roots);
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expected = [1, -1];
|
||||||
|
|
||||||
|
equals(solve(1), expected,
|
||||||
|
'Numerical.solveQuadratic().');
|
||||||
|
equals(solve(Numerical.EPSILON), expected,
|
||||||
|
'Numerical.solveQuadratic() with an identical set of' +
|
||||||
|
'coefficients at different scale.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Numerical.solveCubic()', function() {
|
||||||
|
function solve(s) {
|
||||||
|
var roots = [],
|
||||||
|
count = Numerical.solveCubic(0.5 * s, -s, -s, -s, roots);
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expected = [2.919639565839418];
|
||||||
|
|
||||||
|
equals(solve(1), expected,
|
||||||
|
'Numerical.solveCubic().');
|
||||||
|
equals(solve(Numerical.EPSILON), expected,
|
||||||
|
'Numerical.solveCubic() with an identical set of' +
|
||||||
|
'coefficients at different scale.');
|
||||||
|
});
|
|
@ -58,3 +58,5 @@
|
||||||
|
|
||||||
/*#*/ include('SvgImport.js');
|
/*#*/ include('SvgImport.js');
|
||||||
/*#*/ include('SvgExport.js');
|
/*#*/ include('SvgExport.js');
|
||||||
|
|
||||||
|
/*#*/ include('Numerical.js');
|
||||||
|
|
Loading…
Reference in a new issue