blob: d4a5898a1d16a60e521c14baf66b702cd02faa0f [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 */
caryclarkccec0f92015-03-24 07:28:17 -07007#include "SkGeometry.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +00008#include "SkLineParameters.h"
9#include "SkPathOpsCubic.h"
10#include "SkPathOpsLine.h"
11#include "SkPathOpsQuad.h"
12#include "SkPathOpsRect.h"
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000013#include "SkTSort.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +000014
15const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework
16
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000017// give up when changing t no longer moves point
18// also, copy point rather than recompute it when it does change
19double SkDCubic::binarySearch(double min, double max, double axisIntercept,
20 SearchAxis xAxis) const {
21 double t = (min + max) / 2;
22 double step = (t - min) / 2;
23 SkDPoint cubicAtT = ptAtT(t);
24 double calcPos = (&cubicAtT.fX)[xAxis];
25 double calcDist = calcPos - axisIntercept;
26 do {
27 double priorT = t - step;
28 SkASSERT(priorT >= min);
29 SkDPoint lessPt = ptAtT(priorT);
caryclarkccec0f92015-03-24 07:28:17 -070030 if (approximately_equal_half(lessPt.fX, cubicAtT.fX)
31 && approximately_equal_half(lessPt.fY, cubicAtT.fY)) {
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000032 return -1; // binary search found no point at this axis intercept
33 }
34 double lessDist = (&lessPt.fX)[xAxis] - axisIntercept;
35#if DEBUG_CUBIC_BINARY_SEARCH
36 SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist,
37 step, lessDist);
38#endif
39 double lastStep = step;
40 step /= 2;
41 if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) {
42 t = priorT;
43 } else {
44 double nextT = t + lastStep;
caryclarkccec0f92015-03-24 07:28:17 -070045 if (nextT > max) {
46 return -1;
47 }
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000048 SkDPoint morePt = ptAtT(nextT);
caryclarkccec0f92015-03-24 07:28:17 -070049 if (approximately_equal_half(morePt.fX, cubicAtT.fX)
50 && approximately_equal_half(morePt.fY, cubicAtT.fY)) {
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +000051 return -1; // binary search found no point at this axis intercept
52 }
53 double moreDist = (&morePt.fX)[xAxis] - axisIntercept;
54 if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) {
55 continue;
56 }
57 t = nextT;
58 }
59 SkDPoint testAtT = ptAtT(t);
60 cubicAtT = testAtT;
61 calcPos = (&cubicAtT.fX)[xAxis];
62 calcDist = calcPos - axisIntercept;
63 } while (!approximately_equal(calcPos, axisIntercept));
64 return t;
65}
66
caryclark@google.com07393ca2013-04-08 11:47:37 +000067// FIXME: cache keep the bounds and/or precision with the caller?
68double SkDCubic::calcPrecision() const {
69 SkDRect dRect;
70 dRect.setBounds(*this); // OPTIMIZATION: just use setRawBounds ?
71 double width = dRect.fRight - dRect.fLeft;
72 double height = dRect.fBottom - dRect.fTop;
73 return (width > height ? width : height) / gPrecisionUnit;
74}
75
76bool SkDCubic::clockwise() const {
77 double sum = (fPts[0].fX - fPts[3].fX) * (fPts[0].fY + fPts[3].fY);
78 for (int idx = 0; idx < 3; ++idx) {
79 sum += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY);
80 }
81 return sum <= 0;
82}
83
84void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) {
85 *A = src[6]; // d
86 *B = src[4] * 3; // 3*c
87 *C = src[2] * 3; // 3*b
88 *D = src[0]; // a
89 *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d
90 *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c
91 *C -= 3 * *D; // C = -3*a + 3*b
92}
93
caryclark@google.com07393ca2013-04-08 11:47:37 +000094bool SkDCubic::endsAreExtremaInXOrY() const {
95 return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX)
96 && between(fPts[0].fX, fPts[2].fX, fPts[3].fX))
97 || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
98 && between(fPts[0].fY, fPts[2].fY, fPts[3].fY));
99}
100
caryclarkccec0f92015-03-24 07:28:17 -0700101// Do a quick reject by rotating all points relative to a line formed by
102// a pair of one cubic's points. If the 2nd cubic's points
103// are on the line or on the opposite side from the 1st cubic's 'odd man', the
104// curves at most intersect at the endpoints.
105/* if returning true, check contains true if cubic's hull collapsed, making the cubic linear
106 if returning false, check contains true if the the cubic pair have only the end point in common
107*/
108bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const {
109 bool linear = true;
110 char hullOrder[4];
111 int hullCount = convexHull(hullOrder);
112 int end1 = hullOrder[0];
113 int hullIndex = 0;
114 const SkDPoint* endPt[2];
115 endPt[0] = &fPts[end1];
116 do {
117 hullIndex = (hullIndex + 1) % hullCount;
118 int end2 = hullOrder[hullIndex];
119 endPt[1] = &fPts[end2];
120 double origX = endPt[0]->fX;
121 double origY = endPt[0]->fY;
122 double adj = endPt[1]->fX - origX;
123 double opp = endPt[1]->fY - origY;
124 int oddManMask = other_two(end1, end2);
125 int oddMan = end1 ^ oddManMask;
126 double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp;
127 int oddMan2 = end2 ^ oddManMask;
128 double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp;
129 if (sign * sign2 < 0) {
130 continue;
131 }
132 if (approximately_zero(sign)) {
133 sign = sign2;
134 if (approximately_zero(sign)) {
135 continue;
136 }
137 }
138 linear = false;
139 bool foundOutlier = false;
140 for (int n = 0; n < kPointCount; ++n) {
141 double test = (c2[n].fY - origY) * adj - (c2[n].fX - origX) * opp;
142 if (test * sign > 0 && !precisely_zero(test)) {
143 foundOutlier = true;
144 break;
145 }
146 }
147 if (!foundOutlier) {
148 return false;
149 }
150 endPt[0] = endPt[1];
151 end1 = end2;
152 } while (hullIndex);
153 *isLinear = linear;
154 return true;
155}
156
caryclark@google.com07393ca2013-04-08 11:47:37 +0000157bool SkDCubic::isLinear(int startIndex, int endIndex) const {
158 SkLineParameters lineParameters;
159 lineParameters.cubicEndPoints(*this, startIndex, endIndex);
160 // FIXME: maybe it's possible to avoid this and compare non-normalized
161 lineParameters.normalize();
caryclarkccec0f92015-03-24 07:28:17 -0700162 double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY),
163 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
164 double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY),
165 fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY);
166 largest = SkTMax(largest, -tiniest);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000167 double distance = lineParameters.controlPtDistance(*this, 1);
caryclarkccec0f92015-03-24 07:28:17 -0700168 if (!approximately_zero_when_compared_to(distance, largest)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000169 return false;
170 }
171 distance = lineParameters.controlPtDistance(*this, 2);
caryclarkccec0f92015-03-24 07:28:17 -0700172 return approximately_zero_when_compared_to(distance, largest);
173}
174
175bool SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) {
176 SkScalar d[3];
177 SkCubicType cubicType = SkClassifyCubic(pointsPtr, d);
178 if (cubicType == kLoop_SkCubicType) {
179 // crib code from gpu path utils that finds t values where loop self-intersects
180 // use it to find mid of t values which should be a friendly place to chop
181 SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]);
182 SkScalar ls = d[1] - tempSqrt;
183 SkScalar lt = 2.f * d[0];
184 SkScalar ms = d[1] + tempSqrt;
185 SkScalar mt = 2.f * d[0];
186 if (between(0, ls, lt) || between(0, ms, mt)) {
187 ls = ls / lt;
188 ms = ms / mt;
189 SkScalar smaller = SkTMax(0.f, SkTMin(ls, ms));
190 SkScalar larger = SkTMin(1.f, SkTMax(ls, ms));
191 *t = (smaller + larger) / 2;
192 return *t > 0 && *t < 1;
193 }
194 } else if (cubicType == kSerpentine_SkCubicType) {
195 SkDCubic cubic;
196 cubic.set(pointsPtr);
197 double inflectionTs[2];
198 int infTCount = cubic.findInflections(inflectionTs);
199 if (infTCount == 2) {
200 double maxCurvature[3];
201 int roots = cubic.findMaxCurvature(maxCurvature);
202 for (int index = 0; index < roots; ++index) {
203 if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) {
204 *t = maxCurvature[index];
205 return true;
206 }
207 }
208 } else if (infTCount == 1) {
209 *t = inflectionTs[0];
210 return *t > 0 && *t < 1;
211 }
212 return false;
213 }
214 return false;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000215}
216
217bool SkDCubic::monotonicInY() const {
218 return between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
219 && between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
220}
221
caryclarkccec0f92015-03-24 07:28:17 -0700222void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const {
223 int offset = (int) !SkToBool(index);
224 o1Pts[0] = &fPts[offset];
225 o1Pts[1] = &fPts[++offset];
226 o1Pts[2] = &fPts[++offset];
227}
228
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000229int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept,
230 SearchAxis xAxis, double* validRoots) const {
231 extrema += findInflections(&extremeTs[extrema]);
232 extremeTs[extrema++] = 0;
233 extremeTs[extrema] = 1;
234 SkTQSort(extremeTs, extremeTs + extrema);
235 int validCount = 0;
236 for (int index = 0; index < extrema; ) {
237 double min = extremeTs[index];
238 double max = extremeTs[++index];
239 if (min == max) {
240 continue;
241 }
242 double newT = binarySearch(min, max, axisIntercept, xAxis);
243 if (newT >= 0) {
244 validRoots[validCount++] = newT;
245 }
246 }
247 return validCount;
248}
249
caryclark@google.com07393ca2013-04-08 11:47:37 +0000250// cubic roots
251
252static const double PI = 3.141592653589793;
253
254// from SkGeometry.cpp (and Numeric Solutions, 5.6)
255int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) {
256 double s[3];
257 int realRoots = RootsReal(A, B, C, D, s);
258 int foundRoots = SkDQuad::AddValidTs(s, realRoots, t);
259 return foundRoots;
260}
261
262int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) {
263#ifdef SK_DEBUG
264 // create a string mathematica understands
265 // GDB set print repe 15 # if repeated digits is a bother
266 // set print elements 400 # if line doesn't fit
267 char str[1024];
268 sk_bzero(str, sizeof(str));
269 SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
270 A, B, C, D);
caryclark@google.com570863f2013-09-16 15:55:01 +0000271 SkPathOpsDebug::MathematicaIze(str, sizeof(str));
caryclark@google.com07393ca2013-04-08 11:47:37 +0000272#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
273 SkDebugf("%s\n", str);
274#endif
275#endif
276 if (approximately_zero(A)
277 && approximately_zero_when_compared_to(A, B)
278 && approximately_zero_when_compared_to(A, C)
279 && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic
280 return SkDQuad::RootsReal(B, C, D, s);
281 }
282 if (approximately_zero_when_compared_to(D, A)
283 && approximately_zero_when_compared_to(D, B)
284 && approximately_zero_when_compared_to(D, C)) { // 0 is one root
285 int num = SkDQuad::RootsReal(A, B, C, s);
286 for (int i = 0; i < num; ++i) {
287 if (approximately_zero(s[i])) {
288 return num;
289 }
290 }
291 s[num++] = 0;
292 return num;
293 }
294 if (approximately_zero(A + B + C + D)) { // 1 is one root
295 int num = SkDQuad::RootsReal(A, A + B, -D, s);
296 for (int i = 0; i < num; ++i) {
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000297 if (AlmostDequalUlps(s[i], 1)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000298 return num;
299 }
300 }
301 s[num++] = 1;
302 return num;
303 }
304 double a, b, c;
305 {
306 double invA = 1 / A;
307 a = B * invA;
308 b = C * invA;
309 c = D * invA;
310 }
311 double a2 = a * a;
312 double Q = (a2 - b * 3) / 9;
313 double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
314 double R2 = R * R;
315 double Q3 = Q * Q * Q;
316 double R2MinusQ3 = R2 - Q3;
317 double adiv3 = a / 3;
318 double r;
319 double* roots = s;
320 if (R2MinusQ3 < 0) { // we have 3 real roots
321 double theta = acos(R / sqrt(Q3));
322 double neg2RootQ = -2 * sqrt(Q);
323
324 r = neg2RootQ * cos(theta / 3) - adiv3;
325 *roots++ = r;
326
327 r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000328 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000329 *roots++ = r;
330 }
331 r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000332 if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000333 *roots++ = r;
334 }
335 } else { // we have 1 real root
336 double sqrtR2MinusQ3 = sqrt(R2MinusQ3);
337 double A = fabs(R) + sqrtR2MinusQ3;
338 A = SkDCubeRoot(A);
339 if (R > 0) {
340 A = -A;
341 }
342 if (A != 0) {
343 A += Q / A;
344 }
345 r = A - adiv3;
346 *roots++ = r;
commit-bot@chromium.org2db7fe72014-05-07 15:31:40 +0000347 if (AlmostDequalUlps((double) R2, (double) Q3)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000348 r = -A / 2 - adiv3;
caryclark@google.com7eaa53d2013-10-02 14:49:34 +0000349 if (!AlmostDequalUlps(s[0], r)) {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000350 *roots++ = r;
351 }
352 }
353 }
354 return static_cast<int>(roots - s);
355}
356
357// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf
358// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3
359// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2
360// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2
361static double derivative_at_t(const double* src, double t) {
362 double one_t = 1 - t;
363 double a = src[0];
364 double b = src[2];
365 double c = src[4];
366 double d = src[6];
367 return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
368}
369
370// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
371SkDVector SkDCubic::dxdyAtT(double t) const {
372 SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
373 return result;
374}
375
376// OPTIMIZE? share code with formulate_F1DotF2
377int SkDCubic::findInflections(double tValues[]) const {
378 double Ax = fPts[1].fX - fPts[0].fX;
379 double Ay = fPts[1].fY - fPts[0].fY;
380 double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX;
381 double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY;
382 double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX;
383 double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY;
384 return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues);
385}
386
387static void formulate_F1DotF2(const double src[], double coeff[4]) {
388 double a = src[2] - src[0];
389 double b = src[4] - 2 * src[2] + src[0];
390 double c = src[6] + 3 * (src[2] - src[4]) - src[0];
391 coeff[0] = c * c;
392 coeff[1] = 3 * b * c;
393 coeff[2] = 2 * b * b + c * a;
394 coeff[3] = a * b;
395}
396
397/** SkDCubic'(t) = At^2 + Bt + C, where
398 A = 3(-a + 3(b - c) + d)
399 B = 6(a - 2b + c)
400 C = 3(b - a)
401 Solve for t, keeping only those that fit between 0 < t < 1
402*/
403int SkDCubic::FindExtrema(double a, double b, double c, double d, double tValues[2]) {
404 // we divide A,B,C by 3 to simplify
405 double A = d - a + 3*(b - c);
406 double B = 2*(a - b - b + c);
407 double C = b - a;
408
409 return SkDQuad::RootsValidT(A, B, C, tValues);
410}
411
412/* from SkGeometry.cpp
413 Looking for F' dot F'' == 0
414
415 A = b - a
416 B = c - 2b + a
417 C = d - 3c + 3b - a
418
419 F' = 3Ct^2 + 6Bt + 3A
420 F'' = 6Ct + 6B
421
422 F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
423*/
424int SkDCubic::findMaxCurvature(double tValues[]) const {
425 double coeffX[4], coeffY[4];
426 int i;
427 formulate_F1DotF2(&fPts[0].fX, coeffX);
428 formulate_F1DotF2(&fPts[0].fY, coeffY);
429 for (i = 0; i < 4; i++) {
430 coeffX[i] = coeffX[i] + coeffY[i];
431 }
432 return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues);
433}
434
435SkDPoint SkDCubic::top(double startT, double endT) const {
436 SkDCubic sub = subDivide(startT, endT);
437 SkDPoint topPt = sub[0];
438 if (topPt.fY > sub[3].fY || (topPt.fY == sub[3].fY && topPt.fX > sub[3].fX)) {
439 topPt = sub[3];
440 }
441 double extremeTs[2];
442 if (!sub.monotonicInY()) {
443 int roots = FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, sub[3].fY, extremeTs);
444 for (int index = 0; index < roots; ++index) {
445 double t = startT + (endT - startT) * extremeTs[index];
caryclark@google.com4fdbb222013-07-23 15:27:41 +0000446 SkDPoint mid = ptAtT(t);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000447 if (topPt.fY > mid.fY || (topPt.fY == mid.fY && topPt.fX > mid.fX)) {
448 topPt = mid;
449 }
450 }
451 }
452 return topPt;
453}
454
caryclark@google.com4fdbb222013-07-23 15:27:41 +0000455SkDPoint SkDCubic::ptAtT(double t) const {
456 if (0 == t) {
457 return fPts[0];
458 }
459 if (1 == t) {
460 return fPts[3];
461 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000462 double one_t = 1 - t;
463 double one_t2 = one_t * one_t;
464 double a = one_t2 * one_t;
465 double b = 3 * one_t2 * t;
466 double t2 = t * t;
467 double c = 3 * one_t * t2;
468 double d = t2 * t;
469 SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX,
470 a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY};
471 return result;
472}
473
474/*
475 Given a cubic c, t1, and t2, find a small cubic segment.
476
477 The new cubic is defined as points A, B, C, and D, where
478 s1 = 1 - t1
479 s2 = 1 - t2
480 A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1
481 D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2
482
483 We don't have B or C. So We define two equations to isolate them.
484 First, compute two reference T values 1/3 and 2/3 from t1 to t2:
485
486 c(at (2*t1 + t2)/3) == E
487 c(at (t1 + 2*t2)/3) == F
488
489 Next, compute where those values must be if we know the values of B and C:
490
491 _12 = A*2/3 + B*1/3
492 12_ = A*1/3 + B*2/3
493 _23 = B*2/3 + C*1/3
494 23_ = B*1/3 + C*2/3
495 _34 = C*2/3 + D*1/3
496 34_ = C*1/3 + D*2/3
497 _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
498 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
499 _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
500 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
501 _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3
502 = A*8/27 + B*12/27 + C*6/27 + D*1/27
503 = E
504 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3
505 = A*1/27 + B*6/27 + C*12/27 + D*8/27
506 = F
507 E*27 = A*8 + B*12 + C*6 + D
508 F*27 = A + B*6 + C*12 + D*8
509
510Group the known values on one side:
511
512 M = E*27 - A*8 - D = B*12 + C* 6
513 N = F*27 - A - D*8 = B* 6 + C*12
514 M*2 - N = B*18
515 N*2 - M = C*18
516 B = (M*2 - N)/18
517 C = (N*2 - M)/18
518 */
519
520static double interp_cubic_coords(const double* src, double t) {
521 double ab = SkDInterp(src[0], src[2], t);
522 double bc = SkDInterp(src[2], src[4], t);
523 double cd = SkDInterp(src[4], src[6], t);
524 double abc = SkDInterp(ab, bc, t);
525 double bcd = SkDInterp(bc, cd, t);
526 double abcd = SkDInterp(abc, bcd, t);
527 return abcd;
528}
529
530SkDCubic SkDCubic::subDivide(double t1, double t2) const {
caryclark@google.comd892bd82013-06-17 14:10:36 +0000531 if (t1 == 0 || t2 == 1) {
532 if (t1 == 0 && t2 == 1) {
533 return *this;
534 }
535 SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1);
536 SkDCubic dst = t1 == 0 ? pair.first() : pair.second();
537 return dst;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000538 }
539 SkDCubic dst;
540 double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1);
541 double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1);
542 double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3);
543 double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3);
544 double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3);
545 double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3);
546 double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2);
547 double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2);
548 double mx = ex * 27 - ax * 8 - dx;
549 double my = ey * 27 - ay * 8 - dy;
550 double nx = fx * 27 - ax - dx * 8;
551 double ny = fy * 27 - ay - dy * 8;
552 /* bx = */ dst[1].fX = (mx * 2 - nx) / 18;
553 /* by = */ dst[1].fY = (my * 2 - ny) / 18;
554 /* cx = */ dst[2].fX = (nx * 2 - mx) / 18;
555 /* cy = */ dst[2].fY = (ny * 2 - my) / 18;
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000556 // FIXME: call align() ?
caryclark@google.com07393ca2013-04-08 11:47:37 +0000557 return dst;
558}
559
skia.committer@gmail.com8f6ef402013-06-05 07:01:06 +0000560void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000561 if (fPts[endIndex].fX == fPts[ctrlIndex].fX) {
562 dstPt->fX = fPts[endIndex].fX;
563 }
564 if (fPts[endIndex].fY == fPts[ctrlIndex].fY) {
565 dstPt->fY = fPts[endIndex].fY;
566 }
567}
568
caryclark@google.com07393ca2013-04-08 11:47:37 +0000569void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
570 double t1, double t2, SkDPoint dst[2]) const {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000571 SkASSERT(t1 != t2);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000572 // this approach assumes that the control points computed directly are accurate enough
573 SkDCubic sub = subDivide(t1, t2);
574 dst[0] = sub[1] + (a - sub[0]);
575 dst[1] = sub[2] + (d - sub[3]);
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000576 if (t1 == 0 || t2 == 0) {
577 align(0, 1, t1 == 0 ? &dst[0] : &dst[1]);
578 }
579 if (t1 == 1 || t2 == 1) {
580 align(3, 2, t1 == 1 ? &dst[0] : &dst[1]);
581 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000582 if (AlmostBequalUlps(dst[0].fX, a.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000583 dst[0].fX = a.fX;
584 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000585 if (AlmostBequalUlps(dst[0].fY, a.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000586 dst[0].fY = a.fY;
587 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000588 if (AlmostBequalUlps(dst[1].fX, d.fX)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000589 dst[1].fX = d.fX;
590 }
commit-bot@chromium.org4431e772014-04-14 17:08:59 +0000591 if (AlmostBequalUlps(dst[1].fY, d.fY)) {
caryclark@google.comcffbcc32013-06-04 17:59:42 +0000592 dst[1].fY = d.fY;
593 }
caryclark@google.com07393ca2013-04-08 11:47:37 +0000594}
595
596/* classic one t subdivision */
597static void interp_cubic_coords(const double* src, double* dst, double t) {
598 double ab = SkDInterp(src[0], src[2], t);
599 double bc = SkDInterp(src[2], src[4], t);
600 double cd = SkDInterp(src[4], src[6], t);
601 double abc = SkDInterp(ab, bc, t);
602 double bcd = SkDInterp(bc, cd, t);
603 double abcd = SkDInterp(abc, bcd, t);
604
605 dst[0] = src[0];
606 dst[2] = ab;
607 dst[4] = abc;
608 dst[6] = abcd;
609 dst[8] = bcd;
610 dst[10] = cd;
611 dst[12] = src[6];
612}
613
614SkDCubicPair SkDCubic::chopAt(double t) const {
615 SkDCubicPair dst;
616 if (t == 0.5) {
617 dst.pts[0] = fPts[0];
618 dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2;
619 dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2;
620 dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4;
621 dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4;
622 dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8;
623 dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8;
624 dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4;
625 dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4;
626 dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2;
627 dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2;
628 dst.pts[6] = fPts[3];
629 return dst;
630 }
631 interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t);
632 interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t);
633 return dst;
634}