@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
83 lines (70 loc) • 2.84 kB
JavaScript
import { assert } from "../assert.js";
import { EPSILON } from "./EPSILON.js";
import { solveQuadratic } from "./solveQuadratic.js";
const ONE_THIRD = 1.0 / 3.0;
const TWO_THIRDS_PI = 2.0 * Math.PI / 3.0;
/**
* Return real solutions for a cubic polynomial: ax³ + bx² + cx + d
* Repeated roots are written once each — multiplicity is not duplicated in the output.
* Imaginary roots are not provided.
*
* @param {number[]|Float32Array|Float64Array} result solutions are written here
* @param {number} result_offset offset into result array where solutions are written to
* @param {number} a
* @param {number} b
* @param {number} c
* @param {number} d
* @returns {number} number of real roots found (0, 1, 2, or 3)
*/
export function solveCubic(result, result_offset, a, b, c, d) {
assert.isNumber(a, 'a');
assert.isNumber(b, 'b');
assert.isNumber(c, 'c');
assert.isNumber(d, 'd');
if (Math.abs(a) < EPSILON) {
// degrade to quadratic
return solveQuadratic(result, result_offset, b, c, d);
}
// normalize: x³ + B·x² + C·x + D = 0
const inv_a = 1 / a;
const B = b * inv_a;
const C = c * inv_a;
const D = d * inv_a;
// depress via x = y - B/3 → y³ + p·y + q = 0
const B_third = B * ONE_THIRD;
const p = C - B * B_third;
const q = 2 * B_third * B_third * B_third - C * B_third + D;
if (Math.abs(p) < EPSILON && Math.abs(q) < EPSILON) {
// triple root at y = 0
result[result_offset] = -B_third;
return 1;
}
// Cardano's discriminant criterion. ratio = (q/2)² + (p/3)³.
// ratio > 0 → one real root, two complex
// ratio = 0 → repeated real roots (single + double, or triple covered above)
// ratio < 0 → three distinct real roots
const half_q = q * 0.5;
const third_p = p * ONE_THIRD;
const ratio = half_q * half_q + third_p * third_p * third_p;
if (ratio > EPSILON) {
const sqrt_ratio = Math.sqrt(ratio);
const u = Math.cbrt(-half_q + sqrt_ratio);
const v = Math.cbrt(-half_q - sqrt_ratio);
result[result_offset] = u + v - B_third;
return 1;
}
if (ratio < -EPSILON) {
// p must be negative for ratio < 0; sqrt(-p/3) is real
const m = 2 * Math.sqrt(-third_p);
const theta_third = ONE_THIRD * Math.acos(3 * q / (p * m));
result[result_offset] = m * Math.cos(theta_third) - B_third;
result[result_offset + 1] = m * Math.cos(theta_third - TWO_THIRDS_PI) - B_third;
result[result_offset + 2] = m * Math.cos(theta_third - 2 * TWO_THIRDS_PI) - B_third;
return 3;
}
// ratio ≈ 0: simple + double root
const u = Math.cbrt(-half_q);
result[result_offset] = 2 * u - B_third;
result[result_offset + 1] = -u - B_third;
return 2;
}