blob: 6fcb348e4ff8fdad349989e9ca218ea70437bbcd [file] [log] [blame]
caryclark@google.com07393ca2013-04-08 11:47:37 +00001/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
caryclark54359292015-03-26 07:52:43 -07007#include "SkGeometry.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +00008#include "SkLineParameters.h"
caryclark1049f122015-04-20 08:31:59 -07009#include "SkPathOpsConic.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +000010#include "SkPathOpsCubic.h"
caryclark03b03ca2015-04-23 09:13:37 -070011#include "SkPathOpsCurve.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +000012#include "SkPathOpsLine.h"
13#include "SkPathOpsQuad.h"
14#include "SkPathOpsRect.h"
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000015#include "SkTSort.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +000016
17const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework
18
caryclark624637c2015-05-11 07:21:27 -070019void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const {
20 if (fPts[endIndex].fX == fPts[ctrlIndex].fX) {
21 dstPt->fX = fPts[endIndex].fX;
22 }
23 if (fPts[endIndex].fY == fPts[ctrlIndex].fY) {
24 dstPt->fY = fPts[endIndex].fY;
25 }
26}
27
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000028// give up when changing t no longer moves point
29// also, copy point rather than recompute it when it does change
30double SkDCubic::binarySearch(double min, double max, double axisIntercept,
31 SearchAxis xAxis) const {
32 double t = (min + max) / 2;
33 double step = (t - min) / 2;
34 SkDPoint cubicAtT = ptAtT(t);
35 double calcPos = (&cubicAtT.fX)[xAxis];
36 double calcDist = calcPos - axisIntercept;
37 do {
38 double priorT = t - step;
39 SkASSERT(priorT >= min);
40 SkDPoint lessPt = ptAtT(priorT);
caryclark54359292015-03-26 07:52:43 -070041 if (approximately_equal_half(lessPt.fX, cubicAtT.fX)
42 && approximately_equal_half(lessPt.fY, cubicAtT.fY)) {
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000043 return -1; // binary search found no point at this axis intercept
44 }
45 double lessDist = (&lessPt.fX)[xAxis] - axisIntercept;
46#if DEBUG_CUBIC_BINARY_SEARCH
47 SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist,
48 step, lessDist);
49#endif
50 double lastStep = step;
51 step /= 2;
52 if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) {
53 t = priorT;
54 } else {
55 double nextT = t + lastStep;
caryclark54359292015-03-26 07:52:43 -070056 if (nextT > max) {
57 return -1;
58 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000059 SkDPoint morePt = ptAtT(nextT);
caryclark54359292015-03-26 07:52:43 -070060 if (approximately_equal_half(morePt.fX, cubicAtT.fX)
61 && approximately_equal_half(morePt.fY, cubicAtT.fY)) {
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000062 return -1; // binary search found no point at this axis intercept
63 }
64 double moreDist = (&morePt.fX)[xAxis] - axisIntercept;
65 if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) {
66 continue;
67 }
68 t = nextT;
69 }
70 SkDPoint testAtT = ptAtT(t);
71 cubicAtT = testAtT;
72 calcPos = (&cubicAtT.fX)[xAxis];
73 calcDist = calcPos - axisIntercept;
74 } while (!approximately_equal(calcPos, axisIntercept));
75 return t;
76}
77
caryclark@google.com07393ca2013-04-08 11:47:37 +000078// FIXME: cache keep the bounds and/or precision with the caller?
79double SkDCubic::calcPrecision() const {
80 SkDRect dRect;
81 dRect.setBounds(*this); // OPTIMIZATION: just use setRawBounds ?
82 double width = dRect.fRight - dRect.fLeft;
83 double height = dRect.fBottom - dRect.fTop;
84 return (width > height ? width : height) / gPrecisionUnit;
85}
86
caryclark624637c2015-05-11 07:21:27 -070087
88/* classic one t subdivision */
89static void interp_cubic_coords(const double* src, double* dst, double t) {
90 double ab = SkDInterp(src[0], src[2], t);
91 double bc = SkDInterp(src[2], src[4], t);
92 double cd = SkDInterp(src[4], src[6], t);
93 double abc = SkDInterp(ab, bc, t);
94 double bcd = SkDInterp(bc, cd, t);
95 double abcd = SkDInterp(abc, bcd, t);
96
97 dst[0] = src[0];
98 dst[2] = ab;
99 dst[4] = abc;
100 dst[6] = abcd;
101 dst[8] = bcd;
102 dst[10] = cd;
103 dst[12] = src[6];
caryclark@google.com07393ca2013-04-08 11:47:37 +0000104}
105
caryclark624637c2015-05-11 07:21:27 -0700106SkDCubicPair SkDCubic::chopAt(double t) const {
107 SkDCubicPair dst;
108 if (t == 0.5) {
109 dst.pts[0] = fPts[0];
110 dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2;
111 dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2;
112 dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4;
113 dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4;
114 dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8;
115 dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8;
116 dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4;
117 dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4;
118 dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2;
119 dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2;
120 dst.pts[6] = fPts[3];
121 return dst;
122 }
123 interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t);
124 interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t);
125 return dst;
caryclark03b03ca2015-04-23 09:13:37 -0700126}
127
caryclark@google.com07393ca2013-04-08 11:47:37 +0000128void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) {
129 *A = src[6]; // d
130 *B = src[4] * 3; // 3*c
131 *C = src[2] * 3; // 3*b
132 *D = src[0]; // a
133 *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d
134 *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c
135 *C -= 3 * *D; // C = -3*a + 3*b
136}
137
caryclark@google.com07393ca2013-04-08 11:47:37 +0000138bool SkDCubic::endsAreExtremaInXOrY() const {
139 return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
140 && between(fPts[0].fX, fPts[2].fX, fPts[3].fX))
141 || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
142 && between(fPts[0].fY, fPts[2].fY, fPts[3].fY));
143}
144
caryclark54359292015-03-26 07:52:43 -0700145// Do a quick reject by rotating all points relative to a line formed by
146// a pair of one cubic's points. If the 2nd cubic's points
147// are on the line or on the opposite side from the 1st cubic's 'odd man', the
148// curves at most intersect at the endpoints.
149/* if returning true, check contains true if cubic's hull collapsed, making the cubic linear
150 if returning false, check contains true if the the cubic pair have only the end point in common
151*/
caryclark1049f122015-04-20 08:31:59 -0700152bool SkDCubic::hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const {
caryclark54359292015-03-26 07:52:43 -0700153 bool linear = true;
154 char hullOrder[4];
155 int hullCount = convexHull(hullOrder);
156 int end1 = hullOrder[0];
157 int hullIndex = 0;
158 const SkDPoint* endPt[2];
159 endPt[0] = &fPts[end1];
160 do {
161 hullIndex = (hullIndex + 1) % hullCount;
162 int end2 = hullOrder[hullIndex];
163 endPt[1] = &fPts[end2];
164 double origX = endPt[0]->fX;
165 double origY = endPt[0]->fY;
166 double adj = endPt[1]->fX - origX;
167 double opp = endPt[1]->fY - origY;
168 int oddManMask = other_two(end1, end2);
169 int oddMan = end1 ^ oddManMask;
170 double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp;
171 int oddMan2 = end2 ^ oddManMask;
172 double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp;
173 if (sign * sign2 < 0) {
174 continue;
175 }
176 if (approximately_zero(sign)) {
177 sign = sign2;
178 if (approximately_zero(sign)) {
179 continue;
180 }
181 }
182 linear = false;
183 bool foundOutlier = false;
caryclark1049f122015-04-20 08:31:59 -0700184 for (int n = 0; n < ptCount; ++n) {
185 double test = (pts[n].fY - origY) * adj - (pts[n].fX - origX) * opp;
caryclark54359292015-03-26 07:52:43 -0700186 if (test * sign > 0 && !precisely_zero(test)) {
187 foundOutlier = true;
188 break;
189 }
190 }
191 if (!foundOutlier) {
192 return false;
193 }
194 endPt[0] = endPt[1];
195 end1 = end2;
196 } while (hullIndex);
197 *isLinear = linear;
198 return true;
199}
200
caryclark1049f122015-04-20 08:31:59 -0700201bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const {
202 return hullIntersects(c2.fPts, c2.kPointCount, isLinear);
203}
204
205bool SkDCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const {
206 return hullIntersects(quad.fPts, quad.kPointCount, isLinear);
207}
208
209bool SkDCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const {
210
211 return hullIntersects(conic.fPts, isLinear);
212}
213
caryclark@google.com07393ca2013-04-08 11:47:37 +0000214bool SkDCubic::isLinear(int startIndex, int endIndex) const {
215 SkLineParameters lineParameters;
216 lineParameters.cubicEndPoints(*this, startIndex, endIndex);
217 // FIXME: maybe it's possible to avoid this and compare non-normalized
218 lineParameters.normalize();
caryclark54359292015-03-26 07:52:43 -0700219 double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY),
220 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
221 double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY),
222 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
223 largest = SkTMax(largest, -tiniest);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000224 double distance = lineParameters.controlPtDistance(*this, 1);
caryclark54359292015-03-26 07:52:43 -0700225 if (!approximately_zero_when_compared_to(distance, largest)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000226 return false;
227 }
228 distance = lineParameters.controlPtDistance(*this, 2);
caryclark54359292015-03-26 07:52:43 -0700229 return approximately_zero_when_compared_to(distance, largest);
230}
231
caryclark6ff734b2015-09-04 05:00:15 -0700232bool SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) {
caryclark54359292015-03-26 07:52:43 -0700233 SkScalar d[3];
234 SkCubicType cubicType = SkClassifyCubic(pointsPtr, d);
235 if (cubicType == kLoop_SkCubicType) {
236 // crib code from gpu path utils that finds t values where loop self-intersects
237 // use it to find mid of t values which should be a friendly place to chop
238 SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]);
239 SkScalar ls = d[1] - tempSqrt;
240 SkScalar lt = 2.f * d[0];
241 SkScalar ms = d[1] + tempSqrt;
242 SkScalar mt = 2.f * d[0];
deanm83496422016-04-26 14:15:21 -0700243 if (roughly_between(0, ls, lt) && roughly_between(0, ms, mt)) {
caryclark54359292015-03-26 07:52:43 -0700244 ls = ls / lt;
245 ms = ms / mt;
deanm83496422016-04-26 14:15:21 -0700246 SkASSERT(roughly_between(0, ls, 1) && roughly_between(0, ms, 1));
247 *t = (ls + ms) / 2;
248 SkASSERT(roughly_between(0, *t, 1));
caryclark54359292015-03-26 07:52:43 -0700249 return *t > 0 && *t < 1;
250 }
caryclark1049f122015-04-20 08:31:59 -0700251 } else if (kSerpentine_SkCubicType == cubicType || kCusp_SkCubicType == cubicType) {
caryclark54359292015-03-26 07:52:43 -0700252 SkDCubic cubic;
253 cubic.set(pointsPtr);
254 double inflectionTs[2];
255 int infTCount = cubic.findInflections(inflectionTs);
256 if (infTCount == 2) {
257 double maxCurvature[3];
258 int roots = cubic.findMaxCurvature(maxCurvature);
caryclark03b03ca2015-04-23 09:13:37 -0700259#if DEBUG_CUBIC_SPLIT
260 SkDebugf("%s\n", __FUNCTION__);
261 cubic.dump();
262 for (int index = 0; index < infTCount; ++index) {
263 SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]);
264 SkDPoint pt = cubic.ptAtT(inflectionTs[index]);
265 SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]);
266 SkDLine perp = {{pt - dPt, pt + dPt}};
267 perp.dump();
268 }
269 for (int index = 0; index < roots; ++index) {
270 SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]);
271 SkDPoint pt = cubic.ptAtT(maxCurvature[index]);
272 SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]);
273 SkDLine perp = {{pt - dPt, pt + dPt}};
274 perp.dump();
275 }
276#endif
caryclark54359292015-03-26 07:52:43 -0700277 for (int index = 0; index < roots; ++index) {
278 if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) {
279 *t = maxCurvature[index];
caryclark55888e42016-07-18 10:01:36 -0700280 return *t > 0 && *t < 1;
caryclark54359292015-03-26 07:52:43 -0700281 }
282 }
283 } else if (infTCount == 1) {
284 *t = inflectionTs[0];
285 return *t > 0 && *t < 1;
286 }
caryclark54359292015-03-26 07:52:43 -0700287 }
288 return false;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000289}
290
caryclarkaec25102015-04-29 08:28:30 -0700291bool SkDCubic::monotonicInX() const {
292 return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
293 && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX);
294}
295
caryclark@google.com07393ca2013-04-08 11:47:37 +0000296bool SkDCubic::monotonicInY() const {
caryclarkaec25102015-04-29 08:28:30 -0700297 return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
298 && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000299}
300
caryclark54359292015-03-26 07:52:43 -0700301void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const {
302 int offset = (int) !SkToBool(index);
303 o1Pts[0] = &fPts[offset];
304 o1Pts[1] = &fPts[++offset];
305 o1Pts[2] = &fPts[++offset];
306}
307
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000308int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept,
309 SearchAxis xAxis, double* validRoots) const {
310 extrema += findInflections(&extremeTs[extrema]);
311 extremeTs[extrema++] = 0;
312 extremeTs[extrema] = 1;
caryclark8a8accb2016-07-22 10:56:26 -0700313 SkASSERT(extrema < 6);
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000314 SkTQSort(extremeTs, extremeTs + extrema);
315 int validCount = 0;
316 for (int index = 0; index < extrema; ) {
317 double min = extremeTs[index];
318 double max = extremeTs[++index];
319 if (min == max) {
320 continue;
321 }
322 double newT = binarySearch(min, max, axisIntercept, xAxis);
323 if (newT >= 0) {
caryclark8a8accb2016-07-22 10:56:26 -0700324 if (validCount >= 3) {
325 return 0;
326 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000327 validRoots[validCount++] = newT;
328 }
329 }
330 return validCount;
331}
332
caryclark@google.com07393ca2013-04-08 11:47:37 +0000333// cubic roots
334
335static const double PI = 3.141592653589793;
336
337// from SkGeometry.cpp (and Numeric Solutions, 5.6)
338int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) {
339 double s[3];
340 int realRoots = RootsReal(A, B, C, D, s);
341 int foundRoots = SkDQuad::AddValidTs(s, realRoots, t);
caryclarkaec25102015-04-29 08:28:30 -0700342 for (int index = 0; index < realRoots; ++index) {
343 double tValue = s[index];
344 if (!approximately_one_or_less(tValue) && between(1, tValue, 1.00005)) {
345 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
346 if (approximately_equal(t[idx2], 1)) {
347 goto nextRoot;
348 }
349 }
350 SkASSERT(foundRoots < 3);
351 t[foundRoots++] = 1;
352 } else if (!approximately_zero_or_more(tValue) && between(-0.00005, tValue, 0)) {
353 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
354 if (approximately_equal(t[idx2], 0)) {
355 goto nextRoot;
356 }
357 }
358 SkASSERT(foundRoots < 3);
359 t[foundRoots++] = 0;
360 }
361nextRoot:
362 ;
363 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000364 return foundRoots;
365}
366
367int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
368#ifdef SK_DEBUG
369 // create a string mathematica understands
370 // GDB set print repe 15 # if repeated digits is a bother
371 // set print elements 400 # if line doesn't fit
372 char str[1024];
373 sk_bzero(str, sizeof(str));
374 SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
375 A, B, C, D);
caryclark@google.com570863f2013-09-16 15:55:01 +0000376 SkPathOpsDebug::MathematicaIze(str, sizeof(str));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000377#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
378 SkDebugf("%s\n", str);
379#endif
380#endif
381 if (approximately_zero(A)
382 && approximately_zero_when_compared_to(A, B)
383 && approximately_zero_when_compared_to(A, C)
384 && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
385 return SkDQuad::RootsReal(B, C, D, s);
386 }
387 if (approximately_zero_when_compared_to(D, A)
388 && approximately_zero_when_compared_to(D, B)
389 && approximately_zero_when_compared_to(D, C)) { // 0 is one root
390 int num = SkDQuad::RootsReal(A, B, C, s);
391 for (int i = 0; i < num; ++i) {
392 if (approximately_zero(s[i])) {
393 return num;
394 }
395 }
396 s[num++] = 0;
397 return num;
398 }
399 if (approximately_zero(A + B + C + D)) { // 1 is one root
400 int num = SkDQuad::RootsReal(A, A + B, -D, s);
401 for (int i = 0; i < num; ++i) {
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000402 if (AlmostDequalUlps(s[i], 1)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000403 return num;
404 }
405 }
406 s[num++] = 1;
407 return num;
408 }
409 double a, b, c;
410 {
411 double invA = 1 / A;
412 a = B * invA;
413 b = C * invA;
414 c = D * invA;
415 }
416 double a2 = a * a;
417 double Q = (a2 - b * 3) / 9;
418 double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
419 double R2 = R * R;
420 double Q3 = Q * Q * Q;
421 double R2MinusQ3 = R2 - Q3;
422 double adiv3 = a / 3;
423 double r;
424 double* roots = s;
425 if (R2MinusQ3 < 0) { // we have 3 real roots
caryclark93ca8842016-05-27 05:24:37 -0700426 // the divide/root can, due to finite precisions, be slightly outside of -1...1
427 double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000428 double neg2RootQ = -2 * sqrt(Q);
429
430 r = neg2RootQ * cos(theta / 3) - adiv3;
431 *roots++ = r;
432
433 r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000434 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000435 *roots++ = r;
436 }
437 r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000438 if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000439 *roots++ = r;
440 }
441 } else { // we have 1 real root
442 double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
443 double A = fabs(R) + sqrtR2MinusQ3;
444 A = SkDCubeRoot(A);
445 if (R > 0) {
446 A = -A;
447 }
448 if (A != 0) {
449 A += Q / A;
450 }
451 r = A - adiv3;
452 *roots++ = r;
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000453 if (AlmostDequalUlps((double) R2, (double) Q3)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000454 r = -A / 2 - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000455 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000456 *roots++ = r;
457 }
458 }
459 }
460 return static_cast<int>(roots - s);
461}
462
463// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf
464// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3
465// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2
466// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2
467static double derivative_at_t(const double* src, double t) {
468 double one_t = 1 - t;
469 double a = src[0];
470 double b = src[2];
471 double c = src[4];
472 double d = src[6];
473 return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
474}
475
476// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
477SkDVector SkDCubic::dxdyAtT(double t) const {
478 SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
caryclark94c902e2015-08-18 07:12:43 -0700479 if (result.fX == 0 && result.fY == 0) {
480 if (t == 0) {
481 result = fPts[2] - fPts[0];
482 } else if (t == 1) {
483 result = fPts[3] - fPts[1];
484 } else {
485 // incomplete
486 SkDebugf("!c");
487 }
488 if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) {
489 result = fPts[3] - fPts[0];
490 }
491 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000492 return result;
493}
494
495// OPTIMIZE? share code with formulate_F1DotF2
496int SkDCubic::findInflections(double tValues[]) const {
497 double Ax = fPts[1].fX - fPts[0].fX;
498 double Ay = fPts[1].fY - fPts[0].fY;
499 double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX;
500 double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY;
501 double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX;
502 double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY;
503 return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues);
504}
505
506static void formulate_F1DotF2(const double src[], double coeff[4]) {
507 double a = src[2] - src[0];
508 double b = src[4] - 2 * src[2] + src[0];
509 double c = src[6] + 3 * (src[2] - src[4]) - src[0];
510 coeff[0] = c * c;
511 coeff[1] = 3 * b * c;
512 coeff[2] = 2 * b * b + c * a;
513 coeff[3] = a * b;
514}
515
516/** SkDCubic'(t) = At^2 + Bt + C, where
517 A = 3(-a + 3(b - c) + d)
518 B = 6(a - 2b + c)
519 C = 3(b - a)
520 Solve for t, keeping only those that fit between 0 < t < 1
521*/
caryclarkaec25102015-04-29 08:28:30 -0700522int SkDCubic::FindExtrema(const double src[], double tValues[2]) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000523 // we divide A,B,C by 3 to simplify
caryclarkaec25102015-04-29 08:28:30 -0700524 double a = src[0];
525 double b = src[2];
526 double c = src[4];
527 double d = src[6];
528 double A = d - a + 3 * (b - c);
529 double B = 2 * (a - b - b + c);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000530 double C = b - a;
531
532 return SkDQuad::RootsValidT(A, B, C, tValues);
533}
534
535/* from SkGeometry.cpp
536 Looking for F' dot F'' == 0
537
538 A = b - a
539 B = c - 2b + a
540 C = d - 3c + 3b - a
541
542 F' = 3Ct^2 + 6Bt + 3A
543 F'' = 6Ct + 6B
544
545 F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
546*/
547int SkDCubic::findMaxCurvature(double tValues[]) const {
548 double coeffX[4], coeffY[4];
549 int i;
550 formulate_F1DotF2(&fPts[0].fX, coeffX);
551 formulate_F1DotF2(&fPts[0].fY, coeffY);
552 for (i = 0; i < 4; i++) {
553 coeffX[i] = coeffX[i] + coeffY[i];
554 }
555 return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues);
556}
557
caryclark@google.com4fdbb222013-07-23 15:27:41 +0000558SkDPoint SkDCubic::ptAtT(double t) const {
559 if (0 == t) {
560 return fPts[0];
561 }
562 if (1 == t) {
563 return fPts[3];
564 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000565 double one_t = 1 - t;
566 double one_t2 = one_t * one_t;
567 double a = one_t2 * one_t;
568 double b = 3 * one_t2 * t;
569 double t2 = t * t;
570 double c = 3 * one_t * t2;
571 double d = t2 * t;
572 SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX,
573 a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY};
574 return result;
575}
576
577/*
578 Given a cubic c, t1, and t2, find a small cubic segment.
579
580 The new cubic is defined as points A, B, C, and D, where
581 s1 = 1 - t1
582 s2 = 1 - t2
583 A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1
584 D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2
585
586 We don't have B or C. So We define two equations to isolate them.
587 First, compute two reference T values 1/3 and 2/3 from t1 to t2:
588
589 c(at (2*t1 + t2)/3) == E
590 c(at (t1 + 2*t2)/3) == F
591
592 Next, compute where those values must be if we know the values of B and C:
593
594 _12 = A*2/3 + B*1/3
595 12_ = A*1/3 + B*2/3
596 _23 = B*2/3 + C*1/3
597 23_ = B*1/3 + C*2/3
598 _34 = C*2/3 + D*1/3
599 34_ = C*1/3 + D*2/3
600 _123 = (A*2/3 + B*1/3)*2/3 + (B*2/3 + C*1/3)*1/3 = A*4/9 + B*4/9 + C*1/9
601 123_ = (A*1/3 + B*2/3)*1/3 + (B*1/3 + C*2/3)*2/3 = A*1/9 + B*4/9 + C*4/9
602 _234 = (B*2/3 + C*1/3)*2/3 + (C*2/3 + D*1/3)*1/3 = B*4/9 + C*4/9 + D*1/9
603 234_ = (B*1/3 + C*2/3)*1/3 + (C*1/3 + D*2/3)*2/3 = B*1/9 + C*4/9 + D*4/9
604 _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3
605 = A*8/27 + B*12/27 + C*6/27 + D*1/27
606 = E
607 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3
608 = A*1/27 + B*6/27 + C*12/27 + D*8/27
609 = F
610 E*27 = A*8 + B*12 + C*6 + D
611 F*27 = A + B*6 + C*12 + D*8
612
613Group the known values on one side:
614
615 M = E*27 - A*8 - D = B*12 + C* 6
616 N = F*27 - A - D*8 = B* 6 + C*12
617 M*2 - N = B*18
618 N*2 - M = C*18
619 B = (M*2 - N)/18
620 C = (N*2 - M)/18
621 */
622
623static double interp_cubic_coords(const double* src, double t) {
624 double ab = SkDInterp(src[0], src[2], t);
625 double bc = SkDInterp(src[2], src[4], t);
626 double cd = SkDInterp(src[4], src[6], t);
627 double abc = SkDInterp(ab, bc, t);
628 double bcd = SkDInterp(bc, cd, t);
629 double abcd = SkDInterp(abc, bcd, t);
630 return abcd;
631}
632
633SkDCubic SkDCubic::subDivide(double t1, double t2) const {
caryclark@google.comd892bd82013-06-17 14:10:36 +0000634 if (t1 == 0 || t2 == 1) {
635 if (t1 == 0 && t2 == 1) {
636 return *this;
637 }
638 SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1);
639 SkDCubic dst = t1 == 0 ? pair.first() : pair.second();
640 return dst;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000641 }
642 SkDCubic dst;
643 double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1);
644 double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1);
645 double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3);
646 double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3);
647 double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3);
648 double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3);
649 double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2);
650 double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2);
651 double mx = ex * 27 - ax * 8 - dx;
652 double my = ey * 27 - ay * 8 - dy;
653 double nx = fx * 27 - ax - dx * 8;
654 double ny = fy * 27 - ay - dy * 8;
655 /* bx = */ dst[1].fX = (mx * 2 - nx) / 18;
656 /* by = */ dst[1].fY = (my * 2 - ny) / 18;
657 /* cx = */ dst[2].fX = (nx * 2 - mx) / 18;
658 /* cy = */ dst[2].fY = (ny * 2 - my) / 18;
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000659 // FIXME: call align() ?
caryclark@google.com07393ca2013-04-08 11:47:37 +0000660 return dst;
661}
662
663void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
664 double t1, double t2, SkDPoint dst[2]) const {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000665 SkASSERT(t1 != t2);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000666 // this approach assumes that the control points computed directly are accurate enough
667 SkDCubic sub = subDivide(t1, t2);
668 dst[0] = sub[1] + (a - sub[0]);
669 dst[1] = sub[2] + (d - sub[3]);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000670 if (t1 == 0 || t2 == 0) {
671 align(0, 1, t1 == 0 ? &dst[0] : &dst[1]);
672 }
673 if (t1 == 1 || t2 == 1) {
674 align(3, 2, t1 == 1 ? &dst[0] : &dst[1]);
675 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000676 if (AlmostBequalUlps(dst[0].fX, a.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000677 dst[0].fX = a.fX;
678 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000679 if (AlmostBequalUlps(dst[0].fY, a.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000680 dst[0].fY = a.fY;
681 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000682 if (AlmostBequalUlps(dst[1].fX, d.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000683 dst[1].fX = d.fX;
684 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000685 if (AlmostBequalUlps(dst[1].fY, d.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000686 dst[1].fY = d.fY;
687 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000688}
689
caryclark624637c2015-05-11 07:21:27 -0700690double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const {
691 double extremeTs[2];
692 double topT = -1;
693 int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs);
694 for (int index = 0; index < roots; ++index) {
695 double t = startT + (endT - startT) * extremeTs[index];
696 SkDPoint mid = dCurve.ptAtT(t);
697 if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) {
698 topT = t;
699 *topPt = mid;
700 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000701 }
caryclark624637c2015-05-11 07:21:27 -0700702 return topT;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000703}