blob: bdae492de07b65a0ff4136a4ff25a78787544ae3 [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 {
caryclarke3a4e992016-09-28 09:22:17 -0700215 if (fPts[0].approximatelyDEqual(fPts[3])) {
216 return ((const SkDQuad *) this)->isLinear(0, 2);
217 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000218 SkLineParameters lineParameters;
219 lineParameters.cubicEndPoints(*this, startIndex, endIndex);
220 // FIXME: maybe it's possible to avoid this and compare non-normalized
221 lineParameters.normalize();
caryclark54359292015-03-26 07:52:43 -0700222 double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY),
223 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
224 double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY),
225 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
226 largest = SkTMax(largest, -tiniest);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000227 double distance = lineParameters.controlPtDistance(*this, 1);
caryclark54359292015-03-26 07:52:43 -0700228 if (!approximately_zero_when_compared_to(distance, largest)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000229 return false;
230 }
231 distance = lineParameters.controlPtDistance(*this, 2);
caryclark54359292015-03-26 07:52:43 -0700232 return approximately_zero_when_compared_to(distance, largest);
233}
234
caryclark6ff734b2015-09-04 05:00:15 -0700235bool SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) {
caryclark54359292015-03-26 07:52:43 -0700236 SkScalar d[3];
237 SkCubicType cubicType = SkClassifyCubic(pointsPtr, d);
238 if (cubicType == kLoop_SkCubicType) {
239 // crib code from gpu path utils that finds t values where loop self-intersects
240 // use it to find mid of t values which should be a friendly place to chop
241 SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]);
242 SkScalar ls = d[1] - tempSqrt;
243 SkScalar lt = 2.f * d[0];
244 SkScalar ms = d[1] + tempSqrt;
245 SkScalar mt = 2.f * d[0];
deanm83496422016-04-26 14:15:21 -0700246 if (roughly_between(0, ls, lt) && roughly_between(0, ms, mt)) {
caryclark54359292015-03-26 07:52:43 -0700247 ls = ls / lt;
248 ms = ms / mt;
deanm83496422016-04-26 14:15:21 -0700249 SkASSERT(roughly_between(0, ls, 1) && roughly_between(0, ms, 1));
250 *t = (ls + ms) / 2;
251 SkASSERT(roughly_between(0, *t, 1));
caryclark54359292015-03-26 07:52:43 -0700252 return *t > 0 && *t < 1;
253 }
caryclark1049f122015-04-20 08:31:59 -0700254 } else if (kSerpentine_SkCubicType == cubicType || kCusp_SkCubicType == cubicType) {
caryclark54359292015-03-26 07:52:43 -0700255 SkDCubic cubic;
256 cubic.set(pointsPtr);
257 double inflectionTs[2];
258 int infTCount = cubic.findInflections(inflectionTs);
259 if (infTCount == 2) {
260 double maxCurvature[3];
261 int roots = cubic.findMaxCurvature(maxCurvature);
caryclark03b03ca2015-04-23 09:13:37 -0700262#if DEBUG_CUBIC_SPLIT
263 SkDebugf("%s\n", __FUNCTION__);
264 cubic.dump();
265 for (int index = 0; index < infTCount; ++index) {
266 SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]);
267 SkDPoint pt = cubic.ptAtT(inflectionTs[index]);
268 SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]);
269 SkDLine perp = {{pt - dPt, pt + dPt}};
270 perp.dump();
271 }
272 for (int index = 0; index < roots; ++index) {
273 SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]);
274 SkDPoint pt = cubic.ptAtT(maxCurvature[index]);
275 SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]);
276 SkDLine perp = {{pt - dPt, pt + dPt}};
277 perp.dump();
278 }
279#endif
caryclark54359292015-03-26 07:52:43 -0700280 for (int index = 0; index < roots; ++index) {
281 if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) {
282 *t = maxCurvature[index];
caryclark55888e42016-07-18 10:01:36 -0700283 return *t > 0 && *t < 1;
caryclark54359292015-03-26 07:52:43 -0700284 }
285 }
286 } else if (infTCount == 1) {
287 *t = inflectionTs[0];
288 return *t > 0 && *t < 1;
289 }
caryclark54359292015-03-26 07:52:43 -0700290 }
291 return false;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000292}
293
caryclarkaec25102015-04-29 08:28:30 -0700294bool SkDCubic::monotonicInX() const {
295 return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
296 && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX);
297}
298
caryclark@google.com07393ca2013-04-08 11:47:37 +0000299bool SkDCubic::monotonicInY() const {
caryclarkaec25102015-04-29 08:28:30 -0700300 return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
301 && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000302}
303
caryclark54359292015-03-26 07:52:43 -0700304void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const {
305 int offset = (int) !SkToBool(index);
306 o1Pts[0] = &fPts[offset];
307 o1Pts[1] = &fPts[++offset];
308 o1Pts[2] = &fPts[++offset];
309}
310
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000311int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept,
312 SearchAxis xAxis, double* validRoots) const {
313 extrema += findInflections(&extremeTs[extrema]);
314 extremeTs[extrema++] = 0;
315 extremeTs[extrema] = 1;
caryclark8a8accb2016-07-22 10:56:26 -0700316 SkASSERT(extrema < 6);
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000317 SkTQSort(extremeTs, extremeTs + extrema);
318 int validCount = 0;
319 for (int index = 0; index < extrema; ) {
320 double min = extremeTs[index];
321 double max = extremeTs[++index];
322 if (min == max) {
323 continue;
324 }
325 double newT = binarySearch(min, max, axisIntercept, xAxis);
326 if (newT >= 0) {
caryclark8a8accb2016-07-22 10:56:26 -0700327 if (validCount >= 3) {
328 return 0;
329 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000330 validRoots[validCount++] = newT;
331 }
332 }
333 return validCount;
334}
335
caryclark@google.com07393ca2013-04-08 11:47:37 +0000336// cubic roots
337
338static const double PI = 3.141592653589793;
339
340// from SkGeometry.cpp (and Numeric Solutions, 5.6)
341int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) {
342 double s[3];
343 int realRoots = RootsReal(A, B, C, D, s);
344 int foundRoots = SkDQuad::AddValidTs(s, realRoots, t);
caryclarkaec25102015-04-29 08:28:30 -0700345 for (int index = 0; index < realRoots; ++index) {
346 double tValue = s[index];
347 if (!approximately_one_or_less(tValue) && between(1, tValue, 1.00005)) {
348 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
349 if (approximately_equal(t[idx2], 1)) {
350 goto nextRoot;
351 }
352 }
353 SkASSERT(foundRoots < 3);
354 t[foundRoots++] = 1;
355 } else if (!approximately_zero_or_more(tValue) && between(-0.00005, tValue, 0)) {
356 for (int idx2 = 0; idx2 < foundRoots; ++idx2) {
357 if (approximately_equal(t[idx2], 0)) {
358 goto nextRoot;
359 }
360 }
361 SkASSERT(foundRoots < 3);
362 t[foundRoots++] = 0;
363 }
364nextRoot:
365 ;
366 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000367 return foundRoots;
368}
369
370int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
371#ifdef SK_DEBUG
372 // create a string mathematica understands
373 // GDB set print repe 15 # if repeated digits is a bother
374 // set print elements 400 # if line doesn't fit
375 char str[1024];
376 sk_bzero(str, sizeof(str));
377 SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
378 A, B, C, D);
caryclark@google.com570863f2013-09-16 15:55:01 +0000379 SkPathOpsDebug::MathematicaIze(str, sizeof(str));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000380#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
381 SkDebugf("%s\n", str);
382#endif
383#endif
384 if (approximately_zero(A)
385 && approximately_zero_when_compared_to(A, B)
386 && approximately_zero_when_compared_to(A, C)
387 && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
388 return SkDQuad::RootsReal(B, C, D, s);
389 }
390 if (approximately_zero_when_compared_to(D, A)
391 && approximately_zero_when_compared_to(D, B)
392 && approximately_zero_when_compared_to(D, C)) { // 0 is one root
393 int num = SkDQuad::RootsReal(A, B, C, s);
394 for (int i = 0; i < num; ++i) {
395 if (approximately_zero(s[i])) {
396 return num;
397 }
398 }
399 s[num++] = 0;
400 return num;
401 }
402 if (approximately_zero(A + B + C + D)) { // 1 is one root
403 int num = SkDQuad::RootsReal(A, A + B, -D, s);
404 for (int i = 0; i < num; ++i) {
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000405 if (AlmostDequalUlps(s[i], 1)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000406 return num;
407 }
408 }
409 s[num++] = 1;
410 return num;
411 }
412 double a, b, c;
413 {
414 double invA = 1 / A;
415 a = B * invA;
416 b = C * invA;
417 c = D * invA;
418 }
419 double a2 = a * a;
420 double Q = (a2 - b * 3) / 9;
421 double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
422 double R2 = R * R;
423 double Q3 = Q * Q * Q;
424 double R2MinusQ3 = R2 - Q3;
425 double adiv3 = a / 3;
426 double r;
427 double* roots = s;
428 if (R2MinusQ3 < 0) { // we have 3 real roots
caryclark93ca8842016-05-27 05:24:37 -0700429 // the divide/root can, due to finite precisions, be slightly outside of -1...1
430 double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000431 double neg2RootQ = -2 * sqrt(Q);
432
433 r = neg2RootQ * cos(theta / 3) - adiv3;
434 *roots++ = r;
435
436 r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000437 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000438 *roots++ = r;
439 }
440 r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000441 if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000442 *roots++ = r;
443 }
444 } else { // we have 1 real root
445 double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
446 double A = fabs(R) + sqrtR2MinusQ3;
447 A = SkDCubeRoot(A);
448 if (R > 0) {
449 A = -A;
450 }
451 if (A != 0) {
452 A += Q / A;
453 }
454 r = A - adiv3;
455 *roots++ = r;
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000456 if (AlmostDequalUlps((double) R2, (double) Q3)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000457 r = -A / 2 - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000458 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000459 *roots++ = r;
460 }
461 }
462 }
463 return static_cast<int>(roots - s);
464}
465
466// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf
467// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3
468// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2
469// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2
470static double derivative_at_t(const double* src, double t) {
471 double one_t = 1 - t;
472 double a = src[0];
473 double b = src[2];
474 double c = src[4];
475 double d = src[6];
476 return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
477}
478
479// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
480SkDVector SkDCubic::dxdyAtT(double t) const {
481 SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
caryclark94c902e2015-08-18 07:12:43 -0700482 if (result.fX == 0 && result.fY == 0) {
483 if (t == 0) {
484 result = fPts[2] - fPts[0];
485 } else if (t == 1) {
486 result = fPts[3] - fPts[1];
487 } else {
488 // incomplete
489 SkDebugf("!c");
490 }
491 if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) {
492 result = fPts[3] - fPts[0];
493 }
494 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000495 return result;
496}
497
498// OPTIMIZE? share code with formulate_F1DotF2
499int SkDCubic::findInflections(double tValues[]) const {
500 double Ax = fPts[1].fX - fPts[0].fX;
501 double Ay = fPts[1].fY - fPts[0].fY;
502 double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX;
503 double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY;
504 double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX;
505 double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY;
506 return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues);
507}
508
509static void formulate_F1DotF2(const double src[], double coeff[4]) {
510 double a = src[2] - src[0];
511 double b = src[4] - 2 * src[2] + src[0];
512 double c = src[6] + 3 * (src[2] - src[4]) - src[0];
513 coeff[0] = c * c;
514 coeff[1] = 3 * b * c;
515 coeff[2] = 2 * b * b + c * a;
516 coeff[3] = a * b;
517}
518
519/** SkDCubic'(t) = At^2 + Bt + C, where
520 A = 3(-a + 3(b - c) + d)
521 B = 6(a - 2b + c)
522 C = 3(b - a)
523 Solve for t, keeping only those that fit between 0 < t < 1
524*/
caryclarkaec25102015-04-29 08:28:30 -0700525int SkDCubic::FindExtrema(const double src[], double tValues[2]) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000526 // we divide A,B,C by 3 to simplify
caryclarkaec25102015-04-29 08:28:30 -0700527 double a = src[0];
528 double b = src[2];
529 double c = src[4];
530 double d = src[6];
531 double A = d - a + 3 * (b - c);
532 double B = 2 * (a - b - b + c);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000533 double C = b - a;
534
535 return SkDQuad::RootsValidT(A, B, C, tValues);
536}
537
538/* from SkGeometry.cpp
539 Looking for F' dot F'' == 0
540
541 A = b - a
542 B = c - 2b + a
543 C = d - 3c + 3b - a
544
545 F' = 3Ct^2 + 6Bt + 3A
546 F'' = 6Ct + 6B
547
548 F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
549*/
550int SkDCubic::findMaxCurvature(double tValues[]) const {
551 double coeffX[4], coeffY[4];
552 int i;
553 formulate_F1DotF2(&fPts[0].fX, coeffX);
554 formulate_F1DotF2(&fPts[0].fY, coeffY);
555 for (i = 0; i < 4; i++) {
556 coeffX[i] = coeffX[i] + coeffY[i];
557 }
558 return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues);
559}
560
caryclark@google.com4fdbb222013-07-23 15:27:41 +0000561SkDPoint SkDCubic::ptAtT(double t) const {
562 if (0 == t) {
563 return fPts[0];
564 }
565 if (1 == t) {
566 return fPts[3];
567 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000568 double one_t = 1 - t;
569 double one_t2 = one_t * one_t;
570 double a = one_t2 * one_t;
571 double b = 3 * one_t2 * t;
572 double t2 = t * t;
573 double c = 3 * one_t * t2;
574 double d = t2 * t;
575 SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX,
576 a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY};
577 return result;
578}
579
580/*
581 Given a cubic c, t1, and t2, find a small cubic segment.
582
583 The new cubic is defined as points A, B, C, and D, where
584 s1 = 1 - t1
585 s2 = 1 - t2
586 A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1
587 D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2
588
589 We don't have B or C. So We define two equations to isolate them.
590 First, compute two reference T values 1/3 and 2/3 from t1 to t2:
591
592 c(at (2*t1 + t2)/3) == E
593 c(at (t1 + 2*t2)/3) == F
594
595 Next, compute where those values must be if we know the values of B and C:
596
597 _12 = A*2/3 + B*1/3
598 12_ = A*1/3 + B*2/3
599 _23 = B*2/3 + C*1/3
600 23_ = B*1/3 + C*2/3
601 _34 = C*2/3 + D*1/3
602 34_ = C*1/3 + D*2/3
603 _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
604 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
605 _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
606 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
607 _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3
608 = A*8/27 + B*12/27 + C*6/27 + D*1/27
609 = E
610 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3
611 = A*1/27 + B*6/27 + C*12/27 + D*8/27
612 = F
613 E*27 = A*8 + B*12 + C*6 + D
614 F*27 = A + B*6 + C*12 + D*8
615
616Group the known values on one side:
617
618 M = E*27 - A*8 - D = B*12 + C* 6
619 N = F*27 - A - D*8 = B* 6 + C*12
620 M*2 - N = B*18
621 N*2 - M = C*18
622 B = (M*2 - N)/18
623 C = (N*2 - M)/18
624 */
625
626static double interp_cubic_coords(const double* src, double t) {
627 double ab = SkDInterp(src[0], src[2], t);
628 double bc = SkDInterp(src[2], src[4], t);
629 double cd = SkDInterp(src[4], src[6], t);
630 double abc = SkDInterp(ab, bc, t);
631 double bcd = SkDInterp(bc, cd, t);
632 double abcd = SkDInterp(abc, bcd, t);
633 return abcd;
634}
635
636SkDCubic SkDCubic::subDivide(double t1, double t2) const {
caryclark@google.comd892bd82013-06-17 14:10:36 +0000637 if (t1 == 0 || t2 == 1) {
638 if (t1 == 0 && t2 == 1) {
639 return *this;
640 }
641 SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1);
642 SkDCubic dst = t1 == 0 ? pair.first() : pair.second();
643 return dst;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000644 }
645 SkDCubic dst;
646 double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1);
647 double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1);
648 double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3);
649 double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3);
650 double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3);
651 double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3);
652 double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2);
653 double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2);
654 double mx = ex * 27 - ax * 8 - dx;
655 double my = ey * 27 - ay * 8 - dy;
656 double nx = fx * 27 - ax - dx * 8;
657 double ny = fy * 27 - ay - dy * 8;
658 /* bx = */ dst[1].fX = (mx * 2 - nx) / 18;
659 /* by = */ dst[1].fY = (my * 2 - ny) / 18;
660 /* cx = */ dst[2].fX = (nx * 2 - mx) / 18;
661 /* cy = */ dst[2].fY = (ny * 2 - my) / 18;
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000662 // FIXME: call align() ?
caryclark@google.com07393ca2013-04-08 11:47:37 +0000663 return dst;
664}
665
666void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
667 double t1, double t2, SkDPoint dst[2]) const {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000668 SkASSERT(t1 != t2);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000669 // this approach assumes that the control points computed directly are accurate enough
670 SkDCubic sub = subDivide(t1, t2);
671 dst[0] = sub[1] + (a - sub[0]);
672 dst[1] = sub[2] + (d - sub[3]);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000673 if (t1 == 0 || t2 == 0) {
674 align(0, 1, t1 == 0 ? &dst[0] : &dst[1]);
675 }
676 if (t1 == 1 || t2 == 1) {
677 align(3, 2, t1 == 1 ? &dst[0] : &dst[1]);
678 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000679 if (AlmostBequalUlps(dst[0].fX, a.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000680 dst[0].fX = a.fX;
681 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000682 if (AlmostBequalUlps(dst[0].fY, a.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000683 dst[0].fY = a.fY;
684 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000685 if (AlmostBequalUlps(dst[1].fX, d.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000686 dst[1].fX = d.fX;
687 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000688 if (AlmostBequalUlps(dst[1].fY, d.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000689 dst[1].fY = d.fY;
690 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000691}
692
caryclark624637c2015-05-11 07:21:27 -0700693double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const {
694 double extremeTs[2];
695 double topT = -1;
696 int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs);
697 for (int index = 0; index < roots; ++index) {
698 double t = startT + (endT - startT) * extremeTs[index];
699 SkDPoint mid = dCurve.ptAtT(t);
700 if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) {
701 topT = t;
702 *topPt = mid;
703 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000704 }
caryclark624637c2015-05-11 07:21:27 -0700705 return topT;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000706}