* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/base/SkCubics.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkFloatingPoint.h"
#include "include/private/base/SkTPin.h"
#include "src/base/SkQuads.h"
#include <algorithm>
#include <cmath>
static bool nearly_equal(double x, double y) {
if (sk_double_nearly_zero(x)) {
return sk_double_nearly_zero(y);
}
return sk_doubles_nearly_equal_ulps(x, y);
}
static bool close_to_a_quadratic(double A, double B) {
if (sk_double_nearly_zero(B)) {
return sk_double_nearly_zero(A);
}
return std::abs(A / B) < 1.0e-7;
}
int SkCubics::RootsReal(double A, double B, double C, double D, double solution[3]) {
if (close_to_a_quadratic(A, B)) {
return SkQuads::RootsReal(B, C, D, solution);
}
if (sk_double_nearly_zero(D)) {
int num = SkQuads::RootsReal(A, B, C, solution);
for (int i = 0; i < num; ++i) {
if (sk_double_nearly_zero(solution[i])) {
return num;
}
}
solution[num++] = 0;
return num;
}
if (sk_double_nearly_zero(A + B + C + D)) {
int num = SkQuads::RootsReal(A, A + B, -D, solution);
for (int i = 0; i < num; ++i) {
if (sk_doubles_nearly_equal_ulps(solution[i], 1)) {
return num;
}
}
solution[num++] = 1;
return num;
}
double a, b, c;
{
double invA = sk_ieee_double_divide(1, A);
a = B * invA;
b = C * invA;
c = D * invA;
}
double a2 = a * a;
double Q = (a2 - b * 3) / 9;
double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
double R2 = R * R;
double Q3 = Q * Q * Q;
double R2MinusQ3 = R2 - Q3;
if (!SkIsFinite(R2MinusQ3)) {
return 0;
}
double adiv3 = a / 3;
double r;
double* roots = solution;
if (R2MinusQ3 < 0) {
const double theta = acos(SkTPin(R / std::sqrt(Q3), -1., 1.));
const double neg2RootQ = -2 * std::sqrt(Q);
r = neg2RootQ * cos(theta / 3) - adiv3;
*roots++ = r;
r = neg2RootQ * cos((theta + 2 * SK_DoublePI) / 3) - adiv3;
if (!nearly_equal(solution[0], r)) {
*roots++ = r;
}
r = neg2RootQ * cos((theta - 2 * SK_DoublePI) / 3) - adiv3;
if (!nearly_equal(solution[0], r) &&
(roots - solution == 1 || !nearly_equal(solution[1], r))) {
*roots++ = r;
}
} else {
const double sqrtR2MinusQ3 = std::sqrt(R2MinusQ3);
A = fabs(R) + sqrtR2MinusQ3;
A = std::cbrt(A);
if (R > 0) {
A = -A;
}
if (!sk_double_nearly_zero(A)) {
A += Q / A;
}
r = A - adiv3;
*roots++ = r;
if (!sk_double_nearly_zero(R2) &&
sk_doubles_nearly_equal_ulps(R2, Q3)) {
r = -A / 2 - adiv3;
if (!nearly_equal(solution[0], r)) {
*roots++ = r;
}
}
}
return static_cast<int>(roots - solution);
}
int SkCubics::RootsValidT(double A, double B, double C, double D,
double solution[3]) {
double allRoots[3] = {0, 0, 0};
int realRoots = SkCubics::RootsReal(A, B, C, D, allRoots);
int foundRoots = 0;
for (int index = 0; index < realRoots; ++index) {
double tValue = allRoots[index];
if (tValue <= 1.00005 && (tValue >= 1.0 || sk_doubles_nearly_equal_ulps(tValue, 1.0))) {
if ((foundRoots < 1 || !sk_doubles_nearly_equal_ulps(solution[0], 1)) &&
(foundRoots < 2 || !sk_doubles_nearly_equal_ulps(solution[1], 1))) {
solution[foundRoots++] = 1;
}
} else if (tValue >= -0.00005 && (tValue <= 0.0 || sk_double_nearly_zero(tValue))) {
if ((foundRoots < 1 || !sk_double_nearly_zero(solution[0])) &&
(foundRoots < 2 || !sk_double_nearly_zero(solution[1]))) {
solution[foundRoots++] = 0;
}
} else if (tValue > 0.0 && tValue < 1.0) {
solution[foundRoots++] = tValue;
}
}
return foundRoots;
}
static bool approximately_zero(double x) {
return std::abs(x) < 0.00000001;
}
static int find_extrema_valid_t(double A, double B, double C,
double t[2]) {
double roots[2] = {0, 0};
int numRoots = SkQuads::RootsReal(3*A, 2*B, C, roots);
int validRoots = 0;
for (int i = 0; i < numRoots; i++) {
double tValue = roots[i];
if (tValue >= 0 && tValue <= 1.0) {
t[validRoots++] = tValue;
}
}
return validRoots;
}
static double binary_search(double A, double B, double C, double D, double start, double stop) {
SkASSERT(start <= stop);
double left = SkCubics::EvalAt(A, B, C, D, start);
if (approximately_zero(left)) {
return start;
}
double right = SkCubics::EvalAt(A, B, C, D, stop);
if (!SkIsFinite(left, right)) {
return -1;
}
if ((left > 0 && right > 0) || (left < 0 && right < 0)) {
return -1;
}
constexpr int maxIterations = 1000;
for (int i = 0; i < maxIterations; i++) {
double step = (start + stop) / 2;
double curr = SkCubics::EvalAt(A, B, C, D, step);
if (approximately_zero(curr)) {
return step;
}
if ((curr < 0 && left < 0) || (curr > 0 && left > 0)) {
start = step;
} else {
stop = step;
}
}
return -1;
}
int SkCubics::BinarySearchRootsValidT(double A, double B, double C, double D,
double solution[3]) {
if (!SkIsFinite(A, B, C, D)) {
return 0;
}
double regions[4] = {0, 0, 0, 1};
double minMax[2] = {0, 0};
int extremaCount = find_extrema_valid_t(A, B, C, minMax);
int startIndex = 2 - extremaCount;
if (extremaCount == 1) {
regions[startIndex + 1] = minMax[0];
}
if (extremaCount == 2) {
regions[startIndex + 1] = std::min(minMax[0], minMax[1]);
regions[startIndex + 2] = std::max(minMax[0], minMax[1]);
}
int foundRoots = 0;
for (;startIndex < 3; startIndex++) {
double root = binary_search(A, B, C, D, regions[startIndex], regions[startIndex + 1]);
if (root >= 0) {
if ((foundRoots < 1 || !approximately_zero(solution[0] - root)) &&
(foundRoots < 2 || !approximately_zero(solution[1] - root))) {
solution[foundRoots++] = root;
}
}
}
return foundRoots;
}