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