1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2023 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkCubics.h"
9*c8dee2aaSAndroid Build Coastguard Worker
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFloatingPoint.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkQuads.h"
14*c8dee2aaSAndroid Build Coastguard Worker
15*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
16*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
17*c8dee2aaSAndroid Build Coastguard Worker
18*c8dee2aaSAndroid Build Coastguard Worker static constexpr double PI = 3.141592653589793;
19*c8dee2aaSAndroid Build Coastguard Worker
nearly_equal(double x,double y)20*c8dee2aaSAndroid Build Coastguard Worker static bool nearly_equal(double x, double y) {
21*c8dee2aaSAndroid Build Coastguard Worker if (sk_double_nearly_zero(x)) {
22*c8dee2aaSAndroid Build Coastguard Worker return sk_double_nearly_zero(y);
23*c8dee2aaSAndroid Build Coastguard Worker }
24*c8dee2aaSAndroid Build Coastguard Worker return sk_doubles_nearly_equal_ulps(x, y);
25*c8dee2aaSAndroid Build Coastguard Worker }
26*c8dee2aaSAndroid Build Coastguard Worker
27*c8dee2aaSAndroid Build Coastguard Worker // When the A coefficient of a cubic is close to 0, there can be floating point error
28*c8dee2aaSAndroid Build Coastguard Worker // that arises from computing a very large root. In those cases, we would rather be
29*c8dee2aaSAndroid Build Coastguard Worker // precise about the smaller 2 roots, so we have this arbitrary cutoff for when A is
30*c8dee2aaSAndroid Build Coastguard Worker // really small or small compared to B.
close_to_a_quadratic(double A,double B)31*c8dee2aaSAndroid Build Coastguard Worker static bool close_to_a_quadratic(double A, double B) {
32*c8dee2aaSAndroid Build Coastguard Worker if (sk_double_nearly_zero(B)) {
33*c8dee2aaSAndroid Build Coastguard Worker return sk_double_nearly_zero(A);
34*c8dee2aaSAndroid Build Coastguard Worker }
35*c8dee2aaSAndroid Build Coastguard Worker return std::abs(A / B) < 1.0e-7;
36*c8dee2aaSAndroid Build Coastguard Worker }
37*c8dee2aaSAndroid Build Coastguard Worker
RootsReal(double A,double B,double C,double D,double solution[3])38*c8dee2aaSAndroid Build Coastguard Worker int SkCubics::RootsReal(double A, double B, double C, double D, double solution[3]) {
39*c8dee2aaSAndroid Build Coastguard Worker if (close_to_a_quadratic(A, B)) {
40*c8dee2aaSAndroid Build Coastguard Worker return SkQuads::RootsReal(B, C, D, solution);
41*c8dee2aaSAndroid Build Coastguard Worker }
42*c8dee2aaSAndroid Build Coastguard Worker if (sk_double_nearly_zero(D)) { // 0 is one root
43*c8dee2aaSAndroid Build Coastguard Worker int num = SkQuads::RootsReal(A, B, C, solution);
44*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < num; ++i) {
45*c8dee2aaSAndroid Build Coastguard Worker if (sk_double_nearly_zero(solution[i])) {
46*c8dee2aaSAndroid Build Coastguard Worker return num;
47*c8dee2aaSAndroid Build Coastguard Worker }
48*c8dee2aaSAndroid Build Coastguard Worker }
49*c8dee2aaSAndroid Build Coastguard Worker solution[num++] = 0;
50*c8dee2aaSAndroid Build Coastguard Worker return num;
51*c8dee2aaSAndroid Build Coastguard Worker }
52*c8dee2aaSAndroid Build Coastguard Worker if (sk_double_nearly_zero(A + B + C + D)) { // 1 is one root
53*c8dee2aaSAndroid Build Coastguard Worker int num = SkQuads::RootsReal(A, A + B, -D, solution);
54*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < num; ++i) {
55*c8dee2aaSAndroid Build Coastguard Worker if (sk_doubles_nearly_equal_ulps(solution[i], 1)) {
56*c8dee2aaSAndroid Build Coastguard Worker return num;
57*c8dee2aaSAndroid Build Coastguard Worker }
58*c8dee2aaSAndroid Build Coastguard Worker }
59*c8dee2aaSAndroid Build Coastguard Worker solution[num++] = 1;
60*c8dee2aaSAndroid Build Coastguard Worker return num;
61*c8dee2aaSAndroid Build Coastguard Worker }
62*c8dee2aaSAndroid Build Coastguard Worker double a, b, c;
63*c8dee2aaSAndroid Build Coastguard Worker {
64*c8dee2aaSAndroid Build Coastguard Worker // If A is zero (e.g. B was nan and thus close_to_a_quadratic was false), we will
65*c8dee2aaSAndroid Build Coastguard Worker // temporarily have infinities rolling about, but will catch that when checking
66*c8dee2aaSAndroid Build Coastguard Worker // R2MinusQ3.
67*c8dee2aaSAndroid Build Coastguard Worker double invA = sk_ieee_double_divide(1, A);
68*c8dee2aaSAndroid Build Coastguard Worker a = B * invA;
69*c8dee2aaSAndroid Build Coastguard Worker b = C * invA;
70*c8dee2aaSAndroid Build Coastguard Worker c = D * invA;
71*c8dee2aaSAndroid Build Coastguard Worker }
72*c8dee2aaSAndroid Build Coastguard Worker double a2 = a * a;
73*c8dee2aaSAndroid Build Coastguard Worker double Q = (a2 - b * 3) / 9;
74*c8dee2aaSAndroid Build Coastguard Worker double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
75*c8dee2aaSAndroid Build Coastguard Worker double R2 = R * R;
76*c8dee2aaSAndroid Build Coastguard Worker double Q3 = Q * Q * Q;
77*c8dee2aaSAndroid Build Coastguard Worker double R2MinusQ3 = R2 - Q3;
78*c8dee2aaSAndroid Build Coastguard Worker // If one of R2 Q3 is infinite or nan, subtracting them will also be infinite/nan.
79*c8dee2aaSAndroid Build Coastguard Worker // If both are infinite or nan, the subtraction will be nan.
80*c8dee2aaSAndroid Build Coastguard Worker // In either case, we have no finite roots.
81*c8dee2aaSAndroid Build Coastguard Worker if (!SkIsFinite(R2MinusQ3)) {
82*c8dee2aaSAndroid Build Coastguard Worker return 0;
83*c8dee2aaSAndroid Build Coastguard Worker }
84*c8dee2aaSAndroid Build Coastguard Worker double adiv3 = a / 3;
85*c8dee2aaSAndroid Build Coastguard Worker double r;
86*c8dee2aaSAndroid Build Coastguard Worker double* roots = solution;
87*c8dee2aaSAndroid Build Coastguard Worker if (R2MinusQ3 < 0) { // we have 3 real roots
88*c8dee2aaSAndroid Build Coastguard Worker // the divide/root can, due to finite precisions, be slightly outside of -1...1
89*c8dee2aaSAndroid Build Coastguard Worker const double theta = acos(SkTPin(R / std::sqrt(Q3), -1., 1.));
90*c8dee2aaSAndroid Build Coastguard Worker const double neg2RootQ = -2 * std::sqrt(Q);
91*c8dee2aaSAndroid Build Coastguard Worker
92*c8dee2aaSAndroid Build Coastguard Worker r = neg2RootQ * cos(theta / 3) - adiv3;
93*c8dee2aaSAndroid Build Coastguard Worker *roots++ = r;
94*c8dee2aaSAndroid Build Coastguard Worker
95*c8dee2aaSAndroid Build Coastguard Worker r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
96*c8dee2aaSAndroid Build Coastguard Worker if (!nearly_equal(solution[0], r)) {
97*c8dee2aaSAndroid Build Coastguard Worker *roots++ = r;
98*c8dee2aaSAndroid Build Coastguard Worker }
99*c8dee2aaSAndroid Build Coastguard Worker r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
100*c8dee2aaSAndroid Build Coastguard Worker if (!nearly_equal(solution[0], r) &&
101*c8dee2aaSAndroid Build Coastguard Worker (roots - solution == 1 || !nearly_equal(solution[1], r))) {
102*c8dee2aaSAndroid Build Coastguard Worker *roots++ = r;
103*c8dee2aaSAndroid Build Coastguard Worker }
104*c8dee2aaSAndroid Build Coastguard Worker } else { // we have 1 real root
105*c8dee2aaSAndroid Build Coastguard Worker const double sqrtR2MinusQ3 = std::sqrt(R2MinusQ3);
106*c8dee2aaSAndroid Build Coastguard Worker A = fabs(R) + sqrtR2MinusQ3;
107*c8dee2aaSAndroid Build Coastguard Worker A = std::cbrt(A); // cube root
108*c8dee2aaSAndroid Build Coastguard Worker if (R > 0) {
109*c8dee2aaSAndroid Build Coastguard Worker A = -A;
110*c8dee2aaSAndroid Build Coastguard Worker }
111*c8dee2aaSAndroid Build Coastguard Worker if (!sk_double_nearly_zero(A)) {
112*c8dee2aaSAndroid Build Coastguard Worker A += Q / A;
113*c8dee2aaSAndroid Build Coastguard Worker }
114*c8dee2aaSAndroid Build Coastguard Worker r = A - adiv3;
115*c8dee2aaSAndroid Build Coastguard Worker *roots++ = r;
116*c8dee2aaSAndroid Build Coastguard Worker if (!sk_double_nearly_zero(R2) &&
117*c8dee2aaSAndroid Build Coastguard Worker sk_doubles_nearly_equal_ulps(R2, Q3)) {
118*c8dee2aaSAndroid Build Coastguard Worker r = -A / 2 - adiv3;
119*c8dee2aaSAndroid Build Coastguard Worker if (!nearly_equal(solution[0], r)) {
120*c8dee2aaSAndroid Build Coastguard Worker *roots++ = r;
121*c8dee2aaSAndroid Build Coastguard Worker }
122*c8dee2aaSAndroid Build Coastguard Worker }
123*c8dee2aaSAndroid Build Coastguard Worker }
124*c8dee2aaSAndroid Build Coastguard Worker return static_cast<int>(roots - solution);
125*c8dee2aaSAndroid Build Coastguard Worker }
126*c8dee2aaSAndroid Build Coastguard Worker
RootsValidT(double A,double B,double C,double D,double solution[3])127*c8dee2aaSAndroid Build Coastguard Worker int SkCubics::RootsValidT(double A, double B, double C, double D,
128*c8dee2aaSAndroid Build Coastguard Worker double solution[3]) {
129*c8dee2aaSAndroid Build Coastguard Worker double allRoots[3] = {0, 0, 0};
130*c8dee2aaSAndroid Build Coastguard Worker int realRoots = SkCubics::RootsReal(A, B, C, D, allRoots);
131*c8dee2aaSAndroid Build Coastguard Worker int foundRoots = 0;
132*c8dee2aaSAndroid Build Coastguard Worker for (int index = 0; index < realRoots; ++index) {
133*c8dee2aaSAndroid Build Coastguard Worker double tValue = allRoots[index];
134*c8dee2aaSAndroid Build Coastguard Worker if (tValue >= 1.0 && tValue <= 1.00005) {
135*c8dee2aaSAndroid Build Coastguard Worker // Make sure we do not already have 1 (or something very close) in the list of roots.
136*c8dee2aaSAndroid Build Coastguard Worker if ((foundRoots < 1 || !sk_doubles_nearly_equal_ulps(solution[0], 1)) &&
137*c8dee2aaSAndroid Build Coastguard Worker (foundRoots < 2 || !sk_doubles_nearly_equal_ulps(solution[1], 1))) {
138*c8dee2aaSAndroid Build Coastguard Worker solution[foundRoots++] = 1;
139*c8dee2aaSAndroid Build Coastguard Worker }
140*c8dee2aaSAndroid Build Coastguard Worker } else if (tValue >= -0.00005 && (tValue <= 0.0 || sk_double_nearly_zero(tValue))) {
141*c8dee2aaSAndroid Build Coastguard Worker // Make sure we do not already have 0 (or something very close) in the list of roots.
142*c8dee2aaSAndroid Build Coastguard Worker if ((foundRoots < 1 || !sk_double_nearly_zero(solution[0])) &&
143*c8dee2aaSAndroid Build Coastguard Worker (foundRoots < 2 || !sk_double_nearly_zero(solution[1]))) {
144*c8dee2aaSAndroid Build Coastguard Worker solution[foundRoots++] = 0;
145*c8dee2aaSAndroid Build Coastguard Worker }
146*c8dee2aaSAndroid Build Coastguard Worker } else if (tValue > 0.0 && tValue < 1.0) {
147*c8dee2aaSAndroid Build Coastguard Worker solution[foundRoots++] = tValue;
148*c8dee2aaSAndroid Build Coastguard Worker }
149*c8dee2aaSAndroid Build Coastguard Worker }
150*c8dee2aaSAndroid Build Coastguard Worker return foundRoots;
151*c8dee2aaSAndroid Build Coastguard Worker }
152*c8dee2aaSAndroid Build Coastguard Worker
approximately_zero(double x)153*c8dee2aaSAndroid Build Coastguard Worker static bool approximately_zero(double x) {
154*c8dee2aaSAndroid Build Coastguard Worker // This cutoff for our binary search hopefully strikes a good balance between
155*c8dee2aaSAndroid Build Coastguard Worker // performance and accuracy.
156*c8dee2aaSAndroid Build Coastguard Worker return std::abs(x) < 0.00000001;
157*c8dee2aaSAndroid Build Coastguard Worker }
158*c8dee2aaSAndroid Build Coastguard Worker
find_extrema_valid_t(double A,double B,double C,double t[2])159*c8dee2aaSAndroid Build Coastguard Worker static int find_extrema_valid_t(double A, double B, double C,
160*c8dee2aaSAndroid Build Coastguard Worker double t[2]) {
161*c8dee2aaSAndroid Build Coastguard Worker // To find the local min and max of a cubic, we take the derivative and
162*c8dee2aaSAndroid Build Coastguard Worker // solve when that is equal to 0.
163*c8dee2aaSAndroid Build Coastguard Worker // d/dt (A*t^3 + B*t^2 + C*t + D) = 3A*t^2 + 2B*t + C
164*c8dee2aaSAndroid Build Coastguard Worker double roots[2] = {0, 0};
165*c8dee2aaSAndroid Build Coastguard Worker int numRoots = SkQuads::RootsReal(3*A, 2*B, C, roots);
166*c8dee2aaSAndroid Build Coastguard Worker int validRoots = 0;
167*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < numRoots; i++) {
168*c8dee2aaSAndroid Build Coastguard Worker double tValue = roots[i];
169*c8dee2aaSAndroid Build Coastguard Worker if (tValue >= 0 && tValue <= 1.0) {
170*c8dee2aaSAndroid Build Coastguard Worker t[validRoots++] = tValue;
171*c8dee2aaSAndroid Build Coastguard Worker }
172*c8dee2aaSAndroid Build Coastguard Worker }
173*c8dee2aaSAndroid Build Coastguard Worker return validRoots;
174*c8dee2aaSAndroid Build Coastguard Worker }
175*c8dee2aaSAndroid Build Coastguard Worker
binary_search(double A,double B,double C,double D,double start,double stop)176*c8dee2aaSAndroid Build Coastguard Worker static double binary_search(double A, double B, double C, double D, double start, double stop) {
177*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(start <= stop);
178*c8dee2aaSAndroid Build Coastguard Worker double left = SkCubics::EvalAt(A, B, C, D, start);
179*c8dee2aaSAndroid Build Coastguard Worker if (approximately_zero(left)) {
180*c8dee2aaSAndroid Build Coastguard Worker return start;
181*c8dee2aaSAndroid Build Coastguard Worker }
182*c8dee2aaSAndroid Build Coastguard Worker double right = SkCubics::EvalAt(A, B, C, D, stop);
183*c8dee2aaSAndroid Build Coastguard Worker if (!SkIsFinite(left, right)) {
184*c8dee2aaSAndroid Build Coastguard Worker return -1; // Not going to deal with one or more endpoints being non-finite.
185*c8dee2aaSAndroid Build Coastguard Worker }
186*c8dee2aaSAndroid Build Coastguard Worker if ((left > 0 && right > 0) || (left < 0 && right < 0)) {
187*c8dee2aaSAndroid Build Coastguard Worker return -1; // We can only have a root if one is above 0 and the other is below 0.
188*c8dee2aaSAndroid Build Coastguard Worker }
189*c8dee2aaSAndroid Build Coastguard Worker
190*c8dee2aaSAndroid Build Coastguard Worker constexpr int maxIterations = 1000; // prevent infinite loop
191*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < maxIterations; i++) {
192*c8dee2aaSAndroid Build Coastguard Worker double step = (start + stop) / 2;
193*c8dee2aaSAndroid Build Coastguard Worker double curr = SkCubics::EvalAt(A, B, C, D, step);
194*c8dee2aaSAndroid Build Coastguard Worker if (approximately_zero(curr)) {
195*c8dee2aaSAndroid Build Coastguard Worker return step;
196*c8dee2aaSAndroid Build Coastguard Worker }
197*c8dee2aaSAndroid Build Coastguard Worker if ((curr < 0 && left < 0) || (curr > 0 && left > 0)) {
198*c8dee2aaSAndroid Build Coastguard Worker // go right
199*c8dee2aaSAndroid Build Coastguard Worker start = step;
200*c8dee2aaSAndroid Build Coastguard Worker } else {
201*c8dee2aaSAndroid Build Coastguard Worker // go left
202*c8dee2aaSAndroid Build Coastguard Worker stop = step;
203*c8dee2aaSAndroid Build Coastguard Worker }
204*c8dee2aaSAndroid Build Coastguard Worker }
205*c8dee2aaSAndroid Build Coastguard Worker return -1;
206*c8dee2aaSAndroid Build Coastguard Worker }
207*c8dee2aaSAndroid Build Coastguard Worker
BinarySearchRootsValidT(double A,double B,double C,double D,double solution[3])208*c8dee2aaSAndroid Build Coastguard Worker int SkCubics::BinarySearchRootsValidT(double A, double B, double C, double D,
209*c8dee2aaSAndroid Build Coastguard Worker double solution[3]) {
210*c8dee2aaSAndroid Build Coastguard Worker if (!SkIsFinite(A, B, C, D)) {
211*c8dee2aaSAndroid Build Coastguard Worker return 0;
212*c8dee2aaSAndroid Build Coastguard Worker }
213*c8dee2aaSAndroid Build Coastguard Worker double regions[4] = {0, 0, 0, 1};
214*c8dee2aaSAndroid Build Coastguard Worker // Find local minima and maxima
215*c8dee2aaSAndroid Build Coastguard Worker double minMax[2] = {0, 0};
216*c8dee2aaSAndroid Build Coastguard Worker int extremaCount = find_extrema_valid_t(A, B, C, minMax);
217*c8dee2aaSAndroid Build Coastguard Worker int startIndex = 2 - extremaCount;
218*c8dee2aaSAndroid Build Coastguard Worker if (extremaCount == 1) {
219*c8dee2aaSAndroid Build Coastguard Worker regions[startIndex + 1] = minMax[0];
220*c8dee2aaSAndroid Build Coastguard Worker }
221*c8dee2aaSAndroid Build Coastguard Worker if (extremaCount == 2) {
222*c8dee2aaSAndroid Build Coastguard Worker // While the roots will be in the range 0 to 1 inclusive, they might not be sorted.
223*c8dee2aaSAndroid Build Coastguard Worker regions[startIndex + 1] = std::min(minMax[0], minMax[1]);
224*c8dee2aaSAndroid Build Coastguard Worker regions[startIndex + 2] = std::max(minMax[0], minMax[1]);
225*c8dee2aaSAndroid Build Coastguard Worker }
226*c8dee2aaSAndroid Build Coastguard Worker // Starting at regions[startIndex] and going up through regions[3], we have
227*c8dee2aaSAndroid Build Coastguard Worker // an ascending list of numbers in the range 0 to 1.0, between which are the possible
228*c8dee2aaSAndroid Build Coastguard Worker // locations of a root.
229*c8dee2aaSAndroid Build Coastguard Worker int foundRoots = 0;
230*c8dee2aaSAndroid Build Coastguard Worker for (;startIndex < 3; startIndex++) {
231*c8dee2aaSAndroid Build Coastguard Worker double root = binary_search(A, B, C, D, regions[startIndex], regions[startIndex + 1]);
232*c8dee2aaSAndroid Build Coastguard Worker if (root >= 0) {
233*c8dee2aaSAndroid Build Coastguard Worker // Check for duplicates
234*c8dee2aaSAndroid Build Coastguard Worker if ((foundRoots < 1 || !approximately_zero(solution[0] - root)) &&
235*c8dee2aaSAndroid Build Coastguard Worker (foundRoots < 2 || !approximately_zero(solution[1] - root))) {
236*c8dee2aaSAndroid Build Coastguard Worker solution[foundRoots++] = root;
237*c8dee2aaSAndroid Build Coastguard Worker }
238*c8dee2aaSAndroid Build Coastguard Worker }
239*c8dee2aaSAndroid Build Coastguard Worker }
240*c8dee2aaSAndroid Build Coastguard Worker return foundRoots;
241*c8dee2aaSAndroid Build Coastguard Worker }
242