Flutter Engine
The Flutter Engine
SkCubics.cpp
Go to the documentation of this file.
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
13#include "src/base/SkQuads.h"
14
15#include <algorithm>
16#include <cmath>
17
18static constexpr double PI = 3.141592653589793;
19
20static bool nearly_equal(double x, double y) {
23 }
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.
31static bool close_to_a_quadratic(double A, double B) {
34 }
35 return std::abs(A / B) < 1.0e-7;
36}
37
38int 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;
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
127int 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
153static 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
159static 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
176static 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);
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
208int 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}
static int step(int x, SkScalar min, SkScalar max)
Definition: BlurTest.cpp:215
#define SkASSERT(cond)
Definition: SkAssert.h:116
static double binary_search(double A, double B, double C, double D, double start, double stop)
Definition: SkCubics.cpp:176
static constexpr double PI
Definition: SkCubics.cpp:18
static bool close_to_a_quadratic(double A, double B)
Definition: SkCubics.cpp:31
static bool approximately_zero(double x)
Definition: SkCubics.cpp:153
static int find_extrema_valid_t(double A, double B, double C, double t[2])
Definition: SkCubics.cpp:159
static bool nearly_equal(double x, double y)
Definition: SkCubics.cpp:20
bool sk_double_nearly_zero(double a)
static bool SkIsFinite(T x, Pack... values)
static constexpr double sk_ieee_double_divide(double numer, double denom)
bool sk_doubles_nearly_equal_ulps(double a, double b, uint8_t maxUlpsDiff=16)
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition: SkTPin.h:19
static int BinarySearchRootsValidT(double A, double B, double C, double D, double solution[3])
Definition: SkCubics.cpp:208
static double EvalAt(double A, double B, double C, double D, double t)
Definition: SkCubics.h:51
static int RootsValidT(double A, double B, double C, double D, double solution[3])
Definition: SkCubics.cpp:127
static int RootsReal(double A, double B, double C, double D, double solution[3])
Definition: SkCubics.cpp:38
static int RootsReal(double A, double B, double C, double solution[2])
Definition: SkQuads.cpp:135
static bool b
struct MyStruct a[10]
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
#define R(r)
double y
double x
string root
Definition: scale_cpu.py:20
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition: SkVx.h:707
SIN Vec< N, float > sqrt(const Vec< N, float > &x)
Definition: SkVx.h:706