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