diff options
author | 2015-03-24 13:55:33 -0700 | |
---|---|---|
committer | 2015-03-24 13:55:33 -0700 | |
commit | 0dc4dd6dda9a7912f696b46d9c02155ec1d1ba5f (patch) | |
tree | 994c85a8e418986415175ddccc71adf924df3846 /src/pathops | |
parent | 82dec0e16ae10026194ce45b67af931700510450 (diff) |
Revert of pathops version two (patchset #16 id:150001 of https://codereview.chromium.org/1002693002/)
Reason for revert:
ASAN investigation
Original issue's description:
> pathops version two
>
> R=reed@google.com
>
> marked 'no commit' to attempt to get trybots to run
>
> TBR=reed@google.com
>
> Committed: https://skia.googlesource.com/skia/+/ccec0f958ffc71a9986d236bc2eb335cb2111119
TBR=caryclark@google.com
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
Review URL: https://codereview.chromium.org/1029993002
Diffstat (limited to 'src/pathops')
55 files changed, 10086 insertions, 6077 deletions
diff --git a/src/pathops/SkAddIntersections.cpp b/src/pathops/SkAddIntersections.cpp index b507eb7560..c27434f9f7 100644 --- a/src/pathops/SkAddIntersections.cpp +++ b/src/pathops/SkAddIntersections.cpp @@ -5,7 +5,6 @@ * found in the LICENSE file. */ #include "SkAddIntersections.h" -#include "SkOpCoincidence.h" #include "SkPathOpsBounds.h" #if DEBUG_ADD_INTERSECTING_TS @@ -131,6 +130,20 @@ static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt, SkDebugf("\n"); } +static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no self intersect " CUBIC_DEBUG_STR "\n", __FUNCTION__, + CUBIC_DEBUG_DATA(wt.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + SkDebugf(" " T_DEBUG_STR(wtTs, 1), i[1][0]); + SkDebugf("\n"); +} + #else static void debugShowLineIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { @@ -155,10 +168,13 @@ static void debugShowCubicQuadIntersection(int , const SkIntersectionHelper& , static void debugShowCubicIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } + +static void debugShowCubicIntersection(int , const SkIntersectionHelper& , + const SkIntersections& ) { +} #endif -bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence, - SkChunkAlloc* allocator) { +bool AddIntersectTs(SkOpContour* test, SkOpContour* next) { if (test != next) { if (AlmostLessUlps(test->bounds().fBottom, next->bounds().fTop)) { return false; @@ -170,11 +186,10 @@ bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coinc } SkIntersectionHelper wt; wt.init(test); + bool foundCommonContour = test == next; do { SkIntersectionHelper wn; wn.init(next); - test->debugValidate(); - next->debugValidate(); if (test == next && !wn.startAfter(wt)) { continue; } @@ -291,22 +306,14 @@ bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coinc break; } case SkIntersectionHelper::kQuad_Segment: { - SkDQuad quad1; - quad1.set(wt.pts()); - SkDQuad quad2; - quad2.set(wn.pts()); - pts = ts.intersect(quad1, quad2); + pts = ts.quadQuad(wt.pts(), wn.pts()); + ts.alignQuadPts(wt.pts(), wn.pts()); debugShowQuadIntersection(pts, wt, wn, ts); break; } case SkIntersectionHelper::kCubic_Segment: { swap = true; - SkDQuad quad1; - quad1.set(wt.pts()); - SkDCubic cubic1 = quad1.toCubic(); - SkDCubic cubic2; - cubic2.set(wn.pts()); - pts = ts.intersect(cubic2, cubic1); + pts = ts.cubicQuad(wn.pts(), wt.pts()); debugShowCubicQuadIntersection(pts, wn, wt, ts); break; } @@ -332,21 +339,12 @@ bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coinc break; } case SkIntersectionHelper::kQuad_Segment: { - SkDCubic cubic1; - cubic1.set(wt.pts()); - SkDQuad quad2; - quad2.set(wn.pts()); - SkDCubic cubic2 = quad2.toCubic(); - pts = ts.intersect(cubic1, cubic2); + pts = ts.cubicQuad(wt.pts(), wn.pts()); debugShowCubicQuadIntersection(pts, wt, wn, ts); break; } case SkIntersectionHelper::kCubic_Segment: { - SkDCubic cubic1; - cubic1.set(wt.pts()); - SkDCubic cubic2; - cubic2.set(wn.pts()); - pts = ts.intersect(cubic1, cubic2); + pts = ts.cubicCubic(wt.pts(), wn.pts()); debugShowCubicIntersection(pts, wt, wn, ts); break; } @@ -357,53 +355,102 @@ bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coinc default: SkASSERT(0); } - int coinIndex = -1; - SkOpPtT* coinPtT[2]; + if (!foundCommonContour && pts > 0) { + test->addCross(next); + next->addCross(test); + foundCommonContour = true; + } + // in addition to recording T values, record matching segment + if (pts == 2) { + if (wn.segmentType() <= SkIntersectionHelper::kLine_Segment + && wt.segmentType() <= SkIntersectionHelper::kLine_Segment) { + if (wt.addCoincident(wn, ts, swap)) { + continue; + } + pts = ts.cleanUpCoincidence(); // prefer (t == 0 or t == 1) + } else if (wn.segmentType() >= SkIntersectionHelper::kQuad_Segment + && wt.segmentType() >= SkIntersectionHelper::kQuad_Segment + && ts.isCoincident(0)) { + SkASSERT(ts.coincidentUsed() == 2); + if (wt.addCoincident(wn, ts, swap)) { + continue; + } + pts = ts.cleanUpCoincidence(); // prefer (t == 0 or t == 1) + } + } + if (pts >= 2) { + for (int pt = 0; pt < pts - 1; ++pt) { + const SkDPoint& point = ts.pt(pt); + const SkDPoint& next = ts.pt(pt + 1); + if (wt.isPartial(ts[swap][pt], ts[swap][pt + 1], point, next) + && wn.isPartial(ts[!swap][pt], ts[!swap][pt + 1], point, next)) { + if (!wt.addPartialCoincident(wn, ts, pt, swap)) { + // remove extra point if two map to same float values + pts = ts.cleanUpCoincidence(); // prefer (t == 0 or t == 1) + } + } + } + } for (int pt = 0; pt < pts; ++pt) { SkASSERT(ts[0][pt] >= 0 && ts[0][pt] <= 1); SkASSERT(ts[1][pt] >= 0 && ts[1][pt] <= 1); - wt.segment()->debugValidate(); - SkOpPtT* testTAt = wt.segment()->addT(ts[swap][pt], SkOpSegment::kAllowAlias, - allocator); - wn.segment()->debugValidate(); - SkOpPtT* nextTAt = wn.segment()->addT(ts[!swap][pt], SkOpSegment::kAllowAlias, - allocator); - testTAt->addOpp(nextTAt); - if (testTAt->fPt != nextTAt->fPt) { - testTAt->span()->unaligned(); - nextTAt->span()->unaligned(); - } - wt.segment()->debugValidate(); - wn.segment()->debugValidate(); - if (!ts.isCoincident(pt)) { - continue; - } - if (coinIndex < 0) { - coinPtT[0] = testTAt; - coinPtT[1] = nextTAt; - coinIndex = pt; - continue; - } - if (coinPtT[0]->span() == testTAt->span()) { - coinIndex = -1; - continue; - } - if (coinPtT[1]->span() == nextTAt->span()) { - coinIndex = -1; // coincidence span collapsed - continue; - } - if (swap) { - SkTSwap(coinPtT[0], coinPtT[1]); - SkTSwap(testTAt, nextTAt); - } - SkASSERT(coinPtT[0]->span()->t() < testTAt->span()->t()); - coincidence->add(coinPtT[0], testTAt, coinPtT[1], nextTAt, allocator); - wt.segment()->debugValidate(); - wn.segment()->debugValidate(); - coinIndex = -1; + SkPoint point = ts.pt(pt).asSkPoint(); + wt.alignTPt(wn, swap, pt, &ts, &point); + int testTAt = wt.addT(wn, point, ts[swap][pt]); + int nextTAt = wn.addT(wt, point, ts[!swap][pt]); + wt.addOtherT(testTAt, ts[!swap][pt], nextTAt); + wn.addOtherT(nextTAt, ts[swap][pt], testTAt); } - SkASSERT(coinIndex < 0); // expect coincidence to be paired } while (wn.advance()); } while (wt.advance()); return true; } + +void AddSelfIntersectTs(SkOpContour* test) { + SkIntersectionHelper wt; + wt.init(test); + do { + if (wt.segmentType() != SkIntersectionHelper::kCubic_Segment) { + continue; + } + SkIntersections ts; + int pts = ts.cubic(wt.pts()); + debugShowCubicIntersection(pts, wt, ts); + if (!pts) { + continue; + } + SkASSERT(pts == 1); + SkASSERT(ts[0][0] >= 0 && ts[0][0] <= 1); + SkASSERT(ts[1][0] >= 0 && ts[1][0] <= 1); + SkPoint point = ts.pt(0).asSkPoint(); + int testTAt = wt.addSelfT(point, ts[0][0]); + int nextTAt = wt.addSelfT(point, ts[1][0]); + wt.addOtherT(testTAt, ts[1][0], nextTAt); + wt.addOtherT(nextTAt, ts[0][0], testTAt); + } while (wt.advance()); +} + +// resolve any coincident pairs found while intersecting, and +// see if coincidence is formed by clipping non-concident segments +bool CoincidenceCheck(SkTArray<SkOpContour*, true>* contourList, int total) { + int contourCount = (*contourList).count(); + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = (*contourList)[cIndex]; + contour->resolveNearCoincidence(); + } + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = (*contourList)[cIndex]; + contour->addCoincidentPoints(); + } + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = (*contourList)[cIndex]; + if (!contour->calcCoincidentWinding()) { + return false; + } + } + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = (*contourList)[cIndex]; + contour->calcPartialCoincidentWinding(); + } + return true; +} diff --git a/src/pathops/SkAddIntersections.h b/src/pathops/SkAddIntersections.h index 23654e5e91..4c1947b635 100644 --- a/src/pathops/SkAddIntersections.h +++ b/src/pathops/SkAddIntersections.h @@ -9,10 +9,10 @@ #include "SkIntersectionHelper.h" #include "SkIntersections.h" +#include "SkTArray.h" -class SkOpCoincidence; - -bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence, - SkChunkAlloc* allocator); +bool AddIntersectTs(SkOpContour* test, SkOpContour* next); +void AddSelfIntersectTs(SkOpContour* test); +bool CoincidenceCheck(SkTArray<SkOpContour*, true>* contourList, int total); #endif diff --git a/src/pathops/SkDCubicIntersection.cpp b/src/pathops/SkDCubicIntersection.cpp new file mode 100644 index 0000000000..2fb35e1827 --- /dev/null +++ b/src/pathops/SkDCubicIntersection.cpp @@ -0,0 +1,704 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkIntersections.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsPoint.h" +#include "SkPathOpsQuad.h" +#include "SkPathOpsRect.h" +#include "SkReduceOrder.h" +#include "SkTSort.h" + +#if ONE_OFF_DEBUG +static const double tLimits1[2][2] = {{0.3, 0.4}, {0.8, 0.9}}; +static const double tLimits2[2][2] = {{-0.8, -0.9}, {-0.8, -0.9}}; +#endif + +#define DEBUG_QUAD_PART ONE_OFF_DEBUG && 1 +#define DEBUG_QUAD_PART_SHOW_SIMPLE DEBUG_QUAD_PART && 0 +#define SWAP_TOP_DEBUG 0 + +static const int kCubicToQuadSubdivisionDepth = 8; // slots reserved for cubic to quads subdivision + +static int quadPart(const SkDCubic& cubic, double tStart, double tEnd, SkReduceOrder* reducer) { + SkDCubic part = cubic.subDivide(tStart, tEnd); + SkDQuad quad = part.toQuad(); + // FIXME: should reduceOrder be looser in this use case if quartic is going to blow up on an + // extremely shallow quadratic? + int order = reducer->reduce(quad); +#if DEBUG_QUAD_PART + SkDebugf("%s cubic=(%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)" + " t=(%1.9g,%1.9g)\n", __FUNCTION__, cubic[0].fX, cubic[0].fY, + cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY, + cubic[3].fX, cubic[3].fY, tStart, tEnd); + SkDebugf(" {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n" + " {{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}},\n", + part[0].fX, part[0].fY, part[1].fX, part[1].fY, part[2].fX, part[2].fY, + part[3].fX, part[3].fY, quad[0].fX, quad[0].fY, + quad[1].fX, quad[1].fY, quad[2].fX, quad[2].fY); +#if DEBUG_QUAD_PART_SHOW_SIMPLE + SkDebugf("%s simple=(%1.9g,%1.9g", __FUNCTION__, reducer->fQuad[0].fX, reducer->fQuad[0].fY); + if (order > 1) { + SkDebugf(" %1.9g,%1.9g", reducer->fQuad[1].fX, reducer->fQuad[1].fY); + } + if (order > 2) { + SkDebugf(" %1.9g,%1.9g", reducer->fQuad[2].fX, reducer->fQuad[2].fY); + } + SkDebugf(")\n"); + SkASSERT(order < 4 && order > 0); +#endif +#endif + return order; +} + +static void intersectWithOrder(const SkDQuad& simple1, int order1, const SkDQuad& simple2, + int order2, SkIntersections& i) { + if (order1 == 3 && order2 == 3) { + i.intersect(simple1, simple2); + } else if (order1 <= 2 && order2 <= 2) { + i.intersect((const SkDLine&) simple1, (const SkDLine&) simple2); + } else if (order1 == 3 && order2 <= 2) { + i.intersect(simple1, (const SkDLine&) simple2); + } else { + SkASSERT(order1 <= 2 && order2 == 3); + i.intersect(simple2, (const SkDLine&) simple1); + i.swapPts(); + } +} + +// this flavor centers potential intersections recursively. In contrast, '2' may inadvertently +// chase intersections near quadratic ends, requiring odd hacks to find them. +static void intersect(const SkDCubic& cubic1, double t1s, double t1e, const SkDCubic& cubic2, + double t2s, double t2e, double precisionScale, SkIntersections& i) { + i.upDepth(); + SkDCubic c1 = cubic1.subDivide(t1s, t1e); + SkDCubic c2 = cubic2.subDivide(t2s, t2e); + SkSTArray<kCubicToQuadSubdivisionDepth, double, true> ts1; + // OPTIMIZE: if c1 == c2, call once (happens when detecting self-intersection) + c1.toQuadraticTs(c1.calcPrecision() * precisionScale, &ts1); + SkSTArray<kCubicToQuadSubdivisionDepth, double, true> ts2; + c2.toQuadraticTs(c2.calcPrecision() * precisionScale, &ts2); + double t1Start = t1s; + int ts1Count = ts1.count(); + for (int i1 = 0; i1 <= ts1Count; ++i1) { + const double tEnd1 = i1 < ts1Count ? ts1[i1] : 1; + const double t1 = t1s + (t1e - t1s) * tEnd1; + SkReduceOrder s1; + int o1 = quadPart(cubic1, t1Start, t1, &s1); + double t2Start = t2s; + int ts2Count = ts2.count(); + for (int i2 = 0; i2 <= ts2Count; ++i2) { + const double tEnd2 = i2 < ts2Count ? ts2[i2] : 1; + const double t2 = t2s + (t2e - t2s) * tEnd2; + if (&cubic1 == &cubic2 && t1Start >= t2Start) { + t2Start = t2; + continue; + } + SkReduceOrder s2; + int o2 = quadPart(cubic2, t2Start, t2, &s2); + #if ONE_OFF_DEBUG + char tab[] = " "; + if (tLimits1[0][0] >= t1Start && tLimits1[0][1] <= t1 + && tLimits1[1][0] >= t2Start && tLimits1[1][1] <= t2) { + SkDebugf("%.*s %s t1=(%1.9g,%1.9g) t2=(%1.9g,%1.9g)", i.depth()*2, tab, + __FUNCTION__, t1Start, t1, t2Start, t2); + SkIntersections xlocals; + xlocals.allowNear(false); + xlocals.allowFlatMeasure(true); + intersectWithOrder(s1.fQuad, o1, s2.fQuad, o2, xlocals); + SkDebugf(" xlocals.fUsed=%d\n", xlocals.used()); + } + #endif + SkIntersections locals; + locals.allowNear(false); + locals.allowFlatMeasure(true); + intersectWithOrder(s1.fQuad, o1, s2.fQuad, o2, locals); + int tCount = locals.used(); + for (int tIdx = 0; tIdx < tCount; ++tIdx) { + double to1 = t1Start + (t1 - t1Start) * locals[0][tIdx]; + double to2 = t2Start + (t2 - t2Start) * locals[1][tIdx]; + // if the computed t is not sufficiently precise, iterate + SkDPoint p1 = cubic1.ptAtT(to1); + SkDPoint p2 = cubic2.ptAtT(to2); + if (p1.approximatelyEqual(p2)) { + // FIXME: local edge may be coincident -- experiment with not propagating coincidence to caller +// SkASSERT(!locals.isCoincident(tIdx)); + if (&cubic1 != &cubic2 || !approximately_equal(to1, to2)) { + if (i.swapped()) { // FIXME: insert should respect swap + i.insert(to2, to1, p1); + } else { + i.insert(to1, to2, p1); + } + } + } else { +/*for random cubics, 16 below catches 99.997% of the intersections. To test for the remaining 0.003% + look for nearly coincident curves. and check each 1/16th section. +*/ + double offset = precisionScale / 16; // FIXME: const is arbitrary: test, refine + double c1Bottom = tIdx == 0 ? 0 : + (t1Start + (t1 - t1Start) * locals[0][tIdx - 1] + to1) / 2; + double c1Min = SkTMax(c1Bottom, to1 - offset); + double c1Top = tIdx == tCount - 1 ? 1 : + (t1Start + (t1 - t1Start) * locals[0][tIdx + 1] + to1) / 2; + double c1Max = SkTMin(c1Top, to1 + offset); + double c2Min = SkTMax(0., to2 - offset); + double c2Max = SkTMin(1., to2 + offset); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 1 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, + __FUNCTION__, + c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max + && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, + to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset + && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, + c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max + && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, + to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset + && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); + SkDebugf("%.*s %s 1 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" + " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", + i.depth()*2, tab, __FUNCTION__, c1Bottom, c1Top, 0., 1., + to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); + SkDebugf("%.*s %s 1 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" + " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, + c1Max, c2Min, c2Max); + #endif + intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 1 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, + i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1); + #endif + if (tCount > 1) { + c1Min = SkTMax(0., to1 - offset); + c1Max = SkTMin(1., to1 + offset); + double c2Bottom = tIdx == 0 ? to2 : + (t2Start + (t2 - t2Start) * locals[1][tIdx - 1] + to2) / 2; + double c2Top = tIdx == tCount - 1 ? to2 : + (t2Start + (t2 - t2Start) * locals[1][tIdx + 1] + to2) / 2; + if (c2Bottom > c2Top) { + SkTSwap(c2Bottom, c2Top); + } + if (c2Bottom == to2) { + c2Bottom = 0; + } + if (c2Top == to2) { + c2Top = 1; + } + c2Min = SkTMax(c2Bottom, to2 - offset); + c2Max = SkTMin(c2Top, to2 + offset); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 2 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, + __FUNCTION__, + c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max + && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, + to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset + && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, + c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max + && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, + to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset + && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); + SkDebugf("%.*s %s 2 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" + " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", + i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top, + to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); + SkDebugf("%.*s %s 2 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" + " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, + c1Max, c2Min, c2Max); + #endif + intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 2 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, + i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1); + #endif + c1Min = SkTMax(c1Bottom, to1 - offset); + c1Max = SkTMin(c1Top, to1 + offset); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 3 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, + __FUNCTION__, + c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max + && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, + to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset + && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, + c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max + && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, + to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset + && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); + SkDebugf("%.*s %s 3 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" + " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", + i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top, + to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); + SkDebugf("%.*s %s 3 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" + " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, + c1Max, c2Min, c2Max); + #endif + intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 3 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, + i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1); + #endif + } + // intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); + // FIXME: if no intersection is found, either quadratics intersected where + // cubics did not, or the intersection was missed. In the former case, expect + // the quadratics to be nearly parallel at the point of intersection, and check + // for that. + } + } + t2Start = t2; + } + t1Start = t1; + } + i.downDepth(); +} + + // if two ends intersect, check middle for coincidence +bool SkIntersections::cubicCheckCoincidence(const SkDCubic& c1, const SkDCubic& c2) { + if (fUsed < 2) { + return false; + } + int last = fUsed - 1; + double tRange1 = fT[0][last] - fT[0][0]; + double tRange2 = fT[1][last] - fT[1][0]; + for (int index = 1; index < 5; ++index) { + double testT1 = fT[0][0] + tRange1 * index / 5; + double testT2 = fT[1][0] + tRange2 * index / 5; + SkDPoint testPt1 = c1.ptAtT(testT1); + SkDPoint testPt2 = c2.ptAtT(testT2); + if (!testPt1.approximatelyEqual(testPt2)) { + return false; + } + } + if (fUsed > 2) { + fPt[1] = fPt[last]; + fT[0][1] = fT[0][last]; + fT[1][1] = fT[1][last]; + fUsed = 2; + } + fIsCoincident[0] = fIsCoincident[1] = 0x03; + return true; +} + +#define LINE_FRACTION 0.1 + +// intersect the end of the cubic with the other. Try lines from the end to control and opposite +// end to determine range of t on opposite cubic. +bool SkIntersections::cubicExactEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2) { + int t1Index = start ? 0 : 3; + double testT = (double) !start; + bool swap = swapped(); + // quad/quad at this point checks to see if exact matches have already been found + // cubic/cubic can't reject so easily since cubics can intersect same point more than once + SkDLine tmpLine; + tmpLine[0] = tmpLine[1] = cubic2[t1Index]; + tmpLine[1].fX += cubic2[2 - start].fY - cubic2[t1Index].fY; + tmpLine[1].fY -= cubic2[2 - start].fX - cubic2[t1Index].fX; + SkIntersections impTs; + impTs.allowNear(false); + impTs.allowFlatMeasure(true); + impTs.intersectRay(cubic1, tmpLine); + for (int index = 0; index < impTs.used(); ++index) { + SkDPoint realPt = impTs.pt(index); + if (!tmpLine[0].approximatelyEqual(realPt)) { + continue; + } + if (swap) { + cubicInsert(testT, impTs[0][index], tmpLine[0], cubic2, cubic1); + } else { + cubicInsert(impTs[0][index], testT, tmpLine[0], cubic1, cubic2); + } + return true; + } + return false; +} + + +void SkIntersections::cubicInsert(double one, double two, const SkDPoint& pt, + const SkDCubic& cubic1, const SkDCubic& cubic2) { + for (int index = 0; index < fUsed; ++index) { + if (fT[0][index] == one) { + double oldTwo = fT[1][index]; + if (oldTwo == two) { + return; + } + SkDPoint mid = cubic2.ptAtT((oldTwo + two) / 2); + if (mid.approximatelyEqual(fPt[index])) { + return; + } + } + if (fT[1][index] == two) { + SkDPoint mid = cubic1.ptAtT((fT[0][index] + two) / 2); + if (mid.approximatelyEqual(fPt[index])) { + return; + } + } + } + insert(one, two, pt); +} + +void SkIntersections::cubicNearEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, + const SkDRect& bounds2) { + SkDLine line; + int t1Index = start ? 0 : 3; + double testT = (double) !start; + // don't bother if the two cubics are connnected + static const int kPointsInCubic = 4; // FIXME: move to DCubic, replace '4' with this + static const int kMaxLineCubicIntersections = 3; + SkSTArray<(kMaxLineCubicIntersections - 1) * kMaxLineCubicIntersections, double, true> tVals; + line[0] = cubic1[t1Index]; + // this variant looks for intersections with the end point and lines parallel to other points + for (int index = 0; index < kPointsInCubic; ++index) { + if (index == t1Index) { + continue; + } + SkDVector dxy1 = cubic1[index] - line[0]; + dxy1 /= SkDCubic::gPrecisionUnit; + line[1] = line[0] + dxy1; + SkDRect lineBounds; + lineBounds.setBounds(line); + if (!bounds2.intersects(&lineBounds)) { + continue; + } + SkIntersections local; + if (!local.intersect(cubic2, line)) { + continue; + } + for (int idx2 = 0; idx2 < local.used(); ++idx2) { + double foundT = local[0][idx2]; + if (approximately_less_than_zero(foundT) + || approximately_greater_than_one(foundT)) { + continue; + } + if (local.pt(idx2).approximatelyEqual(line[0])) { + if (swapped()) { // FIXME: insert should respect swap + insert(foundT, testT, line[0]); + } else { + insert(testT, foundT, line[0]); + } + } else { + tVals.push_back(foundT); + } + } + } + if (tVals.count() == 0) { + return; + } + SkTQSort<double>(tVals.begin(), tVals.end() - 1); + double tMin1 = start ? 0 : 1 - LINE_FRACTION; + double tMax1 = start ? LINE_FRACTION : 1; + int tIdx = 0; + do { + int tLast = tIdx; + while (tLast + 1 < tVals.count() && roughly_equal(tVals[tLast + 1], tVals[tIdx])) { + ++tLast; + } + double tMin2 = SkTMax(tVals[tIdx] - LINE_FRACTION, 0.0); + double tMax2 = SkTMin(tVals[tLast] + LINE_FRACTION, 1.0); + int lastUsed = used(); + if (start ? tMax1 < tMin2 : tMax2 < tMin1) { + ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); + } + if (lastUsed == used()) { + tMin2 = SkTMax(tVals[tIdx] - (1.0 / SkDCubic::gPrecisionUnit), 0.0); + tMax2 = SkTMin(tVals[tLast] + (1.0 / SkDCubic::gPrecisionUnit), 1.0); + if (start ? tMax1 < tMin2 : tMax2 < tMin1) { + ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); + } + } + tIdx = tLast + 1; + } while (tIdx < tVals.count()); + return; +} + +const double CLOSE_ENOUGH = 0.001; + +static bool closeStart(const SkDCubic& cubic, int cubicIndex, SkIntersections& i, SkDPoint& pt) { + if (i[cubicIndex][0] != 0 || i[cubicIndex][1] > CLOSE_ENOUGH) { + return false; + } + pt = cubic.ptAtT((i[cubicIndex][0] + i[cubicIndex][1]) / 2); + return true; +} + +static bool closeEnd(const SkDCubic& cubic, int cubicIndex, SkIntersections& i, SkDPoint& pt) { + int last = i.used() - 1; + if (i[cubicIndex][last] != 1 || i[cubicIndex][last - 1] < 1 - CLOSE_ENOUGH) { + return false; + } + pt = cubic.ptAtT((i[cubicIndex][last] + i[cubicIndex][last - 1]) / 2); + return true; +} + +static bool only_end_pts_in_common(const SkDCubic& c1, const SkDCubic& c2) { +// the idea here is to see at minimum do a quick reject by rotating all points +// to either side of the line formed by connecting the endpoints +// if the opposite curves points are on the line or on the other side, the +// curves at most intersect at the endpoints + for (int oddMan = 0; oddMan < 4; ++oddMan) { + const SkDPoint* endPt[3]; + for (int opp = 1; opp < 4; ++opp) { + int end = oddMan ^ opp; // choose a value not equal to oddMan + endPt[opp - 1] = &c1[end]; + } + for (int triTest = 0; triTest < 3; ++triTest) { + double origX = endPt[triTest]->fX; + double origY = endPt[triTest]->fY; + int oppTest = triTest + 1; + if (3 == oppTest) { + oppTest = 0; + } + double adj = endPt[oppTest]->fX - origX; + double opp = endPt[oppTest]->fY - origY; + if (adj == 0 && opp == 0) { // if the other point equals the test point, ignore it + continue; + } + double sign = (c1[oddMan].fY - origY) * adj - (c1[oddMan].fX - origX) * opp; + if (approximately_zero(sign)) { + goto tryNextHalfPlane; + } + for (int n = 0; n < 4; ++n) { + double test = (c2[n].fY - origY) * adj - (c2[n].fX - origX) * opp; + if (test * sign > 0 && !precisely_zero(test)) { + goto tryNextHalfPlane; + } + } + } + return true; +tryNextHalfPlane: + ; + } + return false; +} + +int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) { + if (fMax == 0) { + fMax = 9; + } + bool selfIntersect = &c1 == &c2; + if (selfIntersect) { + if (c1[0].approximatelyEqual(c1[3])) { + insert(0, 1, c1[0]); + return fUsed; + } + } else { + // OPTIMIZATION: set exact end bits here to avoid cubic exact end later + for (int i1 = 0; i1 < 4; i1 += 3) { + for (int i2 = 0; i2 < 4; i2 += 3) { + if (c1[i1].approximatelyEqual(c2[i2])) { + insert(i1 >> 1, i2 >> 1, c1[i1]); + } + } + } + } + SkASSERT(fUsed < 4); + if (!selfIntersect) { + if (only_end_pts_in_common(c1, c2)) { + return fUsed; + } + if (only_end_pts_in_common(c2, c1)) { + return fUsed; + } + } + // quad/quad does linear test here -- cubic does not + // cubics which are really lines should have been detected in reduce step earlier + int exactEndBits = 0; + if (selfIntersect) { + if (fUsed) { + return fUsed; + } + } else { + exactEndBits |= cubicExactEnd(c1, false, c2) << 0; + exactEndBits |= cubicExactEnd(c1, true, c2) << 1; + swap(); + exactEndBits |= cubicExactEnd(c2, false, c1) << 2; + exactEndBits |= cubicExactEnd(c2, true, c1) << 3; + swap(); + } + if (cubicCheckCoincidence(c1, c2)) { + SkASSERT(!selfIntersect); + return fUsed; + } + // FIXME: pass in cached bounds from caller + SkDRect c2Bounds; + c2Bounds.setBounds(c2); + if (!(exactEndBits & 4)) { + cubicNearEnd(c1, false, c2, c2Bounds); + } + if (!(exactEndBits & 8)) { + if (selfIntersect && fUsed) { + return fUsed; + } + cubicNearEnd(c1, true, c2, c2Bounds); + if (selfIntersect && fUsed && ((approximately_less_than_zero(fT[0][0]) + && approximately_less_than_zero(fT[1][0])) + || (approximately_greater_than_one(fT[0][0]) + && approximately_greater_than_one(fT[1][0])))) { + SkASSERT(fUsed == 1); + fUsed = 0; + return fUsed; + } + } + if (!selfIntersect) { + SkDRect c1Bounds; + c1Bounds.setBounds(c1); // OPTIMIZE use setRawBounds ? + swap(); + if (!(exactEndBits & 1)) { + cubicNearEnd(c2, false, c1, c1Bounds); + } + if (!(exactEndBits & 2)) { + cubicNearEnd(c2, true, c1, c1Bounds); + } + swap(); + } + if (cubicCheckCoincidence(c1, c2)) { + SkASSERT(!selfIntersect); + return fUsed; + } + SkIntersections i; + i.fAllowNear = false; + i.fFlatMeasure = true; + i.fMax = 9; + ::intersect(c1, 0, 1, c2, 0, 1, 1, i); + int compCount = i.used(); + if (compCount) { + int exactCount = used(); + if (exactCount == 0) { + *this = i; + } else { + // at least one is exact or near, and at least one was computed. Eliminate duplicates + for (int exIdx = 0; exIdx < exactCount; ++exIdx) { + for (int cpIdx = 0; cpIdx < compCount; ) { + if (fT[0][0] == i[0][0] && fT[1][0] == i[1][0]) { + i.removeOne(cpIdx); + --compCount; + continue; + } + double tAvg = (fT[0][exIdx] + i[0][cpIdx]) / 2; + SkDPoint pt = c1.ptAtT(tAvg); + if (!pt.approximatelyEqual(fPt[exIdx])) { + ++cpIdx; + continue; + } + tAvg = (fT[1][exIdx] + i[1][cpIdx]) / 2; + pt = c2.ptAtT(tAvg); + if (!pt.approximatelyEqual(fPt[exIdx])) { + ++cpIdx; + continue; + } + i.removeOne(cpIdx); + --compCount; + } + } + // if mid t evaluates to nearly the same point, skip the t + for (int cpIdx = 0; cpIdx < compCount - 1; ) { + double tAvg = (fT[0][cpIdx] + i[0][cpIdx + 1]) / 2; + SkDPoint pt = c1.ptAtT(tAvg); + if (!pt.approximatelyEqual(fPt[cpIdx])) { + ++cpIdx; + continue; + } + tAvg = (fT[1][cpIdx] + i[1][cpIdx + 1]) / 2; + pt = c2.ptAtT(tAvg); + if (!pt.approximatelyEqual(fPt[cpIdx])) { + ++cpIdx; + continue; + } + i.removeOne(cpIdx); + --compCount; + } + // in addition to adding below missing function, think about how to say + append(i); + } + } + // If an end point and a second point very close to the end is returned, the second + // point may have been detected because the approximate quads + // intersected at the end and close to it. Verify that the second point is valid. + if (fUsed <= 1) { + return fUsed; + } + SkDPoint pt[2]; + if (closeStart(c1, 0, *this, pt[0]) && closeStart(c2, 1, *this, pt[1]) + && pt[0].approximatelyEqual(pt[1])) { + removeOne(1); + } + if (closeEnd(c1, 0, *this, pt[0]) && closeEnd(c2, 1, *this, pt[1]) + && pt[0].approximatelyEqual(pt[1])) { + removeOne(used() - 2); + } + // vet the pairs of t values to see if the mid value is also on the curve. If so, mark + // the span as coincident + if (fUsed >= 2 && !coincidentUsed()) { + int last = fUsed - 1; + int match = 0; + for (int index = 0; index < last; ++index) { + double mid1 = (fT[0][index] + fT[0][index + 1]) / 2; + double mid2 = (fT[1][index] + fT[1][index + 1]) / 2; + pt[0] = c1.ptAtT(mid1); + pt[1] = c2.ptAtT(mid2); + if (pt[0].approximatelyEqual(pt[1])) { + match |= 1 << index; + } + } + if (match) { +#if DEBUG_CONCIDENT + if (((match + 1) & match) != 0) { + SkDebugf("%s coincident hole\n", __FUNCTION__); + } +#endif + // for now, assume that everything from start to finish is coincident + if (fUsed > 2) { + fPt[1] = fPt[last]; + fT[0][1] = fT[0][last]; + fT[1][1] = fT[1][last]; + fIsCoincident[0] = 0x03; + fIsCoincident[1] = 0x03; + fUsed = 2; + } + } + } + return fUsed; +} + +// Up promote the quad to a cubic. +// OPTIMIZATION If this is a common use case, optimize by duplicating +// the intersect 3 loop to avoid the promotion / demotion code +int SkIntersections::intersect(const SkDCubic& cubic, const SkDQuad& quad) { + fMax = 7; + SkDCubic up = quad.toCubic(); + (void) intersect(cubic, up); + return used(); +} + +/* http://www.ag.jku.at/compass/compasssample.pdf +( Self-Intersection Problems and Approximate Implicitization by Jan B. Thomassen +Centre of Mathematics for Applications, University of Oslo http://www.cma.uio.no janbth@math.uio.no +SINTEF Applied Mathematics http://www.sintef.no ) +describes a method to find the self intersection of a cubic by taking the gradient of the implicit +form dotted with the normal, and solving for the roots. My math foo is too poor to implement this.*/ + +int SkIntersections::intersect(const SkDCubic& c) { + fMax = 1; + // check to see if x or y end points are the extrema. Are other quick rejects possible? + if (c.endsAreExtremaInXOrY()) { + return false; + } + // OPTIMIZATION: could quick reject if neither end point tangent ray intersected the line + // segment formed by the opposite end point to the control point + (void) intersect(c, c); + if (used() > 1) { + fUsed = 0; + } else if (used() > 0) { + if (approximately_equal_double(fT[0][0], fT[1][0])) { + fUsed = 0; + } else { + SkASSERT(used() == 1); + if (fT[0][0] > fT[1][0]) { + swapPts(); + } + } + } + return used(); +} diff --git a/src/pathops/SkDCubicLineIntersection.cpp b/src/pathops/SkDCubicLineIntersection.cpp index f5fe01503b..696c42e835 100644 --- a/src/pathops/SkDCubicLineIntersection.cpp +++ b/src/pathops/SkDCubicLineIntersection.cpp @@ -93,29 +93,6 @@ public: fAllowNear = allow; } - void checkCoincident() { - int last = fIntersections->used() - 1; - for (int index = 0; index < last; ) { - double cubicMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; - SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); - double t = fLine.nearPoint(cubicMidPt, NULL); - if (t < 0) { - ++index; - continue; - } - if (fIntersections->isCoincident(index)) { - fIntersections->removeOne(index); - --last; - } else if (fIntersections->isCoincident(index + 1)) { - fIntersections->removeOne(index + 1); - --last; - } else { - fIntersections->setCoincident(index++); - } - fIntersections->setCoincident(index); - } - } - // see parallel routine in line quadratic intersections int intersectRay(double roots[3]) { double adj = fLine[1].fX - fLine[0].fX; @@ -154,11 +131,32 @@ public: double cubicT = rootVals[index]; double lineT = findLineT(cubicT); SkDPoint pt; - if (pinTs(&cubicT, &lineT, &pt, kPointUninitialized) && uniqueAnswer(cubicT, pt)) { + if (pinTs(&cubicT, &lineT, &pt, kPointUninitialized)) { + #if ONE_OFF_DEBUG + SkDPoint cPt = fCubic.ptAtT(cubicT); + SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, + cPt.fX, cPt.fY); + #endif + for (int inner = 0; inner < fIntersections->used(); ++inner) { + if (fIntersections->pt(inner) != pt) { + continue; + } + double existingCubicT = (*fIntersections)[0][inner]; + if (cubicT == existingCubicT) { + goto skipInsert; + } + // check if midway on cubic is also same point. If so, discard this + double cubicMidT = (existingCubicT + cubicT) / 2; + SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); + if (cubicMidPt.approximatelyEqual(pt)) { + goto skipInsert; + } + } fIntersections->insert(cubicT, lineT, pt); + skipInsert: + ; } } - checkCoincident(); return fIntersections->used(); } @@ -188,43 +186,20 @@ public: int count = HorizontalIntersect(fCubic, axisIntercept, roots); for (int index = 0; index < count; ++index) { double cubicT = roots[index]; - SkDPoint pt = { fCubic.ptAtT(cubicT).fX, axisIntercept }; + SkDPoint pt; + pt.fX = fCubic.ptAtT(cubicT).fX; + pt.fY = axisIntercept; double lineT = (pt.fX - left) / (right - left); - if (pinTs(&cubicT, &lineT, &pt, kPointInitialized) && uniqueAnswer(cubicT, pt)) { + if (pinTs(&cubicT, &lineT, &pt, kPointInitialized)) { fIntersections->insert(cubicT, lineT, pt); } } if (flipped) { fIntersections->flip(); } - checkCoincident(); return fIntersections->used(); } - bool uniqueAnswer(double cubicT, const SkDPoint& pt) { - for (int inner = 0; inner < fIntersections->used(); ++inner) { - if (fIntersections->pt(inner) != pt) { - continue; - } - double existingCubicT = (*fIntersections)[0][inner]; - if (cubicT == existingCubicT) { - return false; - } - // check if midway on cubic is also same point. If so, discard this - double cubicMidT = (existingCubicT + cubicT) / 2; - SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); - if (cubicMidPt.approximatelyEqual(pt)) { - return false; - } - } -#if ONE_OFF_DEBUG - SkDPoint cPt = fCubic.ptAtT(cubicT); - SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, - cPt.fX, cPt.fY); -#endif - return true; - } - static int VerticalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) { double A, B, C, D; SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D); @@ -251,16 +226,17 @@ public: int count = VerticalIntersect(fCubic, axisIntercept, roots); for (int index = 0; index < count; ++index) { double cubicT = roots[index]; - SkDPoint pt = { axisIntercept, fCubic.ptAtT(cubicT).fY }; + SkDPoint pt; + pt.fX = axisIntercept; + pt.fY = fCubic.ptAtT(cubicT).fY; double lineT = (pt.fY - top) / (bottom - top); - if (pinTs(&cubicT, &lineT, &pt, kPointInitialized) && uniqueAnswer(cubicT, pt)) { + if (pinTs(&cubicT, &lineT, &pt, kPointInitialized)) { fIntersections->insert(cubicT, lineT, pt); } } if (flipped) { fIntersections->flip(); } - checkCoincident(); return fIntersections->used(); } @@ -366,7 +342,7 @@ public: double lT = *lineT = SkPinT(*lineT); SkDPoint lPt = fLine.ptAtT(lT); SkDPoint cPt = fCubic.ptAtT(cT); - if (!lPt.roughlyEqual(cPt)) { + if (!lPt.moreRoughlyEqual(cPt)) { return false; } // FIXME: if points are roughly equal but not approximately equal, need to do diff --git a/src/pathops/SkDCubicToQuads.cpp b/src/pathops/SkDCubicToQuads.cpp index 2d034b69e8..a28564d4c2 100644 --- a/src/pathops/SkDCubicToQuads.cpp +++ b/src/pathops/SkDCubicToQuads.cpp @@ -19,10 +19,62 @@ If this is a degree-elevated cubic, then both equations will give the same answe it's likely not, your best bet is to average them. So, P1 = -1/4 Q0 + 3/4 Q1 + 3/4 Q2 - 1/4 Q3 + +SkDCubic defined by: P1/2 - anchor points, C1/C2 control points +|x| is the euclidean norm of x +mid-point approx of cubic: a quad that shares the same anchors with the cubic and has the + control point at C = (3·C2 - P2 + 3·C1 - P1)/4 + +Algorithm + +pick an absolute precision (prec) +Compute the Tdiv as the root of (cubic) equation +sqrt(3)/18 · |P2 - 3·C2 + 3·C1 - P1|/2 · Tdiv ^ 3 = prec +if Tdiv < 0.5 divide the cubic at Tdiv. First segment [0..Tdiv] can be approximated with by a + quadratic, with a defect less than prec, by the mid-point approximation. + Repeat from step 2 with the second resulted segment (corresponding to 1-Tdiv) +0.5<=Tdiv<1 - simply divide the cubic in two. The two halves can be approximated by the mid-point + approximation +Tdiv>=1 - the entire cubic can be approximated by the mid-point approximation + +confirmed by (maybe stolen from) +http://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html +// maybe in turn derived from http://www.cccg.ca/proceedings/2004/36.pdf +// also stored at http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/bezier%20cccg04%20paper.pdf + */ #include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" #include "SkPathOpsQuad.h" +#include "SkReduceOrder.h" +#include "SkTArray.h" +#include "SkTSort.h" + +#define USE_CUBIC_END_POINTS 1 + +static double calc_t_div(const SkDCubic& cubic, double precision, double start) { + const double adjust = sqrt(3.) / 36; + SkDCubic sub; + const SkDCubic* cPtr; + if (start == 0) { + cPtr = &cubic; + } else { + // OPTIMIZE: special-case half-split ? + sub = cubic.subDivide(start, 1); + cPtr = ⊂ + } + const SkDCubic& c = *cPtr; + double dx = c[3].fX - 3 * (c[2].fX - c[1].fX) - c[0].fX; + double dy = c[3].fY - 3 * (c[2].fY - c[1].fY) - c[0].fY; + double dist = sqrt(dx * dx + dy * dy); + double tDiv3 = precision / (adjust * dist); + double t = SkDCubeRoot(tDiv3); + if (start > 0) { + t = start + (1 - start) * t; + } + return t; +} SkDQuad SkDCubic::toQuad() const { SkDQuad quad; @@ -34,3 +86,101 @@ SkDQuad SkDCubic::toQuad() const { quad[2] = fPts[3]; return quad; } + +static bool add_simple_ts(const SkDCubic& cubic, double precision, SkTArray<double, true>* ts) { + double tDiv = calc_t_div(cubic, precision, 0); + if (tDiv >= 1) { + return true; + } + if (tDiv >= 0.5) { + ts->push_back(0.5); + return true; + } + return false; +} + +static void addTs(const SkDCubic& cubic, double precision, double start, double end, + SkTArray<double, true>* ts) { + double tDiv = calc_t_div(cubic, precision, 0); + double parts = ceil(1.0 / tDiv); + for (double index = 0; index < parts; ++index) { + double newT = start + (index / parts) * (end - start); + if (newT > 0 && newT < 1) { + ts->push_back(newT); + } + } +} + +// flavor that returns T values only, deferring computing the quads until they are needed +// FIXME: when called from recursive intersect 2, this could take the original cubic +// and do a more precise job when calling chop at and sub divide by computing the fractional ts. +// it would still take the prechopped cubic for reduce order and find cubic inflections +void SkDCubic::toQuadraticTs(double precision, SkTArray<double, true>* ts) const { + SkReduceOrder reducer; + int order = reducer.reduce(*this, SkReduceOrder::kAllow_Quadratics); + if (order < 3) { + return; + } + double inflectT[5]; + int inflections = findInflections(inflectT); + SkASSERT(inflections <= 2); + if (!endsAreExtremaInXOrY()) { + inflections += findMaxCurvature(&inflectT[inflections]); + SkASSERT(inflections <= 5); + } + SkTQSort<double>(inflectT, &inflectT[inflections - 1]); + // OPTIMIZATION: is this filtering common enough that it needs to be pulled out into its + // own subroutine? + while (inflections && approximately_less_than_zero(inflectT[0])) { + memmove(inflectT, &inflectT[1], sizeof(inflectT[0]) * --inflections); + } + int start = 0; + int next = 1; + while (next < inflections) { + if (!approximately_equal(inflectT[start], inflectT[next])) { + ++start; + ++next; + continue; + } + memmove(&inflectT[start], &inflectT[next], sizeof(inflectT[0]) * (--inflections - start)); + } + + while (inflections && approximately_greater_than_one(inflectT[inflections - 1])) { + --inflections; + } + SkDCubicPair pair; + if (inflections == 1) { + pair = chopAt(inflectT[0]); + int orderP1 = reducer.reduce(pair.first(), SkReduceOrder::kNo_Quadratics); + if (orderP1 < 2) { + --inflections; + } else { + int orderP2 = reducer.reduce(pair.second(), SkReduceOrder::kNo_Quadratics); + if (orderP2 < 2) { + --inflections; + } + } + } + if (inflections == 0 && add_simple_ts(*this, precision, ts)) { + return; + } + if (inflections == 1) { + pair = chopAt(inflectT[0]); + addTs(pair.first(), precision, 0, inflectT[0], ts); + addTs(pair.second(), precision, inflectT[0], 1, ts); + return; + } + if (inflections > 1) { + SkDCubic part = subDivide(0, inflectT[0]); + addTs(part, precision, 0, inflectT[0], ts); + int last = inflections - 1; + for (int idx = 0; idx < last; ++idx) { + part = subDivide(inflectT[idx], inflectT[idx + 1]); + addTs(part, precision, inflectT[idx], inflectT[idx + 1], ts); + } + part = subDivide(inflectT[last], 1); + addTs(part, precision, inflectT[last], 1, ts); + return; + } + addTs(*this, precision, 0, 1, ts); +} diff --git a/src/pathops/SkDLineIntersection.cpp b/src/pathops/SkDLineIntersection.cpp index ed96b9c5d7..8fc673f2fb 100644 --- a/src/pathops/SkDLineIntersection.cpp +++ b/src/pathops/SkDLineIntersection.cpp @@ -7,6 +7,45 @@ #include "SkIntersections.h" #include "SkPathOpsLine.h" +/* Determine the intersection point of two lines. This assumes the lines are not parallel, + and that that the lines are infinite. + From http://en.wikipedia.org/wiki/Line-line_intersection + */ +SkDPoint SkIntersections::Line(const SkDLine& a, const SkDLine& b) { + double axLen = a[1].fX - a[0].fX; + double ayLen = a[1].fY - a[0].fY; + double bxLen = b[1].fX - b[0].fX; + double byLen = b[1].fY - b[0].fY; + double denom = byLen * axLen - ayLen * bxLen; + SkASSERT(denom); + double term1 = a[1].fX * a[0].fY - a[1].fY * a[0].fX; + double term2 = b[1].fX * b[0].fY - b[1].fY * b[0].fX; + SkDPoint p; + p.fX = (term1 * bxLen - axLen * term2) / denom; + p.fY = (term1 * byLen - ayLen * term2) / denom; + return p; +} + +int SkIntersections::cleanUpCoincidence() { + do { + int last = fUsed - 1; + for (int index = 0; index < last; ++index) { + if (fT[0][index] == fT[0][index + 1]) { + removeOne(index + (int) (fT[1][index] == 0 || fT[1][index] == 1)); + goto tryAgain; + } + } + for (int index = 0; index < last; ++index) { + if (fT[1][index] == fT[1][index + 1]) { + removeOne(index + (int) (fT[0][index] == 0 || fT[0][index] == 1)); + goto tryAgain; + } + } + return fUsed; +tryAgain: ; + } while (true); +} + void SkIntersections::cleanUpParallelLines(bool parallel) { while (fUsed > 2) { removeOne(1); @@ -19,9 +58,6 @@ void SkIntersections::cleanUpParallelLines(bool parallel) { removeOne(endMatch); } } - if (fUsed == 2) { - fIsCoincident[0] = fIsCoincident[1] = 0x03; - } } void SkIntersections::computePoints(const SkDLine& line, int used) { @@ -45,6 +81,12 @@ int SkIntersections::intersectRay(const SkDLine& a, const SkDLine& b) { SkDVector ab0 = a[0] - b[0]; double numerA = ab0.fY * bLen.fX - bLen.fY * ab0.fX; double numerB = ab0.fY * aLen.fX - aLen.fY * ab0.fX; +#if 0 + if (!between(0, numerA, denom) || !between(0, numerB, denom)) { + fUsed = 0; + return 0; + } +#endif numerA /= denom; numerB /= denom; int used; @@ -148,6 +190,7 @@ int SkIntersections::intersect(const SkDLine& a, const SkDLine& b) { } SkASSERT(a[iA] != b[nearer]); SkASSERT(iA == (bNearA[nearer] > 0.5)); + fNearlySame[iA] = true; insertNear(iA, nearer, a[iA], b[nearer]); aNearB[iA] = -1; bNearA[nearer] = -1; @@ -192,6 +235,18 @@ static double horizontal_intercept(const SkDLine& line, double y) { return SkPinT((y - line[0].fY) / (line[1].fY - line[0].fY)); } +int SkIntersections::horizontal(const SkDLine& line, double y) { + fMax = 2; + int horizontalType = horizontal_coincident(line, y); + if (horizontalType == 1) { + fT[0][0] = horizontal_intercept(line, y); + } else if (horizontalType == 2) { + fT[0][0] = 0; + fT[0][1] = 1; + } + return fUsed = horizontalType; +} + int SkIntersections::horizontal(const SkDLine& line, double left, double right, double y, bool flipped) { fMax = 3; // clean up parallel at the end will limit the result to 2 at the most @@ -268,6 +323,18 @@ static double vertical_intercept(const SkDLine& line, double x) { return SkPinT((x - line[0].fX) / (line[1].fX - line[0].fX)); } +int SkIntersections::vertical(const SkDLine& line, double x) { + fMax = 2; + int verticalType = vertical_coincident(line, x); + if (verticalType == 1) { + fT[0][0] = vertical_intercept(line, x); + } else if (verticalType == 2) { + fT[0][0] = 0; + fT[0][1] = 1; + } + return fUsed = verticalType; +} + int SkIntersections::vertical(const SkDLine& line, double top, double bottom, double x, bool flipped) { fMax = 3; // cleanup parallel lines will bring this back line @@ -326,3 +393,14 @@ int SkIntersections::vertical(const SkDLine& line, double top, double bottom, return fUsed; } +// from http://www.bryceboe.com/wordpress/wp-content/uploads/2006/10/intersect.py +// 4 subs, 2 muls, 1 cmp +static bool ccw(const SkDPoint& A, const SkDPoint& B, const SkDPoint& C) { + return (C.fY - A.fY) * (B.fX - A.fX) > (B.fY - A.fY) * (C.fX - A.fX); +} + +// 16 subs, 8 muls, 6 cmps +bool SkIntersections::Test(const SkDLine& a, const SkDLine& b) { + return ccw(a[0], b[0], b[1]) != ccw(a[1], b[0], b[1]) + && ccw(a[0], a[1], b[0]) != ccw(a[0], a[1], b[1]); +} diff --git a/src/pathops/SkDQuadImplicit.cpp b/src/pathops/SkDQuadImplicit.cpp new file mode 100644 index 0000000000..f0f66d1a10 --- /dev/null +++ b/src/pathops/SkDQuadImplicit.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkDQuadImplicit.h" + +/* from http://tom.cs.byu.edu/~tom/papers/cvgip84.pdf 4.1 + * + * This paper proves that Syvester's method can compute the implicit form of + * the quadratic from the parameterized form. + * + * Given x = a*t*t + b*t + c (the parameterized form) + * y = d*t*t + e*t + f + * + * we want to find an equation of the implicit form: + * + * A*x*x + B*x*y + C*y*y + D*x + E*y + F = 0 + * + * The implicit form can be expressed as a 4x4 determinant, as shown. + * + * The resultant obtained by Syvester's method is + * + * | a b (c - x) 0 | + * | 0 a b (c - x) | + * | d e (f - y) 0 | + * | 0 d e (f - y) | + * + * which expands to + * + * d*d*x*x + -2*a*d*x*y + a*a*y*y + * + (-2*c*d*d + b*e*d - a*e*e + 2*a*f*d)*x + * + (-2*f*a*a + e*b*a - d*b*b + 2*d*c*a)*y + * + + * | a b c 0 | + * | 0 a b c | == 0. + * | d e f 0 | + * | 0 d e f | + * + * Expanding the constant determinant results in + * + * | a b c | | b c 0 | + * a*| e f 0 | + d*| a b c | == + * | d e f | | d e f | + * + * a*(a*f*f + c*e*e - c*f*d - b*e*f) + d*(b*b*f + c*c*d - c*a*f - c*e*b) + * + */ + +// use the tricky arithmetic path, but leave the original to compare just in case +static bool straight_forward = false; + +SkDQuadImplicit::SkDQuadImplicit(const SkDQuad& q) { + double a, b, c; + SkDQuad::SetABC(&q[0].fX, &a, &b, &c); + double d, e, f; + SkDQuad::SetABC(&q[0].fY, &d, &e, &f); + // compute the implicit coefficients + if (straight_forward) { // 42 muls, 13 adds + fP[kXx_Coeff] = d * d; + fP[kXy_Coeff] = -2 * a * d; + fP[kYy_Coeff] = a * a; + fP[kX_Coeff] = -2*c*d*d + b*e*d - a*e*e + 2*a*f*d; + fP[kY_Coeff] = -2*f*a*a + e*b*a - d*b*b + 2*d*c*a; + fP[kC_Coeff] = a*(a*f*f + c*e*e - c*f*d - b*e*f) + + d*(b*b*f + c*c*d - c*a*f - c*e*b); + } else { // 26 muls, 11 adds + double aa = a * a; + double ad = a * d; + double dd = d * d; + fP[kXx_Coeff] = dd; + fP[kXy_Coeff] = -2 * ad; + fP[kYy_Coeff] = aa; + double be = b * e; + double bde = be * d; + double cdd = c * dd; + double ee = e * e; + fP[kX_Coeff] = -2*cdd + bde - a*ee + 2*ad*f; + double aaf = aa * f; + double abe = a * be; + double ac = a * c; + double bb_2ac = b*b - 2*ac; + fP[kY_Coeff] = -2*aaf + abe - d*bb_2ac; + fP[kC_Coeff] = aaf*f + ac*ee + d*f*bb_2ac - abe*f + c*cdd - c*bde; + } +} + + /* Given a pair of quadratics, determine their parametric coefficients. + * If the scaled coefficients are nearly equal, then the part of the quadratics + * may be coincident. + * OPTIMIZATION -- since comparison short-circuits on no match, + * lazily compute the coefficients, comparing the easiest to compute first. + * xx and yy first; then xy; and so on. + */ +bool SkDQuadImplicit::match(const SkDQuadImplicit& p2) const { + int first = 0; + for (int index = 0; index <= kC_Coeff; ++index) { + if (approximately_zero(fP[index]) && approximately_zero(p2.fP[index])) { + first += first == index; + continue; + } + if (first == index) { + continue; + } + if (!AlmostDequalUlps(fP[index] * p2.fP[first], fP[first] * p2.fP[index])) { + return false; + } + } + return true; +} + +bool SkDQuadImplicit::Match(const SkDQuad& quad1, const SkDQuad& quad2) { + SkDQuadImplicit i1(quad1); // a'xx , b'xy , c'yy , d'x , e'y , f + SkDQuadImplicit i2(quad2); + return i1.match(i2); +} diff --git a/src/pathops/SkDQuadImplicit.h b/src/pathops/SkDQuadImplicit.h new file mode 100644 index 0000000000..24f1aac2ef --- /dev/null +++ b/src/pathops/SkDQuadImplicit.h @@ -0,0 +1,39 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkDQuadImplicit_DEFINED +#define SkDQuadImplicit_DEFINED + +#include "SkPathOpsQuad.h" + +class SkDQuadImplicit { +public: + explicit SkDQuadImplicit(const SkDQuad& q); + + bool match(const SkDQuadImplicit& two) const; + static bool Match(const SkDQuad& quad1, const SkDQuad& quad2); + + double x2() const { return fP[kXx_Coeff]; } + double xy() const { return fP[kXy_Coeff]; } + double y2() const { return fP[kYy_Coeff]; } + double x() const { return fP[kX_Coeff]; } + double y() const { return fP[kY_Coeff]; } + double c() const { return fP[kC_Coeff]; } + +private: + enum Coeffs { + kXx_Coeff, + kXy_Coeff, + kYy_Coeff, + kX_Coeff, + kY_Coeff, + kC_Coeff, + }; + + double fP[kC_Coeff + 1]; +}; + +#endif diff --git a/src/pathops/SkDQuadIntersection.cpp b/src/pathops/SkDQuadIntersection.cpp new file mode 100644 index 0000000000..fcb9171f32 --- /dev/null +++ b/src/pathops/SkDQuadIntersection.cpp @@ -0,0 +1,617 @@ +// Another approach is to start with the implicit form of one curve and solve +// (seek implicit coefficients in QuadraticParameter.cpp +// by substituting in the parametric form of the other. +// The downside of this approach is that early rejects are difficult to come by. +// http://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html#step + +#include "SkDQuadImplicit.h" +#include "SkIntersections.h" +#include "SkPathOpsLine.h" +#include "SkQuarticRoot.h" +#include "SkTArray.h" +#include "SkTSort.h" + +/* given the implicit form 0 = Ax^2 + Bxy + Cy^2 + Dx + Ey + F + * and given x = at^2 + bt + c (the parameterized form) + * y = dt^2 + et + f + * then + * 0 = A(at^2+bt+c)(at^2+bt+c)+B(at^2+bt+c)(dt^2+et+f)+C(dt^2+et+f)(dt^2+et+f)+D(at^2+bt+c)+E(dt^2+et+f)+F + */ + +static int findRoots(const SkDQuadImplicit& i, const SkDQuad& quad, double roots[4], + bool oneHint, bool flip, int firstCubicRoot) { + SkDQuad flipped; + const SkDQuad& q = flip ? (flipped = quad.flip()) : quad; + double a, b, c; + SkDQuad::SetABC(&q[0].fX, &a, &b, &c); + double d, e, f; + SkDQuad::SetABC(&q[0].fY, &d, &e, &f); + const double t4 = i.x2() * a * a + + i.xy() * a * d + + i.y2() * d * d; + const double t3 = 2 * i.x2() * a * b + + i.xy() * (a * e + b * d) + + 2 * i.y2() * d * e; + const double t2 = i.x2() * (b * b + 2 * a * c) + + i.xy() * (c * d + b * e + a * f) + + i.y2() * (e * e + 2 * d * f) + + i.x() * a + + i.y() * d; + const double t1 = 2 * i.x2() * b * c + + i.xy() * (c * e + b * f) + + 2 * i.y2() * e * f + + i.x() * b + + i.y() * e; + const double t0 = i.x2() * c * c + + i.xy() * c * f + + i.y2() * f * f + + i.x() * c + + i.y() * f + + i.c(); + int rootCount = SkReducedQuarticRoots(t4, t3, t2, t1, t0, oneHint, roots); + if (rootCount < 0) { + rootCount = SkQuarticRootsReal(firstCubicRoot, t4, t3, t2, t1, t0, roots); + } + if (flip) { + for (int index = 0; index < rootCount; ++index) { + roots[index] = 1 - roots[index]; + } + } + return rootCount; +} + +static int addValidRoots(const double roots[4], const int count, double valid[4]) { + int result = 0; + int index; + for (index = 0; index < count; ++index) { + if (!approximately_zero_or_more(roots[index]) || !approximately_one_or_less(roots[index])) { + continue; + } + double t = 1 - roots[index]; + if (approximately_less_than_zero(t)) { + t = 0; + } else if (approximately_greater_than_one(t)) { + t = 1; + } + SkASSERT(t >= 0 && t <= 1); + valid[result++] = t; + } + return result; +} + +static bool only_end_pts_in_common(const SkDQuad& q1, const SkDQuad& q2) { +// the idea here is to see at minimum do a quick reject by rotating all points +// to either side of the line formed by connecting the endpoints +// if the opposite curves points are on the line or on the other side, the +// curves at most intersect at the endpoints + for (int oddMan = 0; oddMan < 3; ++oddMan) { + const SkDPoint* endPt[2]; + for (int opp = 1; opp < 3; ++opp) { + int end = oddMan ^ opp; // choose a value not equal to oddMan + if (3 == end) { // and correct so that largest value is 1 or 2 + end = opp; + } + endPt[opp - 1] = &q1[end]; + } + double origX = endPt[0]->fX; + double origY = endPt[0]->fY; + double adj = endPt[1]->fX - origX; + double opp = endPt[1]->fY - origY; + double sign = (q1[oddMan].fY - origY) * adj - (q1[oddMan].fX - origX) * opp; + if (approximately_zero(sign)) { + goto tryNextHalfPlane; + } + for (int n = 0; n < 3; ++n) { + double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; + if (test * sign > 0 && !precisely_zero(test)) { + goto tryNextHalfPlane; + } + } + return true; +tryNextHalfPlane: + ; + } + return false; +} + +// returns false if there's more than one intercept or the intercept doesn't match the point +// returns true if the intercept was successfully added or if the +// original quads need to be subdivided +static bool add_intercept(const SkDQuad& q1, const SkDQuad& q2, double tMin, double tMax, + SkIntersections* i, bool* subDivide) { + double tMid = (tMin + tMax) / 2; + SkDPoint mid = q2.ptAtT(tMid); + SkDLine line; + line[0] = line[1] = mid; + SkDVector dxdy = q2.dxdyAtT(tMid); + line[0] -= dxdy; + line[1] += dxdy; + SkIntersections rootTs; + rootTs.allowNear(false); + int roots = rootTs.intersect(q1, line); + if (roots == 0) { + if (subDivide) { + *subDivide = true; + } + return true; + } + if (roots == 2) { + return false; + } + SkDPoint pt2 = q1.ptAtT(rootTs[0][0]); + if (!pt2.approximatelyEqual(mid)) { + return false; + } + i->insertSwap(rootTs[0][0], tMid, pt2); + return true; +} + +static bool is_linear_inner(const SkDQuad& q1, double t1s, double t1e, const SkDQuad& q2, + double t2s, double t2e, SkIntersections* i, bool* subDivide) { + SkDQuad hull = q1.subDivide(t1s, t1e); + SkDLine line = {{hull[2], hull[0]}}; + const SkDLine* testLines[] = { &line, (const SkDLine*) &hull[0], (const SkDLine*) &hull[1] }; + const size_t kTestCount = SK_ARRAY_COUNT(testLines); + SkSTArray<kTestCount * 2, double, true> tsFound; + for (size_t index = 0; index < kTestCount; ++index) { + SkIntersections rootTs; + rootTs.allowNear(false); + int roots = rootTs.intersect(q2, *testLines[index]); + for (int idx2 = 0; idx2 < roots; ++idx2) { + double t = rootTs[0][idx2]; +#if 0 // def SK_DEBUG // FIXME : accurate for error = 16, error of 17.5 seen +// {{{136.08723965397621, 1648.2814535211637}, {593.49031197259478, 1190.8784277439891}, {593.49031197259478, 544.0128173828125}}} +// {{{-968.181396484375, 544.0128173828125}, {592.2825927734375, 870.552490234375}, {593.435302734375, 557.8828125}}} + + SkDPoint qPt = q2.ptAtT(t); + SkDPoint lPt = testLines[index]->ptAtT(rootTs[1][idx2]); + SkASSERT(qPt.approximatelyDEqual(lPt)); +#endif + if (approximately_negative(t - t2s) || approximately_positive(t - t2e)) { + continue; + } + tsFound.push_back(rootTs[0][idx2]); + } + } + int tCount = tsFound.count(); + if (tCount <= 0) { + return true; + } + double tMin, tMax; + if (tCount == 1) { + tMin = tMax = tsFound[0]; + } else { + SkASSERT(tCount > 1); + SkTQSort<double>(tsFound.begin(), tsFound.end() - 1); + tMin = tsFound[0]; + tMax = tsFound[tsFound.count() - 1]; + } + SkDPoint end = q2.ptAtT(t2s); + bool startInTriangle = hull.pointInHull(end); + if (startInTriangle) { + tMin = t2s; + } + end = q2.ptAtT(t2e); + bool endInTriangle = hull.pointInHull(end); + if (endInTriangle) { + tMax = t2e; + } + int split = 0; + SkDVector dxy1, dxy2; + if (tMin != tMax || tCount > 2) { + dxy2 = q2.dxdyAtT(tMin); + for (int index = 1; index < tCount; ++index) { + dxy1 = dxy2; + dxy2 = q2.dxdyAtT(tsFound[index]); + double dot = dxy1.dot(dxy2); + if (dot < 0) { + split = index - 1; + break; + } + } + } + if (split == 0) { // there's one point + if (add_intercept(q1, q2, tMin, tMax, i, subDivide)) { + return true; + } + i->swap(); + return is_linear_inner(q2, tMin, tMax, q1, t1s, t1e, i, subDivide); + } + // At this point, we have two ranges of t values -- treat each separately at the split + bool result; + if (add_intercept(q1, q2, tMin, tsFound[split - 1], i, subDivide)) { + result = true; + } else { + i->swap(); + result = is_linear_inner(q2, tMin, tsFound[split - 1], q1, t1s, t1e, i, subDivide); + } + if (add_intercept(q1, q2, tsFound[split], tMax, i, subDivide)) { + result = true; + } else { + i->swap(); + result |= is_linear_inner(q2, tsFound[split], tMax, q1, t1s, t1e, i, subDivide); + } + return result; +} + +static double flat_measure(const SkDQuad& q) { + SkDVector mid = q[1] - q[0]; + SkDVector dxy = q[2] - q[0]; + double length = dxy.length(); // OPTIMIZE: get rid of sqrt + return fabs(mid.cross(dxy) / length); +} + +// FIXME ? should this measure both and then use the quad that is the flattest as the line? +static bool is_linear(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) { + if (i->flatMeasure()) { + // for backward compatibility, use the old method when called from cubics + // FIXME: figure out how to fix cubics when it calls the new path + double measure = flat_measure(q1); + // OPTIMIZE: (get rid of sqrt) use approximately_zero + if (!approximately_zero_sqrt(measure)) { // approximately_zero_sqrt + return false; + } + } else { + if (!q1.isLinear(0, 2)) { + return false; + } + } + return is_linear_inner(q1, 0, 1, q2, 0, 1, i, NULL); +} + +// FIXME: if flat measure is sufficiently large, then probably the quartic solution failed +// avoid imprecision incurred with chopAt +static void relaxed_is_linear(const SkDQuad* q1, double s1, double e1, const SkDQuad* q2, + double s2, double e2, SkIntersections* i) { + double m1 = flat_measure(*q1); + double m2 = flat_measure(*q2); + i->reset(); + const SkDQuad* rounder, *flatter; + double sf, midf, ef, sr, er; + if (m2 < m1) { + rounder = q1; + sr = s1; + er = e1; + flatter = q2; + sf = s2; + midf = (s2 + e2) / 2; + ef = e2; + } else { + rounder = q2; + sr = s2; + er = e2; + flatter = q1; + sf = s1; + midf = (s1 + e1) / 2; + ef = e1; + } + bool subDivide = false; + is_linear_inner(*flatter, sf, ef, *rounder, sr, er, i, &subDivide); + if (subDivide) { + relaxed_is_linear(flatter, sf, midf, rounder, sr, er, i); + relaxed_is_linear(flatter, midf, ef, rounder, sr, er, i); + } + if (m2 < m1) { + i->swapPts(); + } +} + +// each time through the loop, this computes values it had from the last loop +// if i == j == 1, the center values are still good +// otherwise, for i != 1 or j != 1, four of the values are still good +// and if i == 1 ^ j == 1, an additional value is good +static bool binary_search(const SkDQuad& quad1, const SkDQuad& quad2, double* t1Seed, + double* t2Seed, SkDPoint* pt) { + double tStep = ROUGH_EPSILON; + SkDPoint t1[3], t2[3]; + int calcMask = ~0; + do { + if (calcMask & (1 << 1)) t1[1] = quad1.ptAtT(*t1Seed); + if (calcMask & (1 << 4)) t2[1] = quad2.ptAtT(*t2Seed); + if (t1[1].approximatelyEqual(t2[1])) { + *pt = t1[1]; + #if ONE_OFF_DEBUG + SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) == (%1.9g,%1.9g)\n", __FUNCTION__, + t1Seed, t2Seed, t1[1].fX, t1[1].fY, t2[1].fX, t2[1].fY); + #endif + if (*t1Seed < 0) { + *t1Seed = 0; + } else if (*t1Seed > 1) { + *t1Seed = 1; + } + if (*t2Seed < 0) { + *t2Seed = 0; + } else if (*t2Seed > 1) { + *t2Seed = 1; + } + return true; + } + if (calcMask & (1 << 0)) t1[0] = quad1.ptAtT(SkTMax(0., *t1Seed - tStep)); + if (calcMask & (1 << 2)) t1[2] = quad1.ptAtT(SkTMin(1., *t1Seed + tStep)); + if (calcMask & (1 << 3)) t2[0] = quad2.ptAtT(SkTMax(0., *t2Seed - tStep)); + if (calcMask & (1 << 5)) t2[2] = quad2.ptAtT(SkTMin(1., *t2Seed + tStep)); + double dist[3][3]; + // OPTIMIZE: using calcMask value permits skipping some distance calcuations + // if prior loop's results are moved to correct slot for reuse + dist[1][1] = t1[1].distanceSquared(t2[1]); + int best_i = 1, best_j = 1; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + if (i == 1 && j == 1) { + continue; + } + dist[i][j] = t1[i].distanceSquared(t2[j]); + if (dist[best_i][best_j] > dist[i][j]) { + best_i = i; + best_j = j; + } + } + } + if (best_i == 1 && best_j == 1) { + tStep /= 2; + if (tStep < FLT_EPSILON_HALF) { + break; + } + calcMask = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5); + continue; + } + if (best_i == 0) { + *t1Seed -= tStep; + t1[2] = t1[1]; + t1[1] = t1[0]; + calcMask = 1 << 0; + } else if (best_i == 2) { + *t1Seed += tStep; + t1[0] = t1[1]; + t1[1] = t1[2]; + calcMask = 1 << 2; + } else { + calcMask = 0; + } + if (best_j == 0) { + *t2Seed -= tStep; + t2[2] = t2[1]; + t2[1] = t2[0]; + calcMask |= 1 << 3; + } else if (best_j == 2) { + *t2Seed += tStep; + t2[0] = t2[1]; + t2[1] = t2[2]; + calcMask |= 1 << 5; + } + } while (true); +#if ONE_OFF_DEBUG + SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) != (%1.9g,%1.9g) %s\n", __FUNCTION__, + t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY); +#endif + return false; +} + +static void lookNearEnd(const SkDQuad& q1, const SkDQuad& q2, int testT, + const SkIntersections& orig, bool swap, SkIntersections* i) { + if (orig.used() == 1 && orig[!swap][0] == testT) { + return; + } + if (orig.used() == 2 && orig[!swap][1] == testT) { + return; + } + SkDLine tmpLine; + int testTIndex = testT << 1; + tmpLine[0] = tmpLine[1] = q2[testTIndex]; + tmpLine[1].fX += q2[1].fY - q2[testTIndex].fY; + tmpLine[1].fY -= q2[1].fX - q2[testTIndex].fX; + SkIntersections impTs; + impTs.intersectRay(q1, tmpLine); + for (int index = 0; index < impTs.used(); ++index) { + SkDPoint realPt = impTs.pt(index); + if (!tmpLine[0].approximatelyPEqual(realPt)) { + continue; + } + if (swap) { + i->insert(testT, impTs[0][index], tmpLine[0]); + } else { + i->insert(impTs[0][index], testT, tmpLine[0]); + } + } +} + +int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) { + fMax = 4; + bool exactMatch = false; + // if the quads share an end point, check to see if they overlap + for (int i1 = 0; i1 < 3; i1 += 2) { + for (int i2 = 0; i2 < 3; i2 += 2) { + if (q1[i1].asSkPoint() == q2[i2].asSkPoint()) { + insert(i1 >> 1, i2 >> 1, q1[i1]); + exactMatch = true; + } + } + } + SkASSERT(fUsed < 3); + if (only_end_pts_in_common(q1, q2)) { + return fUsed; + } + if (only_end_pts_in_common(q2, q1)) { + return fUsed; + } + // see if either quad is really a line + // FIXME: figure out why reduce step didn't find this earlier + if (is_linear(q1, q2, this)) { + return fUsed; + } + SkIntersections swapped; + swapped.setMax(fMax); + if (is_linear(q2, q1, &swapped)) { + swapped.swapPts(); + *this = swapped; + return fUsed; + } + SkIntersections copyI(*this); + lookNearEnd(q1, q2, 0, *this, false, ©I); + lookNearEnd(q1, q2, 1, *this, false, ©I); + lookNearEnd(q2, q1, 0, *this, true, ©I); + lookNearEnd(q2, q1, 1, *this, true, ©I); + int innerEqual = 0; + if (copyI.fUsed >= 2) { + SkASSERT(copyI.fUsed <= 4); + double width = copyI[0][1] - copyI[0][0]; + int midEnd = 1; + for (int index = 2; index < copyI.fUsed; ++index) { + double testWidth = copyI[0][index] - copyI[0][index - 1]; + if (testWidth <= width) { + continue; + } + midEnd = index; + } + for (int index = 0; index < 2; ++index) { + double testT = (copyI[0][midEnd] * (index + 1) + + copyI[0][midEnd - 1] * (2 - index)) / 3; + SkDPoint testPt1 = q1.ptAtT(testT); + testT = (copyI[1][midEnd] * (index + 1) + copyI[1][midEnd - 1] * (2 - index)) / 3; + SkDPoint testPt2 = q2.ptAtT(testT); + innerEqual += testPt1.approximatelyEqual(testPt2); + } + } + bool expectCoincident = copyI.fUsed >= 2 && innerEqual == 2; + if (expectCoincident) { + reset(); + insertCoincident(copyI[0][0], copyI[1][0], copyI.fPt[0]); + int last = copyI.fUsed - 1; + insertCoincident(copyI[0][last], copyI[1][last], copyI.fPt[last]); + return fUsed; + } + SkDQuadImplicit i1(q1); + SkDQuadImplicit i2(q2); + int index; + bool flip1 = q1[2] == q2[0]; + bool flip2 = q1[0] == q2[2]; + bool useCubic = q1[0] == q2[0]; + double roots1[4]; + int rootCount = findRoots(i2, q1, roots1, useCubic, flip1, 0); + // OPTIMIZATION: could short circuit here if all roots are < 0 or > 1 + double roots1Copy[4]; + SkDEBUGCODE(sk_bzero(roots1Copy, sizeof(roots1Copy))); + int r1Count = addValidRoots(roots1, rootCount, roots1Copy); + SkDPoint pts1[4]; + for (index = 0; index < r1Count; ++index) { + pts1[index] = q1.ptAtT(roots1Copy[index]); + } + double roots2[4]; + int rootCount2 = findRoots(i1, q2, roots2, useCubic, flip2, 0); + double roots2Copy[4]; + int r2Count = addValidRoots(roots2, rootCount2, roots2Copy); + SkDPoint pts2[4]; + for (index = 0; index < r2Count; ++index) { + pts2[index] = q2.ptAtT(roots2Copy[index]); + } + bool triedBinary = false; + if (r1Count == r2Count && r1Count <= 1) { + if (r1Count == 1 && used() == 0) { + if (pts1[0].approximatelyEqual(pts2[0])) { + insert(roots1Copy[0], roots2Copy[0], pts1[0]); + } else { + // find intersection by chasing t + triedBinary = true; + if (binary_search(q1, q2, roots1Copy, roots2Copy, pts1)) { + insert(roots1Copy[0], roots2Copy[0], pts1[0]); + } + } + } + return fUsed; + } + int closest[4]; + double dist[4]; + bool foundSomething = false; + for (index = 0; index < r1Count; ++index) { + dist[index] = DBL_MAX; + closest[index] = -1; + for (int ndex2 = 0; ndex2 < r2Count; ++ndex2) { + if (!pts2[ndex2].approximatelyEqual(pts1[index])) { + continue; + } + double dx = pts2[ndex2].fX - pts1[index].fX; + double dy = pts2[ndex2].fY - pts1[index].fY; + double distance = dx * dx + dy * dy; + if (dist[index] <= distance) { + continue; + } + for (int outer = 0; outer < index; ++outer) { + if (closest[outer] != ndex2) { + continue; + } + if (dist[outer] < distance) { + goto next; + } + closest[outer] = -1; + } + dist[index] = distance; + closest[index] = ndex2; + foundSomething = true; + next: + ; + } + } + if (r1Count && r2Count && !foundSomething) { + if (exactMatch) { + SkASSERT(fUsed > 0); + return fUsed; + } + relaxed_is_linear(&q1, 0, 1, &q2, 0, 1, this); + if (fUsed) { + return fUsed; + } + // maybe the curves are nearly coincident + if (!triedBinary && binary_search(q1, q2, roots1Copy, roots2Copy, pts1)) { + insert(roots1Copy[0], roots2Copy[0], pts1[0]); + } + return fUsed; + } + int used = 0; + do { + double lowest = DBL_MAX; + int lowestIndex = -1; + for (index = 0; index < r1Count; ++index) { + if (closest[index] < 0) { + continue; + } + if (roots1Copy[index] < lowest) { + lowestIndex = index; + lowest = roots1Copy[index]; + } + } + if (lowestIndex < 0) { + break; + } + insert(roots1Copy[lowestIndex], roots2Copy[closest[lowestIndex]], + pts1[lowestIndex]); + closest[lowestIndex] = -1; + } while (++used < r1Count); + return fUsed; +} + +void SkIntersections::alignQuadPts(const SkPoint q1[3], const SkPoint q2[3]) { + for (int index = 0; index < used(); ++index) { + const SkPoint result = pt(index).asSkPoint(); + if (q1[0] == result || q1[2] == result || q2[0] == result || q2[2] == result) { + continue; + } + if (SkDPoint::ApproximatelyEqual(q1[0], result)) { + fPt[index].set(q1[0]); +// SkASSERT(way_roughly_zero(fT[0][index])); // this value can be bigger than way rough + fT[0][index] = 0; + } else if (SkDPoint::ApproximatelyEqual(q1[2], result)) { + fPt[index].set(q1[2]); +// SkASSERT(way_roughly_equal(fT[0][index], 1)); + fT[0][index] = 1; + } + if (SkDPoint::ApproximatelyEqual(q2[0], result)) { + fPt[index].set(q2[0]); +// SkASSERT(way_roughly_zero(fT[1][index])); + fT[1][index] = 0; + } else if (SkDPoint::ApproximatelyEqual(q2[2], result)) { + fPt[index].set(q2[2]); +// SkASSERT(way_roughly_equal(fT[1][index], 1)); + fT[1][index] = 1; + } + } +} diff --git a/src/pathops/SkDQuadLineIntersection.cpp b/src/pathops/SkDQuadLineIntersection.cpp index b8a9a641dd..ef8edb02cd 100644 --- a/src/pathops/SkDQuadLineIntersection.cpp +++ b/src/pathops/SkDQuadLineIntersection.cpp @@ -105,29 +105,6 @@ public: fAllowNear = allow; } - void checkCoincident() { - int last = fIntersections->used() - 1; - for (int index = 0; index < last; ) { - double quadMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; - SkDPoint quadMidPt = fQuad.ptAtT(quadMidT); - double t = fLine.nearPoint(quadMidPt, NULL); - if (t < 0) { - ++index; - continue; - } - if (fIntersections->isCoincident(index)) { - fIntersections->removeOne(index); - --last; - } else if (fIntersections->isCoincident(index + 1)) { - fIntersections->removeOne(index + 1); - --last; - } else { - fIntersections->setCoincident(index++); - } - fIntersections->setCoincident(index); - } - } - int intersectRay(double roots[2]) { /* solve by rotating line+quad so line is horizontal, then finding the roots @@ -163,17 +140,20 @@ public: if (fAllowNear) { addNearEndPoints(); } - double rootVals[2]; - int roots = intersectRay(rootVals); - for (int index = 0; index < roots; ++index) { - double quadT = rootVals[index]; - double lineT = findLineT(quadT); - SkDPoint pt; - if (pinTs(&quadT, &lineT, &pt, kPointUninitialized) && uniqueAnswer(quadT, pt)) { - fIntersections->insert(quadT, lineT, pt); + if (fIntersections->used() == 2) { + // FIXME : need sharable code that turns spans into coincident if middle point is on + } else { + double rootVals[2]; + int roots = intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + double lineT = findLineT(quadT); + SkDPoint pt; + if (pinTs(&quadT, &lineT, &pt, kPointUninitialized)) { + fIntersections->insert(quadT, lineT, pt); + } } } - checkCoincident(); return fIntersections->used(); } @@ -198,41 +178,16 @@ public: double quadT = rootVals[index]; SkDPoint pt = fQuad.ptAtT(quadT); double lineT = (pt.fX - left) / (right - left); - if (pinTs(&quadT, &lineT, &pt, kPointInitialized) && uniqueAnswer(quadT, pt)) { + if (pinTs(&quadT, &lineT, &pt, kPointInitialized)) { fIntersections->insert(quadT, lineT, pt); } } if (flipped) { fIntersections->flip(); } - checkCoincident(); return fIntersections->used(); } - bool uniqueAnswer(double quadT, const SkDPoint& pt) { - for (int inner = 0; inner < fIntersections->used(); ++inner) { - if (fIntersections->pt(inner) != pt) { - continue; - } - double existingQuadT = (*fIntersections)[0][inner]; - if (quadT == existingQuadT) { - return false; - } - // check if midway on quad is also same point. If so, discard this - double quadMidT = (existingQuadT + quadT) / 2; - SkDPoint quadMidPt = fQuad.ptAtT(quadMidT); - if (quadMidPt.approximatelyEqual(pt)) { - return false; - } - } -#if ONE_OFF_DEBUG - SkDPoint qPt = fQuad.ptAtT(quadT); - SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, - qPt.fX, qPt.fY); -#endif - return true; - } - int verticalIntersect(double axisIntercept, double roots[2]) { double D = fQuad[2].fX; // f double E = fQuad[1].fX; // e @@ -254,14 +209,13 @@ public: double quadT = rootVals[index]; SkDPoint pt = fQuad.ptAtT(quadT); double lineT = (pt.fY - top) / (bottom - top); - if (pinTs(&quadT, &lineT, &pt, kPointInitialized) && uniqueAnswer(quadT, pt)) { + if (pinTs(&quadT, &lineT, &pt, kPointInitialized)) { fIntersections->insert(quadT, lineT, pt); } } if (flipped) { fIntersections->flip(); } - checkCoincident(); return fIntersections->used(); } diff --git a/src/pathops/SkIntersectionHelper.h b/src/pathops/SkIntersectionHelper.h index c633fd02df..3569c934de 100644 --- a/src/pathops/SkIntersectionHelper.h +++ b/src/pathops/SkIntersectionHelper.h @@ -5,7 +5,6 @@ * found in the LICENSE file. */ #include "SkOpContour.h" -#include "SkOpSegment.h" #include "SkPath.h" #ifdef SK_DEBUG @@ -22,9 +21,42 @@ public: kCubic_Segment = SkPath::kCubic_Verb, }; + bool addCoincident(SkIntersectionHelper& other, const SkIntersections& ts, bool swap) { + return fContour->addCoincident(fIndex, other.fContour, other.fIndex, ts, swap); + } + + // FIXME: does it make sense to write otherIndex now if we're going to + // fix it up later? + void addOtherT(int index, double otherT, int otherIndex) { + fContour->addOtherT(fIndex, index, otherT, otherIndex); + } + + bool addPartialCoincident(SkIntersectionHelper& other, const SkIntersections& ts, int index, + bool swap) { + return fContour->addPartialCoincident(fIndex, other.fContour, other.fIndex, ts, index, + swap); + } + + // Avoid collapsing t values that are close to the same since + // we walk ts to describe consecutive intersections. Since a pair of ts can + // be nearly equal, any problems caused by this should be taken care + // of later. + // On the edge or out of range values are negative; add 2 to get end + int addT(const SkIntersectionHelper& other, const SkPoint& pt, double newT) { + return fContour->addT(fIndex, other.fContour, other.fIndex, pt, newT); + } + + int addSelfT(const SkPoint& pt, double newT) { + return fContour->addSelfT(fIndex, pt, newT); + } + bool advance() { - fSegment = fSegment->next(); - return fSegment != NULL; + return ++fIndex < fLast; + } + + void alignTPt(SkIntersectionHelper& other, bool swap, int index, + SkIntersections* ts, SkPoint* point) { + fContour->alignTPt(fIndex, other.fContour, other.fIndex, swap, index, ts, point); } SkScalar bottom() const { @@ -32,15 +64,30 @@ public: } const SkPathOpsBounds& bounds() const { - return fSegment->bounds(); + return fContour->segments()[fIndex].bounds(); } - SkOpContour* contour() const { - return fSegment->contour(); + void init(SkOpContour* contour) { + fContour = contour; + fIndex = 0; + fLast = contour->segments().count(); } - void init(SkOpContour* contour) { - fSegment = contour->first(); + bool isAdjacent(const SkIntersectionHelper& next) { + return fContour == next.fContour && fIndex + 1 == next.fIndex; + } + + bool isFirstLast(const SkIntersectionHelper& next) { + return fContour == next.fContour && fIndex == 0 + && next.fIndex == fLast - 1; + } + + bool isPartial(double t1, double t2, const SkDPoint& pt1, const SkDPoint& pt2) const { + const SkOpSegment& segment = fContour->segments()[fIndex]; + double mid = (t1 + t2) / 2; + SkDPoint midPtByT = segment.dPtAtT(mid); + SkDPoint midPtByAvg = SkDPoint::Mid(pt1, pt2); + return midPtByT.approximatelyPEqual(midPtByAvg); } SkScalar left() const { @@ -48,40 +95,41 @@ public: } const SkPoint* pts() const { - return fSegment->pts(); + return fContour->segments()[fIndex].pts(); } SkScalar right() const { return bounds().fRight; } - SkOpSegment* segment() const { - return fSegment; - } - SegmentType segmentType() const { - SegmentType type = (SegmentType) fSegment->verb(); + const SkOpSegment& segment = fContour->segments()[fIndex]; + SegmentType type = (SegmentType) segment.verb(); if (type != kLine_Segment) { return type; } - if (fSegment->isHorizontal()) { + if (segment.isHorizontal()) { return kHorizontalLine_Segment; } - if (fSegment->isVertical()) { + if (segment.isVertical()) { return kVerticalLine_Segment; } return kLine_Segment; } bool startAfter(const SkIntersectionHelper& after) { - fSegment = after.fSegment->next(); - return fSegment != NULL; + fIndex = after.fIndex; + return advance(); } SkScalar top() const { return bounds().fTop; } + SkPath::Verb verb() const { + return fContour->segments()[fIndex].verb(); + } + SkScalar x() const { return bounds().fLeft; } @@ -99,5 +147,10 @@ public: } private: - SkOpSegment* fSegment; + // utility callable by the user from the debugger when the implementation code is linked in + void dump() const; + + SkOpContour* fContour; + int fIndex; + int fLast; }; diff --git a/src/pathops/SkIntersections.cpp b/src/pathops/SkIntersections.cpp index 007efa7ff1..e9875cf69d 100644 --- a/src/pathops/SkIntersections.cpp +++ b/src/pathops/SkIntersections.cpp @@ -7,25 +7,26 @@ #include "SkIntersections.h" -int SkIntersections::closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, - double* closestDist) const { - int closest = -1; - *closestDist = SK_ScalarMax; - for (int index = 0; index < fUsed; ++index) { - if (!between(rangeStart, fT[0][index], rangeEnd)) { - continue; - } - const SkDPoint& iPt = fPt[index]; - double dist = testPt.distanceSquared(iPt); - if (*closestDist > dist) { - *closestDist = dist; - closest = index; - } +void SkIntersections::append(const SkIntersections& i) { + for (int index = 0; index < i.fUsed; ++index) { + insert(i[0][index], i[1][index], i.pt(index)); } - return closest; } -// called only by test code +int (SkIntersections::* const CurveVertical[])(const SkPoint[], SkScalar, SkScalar, SkScalar, bool) = { + NULL, + &SkIntersections::verticalLine, + &SkIntersections::verticalQuad, + &SkIntersections::verticalCubic +}; + +int ( SkIntersections::* const CurveRay[])(const SkPoint[], const SkDLine&) = { + NULL, + &SkIntersections::lineRay, + &SkIntersections::quadRay, + &SkIntersections::cubicRay +}; + int SkIntersections::coincidentUsed() const { if (!fIsCoincident[0]) { SkASSERT(!fIsCoincident[1]); @@ -47,12 +48,12 @@ int SkIntersections::coincidentUsed() const { return count; } -int (SkIntersections::* const CurveVertical[])(const SkPoint[], SkScalar, SkScalar, SkScalar, bool) = { - NULL, - &SkIntersections::verticalLine, - &SkIntersections::verticalQuad, - &SkIntersections::verticalCubic -}; +int SkIntersections::cubicRay(const SkPoint pts[4], const SkDLine& line) { + SkDCubic cubic; + cubic.set(pts); + fMax = 3; + return intersectRay(cubic, line); +} void SkIntersections::flip() { for (int index = 0; index < fUsed; ++index) { @@ -104,6 +105,7 @@ int SkIntersections::insert(double one, double two, const SkDPoint& pt) { int remaining = fUsed - index; if (remaining > 0) { memmove(&fPt[index + 1], &fPt[index], sizeof(fPt[0]) * remaining); + memmove(&fPt2[index + 1], &fPt2[index], sizeof(fPt2[0]) * remaining); memmove(&fT[0][index + 1], &fT[0][index], sizeof(fT[0][0]) * remaining); memmove(&fT[1][index + 1], &fT[1][index], sizeof(fT[1][0]) * remaining); int clearMask = ~((1 << index) - 1); @@ -123,53 +125,39 @@ void SkIntersections::insertNear(double one, double two, const SkDPoint& pt1, co SkASSERT(one == 0 || one == 1); SkASSERT(two == 0 || two == 1); SkASSERT(pt1 != pt2); - fNearlySame[one ? 1 : 0] = true; + SkASSERT(fNearlySame[(int) one]); (void) insert(one, two, pt1); - fPt2[one ? 1 : 0] = pt2; + fPt2[one ? fUsed - 1 : 0] = pt2; } -int SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) { +void SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) { int index = insertSwap(one, two, pt); - if (index >= 0) { - setCoincident(index); - } - return index; -} - -void SkIntersections::setCoincident(int index) { - SkASSERT(index >= 0); int bit = 1 << index; fIsCoincident[0] |= bit; fIsCoincident[1] |= bit; } -void SkIntersections::merge(const SkIntersections& a, int aIndex, const SkIntersections& b, - int bIndex) { - this->reset(); - fT[0][0] = a.fT[0][aIndex]; - fT[1][0] = b.fT[0][bIndex]; - fPt[0] = a.fPt[aIndex]; - fPt2[0] = b.fPt[bIndex]; - fUsed = 1; +int SkIntersections::lineRay(const SkPoint pts[2], const SkDLine& line) { + SkDLine l; + l.set(pts); + fMax = 2; + return intersectRay(l, line); } -int SkIntersections::mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const { - int result = -1; - for (int index = 0; index < fUsed; ++index) { - if (!between(rangeStart, fT[0][index], rangeEnd)) { - continue; - } - if (result < 0) { - result = index; - continue; - } - SkDVector best = fPt[result] - origin; - SkDVector test = fPt[index] - origin; - if (test.crossCheck(best) < 0) { - result = index; - } +void SkIntersections::offset(int base, double start, double end) { + for (int index = base; index < fUsed; ++index) { + double val = fT[fSwap][index]; + val *= end - start; + val += start; + fT[fSwap][index] = val; } - return result; +} + +int SkIntersections::quadRay(const SkPoint pts[3], const SkDLine& line) { + SkDQuad quad; + quad.set(pts); + fMax = 2; + return intersectRay(quad, line); } void SkIntersections::quickRemoveOne(int index, int replace) { @@ -184,6 +172,7 @@ void SkIntersections::removeOne(int index) { return; } memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining); + memmove(&fPt2[index], &fPt2[index + 1], sizeof(fPt2[0]) * remaining); memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining); memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining); // SkASSERT(fIsCoincident[0] == 0); @@ -193,6 +182,13 @@ void SkIntersections::removeOne(int index) { fIsCoincident[1] -= ((fIsCoincident[1] >> 1) & ~((1 << index) - 1)) + coBit; } +void SkIntersections::swapPts() { + int index; + for (index = 0; index < fUsed; ++index) { + SkTSwap(fT[0][index], fT[1][index]); + } +} + int SkIntersections::verticalLine(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { SkDLine line; diff --git a/src/pathops/SkIntersections.h b/src/pathops/SkIntersections.h index 15bac19def..a1bde512db 100644 --- a/src/pathops/SkIntersections.h +++ b/src/pathops/SkIntersections.h @@ -16,6 +16,7 @@ class SkIntersections { public: SkIntersections() : fSwap(0) + , fFlatMeasure(false) #ifdef SK_DEBUG , fDepth(0) #endif @@ -23,6 +24,7 @@ public: sk_bzero(fPt, sizeof(fPt)); sk_bzero(fPt2, sizeof(fPt2)); sk_bzero(fT, sizeof(fT)); + sk_bzero(fIsCoincident, sizeof(fIsCoincident)); sk_bzero(fNearlySame, sizeof(fNearlySame)); reset(); fMax = 0; // require that the caller set the max @@ -30,7 +32,7 @@ public: class TArray { public: - explicit TArray(const double ts[10]) : fTArray(ts) {} + explicit TArray(const double ts[9]) : fTArray(ts) {} double operator[](int n) const { return fTArray[n]; } @@ -38,15 +40,28 @@ public: }; TArray operator[](int n) const { return TArray(fT[n]); } + void allowFlatMeasure(bool flatAllowed) { + fFlatMeasure = flatAllowed; + } + void allowNear(bool nearAllowed) { fAllowNear = nearAllowed; } - void clearCoincidence(int index) { - SkASSERT(index >= 0); - int bit = 1 << index; - fIsCoincident[0] &= ~bit; - fIsCoincident[1] &= ~bit; + int cubic(const SkPoint a[4]) { + SkDCubic cubic; + cubic.set(a); + fMax = 1; // self intersect + return intersect(cubic); + } + + int cubicCubic(const SkPoint a[4], const SkPoint b[4]) { + SkDCubic aCubic; + aCubic.set(a); + SkDCubic bCubic; + bCubic.set(b); + fMax = 9; + return intersect(aCubic, bCubic); } int cubicHorizontal(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y, @@ -73,6 +88,19 @@ public: return intersect(cubic, line); } + int cubicQuad(const SkPoint a[4], const SkPoint b[3]) { + SkDCubic cubic; + cubic.set(a); + SkDQuad quad; + quad.set(b); + fMax = 7; + return intersect(cubic, quad); + } + + bool flatMeasure() const { + return fFlatMeasure; + } + bool hasT(double t) const { SkASSERT(t == 0 || t == 1); return fUsed > 0 && (t == 0 ? fT[0][0] == 0 : fT[0][fUsed - 1] == 1); @@ -150,11 +178,19 @@ public: return intersect(quad, line); } + int quadQuad(const SkPoint a[3], const SkPoint b[3]) { + SkDQuad aQuad; + aQuad.set(a); + SkDQuad bQuad; + bQuad.set(b); + fMax = 4; + return intersect(aQuad, bQuad); + } + // leaves swap, max alone void reset() { fAllowNear = true; fUsed = 0; - sk_bzero(fIsCoincident, sizeof(fIsCoincident)); } void set(bool swap, int tIndex, double t) { @@ -169,6 +205,8 @@ public: fSwap ^= true; } + void swapPts(); + bool swapped() const { return fSwap; } @@ -181,27 +219,19 @@ public: SkASSERT(--fDepth >= 0); } - bool unBumpT(int index) { - SkASSERT(fUsed == 1); - fT[0][index] = fT[0][index] * (1 + BUMP_EPSILON * 2) - BUMP_EPSILON; - if (!between(0, fT[0][index], 1)) { - fUsed = 0; - return false; - } - return true; - } - void upDepth() { SkASSERT(++fDepth < 16); } void alignQuadPts(const SkPoint a[3], const SkPoint b[3]); + void append(const SkIntersections& ); int cleanUpCoincidence(); - int closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, double* dist) const; int coincidentUsed() const; void cubicInsert(double one, double two, const SkDPoint& pt, const SkDCubic& c1, const SkDCubic& c2); + int cubicRay(const SkPoint pts[4], const SkDLine& line); void flip(); + int horizontal(const SkDLine&, double y); int horizontal(const SkDLine&, double left, double right, double y, bool flipped); int horizontal(const SkDQuad&, double left, double right, double y, bool flipped); int horizontal(const SkDQuad&, double left, double right, double y, double tRange[2]); @@ -212,20 +242,25 @@ public: int insert(double one, double two, const SkDPoint& pt); void insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2); // start if index == 0 : end if index == 1 - int insertCoincident(double one, double two, const SkDPoint& pt); + void insertCoincident(double one, double two, const SkDPoint& pt); int intersect(const SkDLine&, const SkDLine&); int intersect(const SkDQuad&, const SkDLine&); int intersect(const SkDQuad&, const SkDQuad&); + int intersect(const SkDCubic&); // return true if cubic self-intersects int intersect(const SkDCubic&, const SkDLine&); + int intersect(const SkDCubic&, const SkDQuad&); int intersect(const SkDCubic&, const SkDCubic&); int intersectRay(const SkDLine&, const SkDLine&); int intersectRay(const SkDQuad&, const SkDLine&); int intersectRay(const SkDCubic&, const SkDLine&); - void merge(const SkIntersections& , int , const SkIntersections& , int ); - int mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const; + static SkDPoint Line(const SkDLine&, const SkDLine&); + int lineRay(const SkPoint pts[2], const SkDLine& line); + void offset(int base, double start, double end); void quickRemoveOne(int index, int replace); + int quadRay(const SkPoint pts[3], const SkDLine& line); void removeOne(int index); - void setCoincident(int index); + static bool Test(const SkDLine& , const SkDLine&); + int vertical(const SkDLine&, double x); int vertical(const SkDLine&, double top, double bottom, double x, bool flipped); int vertical(const SkDQuad&, double top, double bottom, double x, bool flipped); int vertical(const SkDCubic&, double top, double bottom, double x, bool flipped); @@ -241,8 +276,6 @@ public: #endif } - void dump() const; // implemented for testing only - private: bool cubicCheckCoincidence(const SkDCubic& c1, const SkDCubic& c2); bool cubicExactEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2); @@ -250,20 +283,22 @@ private: void cleanUpParallelLines(bool parallel); void computePoints(const SkDLine& line, int used); - SkDPoint fPt[10]; // FIXME: since scans store points as SkPoint, this should also - SkDPoint fPt2[2]; // used by nearly same to store alternate intersection point - double fT[2][10]; + SkDPoint fPt[9]; // FIXME: since scans store points as SkPoint, this should also + SkDPoint fPt2[9]; // used by nearly same to store alternate intersection point + double fT[2][9]; uint16_t fIsCoincident[2]; // bit set for each curve's coincident T bool fNearlySame[2]; // true if end points nearly match unsigned char fUsed; unsigned char fMax; bool fAllowNear; bool fSwap; + bool fFlatMeasure; // backwards-compatibility when cubics uses quad intersection #ifdef SK_DEBUG int fDepth; #endif }; +extern int (SkIntersections::* const CurveRay[])(const SkPoint[], const SkDLine& ); extern int (SkIntersections::* const CurveVertical[])(const SkPoint[], SkScalar top, SkScalar bottom, SkScalar x, bool flipped); diff --git a/src/pathops/SkOpAngle.cpp b/src/pathops/SkOpAngle.cpp index c13a51a8cc..b3a188c1e8 100644 --- a/src/pathops/SkOpAngle.cpp +++ b/src/pathops/SkOpAngle.cpp @@ -4,26 +4,26 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ +#include "SkIntersections.h" #include "SkOpAngle.h" #include "SkOpSegment.h" #include "SkPathOpsCurve.h" #include "SkTSort.h" +#if DEBUG_ANGLE +#include "SkString.h" +#endif + /* Angles are sorted counterclockwise. The smallest angle has a positive x and the smallest positive y. The largest angle has a positive x and a zero y. */ #if DEBUG_ANGLE - static bool CompareResult(const char* func, SkString* bugOut, SkString* bugPart, int append, - bool compare) { + static bool CompareResult(SkString* bugOut, int append, bool compare) { SkDebugf("%s %c %d\n", bugOut->c_str(), compare ? 'T' : 'F', append); - SkDebugf("%sPart %s\n", func, bugPart[0].c_str()); - SkDebugf("%sPart %s\n", func, bugPart[1].c_str()); - SkDebugf("%sPart %s\n", func, bugPart[2].c_str()); return compare; } - #define COMPARE_RESULT(append, compare) CompareResult(__FUNCTION__, &bugOut, bugPart, append, \ - compare) + #define COMPARE_RESULT(append, compare) CompareResult(&bugOut, append, compare) #else #define COMPARE_RESULT(append, compare) compare #endif @@ -58,50 +58,51 @@ */ // return true if lh < this < rh -bool SkOpAngle::after(SkOpAngle* test) { - SkOpAngle* lh = test; - SkOpAngle* rh = lh->fNext; - SkASSERT(lh != rh); +bool SkOpAngle::after(const SkOpAngle* test) const { + const SkOpAngle& lh = *test; + const SkOpAngle& rh = *lh.fNext; + SkASSERT(&lh != &rh); #if DEBUG_ANGLE SkString bugOut; bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__, - lh->segment()->debugID(), lh->debugID(), lh->fSectorStart, lh->fSectorEnd, - lh->fStart->t(), lh->fEnd->t(), - segment()->debugID(), debugID(), fSectorStart, fSectorEnd, fStart->t(), fEnd->t(), - rh->segment()->debugID(), rh->debugID(), rh->fSectorStart, rh->fSectorEnd, - rh->fStart->t(), rh->fEnd->t()); - SkString bugPart[3] = { lh->debugPart(), this->debugPart(), rh->debugPart() }; + lh.fSegment->debugID(), lh.debugID(), lh.fSectorStart, lh.fSectorEnd, + lh.fSegment->t(lh.fStart), lh.fSegment->t(lh.fEnd), + fSegment->debugID(), debugID(), fSectorStart, fSectorEnd, fSegment->t(fStart), + fSegment->t(fEnd), + rh.fSegment->debugID(), rh.debugID(), rh.fSectorStart, rh.fSectorEnd, + rh.fSegment->t(rh.fStart), rh.fSegment->t(rh.fEnd)); #endif - if (lh->fComputeSector && !lh->computeSector()) { + if (lh.fComputeSector && !const_cast<SkOpAngle&>(lh).computeSector()) { return COMPARE_RESULT(1, true); } - if (fComputeSector && !this->computeSector()) { + if (fComputeSector && !const_cast<SkOpAngle*>(this)->computeSector()) { return COMPARE_RESULT(2, true); } - if (rh->fComputeSector && !rh->computeSector()) { + if (rh.fComputeSector && !const_cast<SkOpAngle&>(rh).computeSector()) { return COMPARE_RESULT(3, true); } #if DEBUG_ANGLE // reset bugOut with computed sectors bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__, - lh->segment()->debugID(), lh->debugID(), lh->fSectorStart, lh->fSectorEnd, - lh->fStart->t(), lh->fEnd->t(), - segment()->debugID(), debugID(), fSectorStart, fSectorEnd, fStart->t(), fEnd->t(), - rh->segment()->debugID(), rh->debugID(), rh->fSectorStart, rh->fSectorEnd, - rh->fStart->t(), rh->fEnd->t()); + lh.fSegment->debugID(), lh.debugID(), lh.fSectorStart, lh.fSectorEnd, + lh.fSegment->t(lh.fStart), lh.fSegment->t(lh.fEnd), + fSegment->debugID(), debugID(), fSectorStart, fSectorEnd, fSegment->t(fStart), + fSegment->t(fEnd), + rh.fSegment->debugID(), rh.debugID(), rh.fSectorStart, rh.fSectorEnd, + rh.fSegment->t(rh.fStart), rh.fSegment->t(rh.fEnd)); #endif - bool ltrOverlap = (lh->fSectorMask | rh->fSectorMask) & fSectorMask; - bool lrOverlap = lh->fSectorMask & rh->fSectorMask; + bool ltrOverlap = (lh.fSectorMask | rh.fSectorMask) & fSectorMask; + bool lrOverlap = lh.fSectorMask & rh.fSectorMask; int lrOrder; // set to -1 if either order works if (!lrOverlap) { // no lh/rh sector overlap if (!ltrOverlap) { // no lh/this/rh sector overlap - return COMPARE_RESULT(4, (lh->fSectorEnd > rh->fSectorStart) - ^ (fSectorStart > lh->fSectorEnd) ^ (fSectorStart > rh->fSectorStart)); + return COMPARE_RESULT(4, (lh.fSectorEnd > rh.fSectorStart) + ^ (fSectorStart > lh.fSectorEnd) ^ (fSectorStart > rh.fSectorStart)); } - int lrGap = (rh->fSectorStart - lh->fSectorStart + 32) & 0x1f; + int lrGap = (rh.fSectorStart - lh.fSectorStart + 32) & 0x1f; /* A tiny change can move the start +/- 4. The order can only be determined if lr gap is not 12 to 20 or -12 to -20. -31 ..-21 1 @@ -114,24 +115,24 @@ bool SkOpAngle::after(SkOpAngle* test) { */ lrOrder = lrGap > 20 ? 0 : lrGap > 11 ? -1 : 1; } else { - lrOrder = (int) lh->orderable(rh); + lrOrder = (int) lh.orderable(rh); if (!ltrOverlap) { return COMPARE_RESULT(5, !lrOrder); } } int ltOrder; - SkASSERT((lh->fSectorMask & fSectorMask) || (rh->fSectorMask & fSectorMask)); - if (lh->fSectorMask & fSectorMask) { - ltOrder = (int) lh->orderable(this); + SkASSERT((lh.fSectorMask & fSectorMask) || (rh.fSectorMask & fSectorMask)); + if (lh.fSectorMask & fSectorMask) { + ltOrder = (int) lh.orderable(*this); } else { - int ltGap = (fSectorStart - lh->fSectorStart + 32) & 0x1f; + int ltGap = (fSectorStart - lh.fSectorStart + 32) & 0x1f; ltOrder = ltGap > 20 ? 0 : ltGap > 11 ? -1 : 1; } int trOrder; - if (rh->fSectorMask & fSectorMask) { + if (rh.fSectorMask & fSectorMask) { trOrder = (int) orderable(rh); } else { - int trGap = (rh->fSectorStart - fSectorStart + 32) & 0x1f; + int trGap = (rh.fSectorStart - fSectorStart + 32) & 0x1f; trOrder = trGap > 20 ? 0 : trGap > 11 ? -1 : 1; } if (lrOrder >= 0 && ltOrder >= 0 && trOrder >= 0) { @@ -144,20 +145,20 @@ bool SkOpAngle::after(SkOpAngle* test) { if (ltOrder == 0 && lrOrder == 0) { SkASSERT(trOrder < 0); // FIXME : once this is verified to work, remove one opposite angle call - SkDEBUGCODE(bool lrOpposite = lh->oppositePlanes(rh)); - bool ltOpposite = lh->oppositePlanes(this); + SkDEBUGCODE(bool lrOpposite = lh.oppositePlanes(rh)); + bool ltOpposite = lh.oppositePlanes(*this); SkASSERT(lrOpposite != ltOpposite); return COMPARE_RESULT(8, ltOpposite); } else if (ltOrder == 1 && trOrder == 0) { SkASSERT(lrOrder < 0); - SkDEBUGCODE(bool ltOpposite = lh->oppositePlanes(this)); + SkDEBUGCODE(bool ltOpposite = lh.oppositePlanes(*this)); bool trOpposite = oppositePlanes(rh); SkASSERT(ltOpposite != trOpposite); return COMPARE_RESULT(9, trOpposite); } else if (lrOrder == 1 && trOrder == 1) { SkASSERT(ltOrder < 0); SkDEBUGCODE(bool trOpposite = oppositePlanes(rh)); - bool lrOpposite = lh->oppositePlanes(rh); + bool lrOpposite = lh.oppositePlanes(rh); SkASSERT(lrOpposite != trOpposite); return COMPARE_RESULT(10, lrOpposite); } @@ -172,50 +173,77 @@ bool SkOpAngle::after(SkOpAngle* test) { // given a line, see if the opposite curve's convex hull is all on one side // returns -1=not on one side 0=this CW of test 1=this CCW of test -int SkOpAngle::allOnOneSide(const SkOpAngle* test) { +int SkOpAngle::allOnOneSide(const SkOpAngle& test) const { SkASSERT(!fIsCurve); - SkASSERT(test->fIsCurve); - const SkDPoint& origin = test->fCurvePart[0]; + SkASSERT(test.fIsCurve); + const SkDPoint& origin = test.fCurvePart[0]; SkVector line; - if (segment()->verb() == SkPath::kLine_Verb) { - const SkPoint* linePts = segment()->pts(); - int lineStart = fStart->t() < fEnd->t() ? 0 : 1; + if (fSegment->verb() == SkPath::kLine_Verb) { + const SkPoint* linePts = fSegment->pts(); + int lineStart = fStart < fEnd ? 0 : 1; line = linePts[lineStart ^ 1] - linePts[lineStart]; } else { SkPoint shortPts[2] = { fCurvePart[0].asSkPoint(), fCurvePart[1].asSkPoint() }; line = shortPts[1] - shortPts[0]; } float crosses[3]; - SkPath::Verb testVerb = test->segment()->verb(); + SkPath::Verb testVerb = test.fSegment->verb(); int iMax = SkPathOpsVerbToPoints(testVerb); // SkASSERT(origin == test.fCurveHalf[0]); - const SkDCubic& testCurve = test->fCurvePart; - for (int index = 1; index <= iMax; ++index) { - float xy1 = (float) (line.fX * (testCurve[index].fY - origin.fY)); - float xy2 = (float) (line.fY * (testCurve[index].fX - origin.fX)); - crosses[index - 1] = AlmostEqualUlps(xy1, xy2) ? 0 : xy1 - xy2; - } - if (crosses[0] * crosses[1] < 0) { - return -1; - } - if (SkPath::kCubic_Verb == testVerb) { - if (crosses[0] * crosses[2] < 0 || crosses[1] * crosses[2] < 0) { + const SkDCubic& testCurve = test.fCurvePart; +// do { + for (int index = 1; index <= iMax; ++index) { + float xy1 = (float) (line.fX * (testCurve[index].fY - origin.fY)); + float xy2 = (float) (line.fY * (testCurve[index].fX - origin.fX)); + crosses[index - 1] = AlmostEqualUlps(xy1, xy2) ? 0 : xy1 - xy2; + } + if (crosses[0] * crosses[1] < 0) { return -1; } - } - if (crosses[0]) { - return crosses[0] < 0; - } - if (crosses[1]) { - return crosses[1] < 0; - } - if (SkPath::kCubic_Verb == testVerb && crosses[2]) { - return crosses[2] < 0; - } + if (SkPath::kCubic_Verb == testVerb) { + if (crosses[0] * crosses[2] < 0 || crosses[1] * crosses[2] < 0) { + return -1; + } + } + if (crosses[0]) { + return crosses[0] < 0; + } + if (crosses[1]) { + return crosses[1] < 0; + } + if (SkPath::kCubic_Verb == testVerb && crosses[2]) { + return crosses[2] < 0; + } fUnorderable = true; return -1; } +bool SkOpAngle::calcSlop(double x, double y, double rx, double ry, bool* result) const { + double absX = fabs(x); + double absY = fabs(y); + double length = absX < absY ? absX / 2 + absY : absX + absY / 2; + int exponent; + (void) frexp(length, &exponent); + double epsilon = ldexp(FLT_EPSILON, exponent); + SkPath::Verb verb = fSegment->verb(); + SkASSERT(verb == SkPath::kQuad_Verb || verb == SkPath::kCubic_Verb); + // FIXME: the quad and cubic factors are made up ; determine actual values + double slop = verb == SkPath::kQuad_Verb ? 4 * epsilon : 512 * epsilon; + double xSlop = slop; + double ySlop = x * y < 0 ? -xSlop : xSlop; // OPTIMIZATION: use copysign / _copysign ? + double x1 = x - xSlop; + double y1 = y + ySlop; + double x_ry1 = x1 * ry; + double rx_y1 = rx * y1; + *result = x_ry1 < rx_y1; + double x2 = x + xSlop; + double y2 = y - ySlop; + double x_ry2 = x2 * ry; + double rx_y2 = rx * y2; + bool less2 = x_ry2 < rx_y2; + return *result == less2; +} + bool SkOpAngle::checkCrossesZero() const { int start = SkTMin(fSectorStart, fSectorEnd); int end = SkTMax(fSectorStart, fSectorEnd); @@ -223,94 +251,31 @@ bool SkOpAngle::checkCrossesZero() const { return crossesZero; } -// loop looking for a pair of angle parts that are too close to be sorted -/* This is called after other more simple intersection and angle sorting tests have been exhausted. - This should be rarely called -- the test below is thorough and time consuming. - This checks the distance between start points; the distance between -*/ -void SkOpAngle::checkNearCoincidence() { - SkOpAngle* test = this; - do { - SkOpSegment* testSegment = test->segment(); - double testStartT = test->start()->t(); - SkDPoint testStartPt = testSegment->dPtAtT(testStartT); - double testEndT = test->end()->t(); - SkDPoint testEndPt = testSegment->dPtAtT(testEndT); - double testLenSq = testStartPt.distanceSquared(testEndPt); - if (0) { - SkDebugf("%s testLenSq=%1.9g id=%d\n", __FUNCTION__, testLenSq, testSegment->debugID()); - } - double testMidT = (testStartT + testEndT) / 2; - SkOpAngle* next = test; - while ((next = next->fNext) != this) { - SkOpSegment* nextSegment = next->segment(); - double testMidDistSq = testSegment->distSq(testMidT, next); - double testEndDistSq = testSegment->distSq(testEndT, next); - double nextStartT = next->start()->t(); - SkDPoint nextStartPt = nextSegment->dPtAtT(nextStartT); - double distSq = testStartPt.distanceSquared(nextStartPt); - double nextEndT = next->end()->t(); - double nextMidT = (nextStartT + nextEndT) / 2; - double nextMidDistSq = nextSegment->distSq(nextMidT, test); - double nextEndDistSq = nextSegment->distSq(nextEndT, test); - if (0) { - SkDebugf("%s distSq=%1.9g testId=%d nextId=%d\n", __FUNCTION__, distSq, - testSegment->debugID(), nextSegment->debugID()); - SkDebugf("%s testMidDistSq=%1.9g\n", __FUNCTION__, testMidDistSq); - SkDebugf("%s testEndDistSq=%1.9g\n", __FUNCTION__, testEndDistSq); - SkDebugf("%s nextMidDistSq=%1.9g\n", __FUNCTION__, nextMidDistSq); - SkDebugf("%s nextEndDistSq=%1.9g\n", __FUNCTION__, nextEndDistSq); - SkDPoint nextEndPt = nextSegment->dPtAtT(nextEndT); - double nextLenSq = nextStartPt.distanceSquared(nextEndPt); - SkDebugf("%s nextLenSq=%1.9g\n", __FUNCTION__, nextLenSq); - SkDebugf("\n"); - } - } - test = test->fNext; - } while (test->fNext != this); -} - -bool SkOpAngle::checkParallel(SkOpAngle* rh) { +bool SkOpAngle::checkParallel(const SkOpAngle& rh) const { SkDVector scratch[2]; const SkDVector* sweep, * tweep; - if (!this->fUnorderedSweep) { - sweep = this->fSweep; + if (!fUnorderedSweep) { + sweep = fSweep; } else { - scratch[0] = this->fCurvePart[1] - this->fCurvePart[0]; + scratch[0] = fCurvePart[1] - fCurvePart[0]; sweep = &scratch[0]; } - if (!rh->fUnorderedSweep) { - tweep = rh->fSweep; + if (!rh.fUnorderedSweep) { + tweep = rh.fSweep; } else { - scratch[1] = rh->fCurvePart[1] - rh->fCurvePart[0]; + scratch[1] = rh.fCurvePart[1] - rh.fCurvePart[0]; tweep = &scratch[1]; } double s0xt0 = sweep->crossCheck(*tweep); if (tangentsDiverge(rh, s0xt0)) { return s0xt0 < 0; } - // compute the perpendicular to the endpoints and see where it intersects the opposite curve - // if the intersections within the t range, do a cross check on those - bool inside; - if (this->endToSide(rh, &inside)) { - return inside; - } - if (rh->endToSide(this, &inside)) { - return !inside; - } - if (this->midToSide(rh, &inside)) { - return inside; - } - if (rh->midToSide(this, &inside)) { - return !inside; - } - // compute the cross check from the mid T values (last resort) - SkDVector m0 = segment()->dPtAtT(this->midT()) - this->fCurvePart[0]; - SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fCurvePart[0]; + SkDVector m0 = fSegment->dPtAtT(midT()) - fCurvePart[0]; + SkDVector m1 = rh.fSegment->dPtAtT(rh.midT()) - rh.fCurvePart[0]; double m0xm1 = m0.crossCheck(m1); if (m0xm1 == 0) { - this->fUnorderable = true; - rh->fUnorderable = true; + fUnorderable = true; + rh.fUnorderable = true; return true; } return m0xm1 < 0; @@ -323,51 +288,48 @@ bool SkOpAngle::computeSector() { if (fComputedSector) { return !fUnorderable; } +// SkASSERT(fSegment->verb() != SkPath::kLine_Verb && small()); fComputedSector = true; - bool stepUp = fStart->t() < fEnd->t(); - const SkOpSpanBase* checkEnd = fEnd; - if (checkEnd->final() && stepUp) { - fUnorderable = true; - return false; - } + int step = fStart < fEnd ? 1 : -1; + int limit = step > 0 ? fSegment->count() : -1; + int checkEnd = fEnd; do { // advance end - const SkOpSegment* other = checkEnd->segment(); - const SkOpSpanBase* oSpan = other->head(); - do { - if (oSpan->segment() != segment()) { + const SkOpSpan& span = fSegment->span(checkEnd); + const SkOpSegment* other = span.fOther; + int oCount = other->count(); + for (int oIndex = 0; oIndex < oCount; ++oIndex) { + const SkOpSpan& oSpan = other->span(oIndex); + if (oSpan.fOther != fSegment) { continue; } - if (oSpan == checkEnd) { + if (oSpan.fOtherIndex == checkEnd) { continue; } - if (!approximately_equal(oSpan->t(), checkEnd->t())) { + if (!approximately_equal(oSpan.fOtherT, span.fT)) { continue; } goto recomputeSector; - } while (!oSpan->final() && (oSpan = oSpan->upCast()->next())); - checkEnd = stepUp ? !checkEnd->final() - ? checkEnd->upCast()->next() : NULL - : checkEnd->prev(); - } while (checkEnd); + } + checkEnd += step; + } while (checkEnd != limit); recomputeSector: - SkOpSpanBase* computedEnd = stepUp ? checkEnd ? checkEnd->prev() : fEnd->segment()->head() - : checkEnd ? checkEnd->upCast()->next() : fEnd->segment()->tail(); - if (checkEnd == fEnd || computedEnd == fEnd || computedEnd == fStart) { + if (checkEnd == fEnd || checkEnd - step == fEnd) { fUnorderable = true; return false; } - SkOpSpanBase* saveEnd = fEnd; - fComputedEnd = fEnd = computedEnd; + int saveEnd = fEnd; + fComputedEnd = fEnd = checkEnd - step; setSpans(); setSector(); fEnd = saveEnd; return !fUnorderable; } -int SkOpAngle::convexHullOverlaps(const SkOpAngle* rh) const { - const SkDVector* sweep = this->fSweep; - const SkDVector* tweep = rh->fSweep; +// returns -1 if overlaps 0 if no overlap cw 1 if no overlap ccw +int SkOpAngle::convexHullOverlaps(const SkOpAngle& rh) const { + const SkDVector* sweep = fSweep; + const SkDVector* tweep = rh.fSweep; double s0xs1 = sweep[0].crossCheck(sweep[1]); double s0xt0 = sweep[0].crossCheck(tweep[0]); double s1xt0 = sweep[1].crossCheck(tweep[0]); @@ -397,8 +359,8 @@ int SkOpAngle::convexHullOverlaps(const SkOpAngle* rh) const { // if the outside sweeps are greater than 180 degress: // first assume the inital tangents are the ordering // if the midpoint direction matches the inital order, that is enough - SkDVector m0 = this->segment()->dPtAtT(this->midT()) - this->fCurvePart[0]; - SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fCurvePart[0]; + SkDVector m0 = fSegment->dPtAtT(midT()) - fCurvePart[0]; + SkDVector m1 = rh.fSegment->dPtAtT(rh.midT()) - rh.fCurvePart[0]; double m0xm1 = m0.crossCheck(m1); if (s0xt0 > 0 && m0xm1 > 0) { return 0; @@ -432,30 +394,34 @@ double SkOpAngle::distEndRatio(double dist) const { return sqrt(longest) / dist; } -bool SkOpAngle::endsIntersect(SkOpAngle* rh) { - SkPath::Verb lVerb = this->segment()->verb(); - SkPath::Verb rVerb = rh->segment()->verb(); +bool SkOpAngle::endsIntersect(const SkOpAngle& rh) const { + SkPath::Verb lVerb = fSegment->verb(); + SkPath::Verb rVerb = rh.fSegment->verb(); int lPts = SkPathOpsVerbToPoints(lVerb); int rPts = SkPathOpsVerbToPoints(rVerb); - SkDLine rays[] = {{{this->fCurvePart[0], rh->fCurvePart[rPts]}}, - {{this->fCurvePart[0], this->fCurvePart[lPts]}}}; + SkDLine rays[] = {{{fCurvePart[0], rh.fCurvePart[rPts]}}, + {{fCurvePart[0], fCurvePart[lPts]}}}; if (rays[0][1] == rays[1][1]) { return checkParallel(rh); } double smallTs[2] = {-1, -1}; bool limited[2] = {false, false}; for (int index = 0; index < 2; ++index) { + const SkOpSegment& segment = index ? *rh.fSegment : *fSegment; + SkIntersections i; int cPts = index ? rPts : lPts; + (*CurveIntersectRay[cPts])(segment.pts(), rays[index], &i); // if the curve is a line, then the line and the ray intersect only at their crossing if (cPts == 1) { // line continue; } - const SkOpSegment& segment = index ? *rh->segment() : *this->segment(); - SkIntersections i; - (*CurveIntersectRay[cPts])(segment.pts(), rays[index], &i); - double tStart = index ? rh->fStart->t() : this->fStart->t(); - double tEnd = index ? rh->fComputedEnd->t() : this->fComputedEnd->t(); - bool testAscends = tStart < (index ? rh->fComputedEnd->t() : this->fComputedEnd->t()); +// SkASSERT(i.used() >= 1); +// if (i.used() <= 1) { +// continue; +// } + double tStart = segment.t(index ? rh.fStart : fStart); + double tEnd = segment.t(index ? rh.fComputedEnd : fComputedEnd); + bool testAscends = index ? rh.fStart < rh.fComputedEnd : fStart < fComputedEnd; double t = testAscends ? 0 : 1; for (int idx2 = 0; idx2 < i.used(); ++idx2) { double testT = i[0][idx2]; @@ -469,6 +435,29 @@ bool SkOpAngle::endsIntersect(SkOpAngle* rh) { limited[index] = approximately_equal_orderable(t, tEnd); } } +#if 0 + if (smallTs[0] < 0 && smallTs[1] < 0) { // if neither ray intersects, do endpoint sort + double m0xm1 = 0; + if (lVerb == SkPath::kLine_Verb) { + SkASSERT(rVerb != SkPath::kLine_Verb); + SkDVector m0 = rays[1][1] - fCurvePart[0]; + SkDPoint endPt; + endPt.set(rh.fSegment->pts()[rh.fStart < rh.fEnd ? rPts : 0]); + SkDVector m1 = endPt - fCurvePart[0]; + m0xm1 = m0.crossCheck(m1); + } + if (rVerb == SkPath::kLine_Verb) { + SkDPoint endPt; + endPt.set(fSegment->pts()[fStart < fEnd ? lPts : 0]); + SkDVector m0 = endPt - fCurvePart[0]; + SkDVector m1 = rays[0][1] - fCurvePart[0]; + m0xm1 = m0.crossCheck(m1); + } + if (m0xm1 != 0) { + return m0xm1 < 0; + } + } +#endif bool sRayLonger = false; SkDVector sCept = {0, 0}; double sCeptT = -1; @@ -478,7 +467,7 @@ bool SkOpAngle::endsIntersect(SkOpAngle* rh) { if (smallTs[index] < 0) { continue; } - const SkOpSegment& segment = index ? *rh->segment() : *this->segment(); + const SkOpSegment& segment = index ? *rh.fSegment : *fSegment; const SkDPoint& dPt = segment.dPtAtT(smallTs[index]); SkDVector cept = dPt - rays[index][0]; // If this point is on the curve, it should have been detected earlier by ordinary @@ -509,7 +498,7 @@ bool SkOpAngle::endsIntersect(SkOpAngle* rh) { double minX, minY, maxX, maxY; minX = minY = SK_ScalarInfinity; maxX = maxY = -SK_ScalarInfinity; - const SkDCubic& curve = index ? rh->fCurvePart : this->fCurvePart; + const SkDCubic& curve = index ? rh.fCurvePart : fCurvePart; int ptCount = index ? rPts : lPts; for (int idx2 = 0; idx2 <= ptCount; ++idx2) { minX = SkTMin(minX, curve[idx2].fX); @@ -519,7 +508,7 @@ bool SkOpAngle::endsIntersect(SkOpAngle* rh) { } double maxWidth = SkTMax(maxX - minX, maxY - minY); delta /= maxWidth; - if (delta > 1e-3 && (useIntersect ^= true)) { // FIXME: move this magic number + if (delta > 1e-4 && (useIntersect ^= true)) { // FIXME: move this magic number sRayLonger = rayLonger; sCept = cept; sCeptT = smallTs[index]; @@ -527,9 +516,9 @@ bool SkOpAngle::endsIntersect(SkOpAngle* rh) { } } if (useIntersect) { - const SkDCubic& curve = sIndex ? rh->fCurvePart : this->fCurvePart; - const SkOpSegment& segment = sIndex ? *rh->segment() : *this->segment(); - double tStart = sIndex ? rh->fStart->t() : fStart->t(); + const SkDCubic& curve = sIndex ? rh.fCurvePart : fCurvePart; + const SkOpSegment& segment = sIndex ? *rh.fSegment : *fSegment; + double tStart = segment.t(sIndex ? rh.fStart : fStart); SkDVector mid = segment.dPtAtT(tStart + (sCeptT - tStart) / 2) - curve[0]; double septDir = mid.crossCheck(sCept); if (!septDir) { @@ -541,65 +530,12 @@ bool SkOpAngle::endsIntersect(SkOpAngle* rh) { } } -bool SkOpAngle::endToSide(const SkOpAngle* rh, bool* inside) const { - const SkOpSegment* segment = this->segment(); - SkPath::Verb verb = segment->verb(); - int pts = SkPathOpsVerbToPoints(verb); - SkDLine rayEnd; - rayEnd[0].set(this->fEnd->pt()); - rayEnd[1] = rayEnd[0]; - SkDVector slopeAtEnd = (*CurveDSlopeAtT[pts])(segment->pts(), this->fEnd->t()); - rayEnd[1].fX += slopeAtEnd.fY; - rayEnd[1].fY -= slopeAtEnd.fX; - SkIntersections iEnd; - const SkOpSegment* oppSegment = rh->segment(); - SkPath::Verb oppVerb = oppSegment->verb(); - int oppPts = SkPathOpsVerbToPoints(oppVerb); - (*CurveIntersectRay[oppPts])(oppSegment->pts(), rayEnd, &iEnd); - double endDist; - int closestEnd = iEnd.closestTo(rh->fStart->t(), rh->fEnd->t(), rayEnd[0], &endDist); - if (closestEnd < 0) { - return false; - } - if (!endDist) { - return false; - } - SkDPoint start; - start.set(this->fStart->pt()); - // OPTIMIZATION: multiple times in the code we find the max scalar - double minX, minY, maxX, maxY; - minX = minY = SK_ScalarInfinity; - maxX = maxY = -SK_ScalarInfinity; - const SkDCubic& curve = rh->fCurvePart; - for (int idx2 = 0; idx2 <= oppPts; ++idx2) { - minX = SkTMin(minX, curve[idx2].fX); - minY = SkTMin(minY, curve[idx2].fY); - maxX = SkTMax(maxX, curve[idx2].fX); - maxY = SkTMax(maxY, curve[idx2].fY); - } - double maxWidth = SkTMax(maxX - minX, maxY - minY); - endDist /= maxWidth; - if (endDist < 5e-11) { // empirically found - return false; - } - const SkDPoint* endPt = &rayEnd[0]; - SkDPoint oppPt = iEnd.pt(closestEnd); - SkDVector vLeft = *endPt - start; - SkDVector vRight = oppPt - start; - double dir = vLeft.crossCheck(vRight); - if (!dir) { - return false; - } - *inside = dir < 0; - return true; -} - // Most of the time, the first one can be found trivially by detecting the smallest sector value. // If all angles have the same sector value, actual sorting is required. -SkOpAngle* SkOpAngle::findFirst() { - SkOpAngle* best = this; +const SkOpAngle* SkOpAngle::findFirst() const { + const SkOpAngle* best = this; int bestStart = SkTMin(fSectorStart, fSectorEnd); - SkOpAngle* angle = this; + const SkOpAngle* angle = this; while ((angle = angle->fNext) != this) { int angleEnd = SkTMax(angle->fSectorStart, angle->fSectorEnd); if (angleEnd < bestStart) { @@ -612,7 +548,7 @@ SkOpAngle* SkOpAngle::findFirst() { } } // back up to the first possible angle - SkOpAngle* firstBest = best; + const SkOpAngle* firstBest = best; angle = best; int bestEnd = SkTMax(best->fSectorStart, best->fSectorEnd); while ((angle = angle->previous()) != firstBest) { @@ -636,7 +572,7 @@ SkOpAngle* SkOpAngle::findFirst() { if (angle->fStop) { return firstBest; } - bool orderable = best->orderable(angle); // note: may return an unorderable angle + bool orderable = best->orderable(*angle); // note: may return an unorderable angle if (orderable == 0) { return angle; } @@ -703,11 +639,6 @@ int SkOpAngle::findSector(SkPath::Verb verb, double x, double y) const { return sector; } -SkOpGlobalState* SkOpAngle::globalState() const { - return this->segment()->globalState(); -} - - // OPTIMIZE: if this loops to only one other angle, after first compare fails, insert on other side // OPTIMIZE: return where insertion succeeded. Then, start next insertion on opposite side void SkOpAngle::insert(SkOpAngle* angle) { @@ -731,6 +662,9 @@ void SkOpAngle::insert(SkOpAngle* angle) { } SkOpAngle* next = fNext; if (next->fNext == this) { + if (angle->overlap(*this)) { // angles are essentially coincident + return; + } if (singleton || angle->after(this)) { this->fNext = angle; angle->fNext = next; @@ -744,6 +678,9 @@ void SkOpAngle::insert(SkOpAngle* angle) { SkOpAngle* last = this; do { SkASSERT(last->fNext == next); + if (angle->overlap(*last) || angle->overlap(*next)) { + return; + } if (angle->after(last)) { last->fNext = angle; angle->fNext = next; @@ -752,49 +689,48 @@ void SkOpAngle::insert(SkOpAngle* angle) { } last = next; next = next->fNext; - if (last == this) { - if (next->fUnorderable) { - fUnorderable = true; - } else { - globalState()->setAngleCoincidence(); - this->fNext = angle; - angle->fNext = next; - angle->fCheckCoincidence = true; - } + if (last == this && next->fUnorderable) { + fUnorderable = true; return; } + SkASSERT(last != this); } while (true); } -SkOpSpanBase* SkOpAngle::lastMarked() const { +bool SkOpAngle::isHorizontal() const { + return !fIsCurve && fSweep[0].fY == 0; +} + +SkOpSpan* SkOpAngle::lastMarked() const { if (fLastMarked) { - if (fLastMarked->chased()) { + if (fLastMarked->fChased) { return NULL; } - fLastMarked->setChased(true); + fLastMarked->fChased = true; } return fLastMarked; } -bool SkOpAngle::loopContains(const SkOpAngle* angle) const { +bool SkOpAngle::loopContains(const SkOpAngle& test) const { if (!fNext) { return false; } const SkOpAngle* first = this; const SkOpAngle* loop = this; - const SkOpSegment* tSegment = angle->fStart->segment(); - double tStart = angle->fStart->t(); - double tEnd = angle->fEnd->t(); + const SkOpSegment* tSegment = test.fSegment; + double tStart = tSegment->span(test.fStart).fT; + double tEnd = tSegment->span(test.fEnd).fT; do { - const SkOpSegment* lSegment = loop->fStart->segment(); + const SkOpSegment* lSegment = loop->fSegment; + // FIXME : use precisely_equal ? or compare points exactly ? if (lSegment != tSegment) { continue; } - double lStart = loop->fStart->t(); + double lStart = lSegment->span(loop->fStart).fT; if (lStart != tEnd) { continue; } - double lEnd = loop->fEnd->t(); + double lEnd = lSegment->span(loop->fEnd).fT; if (lEnd == tStart) { return true; } @@ -846,65 +782,39 @@ bool SkOpAngle::merge(SkOpAngle* angle) { working = next; } while (working != angle); // it's likely that a pair of the angles are unorderable +#if 0 && DEBUG_ANGLE + SkOpAngle* last = angle; + working = angle->fNext; + do { + SkASSERT(last->fNext == working); + last->fNext = working->fNext; + SkASSERT(working->after(last)); + last->fNext = working; + last = working; + working = working->fNext; + } while (last != angle); +#endif debugValidateNext(); return true; } double SkOpAngle::midT() const { - return (fStart->t() + fEnd->t()) / 2; -} - -bool SkOpAngle::midToSide(const SkOpAngle* rh, bool* inside) const { - const SkOpSegment* segment = this->segment(); - SkPath::Verb verb = segment->verb(); - int pts = SkPathOpsVerbToPoints(verb); - const SkPoint& startPt = this->fStart->pt(); - const SkPoint& endPt = this->fEnd->pt(); - SkDPoint dStartPt; - dStartPt.set(startPt); - SkDLine rayMid; - rayMid[0].fX = (startPt.fX + endPt.fX) / 2; - rayMid[0].fY = (startPt.fY + endPt.fY) / 2; - rayMid[1].fX = rayMid[0].fX + (endPt.fY - startPt.fY); - rayMid[1].fY = rayMid[0].fY - (endPt.fX - startPt.fX); - SkIntersections iMid; - (*CurveIntersectRay[pts])(segment->pts(), rayMid, &iMid); - int iOutside = iMid.mostOutside(this->fStart->t(), this->fEnd->t(), dStartPt); - if (iOutside < 0) { - return false; - } - const SkOpSegment* oppSegment = rh->segment(); - SkPath::Verb oppVerb = oppSegment->verb(); - int oppPts = SkPathOpsVerbToPoints(oppVerb); - SkIntersections oppMid; - (*CurveIntersectRay[oppPts])(oppSegment->pts(), rayMid, &oppMid); - int oppOutside = oppMid.mostOutside(rh->fStart->t(), rh->fEnd->t(), dStartPt); - if (oppOutside < 0) { - return false; - } - SkDVector iSide = iMid.pt(iOutside) - dStartPt; - SkDVector oppSide = oppMid.pt(oppOutside) - dStartPt; - double dir = iSide.crossCheck(oppSide); - if (!dir) { - return false; - } - *inside = dir < 0; - return true; + return (fSegment->t(fStart) + fSegment->t(fEnd)) / 2; } -bool SkOpAngle::oppositePlanes(const SkOpAngle* rh) const { - int startSpan = abs(rh->fSectorStart - fSectorStart); +bool SkOpAngle::oppositePlanes(const SkOpAngle& rh) const { + int startSpan = abs(rh.fSectorStart - fSectorStart); return startSpan >= 8; } -bool SkOpAngle::orderable(SkOpAngle* rh) { +bool SkOpAngle::orderable(const SkOpAngle& rh) const { int result; if (!fIsCurve) { - if (!rh->fIsCurve) { + if (!rh.fIsCurve) { double leftX = fTangentHalf.dx(); double leftY = fTangentHalf.dy(); - double rightX = rh->fTangentHalf.dx(); - double rightY = rh->fTangentHalf.dy(); + double rightX = rh.fTangentHalf.dx(); + double rightY = rh.fTangentHalf.dy(); double x_ry = leftX * rightY; double rx_y = rightX * leftY; if (x_ry == rx_y) { @@ -919,14 +829,14 @@ bool SkOpAngle::orderable(SkOpAngle* rh) { if ((result = allOnOneSide(rh)) >= 0) { return result; } - if (fUnorderable || approximately_zero(rh->fSide)) { + if (fUnorderable || approximately_zero(rh.fSide)) { goto unorderable; } - } else if (!rh->fIsCurve) { - if ((result = rh->allOnOneSide(this)) >= 0) { + } else if (!rh.fIsCurve) { + if ((result = rh.allOnOneSide(*this)) >= 0) { return !result; } - if (rh->fUnorderable || approximately_zero(fSide)) { + if (rh.fUnorderable || approximately_zero(fSide)) { goto unorderable; } } @@ -936,10 +846,27 @@ bool SkOpAngle::orderable(SkOpAngle* rh) { return endsIntersect(rh); unorderable: fUnorderable = true; - rh->fUnorderable = true; + rh.fUnorderable = true; return true; } +bool SkOpAngle::overlap(const SkOpAngle& other) const { + int min = SkTMin(fStart, fEnd); + const SkOpSpan& span = fSegment->span(min); + const SkOpSegment* oSeg = other.fSegment; + int oMin = SkTMin(other.fStart, other.fEnd); + const SkOpSpan& oSpan = oSeg->span(oMin); + if (!span.fSmall && !oSpan.fSmall) { + return false; + } + if (fSegment->span(fStart).fPt != oSeg->span(other.fStart).fPt) { + return false; + } + // see if small span is contained by opposite span + return span.fSmall ? oSeg->containsPt(fSegment->span(fEnd).fPt, other.fEnd, other.fStart) + : fSegment->containsPt(oSeg->span(other.fEnd).fPt, fEnd, fStart); +} + // OPTIMIZE: if this shows up in a profile, add a previous pointer // as is, this should be rarely called SkOpAngle* SkOpAngle::previous() const { @@ -953,32 +880,26 @@ SkOpAngle* SkOpAngle::previous() const { } while (true); } -SkOpSegment* SkOpAngle::segment() const { - return fStart->segment(); -} - -void SkOpAngle::set(SkOpSpanBase* start, SkOpSpanBase* end) { +void SkOpAngle::set(const SkOpSegment* segment, int start, int end) { + fSegment = segment; fStart = start; fComputedEnd = fEnd = end; - SkASSERT(start != end); fNext = NULL; - fComputeSector = fComputedSector = fCheckCoincidence = false; + fComputeSector = fComputedSector = false; fStop = false; setSpans(); setSector(); - PATH_OPS_DEBUG_CODE(fID = start->globalState()->nextAngleID()); } void SkOpAngle::setCurveHullSweep() { fUnorderedSweep = false; fSweep[0] = fCurvePart[1] - fCurvePart[0]; - const SkOpSegment* segment = fStart->segment(); - if (SkPath::kLine_Verb == segment->verb()) { + if (SkPath::kLine_Verb == fSegment->verb()) { fSweep[1] = fSweep[0]; return; } fSweep[1] = fCurvePart[2] - fCurvePart[0]; - if (SkPath::kCubic_Verb != segment->verb()) { + if (SkPath::kCubic_Verb != fSegment->verb()) { if (!fSweep[0].fX && !fSweep[0].fY) { fSweep[0] = fSweep[1]; } @@ -1012,16 +933,64 @@ void SkOpAngle::setCurveHullSweep() { fSweep[1] = thirdSweep; } +void SkOpAngle::setSector() { + SkPath::Verb verb = fSegment->verb(); + if (SkPath::kLine_Verb != verb && small()) { + goto deferTilLater; + } + fSectorStart = findSector(verb, fSweep[0].fX, fSweep[0].fY); + if (fSectorStart < 0) { + goto deferTilLater; + } + if (!fIsCurve) { // if it's a line or line-like, note that both sectors are the same + SkASSERT(fSectorStart >= 0); + fSectorEnd = fSectorStart; + fSectorMask = 1 << fSectorStart; + return; + } + SkASSERT(SkPath::kLine_Verb != verb); + fSectorEnd = findSector(verb, fSweep[1].fX, fSweep[1].fY); + if (fSectorEnd < 0) { +deferTilLater: + fSectorStart = fSectorEnd = -1; + fSectorMask = 0; + fComputeSector = true; // can't determine sector until segment length can be found + return; + } + if (fSectorEnd == fSectorStart) { + SkASSERT((fSectorStart & 3) != 3); // if the sector has no span, it can't be an exact angle + fSectorMask = 1 << fSectorStart; + return; + } + bool crossesZero = checkCrossesZero(); + int start = SkTMin(fSectorStart, fSectorEnd); + bool curveBendsCCW = (fSectorStart == start) ^ crossesZero; + // bump the start and end of the sector span if they are on exact compass points + if ((fSectorStart & 3) == 3) { + fSectorStart = (fSectorStart + (curveBendsCCW ? 1 : 31)) & 0x1f; + } + if ((fSectorEnd & 3) == 3) { + fSectorEnd = (fSectorEnd + (curveBendsCCW ? 31 : 1)) & 0x1f; + } + crossesZero = checkCrossesZero(); + start = SkTMin(fSectorStart, fSectorEnd); + int end = SkTMax(fSectorStart, fSectorEnd); + if (!crossesZero) { + fSectorMask = (unsigned) -1 >> (31 - end + start) << start; + } else { + fSectorMask = (unsigned) -1 >> (31 - start) | (-1 << end); + } +} + void SkOpAngle::setSpans() { - fUnorderable = false; + fUnorderable = fSegment->isTiny(this); fLastMarked = NULL; - const SkOpSegment* segment = fStart->segment(); - const SkPoint* pts = segment->pts(); + const SkPoint* pts = fSegment->pts(); SkDEBUGCODE(fCurvePart[2].fX = fCurvePart[2].fY = fCurvePart[3].fX = fCurvePart[3].fY = SK_ScalarNaN); - segment->subDivide(fStart, fEnd, &fCurvePart); + fSegment->subDivide(fStart, fEnd, &fCurvePart); setCurveHullSweep(); - const SkPath::Verb verb = segment->verb(); + const SkPath::Verb verb = fSegment->verb(); if (verb != SkPath::kLine_Verb && !(fIsCurve = fSweep[0].crossCheck(fSweep[1]) != 0)) { SkDLine lineHalf; @@ -1033,9 +1002,9 @@ void SkOpAngle::setSpans() { switch (verb) { case SkPath::kLine_Verb: { SkASSERT(fStart != fEnd); - const SkPoint& cP1 = pts[fStart->t() < fEnd->t()]; + const SkPoint& cP1 = pts[fStart < fEnd]; SkDLine lineHalf; - lineHalf[0].set(fStart->pt()); + lineHalf[0].set(fSegment->span(fStart).fPt); lineHalf[1].set(cP1); fTangentHalf.lineEndPoints(lineHalf); fSide = 0; @@ -1054,8 +1023,8 @@ void SkOpAngle::setSpans() { double testTs[4]; // OPTIMIZATION: keep inflections precomputed with cubic segment? int testCount = SkDCubic::FindInflections(pts, testTs); - double startT = fStart->t(); - double endT = fEnd->t(); + double startT = fSegment->t(fStart); + double endT = fSegment->t(fEnd); double limitT = endT; int index; for (index = 0; index < testCount; ++index) { @@ -1095,63 +1064,19 @@ void SkOpAngle::setSpans() { } } -void SkOpAngle::setSector() { - const SkOpSegment* segment = fStart->segment(); - SkPath::Verb verb = segment->verb(); - fSectorStart = this->findSector(verb, fSweep[0].fX, fSweep[0].fY); - if (fSectorStart < 0) { - goto deferTilLater; - } - if (!fIsCurve) { // if it's a line or line-like, note that both sectors are the same - SkASSERT(fSectorStart >= 0); - fSectorEnd = fSectorStart; - fSectorMask = 1 << fSectorStart; - return; - } - SkASSERT(SkPath::kLine_Verb != verb); - fSectorEnd = this->findSector(verb, fSweep[1].fX, fSweep[1].fY); - if (fSectorEnd < 0) { -deferTilLater: - fSectorStart = fSectorEnd = -1; - fSectorMask = 0; - fComputeSector = true; // can't determine sector until segment length can be found - return; - } - if (fSectorEnd == fSectorStart - && (fSectorStart & 3) != 3) { // if the sector has no span, it can't be an exact angle - fSectorMask = 1 << fSectorStart; - return; - } - bool crossesZero = this->checkCrossesZero(); - int start = SkTMin(fSectorStart, fSectorEnd); - bool curveBendsCCW = (fSectorStart == start) ^ crossesZero; - // bump the start and end of the sector span if they are on exact compass points - if ((fSectorStart & 3) == 3) { - fSectorStart = (fSectorStart + (curveBendsCCW ? 1 : 31)) & 0x1f; - } - if ((fSectorEnd & 3) == 3) { - fSectorEnd = (fSectorEnd + (curveBendsCCW ? 31 : 1)) & 0x1f; - } - crossesZero = this->checkCrossesZero(); - start = SkTMin(fSectorStart, fSectorEnd); - int end = SkTMax(fSectorStart, fSectorEnd); - if (!crossesZero) { - fSectorMask = (unsigned) -1 >> (31 - end + start) << start; - } else { - fSectorMask = (unsigned) -1 >> (31 - start) | (-1 << end); +bool SkOpAngle::small() const { + int min = SkMin32(fStart, fEnd); + int max = SkMax32(fStart, fEnd); + for (int index = min; index < max; ++index) { + const SkOpSpan& mSpan = fSegment->span(index); + if (!mSpan.fSmall) { + return false; + } } + return true; } -int SkOpAngle::sign() const { - SkASSERT(fStart->t() != fEnd->t()); - return fStart->t() < fEnd->t() ? -1 : 1; -} - -SkOpSpan* SkOpAngle::starter() { - return fStart->starter(fEnd); -} - -bool SkOpAngle::tangentsDiverge(const SkOpAngle* rh, double s0xt0) const { +bool SkOpAngle::tangentsDiverge(const SkOpAngle& rh, double s0xt0) const { if (s0xt0 == 0) { return false; } @@ -1165,7 +1090,7 @@ bool SkOpAngle::tangentsDiverge(const SkOpAngle* rh, double s0xt0) const { // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y) // m = v1.cross(v2) / v1.dot(v2) const SkDVector* sweep = fSweep; - const SkDVector* tweep = rh->fSweep; + const SkDVector* tweep = rh.fSweep; double s0dt0 = sweep[0].dot(tweep[0]); if (!s0dt0) { return true; @@ -1175,6 +1100,36 @@ bool SkOpAngle::tangentsDiverge(const SkOpAngle* rh, double s0xt0) const { double sDist = sweep[0].length() * m; double tDist = tweep[0].length() * m; bool useS = fabs(sDist) < fabs(tDist); - double mFactor = fabs(useS ? this->distEndRatio(sDist) : rh->distEndRatio(tDist)); + double mFactor = fabs(useS ? distEndRatio(sDist) : rh.distEndRatio(tDist)); return mFactor < 5000; // empirically found limit } + +SkOpAngleSet::SkOpAngleSet() + : fAngles(NULL) +#if DEBUG_ANGLE + , fCount(0) +#endif +{ +} + +SkOpAngleSet::~SkOpAngleSet() { + SkDELETE(fAngles); +} + +SkOpAngle& SkOpAngleSet::push_back() { + if (!fAngles) { + fAngles = SkNEW_ARGS(SkChunkAlloc, (2)); + } + void* ptr = fAngles->allocThrow(sizeof(SkOpAngle)); + SkOpAngle* angle = (SkOpAngle*) ptr; +#if DEBUG_ANGLE + angle->setID(++fCount); +#endif + return *angle; +} + +void SkOpAngleSet::reset() { + if (fAngles) { + fAngles->reset(); + } +} diff --git a/src/pathops/SkOpAngle.h b/src/pathops/SkOpAngle.h index 84b37010c9..1dc4250613 100644 --- a/src/pathops/SkOpAngle.h +++ b/src/pathops/SkOpAngle.h @@ -7,18 +7,17 @@ #ifndef SkOpAngle_DEFINED #define SkOpAngle_DEFINED +#include "SkChunkAlloc.h" #include "SkLineParameters.h" -#if DEBUG_ANGLE -#include "SkString.h" -#endif -class SkOpContour; -class SkOpPtT; class SkOpSegment; -class SkOpSpanBase; -class SkOpSpan; +struct SkOpSpan; -struct SkOpAngle { +// sorting angles +// given angles of {dx dy ddx ddy dddx dddy} sort them +class SkOpAngle { +public: + enum { kStackBasedCount = 8 }; // FIXME: determine what this should be enum IncludeType { kUnaryWinding, kUnaryXor, @@ -26,66 +25,29 @@ struct SkOpAngle { kBinaryOpp, }; - bool after(SkOpAngle* test); - int allOnOneSide(const SkOpAngle* test); - bool checkCrossesZero() const; - void checkNearCoincidence(); - bool checkParallel(SkOpAngle* ); - bool computeSector(); - int convexHullOverlaps(const SkOpAngle* ) const; - - const SkOpAngle* debugAngle(int id) const; - SkOpContour* debugContour(int id); - int debugID() const { - return PATH_OPS_DEBUG_RELEASE(fID, -1); + int end() const { + return fEnd; } -#if DEBUG_SORT - void debugLoop() const; -#endif + const SkOpAngle* findFirst() const; -#if DEBUG_ANGLE - SkString debugPart() const; -#endif - const SkOpPtT* debugPtT(int id) const; - const SkOpSegment* debugSegment(int id) const; - const SkOpSpanBase* debugSpan(int id) const; - void debugValidate() const; - void debugValidateNext() const; // in debug builds, verify that angle loop is uncorrupted - double distEndRatio(double dist) const; - // available to testing only - void dump() const; - void dumpCurves() const; - void dumpLoop() const; - void dumpOne(bool functionHeader) const; - void dumpTo(const SkOpSegment* fromSeg, const SkOpAngle* ) const; - void dumpTest() const; - - SkOpSpanBase* end() const { - return fEnd; + bool inLoop() const { + return !!fNext; } - bool endsIntersect(SkOpAngle* ); - bool endToSide(const SkOpAngle* rh, bool* inside) const; - SkOpAngle* findFirst(); - int findSector(SkPath::Verb verb, double x, double y) const; - SkOpGlobalState* globalState() const; void insert(SkOpAngle* ); - SkOpSpanBase* lastMarked() const; - bool loopContains(const SkOpAngle* ) const; + bool isHorizontal() const; + SkOpSpan* lastMarked() const; + bool loopContains(const SkOpAngle& ) const; int loopCount() const; void markStops(); bool merge(SkOpAngle* ); - double midT() const; - bool midToSide(const SkOpAngle* rh, bool* inside) const; SkOpAngle* next() const { return fNext; } - bool oppositePlanes(const SkOpAngle* rh) const; - bool orderable(SkOpAngle* rh); // false == this < rh ; true == this > rh SkOpAngle* previous() const; int sectorEnd() const { @@ -96,57 +58,120 @@ struct SkOpAngle { return fSectorStart; } - SkOpSegment* segment() const; + void set(const SkOpSegment* segment, int start, int end); - void set(SkOpSpanBase* start, SkOpSpanBase* end); - void setCurveHullSweep(); + void setLastMarked(SkOpSpan* marked) { + fLastMarked = marked; + } - void setID(int id) { - PATH_OPS_DEBUG_CODE(fID = id); + SkOpSegment* segment() const { + return const_cast<SkOpSegment*>(fSegment); } - void setLastMarked(SkOpSpanBase* marked) { - fLastMarked = marked; + int sign() const { + return SkSign32(fStart - fEnd); } - void setSector(); - void setSpans(); - int sign() const; + bool small() const; - SkOpSpanBase* start() const { + int start() const { return fStart; } - SkOpSpan* starter(); - bool tangentsDiverge(const SkOpAngle* rh, double s0xt0) const; - bool unorderable() const { return fUnorderable; } - SkDCubic fCurvePart; // the curve from start to end + // available to testing only +#if DEBUG_SORT + void debugLoop() const; // called by code during run +#endif +#if DEBUG_ANGLE + void debugSameAs(const SkOpAngle* compare) const; +#endif + void dump() const; + void dumpLoop() const; + void dumpTo(const SkOpSegment* fromSeg, const SkOpAngle* ) const; + +#if DEBUG_ANGLE + int debugID() const { return fID; } + + void setID(int id) { + fID = id; + } +#else + int debugID() const { return 0; } +#endif + +#if DEBUG_VALIDATE + void debugValidateLoop() const; +#endif + +private: + bool after(const SkOpAngle* test) const; + int allOnOneSide(const SkOpAngle& test) const; + bool calcSlop(double x, double y, double rx, double ry, bool* result) const; + bool checkCrossesZero() const; + bool checkParallel(const SkOpAngle& ) const; + bool computeSector(); + int convexHullOverlaps(const SkOpAngle& ) const; + double distEndRatio(double dist) const; + int findSector(SkPath::Verb verb, double x, double y) const; + bool endsIntersect(const SkOpAngle& ) const; + double midT() const; + bool oppositePlanes(const SkOpAngle& rh) const; + bool orderable(const SkOpAngle& rh) const; // false == this < rh ; true == this > rh + bool overlap(const SkOpAngle& test) const; + void setCurveHullSweep(); + void setSector(); + void setSpans(); + bool tangentsDiverge(const SkOpAngle& rh, double s0xt0) const; + + SkDCubic fCurvePart; // the curve from start to end double fSide; SkLineParameters fTangentHalf; // used only to sort a pair of lines or line-like sections + const SkOpSegment* fSegment; SkOpAngle* fNext; - SkOpSpanBase* fLastMarked; + SkOpSpan* fLastMarked; SkDVector fSweep[2]; - SkOpSpanBase* fStart; - SkOpSpanBase* fEnd; - SkOpSpanBase* fComputedEnd; + int fStart; + int fEnd; + int fComputedEnd; int fSectorMask; int8_t fSectorStart; // in 32nds of a circle int8_t fSectorEnd; bool fIsCurve; - bool fStop; // set if ordered angle is greater than the previous - bool fUnorderable; + bool fStop; // set if ordered angle is greater than the previous + mutable bool fUnorderable; // this is editable by orderable() bool fUnorderedSweep; // set when a cubic's first control point between the sweep vectors bool fComputeSector; bool fComputedSector; - bool fCheckCoincidence; - PATH_OPS_DEBUG_CODE(int fID); +#if DEBUG_ANGLE + int fID; +#endif +#if DEBUG_VALIDATE + void debugValidateNext() const; // in debug builds, verify that angle loop is uncorrupted +#else + void debugValidateNext() const {} +#endif + void dumpOne(bool showFunc) const; // available to testing only + void dumpPartials() const; // utility to be called by user from debugger + friend class PathOpsAngleTester; }; - +class SkOpAngleSet { +public: + SkOpAngleSet(); + ~SkOpAngleSet(); + SkOpAngle& push_back(); + void reset(); +private: + void dump() const; // utility to be called by user from debugger + SkChunkAlloc* fAngles; +#if DEBUG_ANGLE + int fCount; +#endif +}; #endif diff --git a/src/pathops/SkOpCoincidence.cpp b/src/pathops/SkOpCoincidence.cpp deleted file mode 100755 index 45eee0a38e..0000000000 --- a/src/pathops/SkOpCoincidence.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright 2015 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -#include "SkOpCoincidence.h" -#include "SkOpSegment.h" -#include "SkPathOpsTSect.h" - -void SkOpCoincidence::add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, - SkOpPtT* oppPtTEnd, SkChunkAlloc* allocator) { - SkASSERT(coinPtTStart->fT < coinPtTEnd->fT); - bool flipped = oppPtTStart->fT > oppPtTEnd->fT; - SkCoincidentSpans* coinRec = SkOpTAllocator<SkCoincidentSpans>::Allocate(allocator); - coinRec->fNext = this->fHead; - coinRec->fCoinPtTStart = coinPtTStart; - coinRec->fCoinPtTEnd = coinPtTEnd; - coinRec->fOppPtTStart = oppPtTStart; - coinRec->fOppPtTEnd = oppPtTEnd; - coinRec->fFlipped = flipped; - this->fHead = coinRec; -} - -static void tRange(const SkOpPtT* overS, const SkOpPtT* overE, double tStart, double tEnd, - const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, double* coinTs, double* coinTe) { - double denom = overE->fT - overS->fT; - double start = 0 < denom ? tStart : tEnd; - double end = 0 < denom ? tEnd : tStart; - double sRatio = (start - overS->fT) / denom; - double eRatio = (end - overS->fT) / denom; - *coinTs = coinPtTStart->fT + (coinPtTEnd->fT - coinPtTStart->fT) * sRatio; - *coinTe = coinPtTStart->fT + (coinPtTEnd->fT - coinPtTStart->fT) * eRatio; -} - -bool SkOpCoincidence::addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over1e, - const SkOpPtT* over2s, const SkOpPtT* over2e, double tStart, double tEnd, - SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, - SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd, SkChunkAlloc* allocator) { - double coinTs, coinTe, oppTs, oppTe; - tRange(over1s, over1e, tStart, tEnd, coinPtTStart, coinPtTEnd, &coinTs, &coinTe); - tRange(over2s, over2e, tStart, tEnd, oppPtTStart, oppPtTEnd, &oppTs, &oppTe); - SkOpSegment* coinSeg = coinPtTStart->segment(); - SkOpSegment* oppSeg = oppPtTStart->segment(); - SkASSERT(coinSeg != oppSeg); - SkCoincidentSpans* check = this->fHead; - do { - const SkOpSegment* checkCoinSeg = check->fCoinPtTStart->segment(); - if (checkCoinSeg != coinSeg && checkCoinSeg != oppSeg) { - continue; - } - const SkOpSegment* checkOppSeg = check->fOppPtTStart->segment(); - if (checkOppSeg != coinSeg && checkOppSeg != oppSeg) { - continue; - } - int cTs = coinTs; - int cTe = coinTe; - int oTs = oppTs; - int oTe = oppTe; - if (checkCoinSeg != coinSeg) { - SkASSERT(checkOppSeg != oppSeg); - SkTSwap(cTs, oTs); - SkTSwap(cTe, oTe); - } - int tweenCount = (int) between(check->fCoinPtTStart->fT, cTs, check->fCoinPtTEnd->fT) - + (int) between(check->fCoinPtTStart->fT, cTe, check->fCoinPtTEnd->fT) - + (int) between(check->fOppPtTStart->fT, oTs, check->fOppPtTEnd->fT) - + (int) between(check->fOppPtTStart->fT, oTe, check->fOppPtTEnd->fT); -// SkASSERT(tweenCount == 0 || tweenCount == 4); - if (tweenCount) { - return true; - } - } while ((check = check->fNext)); - if ((over1s->fT < over1e->fT) != (over2s->fT < over2e->fT)) { - SkTSwap(oppTs, oppTe); - } - if (coinTs > coinTe) { - SkTSwap(coinTs, coinTe); - SkTSwap(oppTs, oppTe); - } - SkOpPtT* cs = coinSeg->addMissing(coinTs, oppSeg, allocator); - SkOpPtT* ce = coinSeg->addMissing(coinTe, oppSeg, allocator); - if (cs == ce) { - return false; - } - SkOpPtT* os = oppSeg->addMissing(oppTs, coinSeg, allocator); - SkOpPtT* oe = oppSeg->addMissing(oppTe, coinSeg, allocator); - SkASSERT(os != oe); - cs->addOpp(os); - ce->addOpp(oe); - this->add(cs, ce, os, oe, allocator); - return true; -} - -bool SkOpCoincidence::addMissing(SkChunkAlloc* allocator) { - SkCoincidentSpans* outer = this->fHead; - if (!outer) { - return true; - } - do { - SkCoincidentSpans* inner = outer; - while ((inner = inner->fNext)) { - double overS, overE; - if (this->overlap(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, &overS, &overE)) { - if (!addIfMissing(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, overS, overE, - outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, allocator)) { - return false; - } - } else if (this->overlap(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, &overS, &overE)) { - if (!addIfMissing(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, overS, overE, - outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, allocator)) { - return false; - } - } else if (this->overlap(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, &overS, &overE)) { - if (!addIfMissing(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, overS, overE, - outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, allocator)) { - return false; - } - } else if (this->overlap(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, &overS, &overE)) { - if (!addIfMissing(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, overS, overE, - outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, allocator)) { - return false; - } - } - } - - } while ((outer = outer->fNext)); - return true; -} - - -bool SkOpCoincidence::contains(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, - SkOpPtT* oppPtTEnd, bool flipped) { - SkCoincidentSpans* coin = fHead; - if (!coin) { - return false; - } - do { - if (coin->fCoinPtTStart == coinPtTStart && coin->fCoinPtTEnd == coinPtTEnd - && coin->fOppPtTStart == oppPtTStart && coin->fOppPtTEnd == oppPtTEnd - && coin->fFlipped == flipped) { - return true; - } - } while ((coin = coin->fNext)); - return false; -} - -// walk span sets in parallel, moving winding from one to the other -bool SkOpCoincidence::apply() { - SkCoincidentSpans* coin = fHead; - if (!coin) { - return true; - } - do { - SkOpSpanBase* end = coin->fCoinPtTEnd->span(); - SkOpSpan* start = coin->fCoinPtTStart->span()->upCast(); - SkASSERT(start == start->starter(end)); - bool flipped = coin->fFlipped; - SkOpSpanBase* oEnd = (flipped ? coin->fOppPtTStart : coin->fOppPtTEnd)->span(); - SkOpSpan* oStart = (flipped ? coin->fOppPtTEnd : coin->fOppPtTStart)->span()->upCast(); - SkASSERT(oStart == oStart->starter(oEnd)); - SkOpSegment* segment = start->segment(); - SkOpSegment* oSegment = oStart->segment(); - bool operandSwap = segment->operand() != oSegment->operand(); - if (flipped) { - do { - SkOpSpanBase* oNext = oStart->next(); - if (oNext == oEnd) { - break; - } - oStart = oNext->upCast(); - } while (true); - } - bool isXor = segment->isXor(); - bool oppXor = oSegment->isXor(); - do { - int windValue = start->windValue(); - int oWindValue = oStart->windValue(); - int oppValue = start->oppValue(); - int oOppValue = oStart->oppValue(); - // winding values are added or subtracted depending on direction and wind type - // same or opposite values are summed depending on the operand value - if (windValue >= oWindValue) { - if (operandSwap) { - SkTSwap(oWindValue, oOppValue); - } - if (flipped) { - windValue -= oWindValue; - oppValue -= oOppValue; - } else { - windValue += oWindValue; - oppValue += oOppValue; - } - if (isXor) { - windValue &= 1; - } - if (oppXor) { - oppValue &= 1; - } - oWindValue = oOppValue = 0; - } else { - if (operandSwap) { - SkTSwap(windValue, oppValue); - } - if (flipped) { - oWindValue -= windValue; - oOppValue -= oppValue; - } else { - oWindValue += windValue; - oOppValue += oppValue; - } - if (isXor) { - oOppValue &= 1; - } - if (oppXor) { - oWindValue &= 1; - } - windValue = oppValue = 0; - } - start->setWindValue(windValue); - start->setOppValue(oppValue); - oStart->setWindValue(oWindValue); - oStart->setOppValue(oOppValue); - if (!windValue && !oppValue) { - segment->markDone(start); - } - if (!oWindValue && !oOppValue) { - oSegment->markDone(oStart); - } - SkOpSpanBase* next = start->next(); - SkOpSpanBase* oNext = flipped ? oStart->prev() : oStart->next(); - if (next == end) { - break; - } - start = next->upCast(); - if (!oNext) { - return false; - } - if (!oNext->upCastable()) { - return false; - } - oStart = oNext->upCast(); - } while (true); - } while ((coin = coin->fNext)); - return true; -} - -void SkOpCoincidence::detach(SkCoincidentSpans* remove) { - SkCoincidentSpans* coin = fHead; - SkCoincidentSpans* prev = NULL; - SkCoincidentSpans* next; - do { - next = coin->fNext; - if (coin == remove) { - if (prev) { - prev->fNext = next; - } else { - fHead = next; - } - break; - } - prev = coin; - } while ((coin = next)); - SkASSERT(coin); -} - -void SkOpCoincidence::expand() { - SkCoincidentSpans* coin = fHead; - if (!coin) { - return; - } - do { - SkOpSpan* start = coin->fCoinPtTStart->span()->upCast(); - SkOpSpanBase* end = coin->fCoinPtTEnd->span(); - SkOpSegment* segment = coin->fCoinPtTStart->segment(); - SkOpSegment* oppSegment = coin->fOppPtTStart->segment(); - SkOpSpan* prev = start->prev(); - SkOpPtT* oppPtT; - if (prev && (oppPtT = prev->contains(oppSegment))) { - double midT = (prev->t() + start->t()) / 2; - if (segment->isClose(midT, oppSegment)) { - coin->fCoinPtTStart = prev->ptT(); - coin->fOppPtTStart = oppPtT; - } - } - SkOpSpanBase* next = end->final() ? NULL : end->upCast()->next(); - if (next && (oppPtT = next->contains(oppSegment))) { - double midT = (end->t() + next->t()) / 2; - if (segment->isClose(midT, oppSegment)) { - coin->fCoinPtTEnd = next->ptT(); - coin->fOppPtTEnd = oppPtT; - } - } - } while ((coin = coin->fNext)); -} - -void SkOpCoincidence::fixUp(SkOpPtT* deleted, SkOpPtT* kept) { - SkCoincidentSpans* coin = fHead; - if (!coin) { - return; - } - do { - if (coin->fCoinPtTStart == deleted) { - if (coin->fCoinPtTEnd->span() == kept->span()) { - return this->detach(coin); - } - coin->fCoinPtTStart = kept; - } - if (coin->fCoinPtTEnd == deleted) { - if (coin->fCoinPtTStart->span() == kept->span()) { - return this->detach(coin); - } - coin->fCoinPtTEnd = kept; - } - if (coin->fOppPtTStart == deleted) { - if (coin->fOppPtTEnd->span() == kept->span()) { - return this->detach(coin); - } - coin->fOppPtTStart = kept; - } - if (coin->fOppPtTEnd == deleted) { - if (coin->fOppPtTStart->span() == kept->span()) { - return this->detach(coin); - } - coin->fOppPtTEnd = kept; - } - } while ((coin = coin->fNext)); -} - -void SkOpCoincidence::mark() { - SkCoincidentSpans* coin = fHead; - if (!coin) { - return; - } - do { - SkOpSpanBase* end = coin->fCoinPtTEnd->span(); - SkOpSpanBase* oldEnd = end; - SkOpSpan* start = coin->fCoinPtTStart->span()->starter(&end); - SkOpSpanBase* oEnd = coin->fOppPtTEnd->span(); - SkOpSpanBase* oOldEnd = oEnd; - SkOpSpanBase* oStart = coin->fOppPtTStart->span()->starter(&oEnd); - bool flipped = (end == oldEnd) != (oEnd == oOldEnd); - if (flipped) { - SkTSwap(oStart, oEnd); - } - SkOpSpanBase* next = start; - SkOpSpanBase* oNext = oStart; - // check to see if coincident span could be bigger - - do { - next = next->upCast()->next(); - oNext = flipped ? oNext->prev() : oNext->upCast()->next(); - if (next == end || oNext == oEnd) { - break; - } - if (!next->containsCoinEnd(oNext)) { - next->insertCoinEnd(oNext); - } - SkOpSpan* nextSpan = next->upCast(); - SkOpSpan* oNextSpan = oNext->upCast(); - if (!nextSpan->containsCoincidence(oNextSpan)) { - nextSpan->insertCoincidence(oNextSpan); - } - } while (true); - } while ((coin = coin->fNext)); -} - -bool SkOpCoincidence::overlap(const SkOpPtT* coin1s, const SkOpPtT* coin1e, - const SkOpPtT* coin2s, const SkOpPtT* coin2e, double* overS, double* overE) const { - if (coin1s->segment() != coin2s->segment()) { - return false; - } - *overS = SkTMax(SkTMin(coin1s->fT, coin1e->fT), SkTMin(coin2s->fT, coin2e->fT)); - *overE = SkTMin(SkTMax(coin1s->fT, coin1e->fT), SkTMax(coin2s->fT, coin2e->fT)); - return *overS < *overE; -} diff --git a/src/pathops/SkOpCoincidence.h b/src/pathops/SkOpCoincidence.h index b79b88be88..287bfd12d4 100644 --- a/src/pathops/SkOpCoincidence.h +++ b/src/pathops/SkOpCoincidence.h @@ -19,8 +19,6 @@ struct SkCoincidentSpans { SkOpPtT* fOppPtTStart; SkOpPtT* fOppPtTEnd; bool fFlipped; - - void dump() const; }; class SkOpCoincidence { @@ -30,27 +28,13 @@ public: } void add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, - SkOpPtT* oppPtTEnd, SkChunkAlloc* allocator); - bool addMissing(SkChunkAlloc* allocator); - bool apply(); + SkOpPtT* oppPtTEnd, bool flipped, SkChunkAlloc* allocator); + void apply(); bool contains(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, SkOpPtT* oppPtTEnd, bool flipped); - void detach(SkCoincidentSpans* ); void dump() const; - void expand(); - void fixUp(SkOpPtT* deleted, SkOpPtT* kept); void mark(); -private: - bool addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over1e, - const SkOpPtT* over2s, const SkOpPtT* over2e, double tStart, double tEnd, - SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, - SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd, - SkChunkAlloc* allocator); - bool overlap(const SkOpPtT* coinStart1, const SkOpPtT* coinEnd1, - const SkOpPtT* coinStart2, const SkOpPtT* coinEnd2, - double* overS, double* overE) const; - SkCoincidentSpans* fHead; }; diff --git a/src/pathops/SkOpContour.cpp b/src/pathops/SkOpContour.cpp index d17b18905b..28c072a3c1 100644 --- a/src/pathops/SkOpContour.cpp +++ b/src/pathops/SkOpContour.cpp @@ -4,35 +4,42 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ +#include "SkIntersections.h" #include "SkOpContour.h" -#include "SkOpTAllocator.h" #include "SkPathWriter.h" -#include "SkReduceOrder.h" #include "SkTSort.h" -void SkOpContour::addCurve(SkPath::Verb verb, const SkPoint pts[4], SkChunkAlloc* allocator) { - switch (verb) { - case SkPath::kLine_Verb: { - SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 2); - memcpy(ptStorage, pts, sizeof(SkPoint) * 2); - appendSegment(allocator).addLine(ptStorage, this); - } break; - case SkPath::kQuad_Verb: { - SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 3); - memcpy(ptStorage, pts, sizeof(SkPoint) * 3); - appendSegment(allocator).addQuad(ptStorage, this); - } break; - case SkPath::kCubic_Verb: { - SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 4); - memcpy(ptStorage, pts, sizeof(SkPoint) * 4); - appendSegment(allocator).addCubic(ptStorage, this); - } break; - default: - SkASSERT(0); - } -} - -SkOpSegment* SkOpContour::nonVerticalSegment(SkOpSpanBase** start, SkOpSpanBase** end) { +bool SkOpContour::addCoincident(int index, SkOpContour* other, int otherIndex, + const SkIntersections& ts, bool swap) { + SkPoint pt0 = ts.pt(0).asSkPoint(); + SkPoint pt1 = ts.pt(1).asSkPoint(); + if (pt0 == pt1 || ts[0][0] == ts[0][1] || ts[1][0] == ts[1][1]) { + // FIXME: one could imagine a case where it would be incorrect to ignore this + // suppose two self-intersecting cubics overlap to be coincident -- + // this needs to check that by some measure the t values are far enough apart + // or needs to check to see if the self-intersection bit was set on the cubic segment + return false; + } + SkCoincidence& coincidence = fCoincidences.push_back(); + coincidence.fOther = other; + coincidence.fSegments[0] = index; + coincidence.fSegments[1] = otherIndex; + coincidence.fTs[swap][0] = ts[0][0]; + coincidence.fTs[swap][1] = ts[0][1]; + coincidence.fTs[!swap][0] = ts[1][0]; + coincidence.fTs[!swap][1] = ts[1][1]; + coincidence.fPts[swap][0] = pt0; + coincidence.fPts[swap][1] = pt1; + bool nearStart = ts.nearlySame(0); + bool nearEnd = ts.nearlySame(1); + coincidence.fPts[!swap][0] = nearStart ? ts.pt2(0).asSkPoint() : pt0; + coincidence.fPts[!swap][1] = nearEnd ? ts.pt2(1).asSkPoint() : pt1; + coincidence.fNearly[0] = nearStart; + coincidence.fNearly[1] = nearEnd; + return true; +} + +SkOpSegment* SkOpContour::nonVerticalSegment(int* start, int* end) { int segmentCount = fSortedSegments.count(); SkASSERT(segmentCount > 0); for (int sortedIndex = fFirstSorted; sortedIndex < segmentCount; ++sortedIndex) { @@ -40,27 +47,627 @@ SkOpSegment* SkOpContour::nonVerticalSegment(SkOpSpanBase** start, SkOpSpanBase* if (testSegment->done()) { continue; } - SkOpSpanBase* span = testSegment->head(); - SkOpSpanBase* testS, * testE; - while (SkOpSegment::NextCandidate(span, &testS, &testE)) { - if (!testSegment->isVertical(testS, testE)) { - *start = testS; - *end = testE; + *start = *end = 0; + while (testSegment->nextCandidate(start, end)) { + if (!testSegment->isVertical(*start, *end)) { return testSegment; } - span = span->upCast()->next(); } } return NULL; } +// if one is very large the smaller may have collapsed to nothing +static void bump_out_close_span(double* startTPtr, double* endTPtr) { + double startT = *startTPtr; + double endT = *endTPtr; + if (approximately_negative(endT - startT)) { + if (endT <= 1 - FLT_EPSILON) { + *endTPtr += FLT_EPSILON; + SkASSERT(*endTPtr <= 1); + } else { + *startTPtr -= FLT_EPSILON; + SkASSERT(*startTPtr >= 0); + } + } +} + +// first pass, add missing T values +// second pass, determine winding values of overlaps +void SkOpContour::addCoincidentPoints() { + int count = fCoincidences.count(); + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = fCoincidences[index]; + int thisIndex = coincidence.fSegments[0]; + SkOpSegment& thisOne = fSegments[thisIndex]; + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + SkOpSegment& other = otherContour->fSegments[otherIndex]; + if ((thisOne.done() || other.done()) && thisOne.complete() && other.complete()) { + // OPTIMIZATION: remove from array + continue; + } + #if DEBUG_CONCIDENT + thisOne.debugShowTs("-"); + other.debugShowTs("o"); + #endif + double startT = coincidence.fTs[0][0]; + double endT = coincidence.fTs[0][1]; + bool startSwapped, oStartSwapped, cancelers; + if ((cancelers = startSwapped = startT > endT)) { + SkTSwap(startT, endT); + } + bump_out_close_span(&startT, &endT); + SkASSERT(!approximately_negative(endT - startT)); + double oStartT = coincidence.fTs[1][0]; + double oEndT = coincidence.fTs[1][1]; + if ((oStartSwapped = oStartT > oEndT)) { + SkTSwap(oStartT, oEndT); + cancelers ^= true; + } + bump_out_close_span(&oStartT, &oEndT); + SkASSERT(!approximately_negative(oEndT - oStartT)); + const SkPoint& startPt = coincidence.fPts[0][startSwapped]; + if (cancelers) { + // make sure startT and endT have t entries + if (startT > 0 || oEndT < 1 + || thisOne.isMissing(startT, startPt) || other.isMissing(oEndT, startPt)) { + thisOne.addTPair(startT, &other, oEndT, true, startPt, + coincidence.fPts[1][startSwapped]); + } + const SkPoint& oStartPt = coincidence.fPts[1][oStartSwapped]; + if (oStartT > 0 || endT < 1 + || thisOne.isMissing(endT, oStartPt) || other.isMissing(oStartT, oStartPt)) { + other.addTPair(oStartT, &thisOne, endT, true, oStartPt, + coincidence.fPts[0][oStartSwapped]); + } + } else { + if (startT > 0 || oStartT > 0 + || thisOne.isMissing(startT, startPt) || other.isMissing(oStartT, startPt)) { + thisOne.addTPair(startT, &other, oStartT, true, startPt, + coincidence.fPts[1][startSwapped]); + } + const SkPoint& oEndPt = coincidence.fPts[1][!oStartSwapped]; + if (endT < 1 || oEndT < 1 + || thisOne.isMissing(endT, oEndPt) || other.isMissing(oEndT, oEndPt)) { + other.addTPair(oEndT, &thisOne, endT, true, oEndPt, + coincidence.fPts[0][!oStartSwapped]); + } + } + #if DEBUG_CONCIDENT + thisOne.debugShowTs("+"); + other.debugShowTs("o"); + #endif + } + // if there are multiple pairs of coincidence that share an edge, see if the opposite + // are also coincident + for (int index = 0; index < count - 1; ++index) { + const SkCoincidence& coincidence = fCoincidences[index]; + int thisIndex = coincidence.fSegments[0]; + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + for (int idx2 = 1; idx2 < count; ++idx2) { + const SkCoincidence& innerCoin = fCoincidences[idx2]; + int innerThisIndex = innerCoin.fSegments[0]; + if (thisIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 1, innerCoin, 1, false); + } + if (this == otherContour && otherIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 0, innerCoin, 1, false); + } + SkOpContour* innerOtherContour = innerCoin.fOther; + innerThisIndex = innerCoin.fSegments[1]; + if (this == innerOtherContour && thisIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 1, innerCoin, 0, false); + } + if (otherContour == innerOtherContour && otherIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 0, innerCoin, 0, false); + } + } + } +} + +bool SkOpContour::addPartialCoincident(int index, SkOpContour* other, int otherIndex, + const SkIntersections& ts, int ptIndex, bool swap) { + SkPoint pt0 = ts.pt(ptIndex).asSkPoint(); + SkPoint pt1 = ts.pt(ptIndex + 1).asSkPoint(); + if (SkDPoint::ApproximatelyEqual(pt0, pt1)) { + // FIXME: one could imagine a case where it would be incorrect to ignore this + // suppose two self-intersecting cubics overlap to form a partial coincidence -- + // although it isn't clear why the regular coincidence could wouldn't pick this up + // this is exceptional enough to ignore for now + return false; + } + SkCoincidence& coincidence = fPartialCoincidences.push_back(); + coincidence.fOther = other; + coincidence.fSegments[0] = index; + coincidence.fSegments[1] = otherIndex; + coincidence.fTs[swap][0] = ts[0][ptIndex]; + coincidence.fTs[swap][1] = ts[0][ptIndex + 1]; + coincidence.fTs[!swap][0] = ts[1][ptIndex]; + coincidence.fTs[!swap][1] = ts[1][ptIndex + 1]; + coincidence.fPts[0][0] = coincidence.fPts[1][0] = pt0; + coincidence.fPts[0][1] = coincidence.fPts[1][1] = pt1; + coincidence.fNearly[0] = 0; + coincidence.fNearly[1] = 0; + return true; +} + +void SkOpContour::align(const SkOpSegment::AlignedSpan& aligned, bool swap, + SkCoincidence* coincidence) { + for (int idx2 = 0; idx2 < 2; ++idx2) { + if (coincidence->fPts[0][idx2] == aligned.fOldPt + && coincidence->fTs[swap][idx2] == aligned.fOldT) { + SkASSERT(SkDPoint::RoughlyEqual(coincidence->fPts[0][idx2], aligned.fPt)); + coincidence->fPts[0][idx2] = aligned.fPt; + SkASSERT(way_roughly_equal(coincidence->fTs[swap][idx2], aligned.fT)); + coincidence->fTs[swap][idx2] = aligned.fT; + } + } +} + +void SkOpContour::alignCoincidence(const SkOpSegment::AlignedSpan& aligned, + SkTArray<SkCoincidence, true>* coincidences) { + int count = coincidences->count(); + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = (*coincidences)[index]; + int thisIndex = coincidence.fSegments[0]; + const SkOpSegment* thisOne = &fSegments[thisIndex]; + const SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + const SkOpSegment* other = &otherContour->fSegments[otherIndex]; + if (thisOne == aligned.fOther1 && other == aligned.fOther2) { + align(aligned, false, &coincidence); + } else if (thisOne == aligned.fOther2 && other == aligned.fOther1) { + align(aligned, true, &coincidence); + } + } +} + +void SkOpContour::alignTPt(int segmentIndex, const SkOpContour* other, int otherIndex, + bool swap, int tIndex, SkIntersections* ts, SkPoint* point) const { + int zeroPt; + if ((zeroPt = alignT(swap, tIndex, ts)) >= 0) { + alignPt(segmentIndex, point, zeroPt); + } + if ((zeroPt = other->alignT(!swap, tIndex, ts)) >= 0) { + other->alignPt(otherIndex, point, zeroPt); + } +} + +void SkOpContour::alignPt(int index, SkPoint* point, int zeroPt) const { + const SkOpSegment& segment = fSegments[index]; + if (0 == zeroPt) { + *point = segment.pts()[0]; + } else { + *point = segment.pts()[SkPathOpsVerbToPoints(segment.verb())]; + } +} + +int SkOpContour::alignT(bool swap, int tIndex, SkIntersections* ts) const { + double tVal = (*ts)[swap][tIndex]; + if (tVal != 0 && precisely_zero(tVal)) { + ts->set(swap, tIndex, 0); + return 0; + } + if (tVal != 1 && precisely_equal(tVal, 1)) { + ts->set(swap, tIndex, 1); + return 1; + } + return -1; +} + +bool SkOpContour::calcAngles() { + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + if (!fSegments[test].calcAngles()) { + return false; + } + } + return true; +} + +bool SkOpContour::calcCoincidentWinding() { + int count = fCoincidences.count(); +#if DEBUG_CONCIDENT + if (count > 0) { + SkDebugf("%s count=%d\n", __FUNCTION__, count); + } +#endif + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = fCoincidences[index]; + if (!calcCommonCoincidentWinding(coincidence)) { + return false; + } + } + return true; +} + +void SkOpContour::calcPartialCoincidentWinding() { + int count = fPartialCoincidences.count(); +#if DEBUG_CONCIDENT + if (count > 0) { + SkDebugf("%s count=%d\n", __FUNCTION__, count); + } +#endif + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = fPartialCoincidences[index]; + calcCommonCoincidentWinding(coincidence); + } + // if there are multiple pairs of partial coincidence that share an edge, see if the opposite + // are also coincident + for (int index = 0; index < count - 1; ++index) { + const SkCoincidence& coincidence = fPartialCoincidences[index]; + int thisIndex = coincidence.fSegments[0]; + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + for (int idx2 = 1; idx2 < count; ++idx2) { + const SkCoincidence& innerCoin = fPartialCoincidences[idx2]; + int innerThisIndex = innerCoin.fSegments[0]; + if (thisIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 1, innerCoin, 1, true); + } + if (this == otherContour && otherIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 0, innerCoin, 1, true); + } + SkOpContour* innerOtherContour = innerCoin.fOther; + innerThisIndex = innerCoin.fSegments[1]; + if (this == innerOtherContour && thisIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 1, innerCoin, 0, true); + } + if (otherContour == innerOtherContour && otherIndex == innerThisIndex) { + checkCoincidentPair(coincidence, 0, innerCoin, 0, true); + } + } + } +} + +void SkOpContour::checkCoincidentPair(const SkCoincidence& oneCoin, int oneIdx, + const SkCoincidence& twoCoin, int twoIdx, bool partial) { + SkASSERT((oneIdx ? this : oneCoin.fOther) == (twoIdx ? this : twoCoin.fOther)); + SkASSERT(oneCoin.fSegments[!oneIdx] == twoCoin.fSegments[!twoIdx]); + // look for common overlap + double min = SK_ScalarMax; + double max = SK_ScalarMin; + double min1 = oneCoin.fTs[!oneIdx][0]; + double max1 = oneCoin.fTs[!oneIdx][1]; + double min2 = twoCoin.fTs[!twoIdx][0]; + double max2 = twoCoin.fTs[!twoIdx][1]; + bool cancelers = (min1 < max1) != (min2 < max2); + if (min1 > max1) { + SkTSwap(min1, max1); + } + if (min2 > max2) { + SkTSwap(min2, max2); + } + if (between(min1, min2, max1)) { + min = min2; + } + if (between(min1, max2, max1)) { + max = max2; + } + if (between(min2, min1, max2)) { + min = SkTMin(min, min1); + } + if (between(min2, max1, max2)) { + max = SkTMax(max, max1); + } + if (min >= max) { + return; // no overlap + } + // look to see if opposite are different segments + int seg1Index = oneCoin.fSegments[oneIdx]; + int seg2Index = twoCoin.fSegments[twoIdx]; + if (seg1Index == seg2Index) { + return; + } + SkOpContour* contour1 = oneIdx ? oneCoin.fOther : this; + SkOpContour* contour2 = twoIdx ? twoCoin.fOther : this; + SkOpSegment* segment1 = &contour1->fSegments[seg1Index]; + SkOpSegment* segment2 = &contour2->fSegments[seg2Index]; + // find opposite t value ranges corresponding to reference min/max range + const SkOpContour* refContour = oneIdx ? this : oneCoin.fOther; + const int refSegIndex = oneCoin.fSegments[!oneIdx]; + const SkOpSegment* refSegment = &refContour->fSegments[refSegIndex]; + int seg1Start = segment1->findOtherT(min, refSegment); + int seg1End = segment1->findOtherT(max, refSegment); + int seg2Start = segment2->findOtherT(min, refSegment); + int seg2End = segment2->findOtherT(max, refSegment); + // if the opposite pairs already contain min/max, we're done + if (seg1Start >= 0 && seg1End >= 0 && seg2Start >= 0 && seg2End >= 0) { + return; + } + double loEnd = SkTMin(min1, min2); + double hiEnd = SkTMax(max1, max2); + // insert the missing coincident point(s) + double missingT1 = -1; + double otherT1 = -1; + if (seg1Start < 0) { + if (seg2Start < 0) { + return; + } + missingT1 = segment1->calcMissingTStart(refSegment, loEnd, min, max, hiEnd, + segment2, seg1End); + if (missingT1 < 0) { + return; + } + const SkOpSpan* missingSpan = &segment2->span(seg2Start); + otherT1 = missingSpan->fT; + } else if (seg2Start < 0) { + SkASSERT(seg1Start >= 0); + missingT1 = segment2->calcMissingTStart(refSegment, loEnd, min, max, hiEnd, + segment1, seg2End); + if (missingT1 < 0) { + return; + } + const SkOpSpan* missingSpan = &segment1->span(seg1Start); + otherT1 = missingSpan->fT; + } + SkPoint missingPt1; + SkOpSegment* addTo1 = NULL; + SkOpSegment* addOther1 = seg1Start < 0 ? segment2 : segment1; + int minTIndex = refSegment->findExactT(min, addOther1); + SkASSERT(minTIndex >= 0); + if (missingT1 >= 0) { + missingPt1 = refSegment->span(minTIndex).fPt; + addTo1 = seg1Start < 0 ? segment1 : segment2; + } + double missingT2 = -1; + double otherT2 = -1; + if (seg1End < 0) { + if (seg2End < 0) { + return; + } + missingT2 = segment1->calcMissingTEnd(refSegment, loEnd, min, max, hiEnd, + segment2, seg1Start); + if (missingT2 < 0) { + return; + } + const SkOpSpan* missingSpan = &segment2->span(seg2End); + otherT2 = missingSpan->fT; + } else if (seg2End < 0) { + SkASSERT(seg1End >= 0); + missingT2 = segment2->calcMissingTEnd(refSegment, loEnd, min, max, hiEnd, + segment1, seg2Start); + if (missingT2 < 0) { + return; + } + const SkOpSpan* missingSpan = &segment1->span(seg1End); + otherT2 = missingSpan->fT; + } + SkPoint missingPt2; + SkOpSegment* addTo2 = NULL; + SkOpSegment* addOther2 = seg1End < 0 ? segment2 : segment1; + int maxTIndex = refSegment->findExactT(max, addOther2); + SkASSERT(maxTIndex >= 0); + if (missingT2 >= 0) { + missingPt2 = refSegment->span(maxTIndex).fPt; + addTo2 = seg1End < 0 ? segment1 : segment2; + } + if (missingT1 >= 0) { + addTo1->pinT(missingPt1, &missingT1); + addTo1->addTPair(missingT1, addOther1, otherT1, false, missingPt1); + } else { + SkASSERT(minTIndex >= 0); + missingPt1 = refSegment->span(minTIndex).fPt; + } + if (missingT2 >= 0) { + addTo2->pinT(missingPt2, &missingT2); + addTo2->addTPair(missingT2, addOther2, otherT2, false, missingPt2); + } else { + SkASSERT(minTIndex >= 0); + missingPt2 = refSegment->span(maxTIndex).fPt; + } + if (!partial) { + return; + } + if (cancelers) { + if (missingT1 >= 0) { + if (addTo1->reversePoints(missingPt1, missingPt2)) { + SkTSwap(missingPt1, missingPt2); + } + addTo1->addTCancel(missingPt1, missingPt2, addOther1); + } else { + if (addTo2->reversePoints(missingPt1, missingPt2)) { + SkTSwap(missingPt1, missingPt2); + } + addTo2->addTCancel(missingPt1, missingPt2, addOther2); + } + } else if (missingT1 >= 0) { + SkAssertResult(addTo1->addTCoincident(missingPt1, missingPt2, + addTo1 == addTo2 ? missingT2 : otherT2, addOther1)); + } else { + SkAssertResult(addTo2->addTCoincident(missingPt2, missingPt1, + addTo2 == addTo1 ? missingT1 : otherT1, addOther2)); + } +} + +void SkOpContour::joinCoincidence(const SkTArray<SkCoincidence, true>& coincidences, bool partial) { + int count = coincidences.count(); +#if DEBUG_CONCIDENT + if (count > 0) { + SkDebugf("%s count=%d\n", __FUNCTION__, count); + } +#endif + // look for a lineup where the partial implies another adjoining coincidence + for (int index = 0; index < count; ++index) { + const SkCoincidence& coincidence = coincidences[index]; + int thisIndex = coincidence.fSegments[0]; + SkOpSegment& thisOne = fSegments[thisIndex]; + if (thisOne.done()) { + continue; + } + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + SkOpSegment& other = otherContour->fSegments[otherIndex]; + if (other.done()) { + continue; + } + double startT = coincidence.fTs[0][0]; + double endT = coincidence.fTs[0][1]; + if (startT == endT) { // this can happen in very large compares + continue; + } + double oStartT = coincidence.fTs[1][0]; + double oEndT = coincidence.fTs[1][1]; + if (oStartT == oEndT) { + continue; + } + bool swapStart = startT > endT; + bool swapOther = oStartT > oEndT; + const SkPoint* startPt = &coincidence.fPts[0][0]; + const SkPoint* endPt = &coincidence.fPts[0][1]; + if (swapStart) { + SkTSwap(startT, endT); + SkTSwap(oStartT, oEndT); + SkTSwap(startPt, endPt); + } + bool cancel = swapOther != swapStart; + int step = swapStart ? -1 : 1; + int oStep = swapOther ? -1 : 1; + double oMatchStart = cancel ? oEndT : oStartT; + if (partial ? startT != 0 || oMatchStart != 0 : (startT == 0) != (oMatchStart == 0)) { + bool added = false; + if (oMatchStart != 0) { + const SkPoint& oMatchStartPt = cancel ? *endPt : *startPt; + added = thisOne.joinCoincidence(&other, oMatchStart, oMatchStartPt, oStep, cancel); + } + if (!cancel && startT != 0 && !added) { + (void) other.joinCoincidence(&thisOne, startT, *startPt, step, cancel); + } + } + double oMatchEnd = cancel ? oStartT : oEndT; + if (partial ? endT != 1 || oMatchEnd != 1 : (endT == 1) != (oMatchEnd == 1)) { + bool added = false; + if (cancel && endT != 1 && !added) { + (void) other.joinCoincidence(&thisOne, endT, *endPt, -step, cancel); + } + } + } +} + +bool SkOpContour::calcCommonCoincidentWinding(const SkCoincidence& coincidence) { + if (coincidence.fNearly[0] && coincidence.fNearly[1]) { + return true; + } + int thisIndex = coincidence.fSegments[0]; + SkOpSegment& thisOne = fSegments[thisIndex]; + if (thisOne.done()) { + return true; + } + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + SkOpSegment& other = otherContour->fSegments[otherIndex]; + if (other.done()) { + return true; + } + double startT = coincidence.fTs[0][0]; + double endT = coincidence.fTs[0][1]; + const SkPoint* startPt = &coincidence.fPts[0][0]; + const SkPoint* endPt = &coincidence.fPts[0][1]; + bool cancelers; + if ((cancelers = startT > endT)) { + SkTSwap<double>(startT, endT); + SkTSwap<const SkPoint*>(startPt, endPt); + } + bump_out_close_span(&startT, &endT); + SkASSERT(!approximately_negative(endT - startT)); + double oStartT = coincidence.fTs[1][0]; + double oEndT = coincidence.fTs[1][1]; + if (oStartT > oEndT) { + SkTSwap<double>(oStartT, oEndT); + cancelers ^= true; + } + bump_out_close_span(&oStartT, &oEndT); + SkASSERT(!approximately_negative(oEndT - oStartT)); + bool success = true; + if (cancelers) { + thisOne.addTCancel(*startPt, *endPt, &other); + } else { + success = thisOne.addTCoincident(*startPt, *endPt, endT, &other); + } +#if DEBUG_CONCIDENT + thisOne.debugShowTs("p"); + other.debugShowTs("o"); +#endif + return success; +} + +void SkOpContour::resolveNearCoincidence() { + int count = fCoincidences.count(); + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = fCoincidences[index]; + if (!coincidence.fNearly[0] || !coincidence.fNearly[1]) { + continue; + } + int thisIndex = coincidence.fSegments[0]; + SkOpSegment& thisOne = fSegments[thisIndex]; + SkOpContour* otherContour = coincidence.fOther; + int otherIndex = coincidence.fSegments[1]; + SkOpSegment& other = otherContour->fSegments[otherIndex]; + if ((thisOne.done() || other.done()) && thisOne.complete() && other.complete()) { + // OPTIMIZATION: remove from coincidence array + continue; + } + #if DEBUG_CONCIDENT + thisOne.debugShowTs("-"); + other.debugShowTs("o"); + #endif + double startT = coincidence.fTs[0][0]; + double endT = coincidence.fTs[0][1]; + bool cancelers; + if ((cancelers = startT > endT)) { + SkTSwap<double>(startT, endT); + } + if (startT == endT) { // if span is very large, the smaller may have collapsed to nothing + if (endT <= 1 - FLT_EPSILON) { + endT += FLT_EPSILON; + SkASSERT(endT <= 1); + } else { + startT -= FLT_EPSILON; + SkASSERT(startT >= 0); + } + } + SkASSERT(!approximately_negative(endT - startT)); + double oStartT = coincidence.fTs[1][0]; + double oEndT = coincidence.fTs[1][1]; + if (oStartT > oEndT) { + SkTSwap<double>(oStartT, oEndT); + cancelers ^= true; + } + SkASSERT(!approximately_negative(oEndT - oStartT)); + if (cancelers) { + thisOne.blindCancel(coincidence, &other); + } else { + thisOne.blindCoincident(coincidence, &other); + } + } +} + +void SkOpContour::sortAngles() { + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + fSegments[test].sortAngles(); + } +} + +void SkOpContour::sortSegments() { + int segmentCount = fSegments.count(); + fSortedSegments.push_back_n(segmentCount); + for (int test = 0; test < segmentCount; ++test) { + fSortedSegments[test] = &fSegments[test]; + } + SkTQSort<SkOpSegment>(fSortedSegments.begin(), fSortedSegments.end() - 1); + fFirstSorted = 0; +} + void SkOpContour::toPath(SkPathWriter* path) const { - const SkPoint& pt = fHead.pts()[0]; + int segmentCount = fSegments.count(); + const SkPoint& pt = fSegments.front().pts()[0]; path->deferredMove(pt); - const SkOpSegment* segment = &fHead; - do { - segment->addCurveTo(segment->head(), segment->tail(), path, true); - } while ((segment = segment->next())); + for (int test = 0; test < segmentCount; ++test) { + fSegments[test].addCurveTo(0, 1, path, true); + } path->close(); } @@ -99,14 +706,57 @@ void SkOpContour::topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY, } } -SkOpSegment* SkOpContour::undoneSegment(SkOpSpanBase** startPtr, SkOpSpanBase** endPtr) { - SkOpSegment* segment = &fHead; - do { - if (segment->done()) { +SkOpSegment* SkOpContour::undoneSegment(int* start, int* end) { + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + SkOpSegment* testSegment = &fSegments[test]; + if (testSegment->done()) { continue; } - segment->undoneSpan(startPtr, endPtr); - return segment; - } while ((segment = segment->next())); + testSegment->undoneSpan(start, end); + return testSegment; + } return NULL; } + +#if DEBUG_SHOW_WINDING +int SkOpContour::debugShowWindingValues(int totalSegments, int ofInterest) { + int count = fSegments.count(); + int sum = 0; + for (int index = 0; index < count; ++index) { + sum += fSegments[index].debugShowWindingValues(totalSegments, ofInterest); + } +// SkDebugf("%s sum=%d\n", __FUNCTION__, sum); + return sum; +} + +void SkOpContour::debugShowWindingValues(const SkTArray<SkOpContour*, true>& contourList) { +// int ofInterest = 1 << 1 | 1 << 5 | 1 << 9 | 1 << 13; +// int ofInterest = 1 << 4 | 1 << 8 | 1 << 12 | 1 << 16; + int ofInterest = 1 << 5 | 1 << 8; + int total = 0; + int index; + for (index = 0; index < contourList.count(); ++index) { + total += contourList[index]->segments().count(); + } + int sum = 0; + for (index = 0; index < contourList.count(); ++index) { + sum += contourList[index]->debugShowWindingValues(total, ofInterest); + } +// SkDebugf("%s total=%d\n", __FUNCTION__, sum); +} +#endif + +void SkOpContour::setBounds() { + int count = fSegments.count(); + if (count == 0) { + SkDebugf("%s empty contour\n", __FUNCTION__); + SkASSERT(0); + // FIXME: delete empty contour? + return; + } + fBounds = fSegments.front().bounds(); + for (int index = 1; index < count; ++index) { + fBounds.add(fSegments[index].bounds()); + } +} diff --git a/src/pathops/SkOpContour.h b/src/pathops/SkOpContour.h index be1f59f4bb..7a1cc09247 100644 --- a/src/pathops/SkOpContour.h +++ b/src/pathops/SkOpContour.h @@ -8,16 +8,31 @@ #define SkOpContour_DEFINED #include "SkOpSegment.h" -#include "SkTDArray.h" -#include "SkTSort.h" +#include "SkTArray.h" -class SkChunkAlloc; +#if defined(SK_DEBUG) || !FORCE_RELEASE +#include "SkThread.h" +#endif + +class SkIntersections; +class SkOpContour; class SkPathWriter; +struct SkCoincidence { + SkOpContour* fOther; + int fSegments[2]; + double fTs[2][2]; + SkPoint fPts[2][2]; + int fNearly[2]; +}; + class SkOpContour { public: SkOpContour() { reset(); +#if defined(SK_DEBUG) || !FORCE_RELEASE + fID = sk_atomic_inc(&SkPathOpsDebug::gContourID); +#endif } bool operator<(const SkOpContour& rh) const { @@ -26,255 +41,211 @@ public: : fBounds.fTop < rh.fBounds.fTop; } - void addCubic(SkPoint pts[4], SkChunkAlloc* allocator) { - appendSegment(allocator).addCubic(pts, this); - } - - void addCurve(SkPath::Verb verb, const SkPoint pts[4], SkChunkAlloc* allocator); - - void addLine(SkPoint pts[2], SkChunkAlloc* allocator) { - appendSegment(allocator).addLine(pts, this); - } - - void addQuad(SkPoint pts[3], SkChunkAlloc* allocator) { - appendSegment(allocator).addQuad(pts, this); - } - - void align() { - SkASSERT(fCount > 0); - SkOpSegment* segment = &fHead; - do { - segment->align(); - } while ((segment = segment->next())); - } - - SkOpSegment& appendSegment(SkChunkAlloc* allocator) { - SkOpSegment* result = fCount++ - ? SkOpTAllocator<SkOpSegment>::Allocate(allocator) : &fHead; - result->setPrev(fTail); - if (fTail) { - fTail->setNext(result); - } - fTail = result; - return *result; - } + bool addCoincident(int index, SkOpContour* other, int otherIndex, + const SkIntersections& ts, bool swap); + void addCoincidentPoints(); - SkOpContour* appendContour(SkChunkAlloc* allocator) { - SkOpContour* contour = SkOpTAllocator<SkOpContour>::New(allocator); - - SkOpContour* prev = this; - SkOpContour* next; - while ((next = prev->next())) { - prev = next; + void addCross(const SkOpContour* crosser) { +#ifdef DEBUG_CROSS + for (int index = 0; index < fCrosses.count(); ++index) { + SkASSERT(fCrosses[index] != crosser); } - prev->setNext(contour); - return contour; - } - - const SkPathOpsBounds& bounds() const { - return fBounds; +#endif + fCrosses.push_back(crosser); } - void calcAngles(SkChunkAlloc* allocator) { - SkASSERT(fCount > 0); - SkOpSegment* segment = &fHead; - do { - segment->calcAngles(allocator); - } while ((segment = segment->next())); + void addCubic(const SkPoint pts[4]) { + fSegments.push_back().addCubic(pts, fOperand, fXor); + fContainsCurves = fContainsCubics = true; } - void complete() { - setBounds(); + int addLine(const SkPoint pts[2]) { + fSegments.push_back().addLine(pts, fOperand, fXor); + return fSegments.count(); } - int count() const { - return fCount; + void addOtherT(int segIndex, int tIndex, double otherT, int otherIndex) { + fSegments[segIndex].addOtherT(tIndex, otherT, otherIndex); } - int debugID() const { - return PATH_OPS_DEBUG_RELEASE(fID, -1); - } + bool addPartialCoincident(int index, SkOpContour* other, int otherIndex, + const SkIntersections& ts, int ptIndex, bool swap); - int debugIndent() const { - return PATH_OPS_DEBUG_RELEASE(fIndent, 0); + int addQuad(const SkPoint pts[3]) { + fSegments.push_back().addQuad(pts, fOperand, fXor); + fContainsCurves = true; + return fSegments.count(); } -#if DEBUG_ACTIVE_SPANS - void debugShowActiveSpans() { - SkOpSegment* segment = &fHead; - do { - segment->debugShowActiveSpans(); - } while ((segment = segment->next())); + int addT(int segIndex, SkOpContour* other, int otherIndex, const SkPoint& pt, double newT) { + setContainsIntercepts(); + return fSegments[segIndex].addT(&other->fSegments[otherIndex], pt, newT); } -#endif - const SkOpAngle* debugAngle(int id) const { - return PATH_OPS_DEBUG_RELEASE(globalState()->debugAngle(id), NULL); + int addSelfT(int segIndex, const SkPoint& pt, double newT) { + setContainsIntercepts(); + return fSegments[segIndex].addSelfT(pt, newT); } - SkOpContour* debugContour(int id) { - return PATH_OPS_DEBUG_RELEASE(globalState()->debugContour(id), NULL); - } + void align(const SkOpSegment::AlignedSpan& aligned, bool swap, SkCoincidence* coincidence); + void alignCoincidence(const SkOpSegment::AlignedSpan& aligned, + SkTArray<SkCoincidence, true>* coincidences); - const SkOpPtT* debugPtT(int id) const { - return PATH_OPS_DEBUG_RELEASE(globalState()->debugPtT(id), NULL); + void alignCoincidence(const SkOpSegment::AlignedSpan& aligned) { + alignCoincidence(aligned, &fCoincidences); + alignCoincidence(aligned, &fPartialCoincidences); } - const SkOpSegment* debugSegment(int id) const { - return PATH_OPS_DEBUG_RELEASE(globalState()->debugSegment(id), NULL); + void alignMultiples(SkTDArray<SkOpSegment::AlignedSpan>* aligned) { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + SkOpSegment& segment = fSegments[sIndex]; + if (segment.hasMultiples()) { + segment.alignMultiples(aligned); + } + } } - const SkOpSpanBase* debugSpan(int id) const { - return PATH_OPS_DEBUG_RELEASE(globalState()->debugSpan(id), NULL); - } + void alignTPt(int segmentIndex, const SkOpContour* other, int otherIndex, + bool swap, int tIndex, SkIntersections* ts, SkPoint* point) const; - SkOpGlobalState* globalState() const { - return fState; + const SkPathOpsBounds& bounds() const { + return fBounds; } - void debugValidate() const { -#if DEBUG_VALIDATE - const SkOpSegment* segment = &fHead; - const SkOpSegment* prior = NULL; - do { - segment->debugValidate(); - SkASSERT(segment->prev() == prior); - prior = segment; - } while ((segment = segment->next())); - SkASSERT(prior == fTail); -#endif - } + bool calcAngles(); + bool calcCoincidentWinding(); + void calcPartialCoincidentWinding(); - bool done() const { - return fDone; + void checkDuplicates() { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + SkOpSegment& segment = fSegments[sIndex]; + if (segment.count() > 2) { + segment.checkDuplicates(); + } + } } - void dump(); - void dumpAll(); - void dumpAngles() const; - void dumpPt(int ) const; - void dumpPts() const; - void dumpPtsX() const; - void dumpSegment(int ) const; - void dumpSegments(SkPathOp op) const; - void dumpSpan(int ) const; - void dumpSpans() const; - - const SkPoint& end() const { - return fTail->pts()[SkPathOpsVerbToPoints(fTail->verb())]; + bool checkEnds() { + if (!fContainsCurves) { + return true; + } + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + SkOpSegment* segment = &fSegments[sIndex]; + if (segment->verb() == SkPath::kLine_Verb) { + continue; + } + if (segment->done()) { + continue; // likely coincident, nothing to do + } + if (!segment->checkEnds()) { + return false; + } + } + return true; } - SkOpSegment* first() { - SkASSERT(fCount > 0); - return &fHead; + void checkMultiples() { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + SkOpSegment& segment = fSegments[sIndex]; + if (segment.count() > 2) { + segment.checkMultiples(); + fMultiples |= segment.hasMultiples(); + } + } } - const SkOpSegment* first() const { - SkASSERT(fCount > 0); - return &fHead; + void checkSmall() { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + SkOpSegment& segment = fSegments[sIndex]; + // OPTIMIZATION : skip segments that are done? + if (segment.hasSmall()) { + segment.checkSmall(); + } + } } - void indentDump() { - PATH_OPS_DEBUG_CODE(fIndent += 2); + // if same point has different T values, choose a common T + void checkTiny() { + int segmentCount = fSegments.count(); + if (segmentCount <= 2) { + return; + } + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + SkOpSegment& segment = fSegments[sIndex]; + if (segment.hasTiny()) { + segment.checkTiny(); + } + } } - void init(SkOpGlobalState* globalState, bool operand, bool isXor) { - fState = globalState; - fOperand = operand; - fXor = isXor; + void complete() { + setBounds(); + fContainsIntercepts = false; } - bool isXor() const { - return fXor; + bool containsCubics() const { + return fContainsCubics; } - void missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) { - SkASSERT(fCount > 0); - SkOpSegment* segment = &fHead; - do { - if (fState->angleCoincidence()) { - segment->checkAngleCoin(coincidences, allocator); - } else { - segment->missingCoincidence(coincidences, allocator); + bool crosses(const SkOpContour* crosser) const { + for (int index = 0; index < fCrosses.count(); ++index) { + if (fCrosses[index] == crosser) { + return true; } - } while ((segment = segment->next())); + } + return false; } - bool moveNearby() { - SkASSERT(fCount > 0); - SkOpSegment* segment = &fHead; - do { - if (!segment->moveNearby()) { - return false; - } - } while ((segment = segment->next())); - return true; + bool done() const { + return fDone; } - SkOpContour* next() { - return fNext; + const SkPoint& end() const { + const SkOpSegment& segment = fSegments.back(); + return segment.pts()[SkPathOpsVerbToPoints(segment.verb())]; } - const SkOpContour* next() const { - return fNext; + void fixOtherTIndex() { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + fSegments[sIndex].fixOtherTIndex(); + } } - SkOpSegment* nonVerticalSegment(SkOpSpanBase** start, SkOpSpanBase** end); - - bool operand() const { - return fOperand; + bool hasMultiples() const { + return fMultiples; } - bool oppXor() const { - return fOppXor; + void joinCoincidence() { + joinCoincidence(fCoincidences, false); + joinCoincidence(fPartialCoincidences, true); } - void outdentDump() { - PATH_OPS_DEBUG_CODE(fIndent -= 2); - } + SkOpSegment* nonVerticalSegment(int* start, int* end); - void remove(SkOpContour* contour) { - if (contour == this) { - SkASSERT(fCount == 0); - return; - } - SkASSERT(contour->fNext == NULL); - SkOpContour* prev = this; - SkOpContour* next; - while ((next = prev->next()) != contour) { - SkASSERT(next); - prev = next; - } - SkASSERT(prev); - prev->setNext(NULL); + bool operand() const { + return fOperand; } void reset() { - fTail = NULL; - fNext = NULL; - fCount = 0; - fDone = false; - SkDEBUGCODE(fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin)); - SkDEBUGCODE(fFirstSorted = -1); - PATH_OPS_DEBUG_CODE(fIndent = 0); - } - - void setBounds() { - SkASSERT(fCount > 0); - const SkOpSegment* segment = &fHead; - fBounds = segment->bounds(); - while ((segment = segment->next())) { - fBounds.add(segment->bounds()); - } + fSegments.reset(); + fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); + fContainsCurves = fContainsCubics = fContainsIntercepts = fDone = fMultiples = false; } - void setGlobalState(SkOpGlobalState* state) { - fState = state; + void resolveNearCoincidence(); + + SkTArray<SkOpSegment>& segments() { + return fSegments; } - void setNext(SkOpContour* contour) { - SkASSERT(!fNext == !!contour); - fNext = contour; + void setContainsIntercepts() { + fContainsIntercepts = true; } void setOperand(bool isOp) { @@ -283,68 +254,107 @@ public: void setOppXor(bool isOppXor) { fOppXor = isOppXor; + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + fSegments[test].setOppXor(isOppXor); + } } void setXor(bool isXor) { fXor = isXor; } - SkPath::Verb simplifyCubic(SkPoint pts[4]); - - void sortAngles() { - SkASSERT(fCount > 0); - SkOpSegment* segment = &fHead; - do { - segment->sortAngles(); - } while ((segment = segment->next())); - } - - void sortSegments() { - SkOpSegment* segment = &fHead; - do { - *fSortedSegments.append() = segment; - } while ((segment = segment->next())); - SkTQSort<SkOpSegment>(fSortedSegments.begin(), fSortedSegments.end() - 1); - fFirstSorted = 0; - } + void sortAngles(); + void sortSegments(); const SkPoint& start() const { - return fHead.pts()[0]; + return fSegments.front().pts()[0]; } + void toPath(SkPathWriter* path) const; + void toPartialBackward(SkPathWriter* path) const { - const SkOpSegment* segment = fTail; - do { - segment->addCurveTo(segment->tail(), segment->head(), path, true); - } while ((segment = segment->prev())); + int segmentCount = fSegments.count(); + for (int test = segmentCount - 1; test >= 0; --test) { + fSegments[test].addCurveTo(1, 0, path, true); + } } void toPartialForward(SkPathWriter* path) const { - const SkOpSegment* segment = &fHead; - do { - segment->addCurveTo(segment->head(), segment->tail(), path, true); - } while ((segment = segment->next())); + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + fSegments[test].addCurveTo(0, 1, path, true); + } } - void toPath(SkPathWriter* path) const; void topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY, SkOpSegment** topStart); - SkOpSegment* undoneSegment(SkOpSpanBase** startPtr, SkOpSpanBase** endPtr); + SkOpSegment* undoneSegment(int* start, int* end); + + int updateSegment(int index, const SkPoint* pts) { + SkOpSegment& segment = fSegments[index]; + segment.updatePts(pts); + return SkPathOpsVerbToPoints(segment.verb()) + 1; + } + +#if DEBUG_TEST + SkTArray<SkOpSegment>& debugSegments() { + return fSegments; + } +#endif + +#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY + void debugShowActiveSpans() { + for (int index = 0; index < fSegments.count(); ++index) { + fSegments[index].debugShowActiveSpans(); + } + } +#endif + +#if DEBUG_SHOW_WINDING + int debugShowWindingValues(int totalSegments, int ofInterest); + static void debugShowWindingValues(const SkTArray<SkOpContour*, true>& contourList); +#endif + + // available to test routines only + void dump() const; + void dumpAngles() const; + void dumpCoincidence(const SkCoincidence& ) const; + void dumpCoincidences() const; + void dumpPt(int ) const; + void dumpPts() const; + void dumpSpan(int ) const; + void dumpSpans() const; private: - SkOpGlobalState* fState; - SkOpSegment fHead; - SkOpSegment* fTail; - SkOpContour* fNext; - SkTDArray<SkOpSegment*> fSortedSegments; // set by find top segment - SkPathOpsBounds fBounds; - int fCount; + void alignPt(int index, SkPoint* point, int zeroPt) const; + int alignT(bool swap, int tIndex, SkIntersections* ts) const; + bool calcCommonCoincidentWinding(const SkCoincidence& ); + void checkCoincidentPair(const SkCoincidence& oneCoin, int oneIdx, + const SkCoincidence& twoCoin, int twoIdx, bool partial); + void joinCoincidence(const SkTArray<SkCoincidence, true>& , bool partial); + void setBounds(); + + SkTArray<SkOpSegment> fSegments; + SkTArray<SkOpSegment*, true> fSortedSegments; int fFirstSorted; - bool fDone; // set by find top segment + SkTArray<SkCoincidence, true> fCoincidences; + SkTArray<SkCoincidence, true> fPartialCoincidences; + SkTArray<const SkOpContour*, true> fCrosses; + SkPathOpsBounds fBounds; + bool fContainsIntercepts; // FIXME: is this used by anybody? + bool fContainsCubics; + bool fContainsCurves; + bool fDone; + bool fMultiples; // set if some segment has multiple identical intersections with other curves bool fOperand; // true for the second argument to a binary operator - bool fXor; // set if original path had even-odd fill - bool fOppXor; // set if opposite path had even-odd fill - PATH_OPS_DEBUG_CODE(int fID); - PATH_OPS_DEBUG_CODE(int fIndent); + bool fXor; + bool fOppXor; +#if defined(SK_DEBUG) || !FORCE_RELEASE + int debugID() const { return fID; } + int fID; +#else + int debugID() const { return -1; } +#endif }; #endif diff --git a/src/pathops/SkOpEdgeBuilder.cpp b/src/pathops/SkOpEdgeBuilder.cpp index bd21d729b6..803a5f4739 100644 --- a/src/pathops/SkOpEdgeBuilder.cpp +++ b/src/pathops/SkOpEdgeBuilder.cpp @@ -9,7 +9,7 @@ #include "SkReduceOrder.h" void SkOpEdgeBuilder::init() { - fCurrentContour = fContoursHead; + fCurrentContour = NULL; fOperand = false; fXorMask[0] = fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask : kWinding_PathOpsMask; @@ -19,43 +19,32 @@ void SkOpEdgeBuilder::init() { void SkOpEdgeBuilder::addOperand(const SkPath& path) { SkASSERT(fPathVerbs.count() > 0 && fPathVerbs.end()[-1] == SkPath::kDone_Verb); - fPathVerbs.pop(); + fPathVerbs.pop_back(); fPath = &path; fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask : kWinding_PathOpsMask; preFetch(); } -int SkOpEdgeBuilder::count() const { - SkOpContour* contour = fContoursHead; - int count = 0; - while (contour) { - count += contour->count() > 0; - contour = contour->next(); - } - return count; -} - -bool SkOpEdgeBuilder::finish(SkChunkAlloc* allocator) { - fOperand = false; - if (fUnparseable || !walk(allocator)) { +bool SkOpEdgeBuilder::finish() { + if (fUnparseable || !walk()) { return false; } complete(); - if (fCurrentContour && !fCurrentContour->count()) { - fContoursHead->remove(fCurrentContour); + if (fCurrentContour && !fCurrentContour->segments().count()) { + fContours.pop_back(); } return true; } void SkOpEdgeBuilder::closeContour(const SkPoint& curveEnd, const SkPoint& curveStart) { if (!SkDPoint::ApproximatelyEqual(curveEnd, curveStart)) { - *fPathVerbs.append() = SkPath::kLine_Verb; - *fPathPts.append() = curveStart; + fPathVerbs.push_back(SkPath::kLine_Verb); + fPathPts.push_back_n(1, &curveStart); } else { fPathPts[fPathPts.count() - 1] = curveStart; } - *fPathVerbs.append() = SkPath::kClose_Verb; + fPathVerbs.push_back(SkPath::kClose_Verb); } // very tiny points cause numerical instability : don't allow them @@ -68,6 +57,7 @@ static void force_small_to_zero(SkPoint* pt) { } } + int SkOpEdgeBuilder::preFetch() { if (!fPath->isFinite()) { fUnparseable = true; @@ -88,18 +78,18 @@ int SkOpEdgeBuilder::preFetch() { if (!fAllowOpenContours && lastCurve) { closeContour(curve[0], curveStart); } - *fPathVerbs.append() = verb; + fPathVerbs.push_back(verb); force_small_to_zero(&pts[0]); - *fPathPts.append() = pts[0]; + fPathPts.push_back(pts[0]); curveStart = curve[0] = pts[0]; lastCurve = false; continue; case SkPath::kLine_Verb: force_small_to_zero(&pts[1]); if (SkDPoint::ApproximatelyEqual(curve[0], pts[1])) { - uint8_t lastVerb = fPathVerbs.top(); + uint8_t lastVerb = fPathVerbs.back(); if (lastVerb != SkPath::kLine_Verb && lastVerb != SkPath::kMove_Verb) { - fPathPts.top() = pts[1]; + fPathPts.back() = pts[1]; } continue; // skip degenerate points } @@ -119,9 +109,9 @@ int SkOpEdgeBuilder::preFetch() { quadderTol); const int nQuads = quadder.countQuads(); for (int i = 0; i < nQuads; ++i) { - *fPathVerbs.append() = SkPath::kQuad_Verb; + fPathVerbs.push_back(SkPath::kQuad_Verb); } - fPathPts.append(nQuads * 2, &quadPts[1]); + fPathPts.push_back_n(nQuads * 2, &quadPts[1]); curve[0] = pts[2]; lastCurve = true; } @@ -145,16 +135,16 @@ int SkOpEdgeBuilder::preFetch() { case SkPath::kDone_Verb: continue; } - *fPathVerbs.append() = verb; + fPathVerbs.push_back(verb); int ptCount = SkPathOpsVerbToPoints(verb); - fPathPts.append(ptCount, &pts[1]); + fPathPts.push_back_n(ptCount, &pts[1]); curve[0] = pts[ptCount]; lastCurve = true; } while (verb != SkPath::kDone_Verb); if (!fAllowOpenContours && lastCurve) { closeContour(curve[0], curveStart); } - *fPathVerbs.append() = SkPath::kDone_Verb; + fPathVerbs.push_back(SkPath::kDone_Verb); return fPathVerbs.count() - 1; } @@ -163,10 +153,10 @@ bool SkOpEdgeBuilder::close() { return true; } -bool SkOpEdgeBuilder::walk(SkChunkAlloc* allocator) { +bool SkOpEdgeBuilder::walk() { uint8_t* verbPtr = fPathVerbs.begin(); uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf]; - SkPoint* pointsPtr = fPathPts.begin() - 1; + const SkPoint* pointsPtr = fPathPts.begin() - 1; SkPath::Verb verb; while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) { if (verbPtr == endOfFirstHalf) { @@ -175,7 +165,7 @@ bool SkOpEdgeBuilder::walk(SkChunkAlloc* allocator) { verbPtr++; switch (verb) { case SkPath::kMove_Verb: - if (fCurrentContour && fCurrentContour->count()) { + if (fCurrentContour) { if (fAllowOpenContours) { complete(); } else if (!close()) { @@ -183,44 +173,21 @@ bool SkOpEdgeBuilder::walk(SkChunkAlloc* allocator) { } } if (!fCurrentContour) { - fCurrentContour = fContoursHead->appendContour(allocator); + fCurrentContour = fContours.push_back_n(1); + fCurrentContour->setOperand(fOperand); + fCurrentContour->setXor(fXorMask[fOperand] == kEvenOdd_PathOpsMask); } - fCurrentContour->init(fGlobalState, fOperand, - fXorMask[fOperand] == kEvenOdd_PathOpsMask); pointsPtr += 1; continue; case SkPath::kLine_Verb: - fCurrentContour->addLine(pointsPtr, fAllocator); + fCurrentContour->addLine(pointsPtr); break; case SkPath::kQuad_Verb: - fCurrentContour->addQuad(pointsPtr, fAllocator); + fCurrentContour->addQuad(pointsPtr); + break; + case SkPath::kCubic_Verb: + fCurrentContour->addCubic(pointsPtr); break; - case SkPath::kCubic_Verb: { - // split self-intersecting cubics in two before proceeding - // if the cubic is convex, it doesn't self intersect. - SkScalar loopT; - if (SkDCubic::ComplexBreak(pointsPtr, &loopT)) { - SkPoint cubicPair[7]; - SkChopCubicAt(pointsPtr, cubicPair, loopT); - SkPoint cStorage[2][4]; - SkPath::Verb v1 = SkReduceOrder::Cubic(&cubicPair[0], cStorage[0]); - SkPath::Verb v2 = SkReduceOrder::Cubic(&cubicPair[3], cStorage[1]); - if (v1 != SkPath::kMove_Verb && v2 != SkPath::kMove_Verb) { - SkPoint* curve1 = v1 == SkPath::kCubic_Verb ? &cubicPair[0] : cStorage[0]; - SkPoint* curve2 = v2 == SkPath::kCubic_Verb ? &cubicPair[3] : cStorage[1]; - for (size_t index = 0; index < SK_ARRAY_COUNT(curve1); ++index) { - force_small_to_zero(&curve1[index]); - force_small_to_zero(&curve2[index]); - } - fCurrentContour->addCurve(v1, curve1, fAllocator); - fCurrentContour->addCurve(v2, curve2, fAllocator); - } else { - fCurrentContour->addCubic(pointsPtr, fAllocator); - } - } else { - fCurrentContour->addCubic(pointsPtr, fAllocator); - } - } break; case SkPath::kClose_Verb: SkASSERT(fCurrentContour); if (!close()) { @@ -231,11 +198,10 @@ bool SkOpEdgeBuilder::walk(SkChunkAlloc* allocator) { SkDEBUGFAIL("bad verb"); return false; } - SkASSERT(fCurrentContour); - fCurrentContour->debugValidate(); pointsPtr += SkPathOpsVerbToPoints(verb); + SkASSERT(fCurrentContour); } - if (fCurrentContour && fCurrentContour->count() &&!fAllowOpenContours && !close()) { + if (fCurrentContour && !fAllowOpenContours && !close()) { return false; } return true; diff --git a/src/pathops/SkOpEdgeBuilder.h b/src/pathops/SkOpEdgeBuilder.h index 3ecc915833..fd0744572d 100644 --- a/src/pathops/SkOpEdgeBuilder.h +++ b/src/pathops/SkOpEdgeBuilder.h @@ -9,25 +9,20 @@ #include "SkOpContour.h" #include "SkPathWriter.h" +#include "SkTArray.h" class SkOpEdgeBuilder { public: - SkOpEdgeBuilder(const SkPathWriter& path, SkOpContour* contours2, SkChunkAlloc* allocator, - SkOpGlobalState* globalState) - : fAllocator(allocator) // FIXME: replace with const, tune this - , fGlobalState(globalState) - , fPath(path.nativePath()) - , fContoursHead(contours2) + SkOpEdgeBuilder(const SkPathWriter& path, SkTArray<SkOpContour>& contours) + : fPath(path.nativePath()) + , fContours(contours) , fAllowOpenContours(true) { init(); } - SkOpEdgeBuilder(const SkPath& path, SkOpContour* contours2, SkChunkAlloc* allocator, - SkOpGlobalState* globalState) - : fAllocator(allocator) - , fGlobalState(globalState) - , fPath(&path) - , fContoursHead(contours2) + SkOpEdgeBuilder(const SkPath& path, SkTArray<SkOpContour>& contours) + : fPath(&path) + , fContours(contours) , fAllowOpenContours(false) { init(); } @@ -35,19 +30,13 @@ public: void addOperand(const SkPath& path); void complete() { - if (fCurrentContour && fCurrentContour->count()) { + if (fCurrentContour && fCurrentContour->segments().count()) { fCurrentContour->complete(); fCurrentContour = NULL; } } - int count() const; - bool finish(SkChunkAlloc* ); - - const SkOpContour* head() const { - return fContoursHead; - } - + bool finish(); void init(); bool unparseable() const { return fUnparseable; } SkPathOpsMask xorMask() const { return fXorMask[fOperand]; } @@ -56,15 +45,13 @@ private: void closeContour(const SkPoint& curveEnd, const SkPoint& curveStart); bool close(); int preFetch(); - bool walk(SkChunkAlloc* ); + bool walk(); - SkChunkAlloc* fAllocator; - SkOpGlobalState* fGlobalState; const SkPath* fPath; - SkTDArray<SkPoint> fPathPts; - SkTDArray<uint8_t> fPathVerbs; + SkTArray<SkPoint, true> fPathPts; + SkTArray<uint8_t, true> fPathVerbs; SkOpContour* fCurrentContour; - SkOpContour* fContoursHead; + SkTArray<SkOpContour>& fContours; SkPathOpsMask fXorMask[2]; int fSecondHalf; bool fOperand; diff --git a/src/pathops/SkOpSegment.cpp b/src/pathops/SkOpSegment.cpp index 902f273744..1fb5afa028 100644 --- a/src/pathops/SkOpSegment.cpp +++ b/src/pathops/SkOpSegment.cpp @@ -4,20 +4,11 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ -#include "SkOpCoincidence.h" +#include "SkIntersections.h" #include "SkOpContour.h" #include "SkOpSegment.h" #include "SkPathWriter.h" - -/* -After computing raw intersections, post process all segments to: -- find small collections of points that can be collapsed to a single point -- find missing intersections to resolve differences caused by different algorithms - -Consider segments containing tiny or small intervals. Consider coincident segments -because coincidence finds intersections through distance measurement that non-coincident -intersection tests cannot. - */ +#include "SkTSort.h" #define F (false) // discard the edge #define T (true) // keep the edge @@ -42,125 +33,147 @@ static const bool gActiveEdge[kXOR_PathOp + 1][2][2][2][2] = { #undef F #undef T -SkOpAngle* SkOpSegment::activeAngle(SkOpSpanBase* start, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr, bool* done, bool* sortable) { - if (SkOpAngle* result = activeAngleInner(start, startPtr, endPtr, done, sortable)) { +enum { + kOutsideTrackedTCount = 16, // FIXME: determine what this should be + kMissingSpanCount = 4, // FIXME: determine what this should be +}; + +const SkOpAngle* SkOpSegment::activeAngle(int index, int* start, int* end, bool* done, + bool* sortable) const { + if (const SkOpAngle* result = activeAngleInner(index, start, end, done, sortable)) { return result; } - if (SkOpAngle* result = activeAngleOther(start, startPtr, endPtr, done, sortable)) { - return result; + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 + && (precisely_negative(referenceT - fTs[lesser].fT) || fTs[lesser].fTiny)) { + if (const SkOpAngle* result = activeAngleOther(lesser, start, end, done, sortable)) { + return result; + } } + do { + if (const SkOpAngle* result = activeAngleOther(index, start, end, done, sortable)) { + return result; + } + if (++index == fTs.count()) { + break; + } + if (fTs[index - 1].fTiny) { + referenceT = fTs[index].fT; + continue; + } + } while (precisely_negative(fTs[index].fT - referenceT)); return NULL; } -SkOpAngle* SkOpSegment::activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr, bool* done, bool* sortable) { - SkOpSpan* upSpan = start->upCastable(); - if (upSpan) { - if (upSpan->windValue() || upSpan->oppValue()) { - SkOpSpanBase* next = upSpan->next(); - if (!*endPtr) { - *startPtr = start; - *endPtr = next; +const SkOpAngle* SkOpSegment::activeAngleInner(int index, int* start, int* end, bool* done, + bool* sortable) const { + int next = nextExactSpan(index, 1); + if (next > 0) { + const SkOpSpan& upSpan = fTs[index]; + if (upSpan.fWindValue || upSpan.fOppValue) { + if (*end < 0) { + *start = index; + *end = next; } - if (!upSpan->done()) { - if (upSpan->windSum() != SK_MinS32) { - return spanToAngle(start, next); + if (!upSpan.fDone) { + if (upSpan.fWindSum != SK_MinS32) { + return spanToAngle(index, next); } *done = false; } } else { - SkASSERT(upSpan->done()); + SkASSERT(upSpan.fDone); } } - SkOpSpan* downSpan = start->prev(); + int prev = nextExactSpan(index, -1); // edge leading into junction - if (downSpan) { - if (downSpan->windValue() || downSpan->oppValue()) { - if (!*endPtr) { - *startPtr = start; - *endPtr = downSpan; - } - if (!downSpan->done()) { - if (downSpan->windSum() != SK_MinS32) { - return spanToAngle(start, downSpan); + if (prev >= 0) { + const SkOpSpan& downSpan = fTs[prev]; + if (downSpan.fWindValue || downSpan.fOppValue) { + if (*end < 0) { + *start = index; + *end = prev; + } + if (!downSpan.fDone) { + if (downSpan.fWindSum != SK_MinS32) { + return spanToAngle(index, prev); } *done = false; } } else { - SkASSERT(downSpan->done()); + SkASSERT(downSpan.fDone); } } return NULL; } -SkOpAngle* SkOpSegment::activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr, bool* done, bool* sortable) { - SkOpPtT* oPtT = start->ptT()->next(); - SkOpSegment* other = oPtT->segment(); - SkOpSpanBase* oSpan = oPtT->span(); - return other->activeAngleInner(oSpan, startPtr, endPtr, done, sortable); +const SkOpAngle* SkOpSegment::activeAngleOther(int index, int* start, int* end, bool* done, + bool* sortable) const { + const SkOpSpan* span = &fTs[index]; + SkOpSegment* other = span->fOther; + int oIndex = span->fOtherIndex; + return other->activeAngleInner(oIndex, start, end, done, sortable); } -SkPoint SkOpSegment::activeLeftTop(SkOpSpanBase** firstSpan) { +SkPoint SkOpSegment::activeLeftTop(int* firstT) const { SkASSERT(!done()); SkPoint topPt = {SK_ScalarMax, SK_ScalarMax}; + int count = fTs.count(); // see if either end is not done since we want smaller Y of the pair bool lastDone = true; double lastT = -1; - SkOpSpanBase* span = &fHead; - do { - if (lastDone && (span->final() || span->upCast()->done())) { + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (span.fDone && lastDone) { + goto next; + } + if (approximately_negative(span.fT - lastT)) { goto next; } { - const SkPoint& xy = span->pt(); + const SkPoint& xy = xyAtT(&span); if (topPt.fY > xy.fY || (topPt.fY == xy.fY && topPt.fX > xy.fX)) { topPt = xy; - if (firstSpan) { - *firstSpan = span; + if (firstT) { + *firstT = index; } } if (fVerb != SkPath::kLine_Verb && !lastDone) { - SkPoint curveTop = (*CurveTop[SkPathOpsVerbToPoints(fVerb)])(fPts, lastT, - span->t()); + SkPoint curveTop = (*CurveTop[SkPathOpsVerbToPoints(fVerb)])(fPts, lastT, span.fT); if (topPt.fY > curveTop.fY || (topPt.fY == curveTop.fY && topPt.fX > curveTop.fX)) { topPt = curveTop; - if (firstSpan) { - *firstSpan = span; + if (firstT) { + *firstT = index; } } } - lastT = span->t(); + lastT = span.fT; } next: - if (span->final()) { - break; - } - lastDone = span->upCast()->done(); - } while ((span = span->upCast()->next())); + lastDone = span.fDone; + } return topPt; } -bool SkOpSegment::activeOp(SkOpSpanBase* start, SkOpSpanBase* end, int xorMiMask, int xorSuMask, - SkPathOp op) { - int sumMiWinding = this->updateWinding(end, start); - int sumSuWinding = this->updateOppWinding(end, start); +bool SkOpSegment::activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op) { + int sumMiWinding = updateWinding(endIndex, index); + int sumSuWinding = updateOppWinding(endIndex, index); #if DEBUG_LIMIT_WIND_SUM SkASSERT(abs(sumMiWinding) <= DEBUG_LIMIT_WIND_SUM); SkASSERT(abs(sumSuWinding) <= DEBUG_LIMIT_WIND_SUM); #endif - if (this->operand()) { + if (fOperand) { SkTSwap<int>(sumMiWinding, sumSuWinding); } - return this->activeOp(xorMiMask, xorSuMask, start, end, op, &sumMiWinding, &sumSuWinding); + return activeOp(xorMiMask, xorSuMask, index, endIndex, op, &sumMiWinding, &sumSuWinding); } -bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, SkOpSpanBase* end, - SkPathOp op, int* sumMiWinding, int* sumSuWinding) { +bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op, + int* sumMiWinding, int* sumSuWinding) { int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; - this->setUpWindings(start, end, sumMiWinding, sumSuWinding, + setUpWindings(index, endIndex, sumMiWinding, sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); bool miFrom; bool miTo; @@ -180,31 +193,178 @@ bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, Sk bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo]; #if DEBUG_ACTIVE_OP SkDebugf("%s id=%d t=%1.9g tEnd=%1.9g op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", - __FUNCTION__, debugID(), start->t(), end->t(), + __FUNCTION__, debugID(), span(index).fT, span(endIndex).fT, SkPathOpsDebug::kPathOpStr[op], miFrom, miTo, suFrom, suTo, result); #endif return result; } -bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end) { - int sumWinding = updateWinding(end, start); - return activeWinding(start, end, &sumWinding); +bool SkOpSegment::activeWinding(int index, int endIndex) { + int sumWinding = updateWinding(endIndex, index); + return activeWinding(index, endIndex, &sumWinding); } -bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding) { +bool SkOpSegment::activeWinding(int index, int endIndex, int* sumWinding) { int maxWinding; - setUpWinding(start, end, &maxWinding, sumWinding); + setUpWinding(index, endIndex, &maxWinding, sumWinding); bool from = maxWinding != 0; bool to = *sumWinding != 0; bool result = gUnaryActiveEdge[from][to]; return result; } -void SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, - SkPathWriter* path, bool active) const { +void SkOpSegment::addCancelOutsides(const SkPoint& startPt, const SkPoint& endPt, + SkOpSegment* other) { + int tIndex = -1; + int tCount = fTs.count(); + int oIndex = -1; + int oCount = other->fTs.count(); + do { + ++tIndex; + } while (startPt != fTs[tIndex].fPt && tIndex < tCount); + int tIndexStart = tIndex; + do { + ++oIndex; + } while (endPt != other->fTs[oIndex].fPt && oIndex < oCount); + int oIndexStart = oIndex; + const SkPoint* nextPt; + do { + nextPt = &fTs[++tIndex].fPt; + SkASSERT(fTs[tIndex].fT < 1 || startPt != *nextPt); + } while (startPt == *nextPt); + double nextT = fTs[tIndex].fT; + const SkPoint* oNextPt; + do { + oNextPt = &other->fTs[++oIndex].fPt; + SkASSERT(other->fTs[oIndex].fT < 1 || endPt != *oNextPt); + } while (endPt == *oNextPt); + double oNextT = other->fTs[oIndex].fT; + // at this point, spans before and after are at: + // fTs[tIndexStart - 1], fTs[tIndexStart], fTs[tIndex] + // if tIndexStart == 0, no prior span + // if nextT == 1, no following span + + // advance the span with zero winding + // if the following span exists (not past the end, non-zero winding) + // connect the two edges + if (!fTs[tIndexStart].fWindValue) { + if (tIndexStart > 0 && fTs[tIndexStart - 1].fWindValue) { +#if DEBUG_CONCIDENT + SkDebugf("%s 1 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n", + __FUNCTION__, fID, other->fID, tIndexStart - 1, + fTs[tIndexStart].fT, xyAtT(tIndexStart).fX, + xyAtT(tIndexStart).fY); +#endif + SkPoint copy = fTs[tIndexStart].fPt; // add t pair may move the point array + addTPair(fTs[tIndexStart].fT, other, other->fTs[oIndex].fT, false, copy); + } + if (nextT < 1 && fTs[tIndex].fWindValue) { +#if DEBUG_CONCIDENT + SkDebugf("%s 2 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n", + __FUNCTION__, fID, other->fID, tIndex, + fTs[tIndex].fT, xyAtT(tIndex).fX, + xyAtT(tIndex).fY); +#endif + SkPoint copy = fTs[tIndex].fPt; // add t pair may move the point array + addTPair(fTs[tIndex].fT, other, other->fTs[oIndexStart].fT, false, copy); + } + } else { + SkASSERT(!other->fTs[oIndexStart].fWindValue); + if (oIndexStart > 0 && other->fTs[oIndexStart - 1].fWindValue) { +#if DEBUG_CONCIDENT + SkDebugf("%s 3 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n", + __FUNCTION__, fID, other->fID, oIndexStart - 1, + other->fTs[oIndexStart].fT, other->xyAtT(oIndexStart).fX, + other->xyAtT(oIndexStart).fY); + other->debugAddTPair(other->fTs[oIndexStart].fT, *this, fTs[tIndex].fT); +#endif + } + if (oNextT < 1 && other->fTs[oIndex].fWindValue) { +#if DEBUG_CONCIDENT + SkDebugf("%s 4 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n", + __FUNCTION__, fID, other->fID, oIndex, + other->fTs[oIndex].fT, other->xyAtT(oIndex).fX, + other->xyAtT(oIndex).fY); + other->debugAddTPair(other->fTs[oIndex].fT, *this, fTs[tIndexStart].fT); +#endif + } + } +} + +void SkOpSegment::addCoinOutsides(const SkPoint& startPt, const SkPoint& endPt, + SkOpSegment* other) { + // walk this to startPt + // walk other to startPt + // if either is > 0, add a pointer to the other, copying adjacent winding + int tIndex = -1; + int oIndex = -1; + do { + ++tIndex; + } while (startPt != fTs[tIndex].fPt); + int ttIndex = tIndex; + bool checkOtherTMatch = false; + do { + const SkOpSpan& span = fTs[ttIndex]; + if (startPt != span.fPt) { + break; + } + if (span.fOther == other && span.fPt == startPt) { + checkOtherTMatch = true; + break; + } + } while (++ttIndex < count()); + do { + ++oIndex; + } while (startPt != other->fTs[oIndex].fPt); + bool skipAdd = false; + if (checkOtherTMatch) { + int ooIndex = oIndex; + do { + const SkOpSpan& oSpan = other->fTs[ooIndex]; + if (startPt != oSpan.fPt) { + break; + } + if (oSpan.fT == fTs[ttIndex].fOtherT) { + skipAdd = true; + break; + } + } while (++ooIndex < other->count()); + } + if ((tIndex > 0 || oIndex > 0 || fOperand != other->fOperand) && !skipAdd) { + addTPair(fTs[tIndex].fT, other, other->fTs[oIndex].fT, false, startPt); + } + SkPoint nextPt = startPt; + do { + const SkPoint* workPt; + do { + workPt = &fTs[++tIndex].fPt; + } while (nextPt == *workPt); + const SkPoint* oWorkPt; + do { + oWorkPt = &other->fTs[++oIndex].fPt; + } while (nextPt == *oWorkPt); + nextPt = *workPt; + double tStart = fTs[tIndex].fT; + double oStart = other->fTs[oIndex].fT; + if (tStart == 1 && oStart == 1 && fOperand == other->fOperand) { + break; + } + if (*workPt == *oWorkPt) { + addTPair(tStart, other, oStart, false, nextPt); + } + } while (endPt != nextPt); +} + +void SkOpSegment::addCubic(const SkPoint pts[4], bool operand, bool evenOdd) { + init(pts, SkPath::kCubic_Verb, operand, evenOdd); + fBounds.setCubicBounds(pts); +} + +void SkOpSegment::addCurveTo(int start, int end, SkPathWriter* path, bool active) const { SkPoint edge[4]; const SkPoint* ePtr; - if ((start == &fHead && end == &fTail) || (start == &fTail && end == &fHead)) { + int lastT = fTs.count() - 1; + if (lastT < 0 || (start == 0 && end == lastT) || (start == lastT && end == 0)) { ePtr = fPts; } else { // OPTIMIZE? if not active, skip remainder and return xyAtT(end) @@ -212,7 +372,7 @@ void SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, ePtr = edge; } if (active) { - bool reverse = ePtr == fPts && start != &fHead; + bool reverse = ePtr == fPts && start != 0; if (reverse) { path->deferredMoveLine(ePtr[SkPathOpsVerbToPoints(fVerb)]); switch (fVerb) { @@ -228,6 +388,7 @@ void SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, default: SkASSERT(0); } + // return ePtr[0]; } else { path->deferredMoveLine(ePtr[0]); switch (fVerb) { @@ -245,350 +406,1473 @@ void SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, } } } + // return ePtr[SkPathOpsVerbToPoints(fVerb)]; +} + +void SkOpSegment::addEndSpan(int endIndex) { + SkASSERT(span(endIndex).fT == 1 || (span(endIndex).fTiny +// && approximately_greater_than_one(span(endIndex).fT) + )); + int spanCount = fTs.count(); + int startIndex = endIndex - 1; + while (fTs[startIndex].fT == 1 || fTs[startIndex].fTiny) { + --startIndex; + SkASSERT(startIndex > 0); + --endIndex; + } + SkOpAngle& angle = fAngles.push_back(); + angle.set(this, spanCount - 1, startIndex); +#if DEBUG_ANGLE + debugCheckPointsEqualish(endIndex, spanCount); +#endif + setFromAngle(endIndex, &angle); } -SkOpPtT* SkOpSegment::addMissing(double t, SkOpSegment* opp, SkChunkAlloc* allocator) { - SkOpSpanBase* existing = NULL; - SkOpSpanBase* test = &fHead; - double testT; +void SkOpSegment::setFromAngle(int endIndex, SkOpAngle* angle) { + int spanCount = fTs.count(); do { - if ((testT = test->ptT()->fT) >= t) { - if (testT == t) { - existing = test; - } - break; - } - } while ((test = test->upCast()->next())); - SkOpPtT* result; - if (existing && existing->contains(opp)) { - result = existing->ptT(); - } else { - result = this->addT(t, SkOpSegment::kNoAlias, allocator); + fTs[endIndex].fFromAngle = angle; + } while (++endIndex < spanCount); +} + +void SkOpSegment::addLine(const SkPoint pts[2], bool operand, bool evenOdd) { + init(pts, SkPath::kLine_Verb, operand, evenOdd); + fBounds.set(pts, 2); +} + +// add 2 to edge or out of range values to get T extremes +void SkOpSegment::addOtherT(int index, double otherT, int otherIndex) { + SkOpSpan& span = fTs[index]; + if (precisely_zero(otherT)) { + otherT = 0; + } else if (precisely_equal(otherT, 1)) { + otherT = 1; } - SkASSERT(result); - return result; + span.fOtherT = otherT; + span.fOtherIndex = otherIndex; } -SkOpAngle* SkOpSegment::addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** anglePtr, - SkChunkAlloc* allocator) { - SkOpSpan* startSpan = fTail.prev(); - SkASSERT(startSpan); - SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); - *anglePtr = angle; - angle->set(&fTail, startSpan); - fTail.setFromAngle(angle); - SkOpSegment* other = NULL; // these initializations silence a release build warning - SkOpSpan* oStartSpan = NULL; - SkOpSpanBase* oEndSpan = NULL; - SkOpPtT* ptT = fTail.ptT(), * startPtT = ptT; - while ((ptT = ptT->next()) != startPtT) { - other = ptT->segment(); - oStartSpan = ptT->span()->upCastable(); - if (oStartSpan && oStartSpan->windValue()) { - oEndSpan = oStartSpan->next(); +void SkOpSegment::addQuad(const SkPoint pts[3], bool operand, bool evenOdd) { + init(pts, SkPath::kQuad_Verb, operand, evenOdd); + fBounds.setQuadBounds(pts); +} + +SkOpAngle* SkOpSegment::addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** anglePtr) { + int spanIndex = count() - 1; + int startIndex = nextExactSpan(spanIndex, -1); + SkASSERT(startIndex >= 0); + SkOpAngle& angle = fAngles.push_back(); + *anglePtr = ∠ + angle.set(this, spanIndex, startIndex); + setFromAngle(spanIndex, &angle); + SkOpSegment* other; + int oStartIndex, oEndIndex; + do { + const SkOpSpan& span = fTs[spanIndex]; + SkASSERT(span.fT > 0); + other = span.fOther; + oStartIndex = span.fOtherIndex; + oEndIndex = other->nextExactSpan(oStartIndex, 1); + if (oEndIndex > 0 && other->span(oStartIndex).fWindValue) { break; } - oEndSpan = ptT->span(); - oStartSpan = oEndSpan->prev(); - if (oStartSpan && oStartSpan->windValue()) { + oEndIndex = oStartIndex; + oStartIndex = other->nextExactSpan(oEndIndex, -1); + --spanIndex; + } while (oStartIndex < 0 || !other->span(oStartIndex).fWindSum); + SkOpAngle& oAngle = other->fAngles.push_back(); + oAngle.set(other, oStartIndex, oEndIndex); + other->setToAngle(oEndIndex, &oAngle); + *otherPtr = other; + return &oAngle; +} + +SkOpAngle* SkOpSegment::addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** anglePtr) { + int endIndex = nextExactSpan(0, 1); + SkASSERT(endIndex > 0); + SkOpAngle& angle = fAngles.push_back(); + *anglePtr = ∠ + angle.set(this, 0, endIndex); + setToAngle(endIndex, &angle); + int spanIndex = 0; + SkOpSegment* other; + int oStartIndex, oEndIndex; + do { + const SkOpSpan& span = fTs[spanIndex]; + SkASSERT(span.fT < 1); + other = span.fOther; + oEndIndex = span.fOtherIndex; + oStartIndex = other->nextExactSpan(oEndIndex, -1); + if (oStartIndex >= 0 && other->span(oStartIndex).fWindValue) { break; } - } - SkOpAngle* oAngle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); - oAngle->set(oStartSpan, oEndSpan); - oStartSpan->setToAngle(oAngle); + oStartIndex = oEndIndex; + oEndIndex = other->nextExactSpan(oStartIndex, 1); + ++spanIndex; + } while (oEndIndex < 0 || !other->span(oStartIndex).fWindValue); + SkOpAngle& oAngle = other->fAngles.push_back(); + oAngle.set(other, oEndIndex, oStartIndex); + other->setFromAngle(oEndIndex, &oAngle); *otherPtr = other; - return oAngle; + return &oAngle; } -SkOpAngle* SkOpSegment::addSingletonAngles(int step, SkChunkAlloc* allocator) { +SkOpAngle* SkOpSegment::addSingletonAngles(int step) { SkOpSegment* other; SkOpAngle* angle, * otherAngle; if (step > 0) { - otherAngle = addSingletonAngleUp(&other, &angle, allocator); + otherAngle = addSingletonAngleUp(&other, &angle); } else { - otherAngle = addSingletonAngleDown(&other, &angle, allocator); + otherAngle = addSingletonAngleDown(&other, &angle); } angle->insert(otherAngle); return angle; } -SkOpAngle* SkOpSegment::addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** anglePtr, - SkChunkAlloc* allocator) { - SkOpSpanBase* endSpan = fHead.next(); - SkASSERT(endSpan); - SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); - *anglePtr = angle; - angle->set(&fHead, endSpan); - fHead.setToAngle(angle); - SkOpSegment* other = NULL; // these initializations silence a release build warning - SkOpSpan* oStartSpan = NULL; - SkOpSpanBase* oEndSpan = NULL; - SkOpPtT* ptT = fHead.ptT(), * startPtT = ptT; - while ((ptT = ptT->next()) != startPtT) { - other = ptT->segment(); - oEndSpan = ptT->span(); - oStartSpan = oEndSpan->prev(); - if (oStartSpan && oStartSpan->windValue()) { +void SkOpSegment::addStartSpan(int endIndex) { + int index = 0; + SkOpAngle& angle = fAngles.push_back(); + angle.set(this, index, endIndex); +#if DEBUG_ANGLE + debugCheckPointsEqualish(index, endIndex); +#endif + setToAngle(endIndex, &angle); +} + +void SkOpSegment::setToAngle(int endIndex, SkOpAngle* angle) { + int index = 0; + do { + fTs[index].fToAngle = angle; + } while (++index < endIndex); +} + + // Defer all coincident edge processing until + // after normal intersections have been computed + +// no need to be tricky; insert in normal T order +// resolve overlapping ts when considering coincidence later + + // add non-coincident intersection. Resulting edges are sorted in T. +int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) { + SkASSERT(this != other || fVerb == SkPath::kCubic_Verb); + #if 0 // this needs an even rougher association to be useful + SkASSERT(SkDPoint::RoughlyEqual(ptAtT(newT), pt)); + #endif + const SkPoint& firstPt = fPts[0]; + const SkPoint& lastPt = fPts[SkPathOpsVerbToPoints(fVerb)]; + SkASSERT(newT == 0 || !precisely_zero(newT)); + SkASSERT(newT == 1 || !precisely_equal(newT, 1)); + // FIXME: in the pathological case where there is a ton of intercepts, + // binary search? + int insertedAt = -1; + int tCount = fTs.count(); + for (int index = 0; index < tCount; ++index) { + // OPTIMIZATION: if there are three or more identical Ts, then + // the fourth and following could be further insertion-sorted so + // that all the edges are clockwise or counterclockwise. + // This could later limit segment tests to the two adjacent + // neighbors, although it doesn't help with determining which + // circular direction to go in. + const SkOpSpan& span = fTs[index]; + if (newT < span.fT) { + insertedAt = index; break; } - oStartSpan = oEndSpan->upCastable(); - if (oStartSpan && oStartSpan->windValue()) { - oEndSpan = oStartSpan->next(); - break; + if (newT == span.fT) { + if (pt == span.fPt) { + insertedAt = index; + break; + } + if ((pt == firstPt && newT == 0) || (span.fPt == lastPt && newT == 1)) { + insertedAt = index; + break; + } } } - SkOpAngle* oAngle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); - oAngle->set(oEndSpan, oStartSpan); - oEndSpan->setFromAngle(oAngle); - *otherPtr = other; - return oAngle; + SkOpSpan* span; + if (insertedAt >= 0) { + span = fTs.insert(insertedAt); + } else { + insertedAt = tCount; + span = fTs.append(); + } + span->fT = newT; + span->fOtherT = -1; + span->fOther = other; + span->fPt = pt; +#if 0 + // cubics, for instance, may not be exact enough to satisfy this check (e.g., cubicOp69d) + SkASSERT(approximately_equal(xyAtT(newT).fX, pt.fX) + && approximately_equal(xyAtT(newT).fY, pt.fY)); +#endif + span->fFromAngle = NULL; + span->fToAngle = NULL; + span->fWindSum = SK_MinS32; + span->fOppSum = SK_MinS32; + span->fWindValue = 1; + span->fOppValue = 0; + span->fChased = false; + span->fCoincident = false; + span->fLoop = false; + span->fNear = false; + span->fMultiple = false; + span->fSmall = false; + span->fTiny = false; + if ((span->fDone = newT == 1)) { + ++fDoneSpans; + } + setSpanFlags(pt, newT, span); + return insertedAt; } -SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* allocator) { - debugValidate(); - SkPoint pt = this->ptAtT(t); - SkOpSpanBase* span = &fHead; +void SkOpSegment::setSpanFlags(const SkPoint& pt, double newT, SkOpSpan* span) { + int less = -1; +// FIXME: note that this relies on spans being a continguous array +// find range of spans with nearly the same point as this one + // FIXME: SkDPoint::ApproximatelyEqual is better but breaks tests at the moment + while (&span[less + 1] - fTs.begin() > 0 && AlmostEqualUlps(span[less].fPt, pt)) { + if (fVerb == SkPath::kCubic_Verb) { + double tInterval = newT - span[less].fT; + double tMid = newT - tInterval / 2; + SkDPoint midPt = dcubic_xy_at_t(fPts, tMid); + if (!midPt.approximatelyEqual(xyAtT(span))) { + break; + } + } + --less; + } + int more = 1; + // FIXME: SkDPoint::ApproximatelyEqual is better but breaks tests at the moment + while (fTs.end() - &span[more - 1] > 1 && AlmostEqualUlps(span[more].fPt, pt)) { + if (fVerb == SkPath::kCubic_Verb) { + double tEndInterval = span[more].fT - newT; + double tMid = newT - tEndInterval / 2; + SkDPoint midEndPt = dcubic_xy_at_t(fPts, tMid); + if (!midEndPt.approximatelyEqual(xyAtT(span))) { + break; + } + } + ++more; + } + ++less; + --more; + while (more - 1 > less && span[more].fPt == span[more - 1].fPt + && span[more].fT == span[more - 1].fT) { + --more; + } + if (less == more) { + return; + } + if (precisely_negative(span[more].fT - span[less].fT)) { + return; + } +// if the total range of t values is big enough, mark all tiny + bool tiny = span[less].fPt == span[more].fPt; + int index = less; do { - SkOpPtT* result = span->ptT(); - if (t == result->fT) { - return result; + fSmall = span[index].fSmall = true; + fTiny |= span[index].fTiny = tiny; + if (!span[index].fDone) { + span[index].fDone = true; + ++fDoneSpans; + } + } while (++index < more); + return; +} + +void SkOpSegment::resetSpanFlags() { + fSmall = fTiny = false; + fDoneSpans = 0; + int start = 0; + int last = this->count() - 1; + do { + SkOpSpan* startSpan = &this->fTs[start]; + double startT = startSpan->fT; + startSpan->fSmall = startSpan->fTiny = false; // sets range initial + bool terminus = startT == 1; + if ((startSpan->fDone = !startSpan->fWindValue | terminus)) { + ++fDoneSpans; } - if (this->match(result, this, t, pt)) { - // see if any existing alias matches segment, pt, and t - SkOpPtT* loop = result->next(); - bool duplicatePt = false; - while (loop != result) { - bool ptMatch = loop->fPt == pt; - if (loop->segment() == this && loop->fT == t && ptMatch) { - return result; + ++start; // range initial + 1 + if (terminus) { + continue; + } + const SkPoint& pt = startSpan->fPt; + int end = start; // range initial + 1 + while (end <= last) { + const SkOpSpan& endSpan = this->span(end); + if (!AlmostEqualUlps(endSpan.fPt, pt)) { + break; + } + if (fVerb == SkPath::kCubic_Verb) { + double tMid = (startSpan->fT + endSpan.fT) / 2; + SkDPoint midEndPt = dcubic_xy_at_t(fPts, tMid); + if (!midEndPt.approximatelyEqual(xyAtT(startSpan))) { + break; } - duplicatePt |= ptMatch; - loop = loop->next(); - } - if (kNoAlias == allowAlias) { - return result; - } - SkOpPtT* alias = SkOpTAllocator<SkOpPtT>::Allocate(allocator); - alias->init(result->span(), t, pt, duplicatePt); - result->insert(alias); - result->span()->unaligned(); - this->debugValidate(); -#if DEBUG_ADD_T - SkDebugf("%s alias t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, - alias->segment()->debugID(), alias->span()->debugID()); -#endif - return alias; - } - if (t < result->fT) { - SkOpSpan* prev = result->span()->prev(); - SkOpSpan* span = insert(prev, allocator); - span->init(this, prev, t, pt); - this->debugValidate(); -#if DEBUG_ADD_T - SkDebugf("%s insert t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, - span->segment()->debugID(), span->debugID()); -#endif - return span->ptT(); + } + ++end; } - SkASSERT(span != &fTail); - } while ((span = span->upCast()->next())); - SkASSERT(0); - return NULL; + if (start == end) { // end == range final + 1 + continue; + } + while (--end >= start) { // end == range final + const SkOpSpan& endSpan = this->span(end); + const SkOpSpan& priorSpan = this->span(end - 1); + if (endSpan.fPt != priorSpan.fPt || endSpan.fT != priorSpan.fT) { + break; // end == range final + 1 + } + } + if (end < start) { // end == range final + 1 + continue; + } + int index = start - 1; // index == range initial + start = end; // start = range final + 1 + const SkOpSpan& nextSpan = this->span(end); + if (precisely_negative(nextSpan.fT - startSpan->fT)) { + while (++index < end) { + startSpan = &this->fTs[index]; + startSpan->fSmall = startSpan->fTiny = false; // sets range initial + 1 + if ((startSpan->fDone = !startSpan->fWindValue)) { + ++fDoneSpans; + } + } + continue; + } + if (!startSpan->fWindValue) { + --fDoneSpans; // added back below + } + bool tiny = nextSpan.fPt == startSpan->fPt; + do { + fSmall = startSpan->fSmall = true; // sets range initial + fTiny |= startSpan->fTiny = tiny; + startSpan->fDone = true; + ++fDoneSpans; + startSpan = &this->fTs[++index]; + } while (index < end); // loop through tiny small range end (last) + } while (start <= last); } -// choose a solitary t and pt value; remove aliases; align the opposite ends -void SkOpSegment::align() { - debugValidate(); - SkOpSpanBase* span = &fHead; - if (!span->aligned()) { - span->alignEnd(0, fPts[0]); +// set spans from start to end to decrement by one +// note this walks other backwards +// FIXME: there's probably an edge case that can be constructed where +// two span in one segment are separated by float epsilon on one span but +// not the other, if one segment is very small. For this +// case the counts asserted below may or may not be enough to separate the +// spans. Even if the counts work out, what if the spans aren't correctly +// sorted? It feels better in such a case to match the span's other span +// pointer since both coincident segments must contain the same spans. +// FIXME? It seems that decrementing by one will fail for complex paths that +// have three or more coincident edges. Shouldn't this subtract the difference +// between the winding values? +/* |--> |--> +this 0>>>>1>>>>2>>>>3>>>4 0>>>>1>>>>2>>>>3>>>4 0>>>>1>>>>2>>>>3>>>4 +other 2<<<<1<<<<0 2<<<<1<<<<0 2<<<<1<<<<0 + ^ ^ <--| <--| + startPt endPt test/oTest first pos test/oTest final pos +*/ +void SkOpSegment::addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other) { + bool binary = fOperand != other->fOperand; + int index = 0; + while (startPt != fTs[index].fPt) { + SkASSERT(index < fTs.count()); + ++index; + } + while (index > 0 && precisely_equal(fTs[index].fT, fTs[index - 1].fT)) { + --index; + } + bool oFoundEnd = false; + int oIndex = other->fTs.count(); + while (startPt != other->fTs[--oIndex].fPt) { // look for startPt match + SkASSERT(oIndex > 0); + } + double oStartT = other->fTs[oIndex].fT; + // look for first point beyond match + while (startPt == other->fTs[--oIndex].fPt || precisely_equal(oStartT, other->fTs[oIndex].fT)) { + if (!oIndex) { + return; // tiny spans may move in the wrong direction + } + } + SkOpSpan* test = &fTs[index]; + SkOpSpan* oTest = &other->fTs[oIndex]; + SkSTArray<kOutsideTrackedTCount, SkPoint, true> outsidePts; + SkSTArray<kOutsideTrackedTCount, SkPoint, true> oOutsidePts; + bool decrement, track, bigger; + int originalWindValue; + const SkPoint* testPt; + const SkPoint* oTestPt; + do { + SkASSERT(test->fT < 1); + SkASSERT(oTest->fT < 1); + decrement = test->fWindValue && oTest->fWindValue; + track = test->fWindValue || oTest->fWindValue; + bigger = test->fWindValue >= oTest->fWindValue; + testPt = &test->fPt; + double testT = test->fT; + oTestPt = &oTest->fPt; + double oTestT = oTest->fT; + do { + if (decrement) { + if (binary && bigger) { + test->fOppValue--; + } else { + decrementSpan(test); + } + } else if (track) { + TrackOutsidePair(&outsidePts, *testPt, *oTestPt); + } + SkASSERT(index < fTs.count() - 1); + test = &fTs[++index]; + } while (*testPt == test->fPt || precisely_equal(testT, test->fT)); + originalWindValue = oTest->fWindValue; + do { + SkASSERT(oTest->fT < 1); + SkASSERT(originalWindValue == oTest->fWindValue); + if (decrement) { + if (binary && !bigger) { + oTest->fOppValue--; + } else { + other->decrementSpan(oTest); + } + } else if (track) { + TrackOutsidePair(&oOutsidePts, *oTestPt, *testPt); + } + if (!oIndex) { + break; + } + oFoundEnd |= endPt == oTest->fPt; + oTest = &other->fTs[--oIndex]; + } while (*oTestPt == oTest->fPt || precisely_equal(oTestT, oTest->fT)); + } while (endPt != test->fPt && test->fT < 1); + // FIXME: determine if canceled edges need outside ts added + if (!oFoundEnd) { + for (int oIdx2 = oIndex; oIdx2 >= 0; --oIdx2) { + SkOpSpan* oTst2 = &other->fTs[oIdx2]; + if (originalWindValue != oTst2->fWindValue) { + goto skipAdvanceOtherCancel; + } + if (!oTst2->fWindValue) { + goto skipAdvanceOtherCancel; + } + if (endPt == other->fTs[oIdx2].fPt) { + break; + } + } + oFoundEnd = endPt == oTest->fPt; + do { + SkASSERT(originalWindValue == oTest->fWindValue); + if (decrement) { + if (binary && !bigger) { + oTest->fOppValue--; + } else { + other->decrementSpan(oTest); + } + } else if (track) { + TrackOutsidePair(&oOutsidePts, *oTestPt, *testPt); + } + if (!oIndex) { + break; + } + oTest = &other->fTs[--oIndex]; + oFoundEnd |= endPt == oTest->fPt; + } while (!oFoundEnd || endPt == oTest->fPt); + } +skipAdvanceOtherCancel: + int outCount = outsidePts.count(); + if (!done() && outCount) { + addCancelOutsides(outsidePts[0], outsidePts[1], other); + if (outCount > 2) { + addCancelOutsides(outsidePts[outCount - 2], outsidePts[outCount - 1], other); + } + } + if (!other->done() && oOutsidePts.count()) { + other->addCancelOutsides(oOutsidePts[0], oOutsidePts[1], this); + } + setCoincidentRange(startPt, endPt, other); + other->setCoincidentRange(startPt, endPt, this); +} + +int SkOpSegment::addSelfT(const SkPoint& pt, double newT) { + // if the tail nearly intersects itself but not quite, the caller records this separately + int result = addT(this, pt, newT); + SkOpSpan* span = &fTs[result]; + fLoop = span->fLoop = true; + return result; +} + +// find the starting or ending span with an existing loop of angles +// FIXME? replicate for all identical starting/ending spans? +// OPTIMIZE? remove the spans pointing to windValue==0 here or earlier? +// FIXME? assert that only one other span has a valid windValue or oppValue +void SkOpSegment::addSimpleAngle(int index) { + SkOpSpan* span = &fTs[index]; + int idx; + int start, end; + if (span->fT == 0) { + idx = 0; + span = &fTs[0]; + do { + if (span->fToAngle) { + SkASSERT(span->fToAngle->loopCount() == 2); + SkASSERT(!span->fFromAngle); + span->fFromAngle = span->fToAngle->next(); + return; + } + span = &fTs[++idx]; + } while (span->fT == 0); + SkASSERT(!fTs[0].fTiny && fTs[idx].fT > 0); + addStartSpan(idx); + start = 0; + end = idx; + } else { + idx = count() - 1; + span = &fTs[idx]; + do { + if (span->fFromAngle) { + SkASSERT(span->fFromAngle->loopCount() == 2); + SkASSERT(!span->fToAngle); + span->fToAngle = span->fFromAngle->next(); + return; + } + span = &fTs[--idx]; + } while (span->fT == 1); + SkASSERT(!fTs[idx].fTiny && fTs[idx].fT < 1); + addEndSpan(++idx); + start = idx; + end = count(); } - while ((span = span->upCast()->next())) { - if (span == &fTail) { + SkOpSegment* other; + SkOpSpan* oSpan; + index = start; + do { + span = &fTs[index]; + other = span->fOther; + int oFrom = span->fOtherIndex; + oSpan = &other->fTs[oFrom]; + if (oSpan->fT < 1 && oSpan->fWindValue) { break; } - span->align(); + if (oSpan->fT == 0) { + continue; + } + oFrom = other->nextExactSpan(oFrom, -1); + SkOpSpan* oFromSpan = &other->fTs[oFrom]; + SkASSERT(oFromSpan->fT < 1); + if (oFromSpan->fWindValue) { + break; + } + } while (++index < end); + SkOpAngle* angle, * oAngle; + if (span->fT == 0) { + SkASSERT(span->fOtherIndex - 1 >= 0); + SkASSERT(span->fOtherT == 1); + SkDEBUGCODE(int oPriorIndex = other->nextExactSpan(span->fOtherIndex, -1)); + SkDEBUGCODE(const SkOpSpan& oPrior = other->span(oPriorIndex)); + SkASSERT(!oPrior.fTiny && oPrior.fT < 1); + other->addEndSpan(span->fOtherIndex); + angle = span->fToAngle; + oAngle = oSpan->fFromAngle; + } else { + SkASSERT(span->fOtherIndex + 1 < other->count()); + SkASSERT(span->fOtherT == 0); + SkASSERT(!oSpan->fTiny && (other->fTs[span->fOtherIndex + 1].fT > 0 + || (other->fTs[span->fOtherIndex + 1].fFromAngle == NULL + && other->fTs[span->fOtherIndex + 1].fToAngle == NULL))); + int oIndex = 1; + do { + const SkOpSpan& osSpan = other->span(oIndex); + if (osSpan.fFromAngle || osSpan.fT > 0) { + break; + } + ++oIndex; + SkASSERT(oIndex < other->count()); + } while (true); + other->addStartSpan(oIndex); + angle = span->fFromAngle; + oAngle = oSpan->fToAngle; } - if (!span->aligned()) { - span->alignEnd(1, fPts[SkPathOpsVerbToPoints(fVerb)]); + angle->insert(oAngle); +} + +void SkOpSegment::alignMultiples(SkTDArray<AlignedSpan>* alignedArray) { + debugValidate(); + int count = this->count(); + for (int index = 0; index < count; ++index) { + SkOpSpan& span = fTs[index]; + if (!span.fMultiple) { + continue; + } + int end = nextExactSpan(index, 1); + SkASSERT(end > index + 1); + const SkPoint& thisPt = span.fPt; + while (index < end - 1) { + SkOpSegment* other1 = span.fOther; + int oCnt = other1->count(); + for (int idx2 = index + 1; idx2 < end; ++idx2) { + SkOpSpan& span2 = fTs[idx2]; + SkOpSegment* other2 = span2.fOther; + for (int oIdx = 0; oIdx < oCnt; ++oIdx) { + SkOpSpan& oSpan = other1->fTs[oIdx]; + if (oSpan.fOther != other2) { + continue; + } + if (oSpan.fPt == thisPt) { + goto skipExactMatches; + } + } + for (int oIdx = 0; oIdx < oCnt; ++oIdx) { + SkOpSpan& oSpan = other1->fTs[oIdx]; + if (oSpan.fOther != other2) { + continue; + } + if (SkDPoint::RoughlyEqual(oSpan.fPt, thisPt)) { + SkOpSpan& oSpan2 = other2->fTs[oSpan.fOtherIndex]; + if (zero_or_one(span.fOtherT) || zero_or_one(oSpan.fT) + || zero_or_one(span2.fOtherT) || zero_or_one(oSpan2.fT)) { + return; + } + if (!way_roughly_equal(span.fOtherT, oSpan.fT) + || !way_roughly_equal(span2.fOtherT, oSpan2.fT) + || !way_roughly_equal(span2.fOtherT, oSpan.fOtherT) + || !way_roughly_equal(span.fOtherT, oSpan2.fOtherT)) { + return; + } + alignSpan(thisPt, span.fOtherT, other1, span2.fOtherT, + other2, &oSpan, alignedArray); + alignSpan(thisPt, span2.fOtherT, other2, span.fOtherT, + other1, &oSpan2, alignedArray); + break; + } + } + skipExactMatches: + ; + } + ++index; + } } debugValidate(); } -bool SkOpSegment::BetweenTs(const SkOpSpanBase* lesser, double testT, - const SkOpSpanBase* greater) { - if (lesser->t() > greater->t()) { - SkTSwap<const SkOpSpanBase*>(lesser, greater); +void SkOpSegment::alignRange(int lower, int upper, + const SkOpSegment* other, int oLower, int oUpper) { + for (int oIndex = oLower; oIndex <= oUpper; ++oIndex) { + const SkOpSpan& oSpan = other->span(oIndex); + const SkOpSegment* oOther = oSpan.fOther; + if (oOther == this) { + continue; + } + SkOpSpan* matchSpan; + int matchIndex; + const SkOpSpan* refSpan; + for (int iIndex = lower; iIndex <= upper; ++iIndex) { + const SkOpSpan& iSpan = this->span(iIndex); + const SkOpSegment* iOther = iSpan.fOther; + if (iOther == other) { + continue; + } + if (iOther == oOther) { + goto nextI; + } + } + { + // oSpan does not have a match in this + int iCount = this->count(); + const SkOpSpan* iMatch = NULL; + double iMatchTDiff; + matchIndex = -1; + for (int iIndex = 0; iIndex < iCount; ++iIndex) { + const SkOpSpan& iSpan = this->span(iIndex); + const SkOpSegment* iOther = iSpan.fOther; + if (iOther != oOther) { + continue; + } + double testTDiff = fabs(iSpan.fOtherT - oSpan.fOtherT); + if (!iMatch || testTDiff < iMatchTDiff) { + matchIndex = iIndex; + iMatch = &iSpan; + iMatchTDiff = testTDiff; + } + } + if (matchIndex < 0) { + continue; // the entry is missing, & will be picked up later (FIXME: fix it here?) + } + matchSpan = &this->fTs[matchIndex]; + refSpan = &this->span(lower); + if (!SkDPoint::ApproximatelyEqual(matchSpan->fPt, refSpan->fPt)) { + goto nextI; + } + if (matchIndex != lower - 1 && matchIndex != upper + 1) { + // the consecutive spans need to be rearranged to get the missing one close + continue; // FIXME: more work to do + } + } + { + this->fixOtherTIndex(); + SkScalar newT; + if (matchSpan->fT != 0 && matchSpan->fT != 1) { + newT = matchSpan->fT = refSpan->fT; + matchSpan->fOther->fTs[matchSpan->fOtherIndex].fOtherT = refSpan->fT; + } else { // leave span at the start or end there and adjust the neighbors + newT = matchSpan->fT; + for (int iIndex = lower; iIndex <= upper; ++iIndex) { + matchSpan = &this->fTs[iIndex]; + matchSpan->fT = newT; + matchSpan->fOther->fTs[matchSpan->fOtherIndex].fOtherT = newT; + } + } + this->resetSpanFlags(); // fix up small / tiny / done + // align ts of other ranges with adjacent spans that match the aligned points + lower = SkTMin(lower, matchIndex); + while (lower > 0) { + const SkOpSpan& span = this->span(lower - 1); + if (span.fT != newT) { + break; + } + --lower; + } + upper = SkTMax(upper, matchIndex); + int last = this->count() - 1; + while (upper < last) { + const SkOpSpan& span = this->span(upper + 1); + if (span.fT != newT) { + break; + } + ++upper; + } + for (int iIndex = lower; iIndex <= upper; ++iIndex) { + const SkOpSpan& span = this->span(iIndex); + SkOpSegment* aOther = span.fOther; + int aLower = span.fOtherIndex; + SkScalar aT = span.fOtherT; + bool aResetFlags = false; + while (aLower > 0) { + SkOpSpan* aSpan = &aOther->fTs[aLower - 1]; + for (int iIndex = lower; iIndex <= upper; ++iIndex) { + if (aSpan->fPt == this->fTs[iIndex].fPt) { + goto matchFound; + } + } + break; + matchFound: + --aLower; + } + int aUpper = span.fOtherIndex; + int aLast = aOther->count() - 1; + while (aUpper < aLast) { + SkOpSpan* aSpan = &aOther->fTs[aUpper + 1]; + for (int iIndex = lower; iIndex <= upper; ++iIndex) { + if (aSpan->fPt == this->fTs[iIndex].fPt) { + goto matchFound2; + } + } + break; + matchFound2: + ++aUpper; + } + if (aOther->fTs[aLower].fT == 0) { + aT = 0; + } else if (aOther->fTs[aUpper].fT == 1) { + aT = 1; + } + bool aFixed = false; + for (int aIndex = aLower; aIndex <= aUpper; ++aIndex) { + SkOpSpan* aSpan = &aOther->fTs[aIndex]; + if (aSpan->fT == aT) { + continue; + } + SkASSERT(way_roughly_equal(aSpan->fT, aT)); + if (!aFixed) { + aOther->fixOtherTIndex(); + aFixed = true; + } + aSpan->fT = aT; + aSpan->fOther->fTs[aSpan->fOtherIndex].fOtherT = aT; + aResetFlags = true; + } + if (aResetFlags) { + aOther->resetSpanFlags(); + } + } + } +nextI: ; } - return approximately_between(lesser->t(), testT, greater->t()); } -void SkOpSegment::calcAngles(SkChunkAlloc* allocator) { - bool activePrior = !fHead.isCanceled(); - if (activePrior && !fHead.simple()) { - addStartSpan(allocator); +void SkOpSegment::alignSpan(const SkPoint& newPt, double newT, const SkOpSegment* other, + double otherT, const SkOpSegment* other2, SkOpSpan* oSpan, + SkTDArray<AlignedSpan>* alignedArray) { + AlignedSpan* aligned = alignedArray->append(); + aligned->fOldPt = oSpan->fPt; + aligned->fPt = newPt; + aligned->fOldT = oSpan->fT; + aligned->fT = newT; + aligned->fSegment = this; // OPTIMIZE: may be unused, can remove + aligned->fOther1 = other; + aligned->fOther2 = other2; + SkASSERT(SkDPoint::RoughlyEqual(oSpan->fPt, newPt)); + oSpan->fPt = newPt; +// SkASSERT(way_roughly_equal(oSpan->fT, newT)); + oSpan->fT = newT; +// SkASSERT(way_roughly_equal(oSpan->fOtherT, otherT)); + oSpan->fOtherT = otherT; +} + +bool SkOpSegment::alignSpan(int index, double thisT, const SkPoint& thisPt) { + bool aligned = false; + SkOpSpan* span = &fTs[index]; + SkOpSegment* other = span->fOther; + int oIndex = span->fOtherIndex; + SkOpSpan* oSpan = &other->fTs[oIndex]; + if (span->fT != thisT) { + span->fT = thisT; + oSpan->fOtherT = thisT; + aligned = true; + } + if (span->fPt != thisPt) { + span->fPt = thisPt; + oSpan->fPt = thisPt; + aligned = true; + } + double oT = oSpan->fT; + if (oT == 0) { + return aligned; } - SkOpSpan* prior = &fHead; - SkOpSpanBase* spanBase = fHead.next(); - while (spanBase != &fTail) { - if (activePrior) { - SkOpAngle* priorAngle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); - priorAngle->set(spanBase, prior); - spanBase->setFromAngle(priorAngle); + int oStart = other->nextSpan(oIndex, -1) + 1; + oSpan = &other->fTs[oStart]; + int otherIndex = oStart; + if (oT == 1) { + if (aligned) { + while (oSpan->fPt == thisPt && oSpan->fT != 1) { + oSpan->fTiny = true; + ++oSpan; + } } - SkOpSpan* span = spanBase->upCast(); - bool active = !span->isCanceled(); - SkOpSpanBase* next = span->next(); - if (active) { - SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); - angle->set(span, next); - span->setToAngle(angle); + return aligned; + } + oT = oSpan->fT; + int oEnd = other->nextSpan(oIndex, 1); + bool oAligned = false; + if (oSpan->fPt != thisPt) { + oAligned |= other->alignSpan(oStart, oT, thisPt); + } + while (++otherIndex < oEnd) { + SkOpSpan* oNextSpan = &other->fTs[otherIndex]; + if (oNextSpan->fT != oT || oNextSpan->fPt != thisPt) { + oAligned |= other->alignSpan(otherIndex, oT, thisPt); } - activePrior = active; - prior = span; - spanBase = next; } - if (activePrior && !fTail.simple()) { - addEndSpan(allocator); + if (oAligned) { + other->alignSpanState(oStart, oEnd); } + return aligned; } -void SkOpSegment::checkAngleCoin(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) { - SkOpSpanBase* base = &fHead; - SkOpSpan* span; +void SkOpSegment::alignSpanState(int start, int end) { + SkOpSpan* lastSpan = &fTs[--end]; + bool allSmall = lastSpan->fSmall; + bool allTiny = lastSpan->fTiny; + bool allDone = lastSpan->fDone; + SkDEBUGCODE(int winding = lastSpan->fWindValue); + SkDEBUGCODE(int oppWinding = lastSpan->fOppValue); + int index = start; + while (index < end) { + SkOpSpan* span = &fTs[index]; + span->fSmall = allSmall; + span->fTiny = allTiny; + if (span->fDone != allDone) { + span->fDone = allDone; + fDoneSpans += allDone ? 1 : -1; + } + SkASSERT(span->fWindValue == winding); + SkASSERT(span->fOppValue == oppWinding); + ++index; + } +} + +void SkOpSegment::blindCancel(const SkCoincidence& coincidence, SkOpSegment* other) { + bool binary = fOperand != other->fOperand; + int index = 0; + int last = this->count(); do { - SkOpAngle* angle = base->fromAngle(); - if (angle && angle->fCheckCoincidence) { - angle->checkNearCoincidence(); + SkOpSpan& span = this->fTs[--last]; + if (span.fT != 1 && !span.fSmall) { + break; + } + span.fCoincident = true; + } while (true); + int oIndex = other->count(); + do { + SkOpSpan& oSpan = other->fTs[--oIndex]; + if (oSpan.fT != 1 && !oSpan.fSmall) { + break; } - if (base->final()) { - break; + oSpan.fCoincident = true; + } while (true); + do { + SkOpSpan* test = &this->fTs[index]; + int baseWind = test->fWindValue; + int baseOpp = test->fOppValue; + int endIndex = index; + while (++endIndex <= last) { + SkOpSpan* endSpan = &this->fTs[endIndex]; + SkASSERT(endSpan->fT < 1); + if (endSpan->fWindValue != baseWind || endSpan->fOppValue != baseOpp) { + break; + } + endSpan->fCoincident = true; } - span = base->upCast(); - angle = span->toAngle(); - if (angle && angle->fCheckCoincidence) { - angle->checkNearCoincidence(); + SkOpSpan* oTest = &other->fTs[oIndex]; + int oBaseWind = oTest->fWindValue; + int oBaseOpp = oTest->fOppValue; + int oStartIndex = oIndex; + while (--oStartIndex >= 0) { + SkOpSpan* oStartSpan = &other->fTs[oStartIndex]; + if (oStartSpan->fWindValue != oBaseWind || oStartSpan->fOppValue != oBaseOpp) { + break; + } + oStartSpan->fCoincident = true; } - } while ((base = span->next())); + bool decrement = baseWind && oBaseWind; + bool bigger = baseWind >= oBaseWind; + do { + SkASSERT(test->fT < 1); + if (decrement) { + if (binary && bigger) { + test->fOppValue--; + } else { + decrementSpan(test); + } + } + test->fCoincident = true; + test = &fTs[++index]; + } while (index < endIndex); + do { + SkASSERT(oTest->fT < 1); + if (decrement) { + if (binary && !bigger) { + oTest->fOppValue--; + } else { + other->decrementSpan(oTest); + } + } + oTest->fCoincident = true; + oTest = &other->fTs[--oIndex]; + } while (oIndex > oStartIndex); + } while (index <= last && oIndex >= 0); + SkASSERT(index > last); + SkASSERT(oIndex < 0); } -// from http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order -bool SkOpSegment::clockwise(const SkOpSpanBase* start, const SkOpSpanBase* end, bool* swap) const { - SkASSERT(fVerb != SkPath::kLine_Verb); - SkPoint edge[4]; - if (fVerb == SkPath::kCubic_Verb) { - double startT = start->t(); - double endT = end->t(); - bool flip = startT > endT; - SkDCubic cubic; - cubic.set(fPts); - double inflectionTs[2]; - int inflections = cubic.findInflections(inflectionTs); - for (int index = 0; index < inflections; ++index) { - double inflectionT = inflectionTs[index]; - if (between(startT, inflectionT, endT)) { - if (flip) { - if (inflectionT != endT) { - startT = inflectionT; - } - } else { - if (inflectionT != startT) { - endT = inflectionT; - } +void SkOpSegment::blindCoincident(const SkCoincidence& coincidence, SkOpSegment* other) { + bool binary = fOperand != other->fOperand; + int index = 0; + int last = this->count(); + do { + SkOpSpan& span = this->fTs[--last]; + if (span.fT != 1 && !span.fSmall) { + break; + } + span.fCoincident = true; + } while (true); + int oIndex = 0; + int oLast = other->count(); + do { + SkOpSpan& oSpan = other->fTs[--oLast]; + if (oSpan.fT != 1 && !oSpan.fSmall) { + break; + } + oSpan.fCoincident = true; + } while (true); + do { + SkOpSpan* test = &this->fTs[index]; + int baseWind = test->fWindValue; + int baseOpp = test->fOppValue; + int endIndex = index; + SkOpSpan* endSpan; + while (++endIndex <= last) { + endSpan = &this->fTs[endIndex]; + SkASSERT(endSpan->fT < 1); + if (endSpan->fWindValue != baseWind || endSpan->fOppValue != baseOpp) { + break; + } + endSpan->fCoincident = true; + } + SkOpSpan* oTest = &other->fTs[oIndex]; + int oBaseWind = oTest->fWindValue; + int oBaseOpp = oTest->fOppValue; + int oEndIndex = oIndex; + SkOpSpan* oEndSpan; + while (++oEndIndex <= oLast) { + oEndSpan = &this->fTs[oEndIndex]; + SkASSERT(oEndSpan->fT < 1); + if (oEndSpan->fWindValue != oBaseWind || oEndSpan->fOppValue != oBaseOpp) { + break; + } + oEndSpan->fCoincident = true; + } + // consolidate the winding count even if done + if ((test->fWindValue || test->fOppValue) && (oTest->fWindValue || oTest->fOppValue)) { + if (!binary || test->fWindValue + oTest->fOppValue >= 0) { + bumpCoincidentBlind(binary, index, endIndex); + other->bumpCoincidentOBlind(oIndex, oEndIndex); + } else { + other->bumpCoincidentBlind(binary, oIndex, oEndIndex); + bumpCoincidentOBlind(index, endIndex); + } + } + index = endIndex; + oIndex = oEndIndex; + } while (index <= last && oIndex <= oLast); + SkASSERT(index > last); + SkASSERT(oIndex > oLast); +} + +void SkOpSegment::bumpCoincidentBlind(bool binary, int index, int endIndex) { + const SkOpSpan& oTest = fTs[index]; + int oWindValue = oTest.fWindValue; + int oOppValue = oTest.fOppValue; + if (binary) { + SkTSwap<int>(oWindValue, oOppValue); + } + do { + (void) bumpSpan(&fTs[index], oWindValue, oOppValue); + } while (++index < endIndex); +} + +bool SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* indexPtr, + SkTArray<SkPoint, true>* outsideTs) { + int index = *indexPtr; + int oWindValue = oTest.fWindValue; + int oOppValue = oTest.fOppValue; + if (binary) { + SkTSwap<int>(oWindValue, oOppValue); + } + SkOpSpan* const test = &fTs[index]; + SkOpSpan* end = test; + const SkPoint& oStartPt = oTest.fPt; + do { + if (end->fDone && !end->fTiny && !end->fSmall) { // extremely large paths trigger this + return false; + } + if (bumpSpan(end, oWindValue, oOppValue)) { + TrackOutside(outsideTs, oStartPt); + } + end = &fTs[++index]; + } while ((end->fPt == test->fPt || precisely_equal(end->fT, test->fT)) && end->fT < 1); + *indexPtr = index; + return true; +} + +void SkOpSegment::bumpCoincidentOBlind(int index, int endIndex) { + do { + zeroSpan(&fTs[index]); + } while (++index < endIndex); +} + +// because of the order in which coincidences are resolved, this and other +// may not have the same intermediate points. Compute the corresponding +// intermediate T values (using this as the master, other as the follower) +// and walk other conditionally -- hoping that it catches up in the end +bool SkOpSegment::bumpCoincidentOther(const SkOpSpan& test, int* oIndexPtr, + SkTArray<SkPoint, true>* oOutsidePts, const SkPoint& oEndPt) { + int oIndex = *oIndexPtr; + SkOpSpan* const oTest = &fTs[oIndex]; + SkOpSpan* oEnd = oTest; + const SkPoint& oStartPt = oTest->fPt; + double oStartT = oTest->fT; +#if 0 // FIXME : figure out what disabling this breaks + const SkPoint& startPt = test.fPt; + // this is always true since oEnd == oTest && oStartPt == oTest->fPt -- find proper condition + if (oStartPt == oEnd->fPt || precisely_equal(oStartT, oEnd->fT)) { + TrackOutside(oOutsidePts, startPt); + } +#endif + bool foundEnd = false; + while (oStartPt == oEnd->fPt || precisely_equal(oStartT, oEnd->fT)) { + foundEnd |= oEndPt == oEnd->fPt; + zeroSpan(oEnd); + oEnd = &fTs[++oIndex]; + } + *oIndexPtr = oIndex; + return foundEnd; +} + +// FIXME: need to test this case: +// contourA has two segments that are coincident +// contourB has two segments that are coincident in the same place +// each ends up with +2/0 pairs for winding count +// since logic below doesn't transfer count (only increments/decrements) can this be +// resolved to +4/0 ? + +// set spans from start to end to increment the greater by one and decrement +// the lesser +bool SkOpSegment::addTCoincident(const SkPoint& startPt, const SkPoint& endPt, double endT, + SkOpSegment* other) { + bool binary = fOperand != other->fOperand; + int index = 0; + while (startPt != fTs[index].fPt) { + SkASSERT(index < fTs.count()); + ++index; + } + double startT = fTs[index].fT; + while (index > 0 && precisely_equal(fTs[index - 1].fT, startT)) { + --index; + } + int oIndex = 0; + while (startPt != other->fTs[oIndex].fPt) { + SkASSERT(oIndex < other->fTs.count()); + ++oIndex; + } + double oStartT = other->fTs[oIndex].fT; + while (oIndex > 0 && precisely_equal(other->fTs[oIndex - 1].fT, oStartT)) { + --oIndex; + } + SkSTArray<kOutsideTrackedTCount, SkPoint, true> outsidePts; + SkSTArray<kOutsideTrackedTCount, SkPoint, true> oOutsidePts; + SkOpSpan* test = &fTs[index]; + const SkPoint* testPt = &test->fPt; + double testT = test->fT; + SkOpSpan* oTest = &other->fTs[oIndex]; + const SkPoint* oTestPt = &oTest->fPt; + // paths with extreme data will fail this test and eject out of pathops altogether later on + // SkASSERT(AlmostEqualUlps(*testPt, *oTestPt)); + do { + SkASSERT(test->fT < 1); + if (oTest->fT == 1) { + // paths with extreme data may be so mismatched that we fail here + return false; + } + + // consolidate the winding count even if done + bool foundEnd = false; + if ((test->fWindValue == 0 && test->fOppValue == 0) + || (oTest->fWindValue == 0 && oTest->fOppValue == 0)) { + SkDEBUGCODE(int firstWind = test->fWindValue); + SkDEBUGCODE(int firstOpp = test->fOppValue); + do { + SkASSERT(firstWind == fTs[index].fWindValue); + SkASSERT(firstOpp == fTs[index].fOppValue); + ++index; + SkASSERT(index < fTs.count()); + } while (*testPt == fTs[index].fPt); + SkDEBUGCODE(firstWind = oTest->fWindValue); + SkDEBUGCODE(firstOpp = oTest->fOppValue); + do { + SkASSERT(firstWind == other->fTs[oIndex].fWindValue); + SkASSERT(firstOpp == other->fTs[oIndex].fOppValue); + ++oIndex; + SkASSERT(oIndex < other->fTs.count()); + } while (*oTestPt == other->fTs[oIndex].fPt); + } else { + if (!binary || test->fWindValue + oTest->fOppValue >= 0) { + if (!bumpCoincidentThis(*oTest, binary, &index, &outsidePts)) { + return false; + } + foundEnd = other->bumpCoincidentOther(*test, &oIndex, &oOutsidePts, endPt); + } else { + if (!other->bumpCoincidentThis(*test, binary, &oIndex, &oOutsidePts)) { + return false; } + foundEnd = bumpCoincidentOther(*oTest, &index, &outsidePts, endPt); } } - SkDCubic part = cubic.subDivide(startT, endT); - for (int index = 0; index < 4; ++index) { - edge[index] = part[index].asSkPoint(); + test = &fTs[index]; + testPt = &test->fPt; + testT = test->fT; + oTest = &other->fTs[oIndex]; + oTestPt = &oTest->fPt; + if (endPt == *testPt || precisely_equal(endT, testT)) { + break; } - } else { - subDivide(start, end, edge); + if (0 && foundEnd) { // FIXME: this is likely needed but wait until a test case triggers it + break; + } +// SkASSERT(AlmostEqualUlps(*testPt, *oTestPt)); + } while (endPt != *oTestPt); + // in rare cases, one may have ended before the other + if (endPt != *testPt && !precisely_equal(endT, testT)) { + int lastWind = test[-1].fWindValue; + int lastOpp = test[-1].fOppValue; + bool zero = lastWind == 0 && lastOpp == 0; + do { + if (test->fWindValue || test->fOppValue) { + test->fWindValue = lastWind; + test->fOppValue = lastOpp; + if (zero) { + SkASSERT(!test->fDone); + test->fDone = true; + ++fDoneSpans; + } + } + test = &fTs[++index]; + testPt = &test->fPt; + } while (endPt != *testPt); } - bool sumSet = false; - int points = SkPathOpsVerbToPoints(fVerb); - double sum = (edge[0].fX - edge[points].fX) * (edge[0].fY + edge[points].fY); - if (!sumSet) { - for (int idx = 0; idx < points; ++idx){ - sum += (edge[idx + 1].fX - edge[idx].fX) * (edge[idx + 1].fY + edge[idx].fY); + if (endPt != *oTestPt) { + // look ahead to see if zeroing more spans will allows us to catch up + int oPeekIndex = oIndex; + bool success = true; + SkOpSpan* oPeek; + int oCount = other->count(); + do { + oPeek = &other->fTs[oPeekIndex]; + if (++oPeekIndex == oCount) { + success = false; + break; + } + } while (endPt != oPeek->fPt); + if (success) { + // make sure the matching point completes the coincidence span + success = false; + do { + if (oPeek->fOther == this) { + success = true; + break; + } + if (++oPeekIndex == oCount) { + break; + } + oPeek = &other->fTs[oPeekIndex]; + } while (endPt == oPeek->fPt); + } + if (success) { + do { + if (!binary || test->fWindValue + oTest->fOppValue >= 0) { + if (other->bumpCoincidentOther(*test, &oIndex, &oOutsidePts, endPt)) { + break; + } + } else { + if (!other->bumpCoincidentThis(*test, binary, &oIndex, &oOutsidePts)) { + return false; + } + } + oTest = &other->fTs[oIndex]; + oTestPt = &oTest->fPt; + } while (endPt != *oTestPt); } } - if (fVerb == SkPath::kCubic_Verb) { - SkDCubic cubic; - cubic.set(edge); - *swap = sum > 0 && !cubic.monotonicInY(); - } else { - SkDQuad quad; - quad.set(edge); - *swap = sum > 0 && !quad.monotonicInY(); + int outCount = outsidePts.count(); + if (!done() && outCount) { + addCoinOutsides(outsidePts[0], endPt, other); } - return sum <= 0; + if (!other->done() && oOutsidePts.count()) { + other->addCoinOutsides(oOutsidePts[0], endPt, this); + } + setCoincidentRange(startPt, endPt, other); + other->setCoincidentRange(startPt, endPt, this); + return true; } -void SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, - SkOpAngle::IncludeType includeType) { - const SkOpSegment* baseSegment = baseAngle->segment(); - int sumMiWinding = baseSegment->updateWindingReverse(baseAngle); - int sumSuWinding; - bool binary = includeType >= SkOpAngle::kBinarySingle; - if (binary) { - sumSuWinding = baseSegment->updateOppWindingReverse(baseAngle); - if (baseSegment->operand()) { - SkTSwap<int>(sumMiWinding, sumSuWinding); +// FIXME: this doesn't prevent the same span from being added twice +// fix in caller, SkASSERT here? +// FIXME: this may erroneously reject adds for cubic loops +const SkOpSpan* SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, + const SkPoint& pt, const SkPoint& pt2) { + int tCount = fTs.count(); + for (int tIndex = 0; tIndex < tCount; ++tIndex) { + const SkOpSpan& span = fTs[tIndex]; + if (!approximately_negative(span.fT - t)) { + break; + } + if (span.fOther == other) { + bool tsMatch = approximately_equal(span.fT, t); + bool otherTsMatch = approximately_equal(span.fOtherT, otherT); + // FIXME: add cubic loop detecting logic here + // if fLoop bit is set on span, that could be enough if addOtherT copies the bit + // or if a new bit is added ala fOtherLoop + if (tsMatch || otherTsMatch) { +#if DEBUG_ADD_T_PAIR + SkDebugf("%s addTPair duplicate this=%d %1.9g other=%d %1.9g\n", + __FUNCTION__, fID, t, other->fID, otherT); +#endif + return NULL; + } } } - SkOpSegment* nextSegment = nextAngle->segment(); - int maxWinding, sumWinding; - SkOpSpanBase* last; - if (binary) { - int oppMaxWinding, oppSumWinding; - nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, - &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); - last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, - nextAngle); - } else { - nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, - &maxWinding, &sumWinding); - last = nextSegment->markAngle(maxWinding, sumWinding, nextAngle); + int oCount = other->count(); + for (int oIndex = 0; oIndex < oCount; ++oIndex) { + const SkOpSpan& oSpan = other->span(oIndex); + if (!approximately_negative(oSpan.fT - otherT)) { + break; + } + if (oSpan.fOther == this) { + bool otherTsMatch = approximately_equal(oSpan.fT, otherT); + bool tsMatch = approximately_equal(oSpan.fOtherT, t); + if (otherTsMatch || tsMatch) { +#if DEBUG_ADD_T_PAIR + SkDebugf("%s addTPair other duplicate this=%d %1.9g other=%d %1.9g\n", + __FUNCTION__, fID, t, other->fID, otherT); +#endif + return NULL; + } + } } - nextAngle->setLastMarked(last); +#if DEBUG_ADD_T_PAIR + SkDebugf("%s addTPair this=%d %1.9g other=%d %1.9g\n", + __FUNCTION__, fID, t, other->fID, otherT); +#endif + SkASSERT(other != this); + int insertedAt = addT(other, pt, t); + int otherInsertedAt = other->addT(this, pt2, otherT); + this->addOtherT(insertedAt, otherT, otherInsertedAt); + other->addOtherT(otherInsertedAt, t, insertedAt); + this->matchWindingValue(insertedAt, t, borrowWind); + other->matchWindingValue(otherInsertedAt, otherT, borrowWind); + SkOpSpan& span = this->fTs[insertedAt]; + if (pt != pt2) { + span.fNear = true; + SkOpSpan& oSpan = other->fTs[otherInsertedAt]; + oSpan.fNear = true; + } + // if the newly inserted spans match a neighbor on one but not the other, make them agree + int lower = this->nextExactSpan(insertedAt, -1) + 1; + int upper = this->nextExactSpan(insertedAt, 1) - 1; + if (upper < 0) { + upper = this->count() - 1; + } + int oLower = other->nextExactSpan(otherInsertedAt, -1) + 1; + int oUpper = other->nextExactSpan(otherInsertedAt, 1) - 1; + if (oUpper < 0) { + oUpper = other->count() - 1; + } + if (lower == upper && oLower == oUpper) { + return &span; + } +#if DEBUG_CONCIDENT + SkDebugf("%s id=%d lower=%d upper=%d other=%d oLower=%d oUpper=%d\n", __FUNCTION__, + debugID(), lower, upper, other->debugID(), oLower, oUpper); +#endif + // find the nearby spans in one range missing in the other + this->alignRange(lower, upper, other, oLower, oUpper); + other->alignRange(oLower, oUpper, this, lower, upper); + return &span; } -void SkOpSegment::ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, - SkOpAngle::IncludeType includeType) { - const SkOpSegment* baseSegment = baseAngle->segment(); - int sumMiWinding = baseSegment->updateWinding(baseAngle); - int sumSuWinding; - bool binary = includeType >= SkOpAngle::kBinarySingle; - if (binary) { - sumSuWinding = baseSegment->updateOppWinding(baseAngle); - if (baseSegment->operand()) { - SkTSwap<int>(sumMiWinding, sumSuWinding); +const SkOpSpan* SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, + const SkPoint& pt) { + return addTPair(t, other, otherT, borrowWind, pt, pt); +} + +bool SkOpSegment::betweenPoints(double midT, const SkPoint& pt1, const SkPoint& pt2) const { + const SkPoint midPt = ptAtT(midT); + SkPathOpsBounds bounds; + bounds.set(pt1.fX, pt1.fY, pt2.fX, pt2.fY); + bounds.sort(); + return bounds.almostContains(midPt); +} + +bool SkOpSegment::betweenTs(int lesser, double testT, int greater) const { + if (lesser > greater) { + SkTSwap<int>(lesser, greater); + } + return approximately_between(fTs[lesser].fT, testT, fTs[greater].fT); +} + +// in extreme cases (like the buffer overflow test) return false to abort +// for now, if one t value represents two different points, then the values are too extreme +// to generate meaningful results +bool SkOpSegment::calcAngles() { + int spanCount = fTs.count(); + if (spanCount <= 2) { + return spanCount == 2; + } + int index = 1; + const SkOpSpan* firstSpan = &fTs[index]; + int activePrior = checkSetAngle(0); + const SkOpSpan* span = &fTs[0]; + if (firstSpan->fT == 0 || span->fTiny || span->fOtherT != 1 || span->fOther->multipleEnds()) { + index = findStartSpan(0); // curve start intersects + if (fTs[index].fT == 0) { + return false; + } + SkASSERT(index > 0); + if (activePrior >= 0) { + addStartSpan(index); } } - SkOpSegment* nextSegment = nextAngle->segment(); - int maxWinding, sumWinding; - SkOpSpanBase* last; - if (binary) { - int oppMaxWinding, oppSumWinding; - nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, - &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); - last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, - nextAngle); + bool addEnd; + int endIndex = spanCount - 1; + span = &fTs[endIndex - 1]; + if ((addEnd = span->fT == 1 || span->fTiny)) { // if curve end intersects + endIndex = findEndSpan(endIndex); + SkASSERT(endIndex > 0); } else { - nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, - &maxWinding, &sumWinding); - last = nextSegment->markAngle(maxWinding, sumWinding, nextAngle); + addEnd = fTs[endIndex].fOtherT != 0 || fTs[endIndex].fOther->multipleStarts(); } - nextAngle->setLastMarked(last); + SkASSERT(endIndex >= index); + int prior = 0; + while (index < endIndex) { + const SkOpSpan& fromSpan = fTs[index]; // for each intermediate intersection + const SkOpSpan* lastSpan; + span = &fromSpan; + int start = index; + do { + lastSpan = span; + span = &fTs[++index]; + SkASSERT(index < spanCount); + if (!precisely_negative(span->fT - lastSpan->fT) && !lastSpan->fTiny) { + break; + } + if (!SkDPoint::ApproximatelyEqual(lastSpan->fPt, span->fPt)) { + return false; + } + } while (true); + SkOpAngle* angle = NULL; + SkOpAngle* priorAngle; + if (activePrior >= 0) { + int pActive = firstActive(prior); + SkASSERT(pActive < start); + priorAngle = &fAngles.push_back(); + priorAngle->set(this, start, pActive); + } + int active = checkSetAngle(start); + if (active >= 0) { + SkASSERT(active < index); + angle = &fAngles.push_back(); + angle->set(this, active, index); + } + #if DEBUG_ANGLE + debugCheckPointsEqualish(start, index); + #endif + prior = start; + do { + const SkOpSpan* startSpan = &fTs[start - 1]; + if (!startSpan->fSmall || isCanceled(start - 1) || startSpan->fFromAngle + || startSpan->fToAngle) { + break; + } + --start; + } while (start > 0); + do { + if (activePrior >= 0) { + SkASSERT(fTs[start].fFromAngle == NULL); + fTs[start].fFromAngle = priorAngle; + } + if (active >= 0) { + SkASSERT(fTs[start].fToAngle == NULL); + fTs[start].fToAngle = angle; + } + } while (++start < index); + activePrior = active; + } + if (addEnd && activePrior >= 0) { + addEndSpan(endIndex); + } + return true; +} + +int SkOpSegment::checkSetAngle(int tIndex) const { + const SkOpSpan* span = &fTs[tIndex]; + while (span->fTiny /* || span->fSmall */) { + span = &fTs[++tIndex]; + } + return isCanceled(tIndex) ? -1 : tIndex; } // at this point, the span is already ordered, or unorderable -int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end, - SkOpAngle::IncludeType includeType) { +int SkOpSegment::computeSum(int startIndex, int endIndex, SkOpAngle::IncludeType includeType) { SkASSERT(includeType != SkOpAngle::kUnaryXor); - SkOpAngle* firstAngle = this->spanToAngle(end, start); + SkOpAngle* firstAngle = spanToAngle(endIndex, startIndex); if (NULL == firstAngle || NULL == firstAngle->next()) { return SK_NaN32; } @@ -614,7 +1898,7 @@ int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end, baseAngle = NULL; continue; } - int testWinding = angle->starter()->windSum(); + int testWinding = angle->segment()->windSum(angle); if (SK_MinS32 != testWinding) { baseAngle = angle; tryReverse = true; @@ -622,10 +1906,10 @@ int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end, } if (baseAngle) { ComputeOneSum(baseAngle, angle, includeType); - baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : NULL; + baseAngle = SK_MinS32 != angle->segment()->windSum(angle) ? angle : NULL; } } while (next != firstAngle); - if (baseAngle && SK_MinS32 == firstAngle->starter()->windSum()) { + if (baseAngle && SK_MinS32 == firstAngle->segment()->windSum(firstAngle)) { firstAngle = baseAngle; tryReverse = true; } @@ -641,41 +1925,137 @@ int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end, baseAngle = NULL; continue; } - int testWinding = angle->starter()->windSum(); + int testWinding = angle->segment()->windSum(angle); if (SK_MinS32 != testWinding) { baseAngle = angle; continue; } if (baseAngle) { ComputeOneSumReverse(baseAngle, angle, includeType); - baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : NULL; + baseAngle = SK_MinS32 != angle->segment()->windSum(angle) ? angle : NULL; } } while (prior != firstAngle); } - return start->starter(end)->windSum(); + int minIndex = SkMin32(startIndex, endIndex); + return windSum(minIndex); +} + +void SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType includeType) { + const SkOpSegment* baseSegment = baseAngle->segment(); + int sumMiWinding = baseSegment->updateWindingReverse(baseAngle); + int sumSuWinding; + bool binary = includeType >= SkOpAngle::kBinarySingle; + if (binary) { + sumSuWinding = baseSegment->updateOppWindingReverse(baseAngle); + if (baseSegment->operand()) { + SkTSwap<int>(sumMiWinding, sumSuWinding); + } + } + SkOpSegment* nextSegment = nextAngle->segment(); + int maxWinding, sumWinding; + SkOpSpan* last; + if (binary) { + int oppMaxWinding, oppSumWinding; + nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, + &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, + nextAngle); + } else { + nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, + &maxWinding, &sumWinding); + last = nextSegment->markAngle(maxWinding, sumWinding, nextAngle); + } + nextAngle->setLastMarked(last); +} + +void SkOpSegment::ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType includeType) { + const SkOpSegment* baseSegment = baseAngle->segment(); + int sumMiWinding = baseSegment->updateWinding(baseAngle); + int sumSuWinding; + bool binary = includeType >= SkOpAngle::kBinarySingle; + if (binary) { + sumSuWinding = baseSegment->updateOppWinding(baseAngle); + if (baseSegment->operand()) { + SkTSwap<int>(sumMiWinding, sumSuWinding); + } + } + SkOpSegment* nextSegment = nextAngle->segment(); + int maxWinding, sumWinding; + SkOpSpan* last; + if (binary) { + int oppMaxWinding, oppSumWinding; + nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, + &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, + nextAngle); + } else { + nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, + &maxWinding, &sumWinding); + last = nextSegment->markAngle(maxWinding, sumWinding, nextAngle); + } + nextAngle->setLastMarked(last); +} + +bool SkOpSegment::containsPt(const SkPoint& pt, int index, int endIndex) const { + int step = index < endIndex ? 1 : -1; + do { + const SkOpSpan& span = this->span(index); + if (span.fPt == pt) { + const SkOpSpan& endSpan = this->span(endIndex); + return span.fT == endSpan.fT && pt != endSpan.fPt; + } + index += step; + } while (index != endIndex); + return false; +} + +bool SkOpSegment::containsT(double t, const SkOpSegment* other, double otherT) const { + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (t < span.fT) { + return false; + } + if (t == span.fT) { + if (other != span.fOther) { + continue; + } + if (other->fVerb != SkPath::kCubic_Verb) { + return true; + } + if (!other->fLoop) { + return true; + } + double otherMidT = (otherT + span.fOtherT) / 2; + SkPoint otherPt = other->ptAtT(otherMidT); + return SkDPoint::ApproximatelyEqual(span.fPt, otherPt); + } + } + return false; } -SkOpSpan* SkOpSegment::crossedSpanY(const SkPoint& basePt, double mid, bool opp, bool current, - SkScalar* bestY, double* hitT, bool* hitSomething, bool* vertical) { +int SkOpSegment::crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, + bool* hitSomething, double mid, bool opp, bool current) const { SkScalar bottom = fBounds.fBottom; - *vertical = false; + int bestTIndex = -1; if (bottom <= *bestY) { - return NULL; + return bestTIndex; } SkScalar top = fBounds.fTop; if (top >= basePt.fY) { - return NULL; + return bestTIndex; } if (fBounds.fLeft > basePt.fX) { - return NULL; + return bestTIndex; } if (fBounds.fRight < basePt.fX) { - return NULL; + return bestTIndex; } if (fBounds.fLeft == fBounds.fRight) { // if vertical, and directly above test point, wait for another one - *vertical = AlmostEqualUlps(basePt.fX, fBounds.fLeft); - return NULL; + return AlmostEqualUlps(basePt.fX, fBounds.fLeft) ? SK_MinS32 : bestTIndex; } // intersect ray starting at basePt with edge SkIntersections intersections; @@ -685,7 +2065,7 @@ SkOpSpan* SkOpSegment::crossedSpanY(const SkPoint& basePt, double mid, bool opp, int pts = (intersections.*CurveVertical[SkPathOpsVerbToPoints(fVerb)]) (fPts, top, bottom, basePt.fX, false); if (pts == 0 || (current && pts == 1)) { - return NULL; + return bestTIndex; } if (current) { SkASSERT(pts > 1); @@ -713,73 +2093,933 @@ SkOpSpan* SkOpSegment::crossedSpanY(const SkPoint& basePt, double mid, bool opp, continue; } if (pts > 1 && fVerb == SkPath::kLine_Verb) { - *vertical = true; - return NULL; // if the intersection is edge on, wait for another one + return SK_MinS32; // if the intersection is edge on, wait for another one } if (fVerb > SkPath::kLine_Verb) { SkScalar dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, foundT).fX; if (approximately_zero(dx)) { - *vertical = true; - return NULL; // hit vertical, wait for another one + return SK_MinS32; // hit vertical, wait for another one } } *bestY = testY; bestT = foundT; } if (bestT < 0) { - return NULL; + return bestTIndex; } SkASSERT(bestT >= 0); - SkASSERT(bestT < 1); - SkOpSpanBase* testTSpanBase = &this->fHead; - SkOpSpanBase* nextTSpan; - double endT = 0; + SkASSERT(bestT <= 1); + int start; + int end = 0; do { - nextTSpan = testTSpanBase->upCast()->next(); - endT = nextTSpan->t(); - if (endT >= bestT) { - break; - } - testTSpanBase = nextTSpan; - } while (testTSpanBase); - SkOpSpan* bestTSpan = NULL; - SkOpSpan* testTSpan = testTSpanBase->upCast(); - if (!testTSpan->isCanceled()) { + start = end; + end = nextSpan(start, 1); + } while (fTs[end].fT < bestT); + // FIXME: see next candidate for a better pattern to find the next start/end pair + while (start + 1 < end && fTs[start].fDone) { + ++start; + } + if (!isCanceled(start)) { *hitT = bestT; - bestTSpan = testTSpan; + bestTIndex = start; *hitSomething = true; } - return bestTSpan; + return bestTIndex; +} + +bool SkOpSegment::decrementSpan(SkOpSpan* span) { + SkASSERT(span->fWindValue > 0); + if (--(span->fWindValue) == 0) { + if (!span->fOppValue && !span->fDone) { + span->fDone = true; + ++fDoneSpans; + return true; + } + } + return false; +} + +bool SkOpSegment::bumpSpan(SkOpSpan* span, int windDelta, int oppDelta) { + SkASSERT(!span->fDone || span->fTiny || span->fSmall); + span->fWindValue += windDelta; + SkASSERT(span->fWindValue >= 0); + span->fOppValue += oppDelta; + SkASSERT(span->fOppValue >= 0); + if (fXor) { + span->fWindValue &= 1; + } + if (fOppXor) { + span->fOppValue &= 1; + } + if (!span->fWindValue && !span->fOppValue) { + if (!span->fDone) { + span->fDone = true; + ++fDoneSpans; + } + return true; + } + return false; +} + +const SkOpSpan& SkOpSegment::firstSpan(const SkOpSpan& thisSpan) const { + const SkOpSpan* firstSpan = &thisSpan; // rewind to the start + const SkOpSpan* beginSpan = fTs.begin(); + const SkPoint& testPt = thisSpan.fPt; + while (firstSpan > beginSpan && firstSpan[-1].fPt == testPt) { + --firstSpan; + } + return *firstSpan; +} + +const SkOpSpan& SkOpSegment::lastSpan(const SkOpSpan& thisSpan) const { + const SkOpSpan* endSpan = fTs.end() - 1; // last can't be small + const SkOpSpan* lastSpan = &thisSpan; // find the end + const SkPoint& testPt = thisSpan.fPt; + while (lastSpan < endSpan && lastSpan[1].fPt == testPt) { + ++lastSpan; + } + return *lastSpan; +} + +// with a loop, the comparison is move involved +// scan backwards and forwards to count all matching points +// (verify that there are twp scans marked as loops) +// compare that against 2 matching scans for loop plus other results +bool SkOpSegment::calcLoopSpanCount(const SkOpSpan& thisSpan, int* smallCounts) { + const SkOpSpan& firstSpan = this->firstSpan(thisSpan); // rewind to the start + const SkOpSpan& lastSpan = this->lastSpan(thisSpan); // find the end + double firstLoopT = -1, lastLoopT = -1; + const SkOpSpan* testSpan = &firstSpan - 1; + while (++testSpan <= &lastSpan) { + if (testSpan->fLoop) { + firstLoopT = testSpan->fT; + break; + } + } + testSpan = &lastSpan + 1; + while (--testSpan >= &firstSpan) { + if (testSpan->fLoop) { + lastLoopT = testSpan->fT; + break; + } + } + SkASSERT((firstLoopT == -1) == (lastLoopT == -1)); + if (firstLoopT == -1) { + return false; + } + SkASSERT(firstLoopT < lastLoopT); + testSpan = &firstSpan - 1; + smallCounts[0] = smallCounts[1] = 0; + while (++testSpan <= &lastSpan) { + SkASSERT(approximately_equal(testSpan->fT, firstLoopT) + + approximately_equal(testSpan->fT, lastLoopT) == 1); + smallCounts[approximately_equal(testSpan->fT, lastLoopT)]++; + } + return true; +} + +double SkOpSegment::calcMissingTEnd(const SkOpSegment* ref, double loEnd, double min, double max, + double hiEnd, const SkOpSegment* other, int thisStart) { + if (max >= hiEnd) { + return -1; + } + int end = findOtherT(hiEnd, ref); + if (end < 0) { + return -1; + } + double tHi = span(end).fT; + double tLo, refLo; + if (thisStart >= 0) { + tLo = span(thisStart).fT; + refLo = min; + } else { + int start1 = findOtherT(loEnd, ref); + SkASSERT(start1 >= 0); + tLo = span(start1).fT; + refLo = loEnd; + } + double missingT = (max - refLo) / (hiEnd - refLo); + missingT = tLo + missingT * (tHi - tLo); + return missingT; +} + +double SkOpSegment::calcMissingTStart(const SkOpSegment* ref, double loEnd, double min, double max, + double hiEnd, const SkOpSegment* other, int thisEnd) { + if (min <= loEnd) { + return -1; + } + int start = findOtherT(loEnd, ref); + if (start < 0) { + return -1; + } + double tLo = span(start).fT; + double tHi, refHi; + if (thisEnd >= 0) { + tHi = span(thisEnd).fT; + refHi = max; + } else { + int end1 = findOtherT(hiEnd, ref); + if (end1 < 0) { + return -1; + } + tHi = span(end1).fT; + refHi = hiEnd; + } + double missingT = (min - loEnd) / (refHi - loEnd); + missingT = tLo + missingT * (tHi - tLo); + return missingT; +} + +// see if spans with two or more intersections have the same number on the other end +void SkOpSegment::checkDuplicates() { + debugValidate(); + SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans; + int index; + int endIndex = 0; + bool endFound; + do { + index = endIndex; + endIndex = nextExactSpan(index, 1); + if ((endFound = endIndex < 0)) { + endIndex = count(); + } + int dupCount = endIndex - index; + if (dupCount < 2) { + continue; + } + do { + const SkOpSpan* thisSpan = &fTs[index]; + if (thisSpan->fNear) { + continue; + } + SkOpSegment* other = thisSpan->fOther; + int oIndex = thisSpan->fOtherIndex; + int oStart = other->nextExactSpan(oIndex, -1) + 1; + int oEnd = other->nextExactSpan(oIndex, 1); + if (oEnd < 0) { + oEnd = other->count(); + } + int oCount = oEnd - oStart; + // force the other to match its t and this pt if not on an end point + if (oCount != dupCount) { + MissingSpan& missing = missingSpans.push_back(); + missing.fOther = NULL; + SkDEBUGCODE(sk_bzero(&missing, sizeof(missing))); + missing.fPt = thisSpan->fPt; + const SkOpSpan& oSpan = other->span(oIndex); + if (oCount > dupCount) { + missing.fSegment = this; + missing.fT = thisSpan->fT; + other->checkLinks(&oSpan, &missingSpans); + } else { + missing.fSegment = other; + missing.fT = oSpan.fT; + checkLinks(thisSpan, &missingSpans); + } + if (!missingSpans.back().fOther) { + missingSpans.pop_back(); + } + } + } while (++index < endIndex); + } while (!endFound); + int missingCount = missingSpans.count(); + if (missingCount == 0) { + return; + } + SkSTArray<kMissingSpanCount, MissingSpan, true> missingCoincidence; + for (index = 0; index < missingCount; ++index) { + MissingSpan& missing = missingSpans[index]; + SkOpSegment* missingOther = missing.fOther; + if (missing.fSegment == missing.fOther) { + continue; + } +#if 0 // FIXME: this eliminates spurious data from skpwww_argus_presse_fr_41 but breaks + // skpwww_fashionscandal_com_94 -- calcAngles complains, but I don't understand why + if (missing.fSegment->containsT(missing.fT, missing.fOther, missing.fOtherT)) { +#if DEBUG_DUPLICATES + SkDebugf("skip 1 id=%d t=%1.9g other=%d otherT=%1.9g\n", missing.fSegment->fID, + missing.fT, missing.fOther->fID, missing.fOtherT); +#endif + continue; + } + if (missing.fOther->containsT(missing.fOtherT, missing.fSegment, missing.fT)) { +#if DEBUG_DUPLICATES + SkDebugf("skip 2 id=%d t=%1.9g other=%d otherT=%1.9g\n", missing.fOther->fID, + missing.fOtherT, missing.fSegment->fID, missing.fT); +#endif + continue; + } +#endif + // skip if adding would insert point into an existing coincindent span + if (missing.fSegment->inCoincidentSpan(missing.fT, missingOther) + && missingOther->inCoincidentSpan(missing.fOtherT, this)) { + continue; + } + // skip if the created coincident spans are small + if (missing.fSegment->coincidentSmall(missing.fPt, missing.fT, missingOther) + && missingOther->coincidentSmall(missing.fPt, missing.fOtherT, missing.fSegment)) { + continue; + } + const SkOpSpan* added = missing.fSegment->addTPair(missing.fT, missingOther, + missing.fOtherT, false, missing.fPt); + if (added && added->fSmall) { + missing.fSegment->checkSmallCoincidence(*added, &missingCoincidence); + } + } + for (index = 0; index < missingCount; ++index) { + MissingSpan& missing = missingSpans[index]; + missing.fSegment->fixOtherTIndex(); + missing.fOther->fixOtherTIndex(); + } + for (index = 0; index < missingCoincidence.count(); ++index) { + MissingSpan& missing = missingCoincidence[index]; + missing.fSegment->fixOtherTIndex(); + } + debugValidate(); +} + +// look to see if the curve end intersects an intermediary that intersects the other +bool SkOpSegment::checkEnds() { + debugValidate(); + SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans; + int count = fTs.count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + double otherT = span.fOtherT; + if (otherT != 0 && otherT != 1) { // only check ends + continue; + } + const SkOpSegment* other = span.fOther; + // peek start/last describe the range of spans that match the other t of this span + int peekStart = span.fOtherIndex; + while (--peekStart >= 0 && other->fTs[peekStart].fT == otherT) + ; + int otherCount = other->fTs.count(); + int peekLast = span.fOtherIndex; + while (++peekLast < otherCount && other->fTs[peekLast].fT == otherT) + ; + if (++peekStart == --peekLast) { // if there isn't a range, there's nothing to do + continue; + } + // t start/last describe the range of spans that match the t of this span + double t = span.fT; + double tBottom = -1; + int tStart = -1; + int tLast = count; + bool lastSmall = false; + double afterT = t; + for (int inner = 0; inner < count; ++inner) { + double innerT = fTs[inner].fT; + if (innerT <= t && innerT > tBottom) { + if (innerT < t || !lastSmall) { + tStart = inner - 1; + } + tBottom = innerT; + } + if (innerT > afterT) { + if (t == afterT && lastSmall) { + afterT = innerT; + } else { + tLast = inner; + break; + } + } + lastSmall = innerT <= t ? fTs[inner].fSmall : false; + } + for (int peekIndex = peekStart; peekIndex <= peekLast; ++peekIndex) { + if (peekIndex == span.fOtherIndex) { // skip the other span pointed to by this span + continue; + } + const SkOpSpan& peekSpan = other->fTs[peekIndex]; + SkOpSegment* match = peekSpan.fOther; + if (match->done()) { + continue; // if the edge has already been eaten (likely coincidence), ignore it + } + const double matchT = peekSpan.fOtherT; + // see if any of the spans match the other spans + for (int tIndex = tStart + 1; tIndex < tLast; ++tIndex) { + const SkOpSpan& tSpan = fTs[tIndex]; + if (tSpan.fOther == match) { + if (tSpan.fOtherT == matchT) { + goto nextPeekIndex; + } + double midT = (tSpan.fOtherT + matchT) / 2; + if (match->betweenPoints(midT, tSpan.fPt, peekSpan.fPt)) { + goto nextPeekIndex; + } + } + } + if (missingSpans.count() > 0) { + const MissingSpan& lastMissing = missingSpans.back(); + if (lastMissing.fT == t + && lastMissing.fOther == match + && lastMissing.fOtherT == matchT) { + SkASSERT(SkDPoint::ApproximatelyEqual(lastMissing.fPt, peekSpan.fPt)); + continue; + } + } + if (this == match) { + return false; // extremely large paths can trigger this + } +#if DEBUG_CHECK_ALIGN + SkDebugf("%s id=%d missing t=%1.9g other=%d otherT=%1.9g pt=(%1.9g,%1.9g)\n", + __FUNCTION__, fID, t, match->fID, matchT, peekSpan.fPt.fX, peekSpan.fPt.fY); +#endif + // this segment is missing a entry that the other contains + // remember so we can add the missing one and recompute the indices + { + MissingSpan& missing = missingSpans.push_back(); + SkDEBUGCODE(sk_bzero(&missing, sizeof(missing))); + missing.fT = t; + SkASSERT(this != match); + missing.fOther = match; + missing.fOtherT = matchT; + missing.fPt = peekSpan.fPt; + } + break; +nextPeekIndex: + ; + } + } + if (missingSpans.count() == 0) { + debugValidate(); + return true; + } + debugValidate(); + int missingCount = missingSpans.count(); + for (int index = 0; index < missingCount; ++index) { + MissingSpan& missing = missingSpans[index]; + if (this != missing.fOther) { + addTPair(missing.fT, missing.fOther, missing.fOtherT, false, missing.fPt); + } + } + fixOtherTIndex(); + // OPTIMIZATION: this may fix indices more than once. Build an array of unique segments to + // avoid this + for (int index = 0; index < missingCount; ++index) { + missingSpans[index].fOther->fixOtherTIndex(); + } + debugValidate(); + return true; +} + +void SkOpSegment::checkLinks(const SkOpSpan* base, + SkTArray<MissingSpan, true>* missingSpans) const { + const SkOpSpan* first = fTs.begin(); + const SkOpSpan* last = fTs.end() - 1; + SkASSERT(base >= first && last >= base); + const SkOpSegment* other = base->fOther; + const SkOpSpan* oFirst = other->fTs.begin(); + const SkOpSpan* oLast = other->fTs.end() - 1; + const SkOpSpan* oSpan = &other->fTs[base->fOtherIndex]; + const SkOpSpan* test = base; + const SkOpSpan* missing = NULL; + while (test > first && (--test)->fPt == base->fPt) { + if (this == test->fOther) { + continue; + } + CheckOneLink(test, oSpan, oFirst, oLast, &missing, missingSpans); + } + test = base; + while (test < last && (++test)->fPt == base->fPt) { + SkASSERT(this != test->fOther || test->fLoop); + CheckOneLink(test, oSpan, oFirst, oLast, &missing, missingSpans); + } +} + +// see if spans with two or more intersections all agree on common t and point values +void SkOpSegment::checkMultiples() { + debugValidate(); + int index; + int end = 0; + while (fTs[++end].fT == 0) + ; + while (fTs[end].fT < 1) { + int start = index = end; + end = nextExactSpan(index, 1); + if (end <= index) { + return; // buffer overflow example triggers this + } + if (index + 1 == end) { + continue; + } + // force the duplicates to agree on t and pt if not on the end + SkOpSpan& span = fTs[index]; + double thisT = span.fT; + const SkPoint& thisPt = span.fPt; + span.fMultiple = true; + bool aligned = false; + while (++index < end) { + aligned |= alignSpan(index, thisT, thisPt); + } + if (aligned) { + alignSpanState(start, end); + } + fMultiples = true; + } + debugValidate(); +} + +void SkOpSegment::CheckOneLink(const SkOpSpan* test, const SkOpSpan* oSpan, + const SkOpSpan* oFirst, const SkOpSpan* oLast, const SkOpSpan** missingPtr, + SkTArray<MissingSpan, true>* missingSpans) { + SkASSERT(oSpan->fPt == test->fPt); + const SkOpSpan* oTest = oSpan; + while (oTest > oFirst && (--oTest)->fPt == test->fPt) { + if (oTest->fOther == test->fOther && oTest->fOtherT == test->fOtherT) { + return; + } + } + oTest = oSpan; + while (oTest < oLast && (++oTest)->fPt == test->fPt) { + if (oTest->fOther == test->fOther && oTest->fOtherT == test->fOtherT) { + return; + } + } + if (*missingPtr) { + missingSpans->push_back(); + } + MissingSpan& lastMissing = missingSpans->back(); + if (*missingPtr) { + lastMissing = missingSpans->end()[-2]; + } + *missingPtr = test; + lastMissing.fOther = test->fOther; + lastMissing.fOtherT = test->fOtherT; +} + +bool SkOpSegment::checkSmall(int index) const { + if (fTs[index].fSmall) { + return true; + } + double tBase = fTs[index].fT; + while (index > 0 && precisely_negative(tBase - fTs[--index].fT)) + ; + return fTs[index].fSmall; +} + +// a pair of curves may turn into coincident lines -- small may be a hint that that happened +// if a cubic contains a loop, the counts must be adjusted +void SkOpSegment::checkSmall() { + SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans; + const SkOpSpan* beginSpan = fTs.begin(); + const SkOpSpan* thisSpan = beginSpan - 1; + const SkOpSpan* endSpan = fTs.end() - 1; // last can't be small + while (++thisSpan < endSpan) { + if (!thisSpan->fSmall) { + continue; + } + if (!thisSpan->fWindValue) { + continue; + } + const SkOpSpan& firstSpan = this->firstSpan(*thisSpan); + const SkOpSpan& lastSpan = this->lastSpan(*thisSpan); + const SkOpSpan* nextSpan = &firstSpan + 1; + ptrdiff_t smallCount = &lastSpan - &firstSpan + 1; + SkASSERT(1 <= smallCount && smallCount < count()); + if (smallCount <= 1 && !nextSpan->fSmall) { + SkASSERT(1 == smallCount); + checkSmallCoincidence(firstSpan, NULL); + continue; + } + // at this point, check for missing computed intersections + const SkPoint& testPt = firstSpan.fPt; + thisSpan = &firstSpan - 1; + SkOpSegment* other = NULL; + while (++thisSpan <= &lastSpan) { + other = thisSpan->fOther; + if (other != this) { + break; + } + } + SkASSERT(other != this); + int oIndex = thisSpan->fOtherIndex; + const SkOpSpan& oSpan = other->span(oIndex); + const SkOpSpan& oFirstSpan = other->firstSpan(oSpan); + const SkOpSpan& oLastSpan = other->lastSpan(oSpan); + ptrdiff_t oCount = &oLastSpan - &oFirstSpan + 1; + if (fLoop) { + int smallCounts[2]; + SkASSERT(!other->fLoop); // FIXME: we need more complicated logic for pair of loops + if (calcLoopSpanCount(*thisSpan, smallCounts)) { + if (smallCounts[0] && oCount != smallCounts[0]) { + SkASSERT(0); // FIXME: need a working test case to properly code & debug + } + if (smallCounts[1] && oCount != smallCounts[1]) { + SkASSERT(0); // FIXME: need a working test case to properly code & debug + } + goto nextSmallCheck; + } + } + if (other->fLoop) { + int otherCounts[2]; + if (other->calcLoopSpanCount(other->span(oIndex), otherCounts)) { + if (otherCounts[0] && otherCounts[0] != smallCount) { + SkASSERT(0); // FIXME: need a working test case to properly code & debug + } + if (otherCounts[1] && otherCounts[1] != smallCount) { + SkASSERT(0); // FIXME: need a working test case to properly code & debug + } + goto nextSmallCheck; + } + } + if (oCount != smallCount) { // check if number of pts in this match other + MissingSpan& missing = missingSpans.push_back(); + missing.fOther = NULL; + SkDEBUGCODE(sk_bzero(&missing, sizeof(missing))); + missing.fPt = testPt; + const SkOpSpan& oSpan = other->span(oIndex); + if (oCount > smallCount) { + missing.fSegment = this; + missing.fT = thisSpan->fT; + other->checkLinks(&oSpan, &missingSpans); + } else { + missing.fSegment = other; + missing.fT = oSpan.fT; + checkLinks(thisSpan, &missingSpans); + } + if (!missingSpans.back().fOther || missing.fSegment->done()) { + missingSpans.pop_back(); + } + } +nextSmallCheck: + thisSpan = &lastSpan; + } + int missingCount = missingSpans.count(); + for (int index = 0; index < missingCount; ++index) { + MissingSpan& missing = missingSpans[index]; + SkOpSegment* missingOther = missing.fOther; + // note that add t pair may edit span arrays, so prior pointers to spans are no longer valid + if (!missing.fSegment->addTPair(missing.fT, missingOther, missing.fOtherT, false, + missing.fPt)) { + continue; + } + int otherTIndex = missingOther->findT(missing.fOtherT, missing.fPt, missing.fSegment); + const SkOpSpan& otherSpan = missingOther->span(otherTIndex); + if (otherSpan.fSmall) { + const SkOpSpan* nextSpan = &otherSpan; + if (nextSpan->fPt == missing.fPt) { + continue; + } + do { + ++nextSpan; + } while (nextSpan->fSmall); + if (nextSpan->fT == 1) { + continue; + } + SkAssertResult(missing.fSegment->addTCoincident(missing.fPt, nextSpan->fPt, + nextSpan->fT, missingOther)); + } else if (otherSpan.fT > 0) { + const SkOpSpan* priorSpan = &otherSpan; + do { + --priorSpan; + } while (priorSpan->fT == otherSpan.fT); + if (priorSpan->fSmall) { + missing.fSegment->addTCancel(missing.fPt, priorSpan->fPt, missingOther); + } + } + } + // OPTIMIZATION: this may fix indices more than once. Build an array of unique segments to + // avoid this + for (int index = 0; index < missingCount; ++index) { + MissingSpan& missing = missingSpans[index]; + missing.fSegment->fixOtherTIndex(); + missing.fOther->fixOtherTIndex(); + } + debugValidate(); +} + +void SkOpSegment::checkSmallCoincidence(const SkOpSpan& span, + SkTArray<MissingSpan, true>* checkMultiple) { + SkASSERT(span.fSmall); + if (0 && !span.fWindValue) { + return; + } + SkASSERT(&span < fTs.end() - 1); + const SkOpSpan* next = &span + 1; + SkASSERT(!next->fSmall || checkMultiple); + if (checkMultiple) { + while (next->fSmall) { + ++next; + SkASSERT(next < fTs.end()); + } + } + SkOpSegment* other = span.fOther; + while (other != next->fOther) { + if (!checkMultiple) { + return; + } + const SkOpSpan* test = next + 1; + if (test == fTs.end()) { + return; + } + if (test->fPt != next->fPt || !precisely_equal(test->fT, next->fT)) { + return; + } + next = test; + } + SkASSERT(span.fT < next->fT); + int oStartIndex = other->findExactT(span.fOtherT, this); + int oEndIndex = other->findExactT(next->fOtherT, this); + // FIXME: be overly conservative by limiting this to the caller that allows multiple smalls + if (!checkMultiple || fVerb != SkPath::kLine_Verb || other->fVerb != SkPath::kLine_Verb) { + SkPoint mid = ptAtT((span.fT + next->fT) / 2); + const SkOpSpan& oSpanStart = other->fTs[oStartIndex]; + const SkOpSpan& oSpanEnd = other->fTs[oEndIndex]; + SkPoint oMid = other->ptAtT((oSpanStart.fT + oSpanEnd.fT) / 2); + if (!SkDPoint::ApproximatelyEqual(mid, oMid)) { + return; + } + } + // FIXME: again, be overly conservative to avoid breaking existing tests + const SkOpSpan& oSpan = oStartIndex < oEndIndex ? other->fTs[oStartIndex] + : other->fTs[oEndIndex]; + if (checkMultiple && !oSpan.fSmall) { + return; + } +// SkASSERT(oSpan.fSmall); + if (oStartIndex < oEndIndex) { + SkAssertResult(addTCoincident(span.fPt, next->fPt, next->fT, other)); + } else { + addTCancel(span.fPt, next->fPt, other); + } + if (!checkMultiple) { + return; + } + // check to see if either segment is coincident with a third segment -- if it is, and if + // the opposite segment is not already coincident with the third, make it so + // OPTIMIZE: to make this check easier, add coincident and cancel could set a coincident bit + if (span.fWindValue != 1 || span.fOppValue != 0) { +// start here; + // iterate through the spans, looking for the third coincident case + // if we find one, we need to return state to the caller so that the indices can be fixed + // this also suggests that all of this function is fragile since it relies on a valid index + } + // probably should make this a common function rather than copy/paste code + if (oSpan.fWindValue != 1 || oSpan.fOppValue != 0) { + const SkOpSpan* oTest = &oSpan; + while (--oTest >= other->fTs.begin()) { + if (oTest->fPt != oSpan.fPt || !precisely_equal(oTest->fT, oSpan.fT)) { + break; + } + SkOpSegment* testOther = oTest->fOther; + SkASSERT(testOther != this); + // look in both directions to see if there is a coincident span + const SkOpSpan* tTest = testOther->fTs.begin(); + for (int testIndex = 0; testIndex < testOther->count(); ++testIndex) { + if (tTest->fPt != span.fPt) { + ++tTest; + continue; + } + if (testOther->verb() != SkPath::kLine_Verb + || other->verb() != SkPath::kLine_Verb) { + SkPoint mid = ptAtT((span.fT + next->fT) / 2); + SkPoint oMid = other->ptAtT((oTest->fOtherT + tTest->fT) / 2); + if (!SkDPoint::ApproximatelyEqual(mid, oMid)) { + continue; + } + } +#if DEBUG_CONCIDENT + SkDebugf("%s coincident found=%d %1.9g %1.9g\n", __FUNCTION__, testOther->fID, + oTest->fOtherT, tTest->fT); +#endif + if (tTest->fT < oTest->fOtherT) { + SkAssertResult(addTCoincident(span.fPt, next->fPt, next->fT, testOther)); + } else { + addTCancel(span.fPt, next->fPt, testOther); + } + MissingSpan missing; + missing.fSegment = testOther; + checkMultiple->push_back(missing); + break; + } + } + oTest = &oSpan; + while (++oTest < other->fTs.end()) { + if (oTest->fPt != oSpan.fPt || !precisely_equal(oTest->fT, oSpan.fT)) { + break; + } + + } + } } -void SkOpSegment::detach(const SkOpSpan* span) { - if (span->done()) { - --this->fDoneCount; +// if pair of spans on either side of tiny have the same end point and mid point, mark +// them as parallel +void SkOpSegment::checkTiny() { + SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans; + SkOpSpan* thisSpan = fTs.begin() - 1; + const SkOpSpan* endSpan = fTs.end() - 1; // last can't be tiny + while (++thisSpan < endSpan) { + if (!thisSpan->fTiny) { + continue; + } + SkOpSpan* nextSpan = thisSpan + 1; + double thisT = thisSpan->fT; + double nextT = nextSpan->fT; + if (thisT == nextT) { + continue; + } + SkASSERT(thisT < nextT); + SkASSERT(thisSpan->fPt == nextSpan->fPt); + SkOpSegment* thisOther = thisSpan->fOther; + SkOpSegment* nextOther = nextSpan->fOther; + int oIndex = thisSpan->fOtherIndex; + for (int oStep = -1; oStep <= 1; oStep += 2) { + int oEnd = thisOther->nextExactSpan(oIndex, oStep); + if (oEnd < 0) { + continue; + } + const SkOpSpan& oSpan = thisOther->span(oEnd); + int nIndex = nextSpan->fOtherIndex; + for (int nStep = -1; nStep <= 1; nStep += 2) { + int nEnd = nextOther->nextExactSpan(nIndex, nStep); + if (nEnd < 0) { + continue; + } + const SkOpSpan& nSpan = nextOther->span(nEnd); + if (oSpan.fPt != nSpan.fPt) { + continue; + } + double oMidT = (thisSpan->fOtherT + oSpan.fT) / 2; + const SkPoint& oPt = thisOther->ptAtT(oMidT); + double nMidT = (nextSpan->fOtherT + nSpan.fT) / 2; + const SkPoint& nPt = nextOther->ptAtT(nMidT); + if (!AlmostEqualUlps(oPt, nPt)) { + continue; + } +#if DEBUG_CHECK_TINY + SkDebugf("%s [%d] add coincidence [%d] [%d]\n", __FUNCTION__, fID, + thisOther->fID, nextOther->fID); +#endif + // this segment is missing a entry that the other contains + // remember so we can add the missing one and recompute the indices + MissingSpan& missing = missingSpans.push_back(); + SkDEBUGCODE(sk_bzero(&missing, sizeof(missing))); + missing.fSegment = thisOther; + missing.fT = thisSpan->fOtherT; + SkASSERT(this != nextOther); + missing.fOther = nextOther; + missing.fOtherT = nextSpan->fOtherT; + missing.fPt = thisSpan->fPt; + } + } + } + int missingCount = missingSpans.count(); + if (!missingCount) { + return; + } + for (int index = 0; index < missingCount; ++index) { + MissingSpan& missing = missingSpans[index]; + if (missing.fSegment != missing.fOther) { + missing.fSegment->addTPair(missing.fT, missing.fOther, missing.fOtherT, false, + missing.fPt); + } + } + // OPTIMIZE: consolidate to avoid multiple calls to fix index + for (int index = 0; index < missingCount; ++index) { + MissingSpan& missing = missingSpans[index]; + missing.fSegment->fixOtherTIndex(); + missing.fOther->fixOtherTIndex(); } - --this->fCount; } -double SkOpSegment::distSq(double t, SkOpAngle* oppAngle) { - SkDPoint testPt = this->dPtAtT(t); - SkDLine testPerp = {{ testPt, testPt }}; - SkDVector slope = this->dSlopeAtT(t); - testPerp[1].fX += slope.fY; - testPerp[1].fY -= slope.fX; - SkIntersections i; - SkOpSegment* oppSegment = oppAngle->segment(); - int oppPtCount = SkPathOpsVerbToPoints(oppSegment->verb()); - (*CurveIntersectRay[oppPtCount])(oppSegment->pts(), testPerp, &i); - double closestDistSq = SK_ScalarInfinity; - for (int index = 0; index < i.used(); ++index) { - if (!between(oppAngle->start()->t(), i[0][index], oppAngle->end()->t())) { +bool SkOpSegment::coincidentSmall(const SkPoint& pt, double t, const SkOpSegment* other) const { + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = this->span(index); + if (span.fOther != other) { + continue; + } + if (span.fPt == pt) { + continue; + } + if (!AlmostEqualUlps(span.fPt, pt)) { continue; } - double testDistSq = testPt.distanceSquared(i.pt(index)); - if (closestDistSq > testDistSq) { - closestDistSq = testDistSq; + if (fVerb != SkPath::kCubic_Verb) { + return true; } + double tInterval = t - span.fT; + double tMid = t - tInterval / 2; + SkDPoint midPt = dcubic_xy_at_t(fPts, tMid); + return midPt.approximatelyEqual(xyAtT(t)); } - return closestDistSq; + return false; +} + +bool SkOpSegment::findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* other, int oStart, + int oEnd, int step, SkPoint* startPt, SkPoint* endPt, double* endT) const { + SkASSERT(span->fT == 0 || span->fT == 1); + SkASSERT(span->fOtherT == 0 || span->fOtherT == 1); + const SkOpSpan* otherSpan = &other->span(oEnd); + double refT = otherSpan->fT; + const SkPoint& refPt = otherSpan->fPt; + const SkOpSpan* lastSpan = &other->span(step > 0 ? other->count() - 1 : 0); + do { + const SkOpSegment* match = span->fOther; + if (match == otherSpan->fOther) { + // find start of respective spans and see if both have winding + int startIndex, endIndex; + if (span->fOtherT == 1) { + endIndex = span->fOtherIndex; + startIndex = match->nextExactSpan(endIndex, -1); + } else { + startIndex = span->fOtherIndex; + endIndex = match->nextExactSpan(startIndex, 1); + } + const SkOpSpan& startSpan = match->span(startIndex); + if (startSpan.fWindValue != 0) { + // draw ray from endSpan.fPt perpendicular to end tangent and measure distance + // to other segment. + const SkOpSpan& endSpan = match->span(endIndex); + SkDLine ray; + SkVector dxdy; + if (span->fOtherT == 1) { + ray.fPts[0].set(startSpan.fPt); + dxdy = match->dxdy(startIndex); + } else { + ray.fPts[0].set(endSpan.fPt); + dxdy = match->dxdy(endIndex); + } + ray.fPts[1].fX = ray.fPts[0].fX + dxdy.fY; + ray.fPts[1].fY = ray.fPts[0].fY - dxdy.fX; + SkIntersections i; + int roots = (i.*CurveRay[SkPathOpsVerbToPoints(other->verb())])(other->pts(), ray); + for (int index = 0; index < roots; ++index) { + if (ray.fPts[0].approximatelyEqual(i.pt(index))) { + double matchMidT = (match->span(startIndex).fT + + match->span(endIndex).fT) / 2; + SkPoint matchMidPt = match->ptAtT(matchMidT); + double otherMidT = (i[0][index] + other->span(oStart).fT) / 2; + SkPoint otherMidPt = other->ptAtT(otherMidT); + if (SkDPoint::ApproximatelyEqual(matchMidPt, otherMidPt)) { + *startPt = startSpan.fPt; + *endPt = endSpan.fPt; + *endT = endSpan.fT; + return true; + } + } + } + } + return false; + } + if (otherSpan == lastSpan) { + break; + } + otherSpan += step; + } while (otherSpan->fT == refT || otherSpan->fPt == refPt); + return false; +} + +int SkOpSegment::findEndSpan(int endIndex) const { + const SkOpSpan* span = &fTs[--endIndex]; + const SkPoint& lastPt = span->fPt; + double endT = span->fT; + do { + span = &fTs[--endIndex]; + } while (SkDPoint::ApproximatelyEqual(span->fPt, lastPt) && (span->fT == endT || span->fTiny)); + return endIndex + 1; } /* @@ -789,57 +3029,71 @@ double SkOpSegment::distSq(double t, SkOpAngle* oppAngle) { The Opp variable name part designates that the value is for the Opposite operator. Opposite values result from combining coincident spans. */ -SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** nextStart, - SkOpSpanBase** nextEnd, bool* unsortable, SkPathOp op, int xorMiMask, int xorSuMask) { - SkOpSpanBase* start = *nextStart; - SkOpSpanBase* end = *nextEnd; - SkASSERT(start != end); - int step = start->step(end); - SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart - if (other) { +SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd, + bool* unsortable, SkPathOp op, const int xorMiMask, + const int xorSuMask) { + const int startIndex = *nextStart; + const int endIndex = *nextEnd; + SkASSERT(startIndex != endIndex); + SkDEBUGCODE(const int count = fTs.count()); + SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); + int step = SkSign32(endIndex - startIndex); + *nextStart = startIndex; + SkOpSegment* other = isSimple(nextStart, &step); + if (other) + { // mark the smaller of startIndex, endIndex done, and all adjacent // spans with the same T value (but not 'other' spans) #if DEBUG_WINDING SkDebugf("%s simple\n", __FUNCTION__); #endif - SkOpSpan* startSpan = start->starter(end); - if (startSpan->done()) { + int min = SkMin32(startIndex, endIndex); + if (fTs[min].fDone) { + return NULL; + } + markDoneBinary(min); + double startT = other->fTs[*nextStart].fT; + *nextEnd = *nextStart; + do { + *nextEnd += step; + } while (precisely_zero(startT - other->fTs[*nextEnd].fT)); + SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count()); + if (other->isTiny(SkMin32(*nextStart, *nextEnd))) { + *unsortable = true; return NULL; } - markDone(startSpan); - *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); return other; } - SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); - SkASSERT(endNear == end); // is this ever not end? - SkASSERT(endNear); - SkASSERT(start != endNear); - SkASSERT((start->t() < endNear->t()) ^ (step < 0)); + const int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); + SkASSERT(startIndex - endIndex != 0); + SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); // more than one viable candidate -- measure angles to find best - int calcWinding = computeSum(start, endNear, SkOpAngle::kBinaryOpp); + + int calcWinding = computeSum(startIndex, end, SkOpAngle::kBinaryOpp); bool sortable = calcWinding != SK_NaN32; if (!sortable) { *unsortable = true; - markDone(start->starter(end)); + markDoneBinary(SkMin32(startIndex, endIndex)); return NULL; } - SkOpAngle* angle = this->spanToAngle(end, start); + SkOpAngle* angle = spanToAngle(end, startIndex); if (angle->unorderable()) { *unsortable = true; - markDone(start->starter(end)); + markDoneBinary(SkMin32(startIndex, endIndex)); return NULL; } #if DEBUG_SORT SkDebugf("%s\n", __FUNCTION__); angle->debugLoop(); #endif - int sumMiWinding = updateWinding(end, start); + int sumMiWinding = updateWinding(endIndex, startIndex); if (sumMiWinding == SK_MinS32) { *unsortable = true; - markDone(start->starter(end)); + markDoneBinary(SkMin32(startIndex, endIndex)); return NULL; } - int sumSuWinding = updateOppWinding(end, start); + int sumSuWinding = updateOppWinding(endIndex, startIndex); if (operand()) { SkTSwap<int>(sumMiWinding, sumSuWinding); } @@ -856,6 +3110,11 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBa if (activeAngle) { ++activeCount; if (!foundAngle || (foundDone && activeCount & 1)) { + if (nextSegment->isTiny(nextAngle)) { + *unsortable = true; + markDoneBinary(SkMin32(startIndex, endIndex)); + return NULL; + } foundAngle = nextAngle; foundDone = nextSegment->done(nextAngle); } @@ -863,24 +3122,30 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBa if (nextSegment->done()) { continue; } + if (nextSegment->isTiny(nextAngle)) { + continue; + } if (!activeAngle) { - (void) nextSegment->markAndChaseDone(nextAngle->start(), nextAngle->end()); + (void) nextSegment->markAndChaseDoneBinary(nextAngle->start(), nextAngle->end()); } - SkOpSpanBase* last = nextAngle->lastMarked(); + SkOpSpan* last = nextAngle->lastMarked(); if (last) { SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last)); *chase->append() = last; #if DEBUG_WINDING - SkDebugf("%s chase.append segment=%d span=%d", __FUNCTION__, - last->segment()->debugID(), last->debugID()); - if (!last->final()) { - SkDebugf(" windSum=%d", last->upCast()->windSum()); - } - SkDebugf("\n"); + SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__, + last->fOther->fTs[last->fOtherIndex].fOther->debugID(), last->fWindSum, + last->fSmall); #endif } } while ((nextAngle = nextAngle->next()) != angle); - start->segment()->markDone(start->starter(end)); +#if DEBUG_ANGLE + if (foundAngle) { + foundAngle->debugSameAs(foundAngle); + } +#endif + + markDoneBinary(SkMin32(startIndex, endIndex)); if (!foundAngle) { return NULL; } @@ -894,55 +3159,62 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBa return nextSegment; } -SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpanBase*>* chase, - SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable) { - SkOpSpanBase* start = *nextStart; - SkOpSpanBase* end = *nextEnd; - SkASSERT(start != end); - int step = start->step(end); - SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart - if (other) { +SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, + int* nextEnd, bool* unsortable) { + const int startIndex = *nextStart; + const int endIndex = *nextEnd; + SkASSERT(startIndex != endIndex); + SkDEBUGCODE(const int count = fTs.count()); + SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); + int step = SkSign32(endIndex - startIndex); + *nextStart = startIndex; + SkOpSegment* other = isSimple(nextStart, &step); + if (other) + { // mark the smaller of startIndex, endIndex done, and all adjacent // spans with the same T value (but not 'other' spans) #if DEBUG_WINDING SkDebugf("%s simple\n", __FUNCTION__); #endif - SkOpSpan* startSpan = start->starter(end); - if (startSpan->done()) { + int min = SkMin32(startIndex, endIndex); + if (fTs[min].fDone) { + return NULL; + } + markDoneUnary(min); + double startT = other->fTs[*nextStart].fT; + *nextEnd = *nextStart; + do { + *nextEnd += step; + } while (precisely_zero(startT - other->fTs[*nextEnd].fT)); + SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count()); + if (other->isTiny(SkMin32(*nextStart, *nextEnd))) { + *unsortable = true; return NULL; } - markDone(startSpan); - *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); return other; } - SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); - SkASSERT(endNear == end); // is this ever not end? - SkASSERT(endNear); - SkASSERT(start != endNear); - SkASSERT((start->t() < endNear->t()) ^ (step < 0)); + const int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); + SkASSERT(startIndex - endIndex != 0); + SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); // more than one viable candidate -- measure angles to find best - int calcWinding = computeSum(start, endNear, SkOpAngle::kUnaryWinding); + + int calcWinding = computeSum(startIndex, end, SkOpAngle::kUnaryWinding); bool sortable = calcWinding != SK_NaN32; if (!sortable) { *unsortable = true; - markDone(start->starter(end)); - return NULL; - } - SkOpAngle* angle = this->spanToAngle(end, start); - if (angle->unorderable()) { - *unsortable = true; - markDone(start->starter(end)); + markDoneUnary(SkMin32(startIndex, endIndex)); return NULL; } + SkOpAngle* angle = spanToAngle(end, startIndex); #if DEBUG_SORT SkDebugf("%s\n", __FUNCTION__); angle->debugLoop(); #endif - int sumWinding = updateWinding(end, start); + int sumWinding = updateWinding(endIndex, startIndex); SkOpAngle* nextAngle = angle->next(); const SkOpAngle* foundAngle = NULL; bool foundDone = false; - // iterate through the angle, and compute everyone's winding SkOpSegment* nextSegment; int activeCount = 0; do { @@ -952,6 +3224,11 @@ SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpanBase*>* chase, if (activeAngle) { ++activeCount; if (!foundAngle || (foundDone && activeCount & 1)) { + if (nextSegment->isTiny(nextAngle)) { + *unsortable = true; + markDoneUnary(SkMin32(startIndex, endIndex)); + return NULL; + } foundAngle = nextAngle; foundDone = nextSegment->done(nextAngle); } @@ -959,24 +3236,24 @@ SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpanBase*>* chase, if (nextSegment->done()) { continue; } + if (nextSegment->isTiny(nextAngle)) { + continue; + } if (!activeAngle) { - (void) nextSegment->markAndChaseDone(nextAngle->start(), nextAngle->end()); + nextSegment->markAndChaseDoneUnary(nextAngle->start(), nextAngle->end()); } - SkOpSpanBase* last = nextAngle->lastMarked(); + SkOpSpan* last = nextAngle->lastMarked(); if (last) { SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last)); *chase->append() = last; #if DEBUG_WINDING - SkDebugf("%s chase.append segment=%d span=%d", __FUNCTION__, - last->segment()->debugID(), last->debugID()); - if (!last->final()) { - SkDebugf(" windSum=%d", last->upCast()->windSum()); - } - SkDebugf("\n"); + SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__, + last->fOther->fTs[last->fOtherIndex].fOther->debugID(), last->fWindSum, + last->fSmall); #endif } } while ((nextAngle = nextAngle->next()) != angle); - start->segment()->markDone(start->starter(end)); + markDoneUnary(SkMin32(startIndex, endIndex)); if (!foundAngle) { return NULL; } @@ -990,39 +3267,57 @@ SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpanBase*>* chase, return nextSegment; } -SkOpSegment* SkOpSegment::findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, - bool* unsortable) { - SkOpSpanBase* start = *nextStart; - SkOpSpanBase* end = *nextEnd; - SkASSERT(start != end); - int step = start->step(end); - SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart - if (other) { - // mark the smaller of startIndex, endIndex done, and all adjacent - // spans with the same T value (but not 'other' spans) +SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsortable) { + const int startIndex = *nextStart; + const int endIndex = *nextEnd; + SkASSERT(startIndex != endIndex); + SkDEBUGCODE(int count = fTs.count()); + SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); + int step = SkSign32(endIndex - startIndex); +// Detect cases where all the ends canceled out (e.g., +// there is no angle) and therefore there's only one valid connection + *nextStart = startIndex; + SkOpSegment* other = isSimple(nextStart, &step); + if (other) + { #if DEBUG_WINDING SkDebugf("%s simple\n", __FUNCTION__); #endif - SkOpSpan* startSpan = start->starter(end); - if (startSpan->done()) { + int min = SkMin32(startIndex, endIndex); + if (fTs[min].fDone) { return NULL; } - markDone(startSpan); - *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); + markDone(min, 1); + double startT = other->fTs[*nextStart].fT; + // FIXME: I don't know why the logic here is difference from the winding case + SkDEBUGCODE(bool firstLoop = true;) + if ((approximately_less_than_zero(startT) && step < 0) + || (approximately_greater_than_one(startT) && step > 0)) { + step = -step; + SkDEBUGCODE(firstLoop = false;) + } + do { + *nextEnd = *nextStart; + do { + *nextEnd += step; + } while (precisely_zero(startT - other->fTs[*nextEnd].fT)); + if (other->fTs[SkMin32(*nextStart, *nextEnd)].fWindValue) { + break; + } + SkASSERT(firstLoop); + SkDEBUGCODE(firstLoop = false;) + step = -step; + } while (true); + SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count()); return other; } - SkDEBUGCODE(SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() \ - : (*nextStart)->prev()); - SkASSERT(endNear == end); // is this ever not end? - SkASSERT(endNear); - SkASSERT(start != endNear); - SkASSERT((start->t() < endNear->t()) ^ (step < 0)); - SkOpAngle* angle = this->spanToAngle(end, start); - if (angle->unorderable()) { - *unsortable = true; - markDone(start->starter(end)); - return NULL; - } + SkASSERT(startIndex - endIndex != 0); + SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); + // parallel block above with presorted version + int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); + SkOpAngle* angle = spanToAngle(end, startIndex); + SkASSERT(angle); #if DEBUG_SORT SkDebugf("%s\n", __FUNCTION__); angle->debugLoop(); @@ -1030,13 +3325,16 @@ SkOpSegment* SkOpSegment::findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** n SkOpAngle* nextAngle = angle->next(); const SkOpAngle* foundAngle = NULL; bool foundDone = false; - // iterate through the angle, and compute everyone's winding SkOpSegment* nextSegment; int activeCount = 0; do { nextSegment = nextAngle->segment(); ++activeCount; if (!foundAngle || (foundDone && activeCount & 1)) { + if (nextSegment->isTiny(nextAngle)) { + *unsortable = true; + return NULL; + } foundAngle = nextAngle; if (!(foundDone = nextSegment->done(nextAngle))) { break; @@ -1044,7 +3342,7 @@ SkOpSegment* SkOpSegment::findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** n } nextAngle = nextAngle->next(); } while (nextAngle != angle); - start->segment()->markDone(start->starter(end)); + markDone(SkMin32(startIndex, endIndex), 1); if (!foundAngle) { return NULL; } @@ -1058,39 +3356,105 @@ SkOpSegment* SkOpSegment::findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** n return nextSegment; } -SkOpSegment* SkOpSegment::findTop(bool firstPass, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, - bool* unsortable, SkChunkAlloc* allocator) { +int SkOpSegment::findStartSpan(int startIndex) const { + int index = startIndex; + const SkOpSpan* span = &fTs[index]; + const SkPoint& firstPt = span->fPt; + double firstT = span->fT; + const SkOpSpan* prior; + do { + prior = span; + span = &fTs[++index]; + } while (SkDPoint::ApproximatelyEqual(span->fPt, firstPt) + && (span->fT == firstT || prior->fTiny)); + return index; +} + +int SkOpSegment::findExactT(double t, const SkOpSegment* match) const { + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (span.fT == t && span.fOther == match) { + return index; + } + } + SkASSERT(0); + return -1; +} + + + +int SkOpSegment::findOtherT(double t, const SkOpSegment* match) const { + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (span.fOtherT == t && span.fOther == match) { + return index; + } + } + return -1; +} + +int SkOpSegment::findT(double t, const SkPoint& pt, const SkOpSegment* match) const { + int count = this->count(); + // prefer exact matches over approximate matches + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (span.fT == t && span.fOther == match) { + return index; + } + } + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (approximately_equal_orderable(span.fT, t) && span.fOther == match) { + return index; + } + } + // Usually, the pair of ts are an exact match. It's possible that the t values have + // been adjusted to make multiple intersections align. In this rare case, look for a + // matching point / match pair instead. + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (span.fPt == pt && span.fOther == match) { + return index; + } + } + SkASSERT(0); + return -1; +} + +SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsortable, + bool firstPass) { // iterate through T intersections and return topmost // topmost tangent from y-min to first pt is closer to horizontal SkASSERT(!done()); - SkOpSpanBase* firstT = NULL; - (void) this->activeLeftTop(&firstT); - if (!firstT) { + int firstT = -1; + /* SkPoint topPt = */ activeLeftTop(&firstT); + if (firstT < 0) { *unsortable = !firstPass; - firstT = &fHead; - while (firstT->upCast()->done()) { - firstT = firstT->upCast()->next(); + firstT = 0; + while (fTs[firstT].fDone) { + SkASSERT(firstT < fTs.count()); + ++firstT; } - *startPtr = firstT; - *endPtr = firstT->upCast()->next(); + *tIndexPtr = firstT; + *endIndexPtr = nextExactSpan(firstT, 1); return this; } // sort the edges to find the leftmost int step = 1; - SkOpSpanBase* end; - if (firstT->final() || firstT->upCast()->done()) { + int end; + if (span(firstT).fDone || (end = nextSpan(firstT, step)) == -1) { step = -1; - end = firstT->prev(); - SkASSERT(end); - } else { - end = firstT->upCast()->next(); + end = nextSpan(firstT, step); + SkASSERT(end != -1); } // if the topmost T is not on end, or is three-way or more, find left // look for left-ness from tLeft to firstT (matching y of other) - SkASSERT(firstT != end); + SkASSERT(firstT - end != 0); SkOpAngle* markAngle = spanToAngle(firstT, end); if (!markAngle) { - markAngle = addSingletonAngles(step, allocator); + markAngle = addSingletonAngles(step); } markAngle->markStops(); const SkOpAngle* baseAngle = markAngle->next() == markAngle && !isVertical() ? markAngle @@ -1103,7 +3467,7 @@ SkOpSegment* SkOpSegment::findTop(bool firstPass, SkOpSpanBase** startPtr, SkOpS const SkOpAngle* angle = baseAngle; do { if (!angle->unorderable()) { - const SkOpSegment* next = angle->segment(); + SkOpSegment* next = angle->segment(); SkPathOpsBounds bounds; next->subDivideBounds(angle->end(), angle->start(), &bounds); bool nearSame = AlmostEqualUlps(top, bounds.top()); @@ -1131,10 +3495,9 @@ SkOpSegment* SkOpSegment::findTop(bool firstPass, SkOpSpanBase** startPtr, SkOpS *unsortable = angle->unorderable(); if (firstPass || !*unsortable) { leftSegment = angle->segment(); - *startPtr = angle->end(); - *endPtr = angle->start(); - const SkOpSpan* firstSpan = (*startPtr)->starter(*endPtr); - if (!firstSpan->done()) { + *tIndexPtr = angle->end(); + *endIndexPtr = angle->start(); + if (!leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fDone) { break; } } @@ -1145,52 +3508,157 @@ SkOpSegment* SkOpSegment::findTop(bool firstPass, SkOpSpanBase** startPtr, SkOpS return NULL; } if (leftSegment->verb() >= SkPath::kQuad_Verb) { - SkOpSpanBase* start = *startPtr; - SkOpSpanBase* end = *endPtr; + const int tIndex = *tIndexPtr; + const int endIndex = *endIndexPtr; bool swap; - if (!leftSegment->clockwise(start, end, &swap)) { + if (!leftSegment->clockwise(tIndex, endIndex, &swap)) { #if DEBUG_SWAP_TOP - SkDebugf("%s swap=%d inflections=%d monotonic=%d\n", + SkDebugf("%s swap=%d inflections=%d serpentine=%d controlledbyends=%d monotonic=%d\n", __FUNCTION__, - swap, leftSegment->debugInflections(start, end), - leftSegment->monotonicInY(start, end)); + swap, leftSegment->debugInflections(tIndex, endIndex), + leftSegment->serpentine(tIndex, endIndex), + leftSegment->controlsContainedByEnds(tIndex, endIndex), + leftSegment->monotonicInY(tIndex, endIndex)); #endif if (swap) { // FIXME: I doubt it makes sense to (necessarily) swap if the edge was not the first // sorted but merely the first not already processed (i.e., not done) - SkTSwap(*startPtr, *endPtr); + SkTSwap(*tIndexPtr, *endIndexPtr); } } } + SkASSERT(!leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fTiny); return leftSegment; } -SkOpGlobalState* SkOpSegment::globalState() const { - return contour()->globalState(); +int SkOpSegment::firstActive(int tIndex) const { + while (fTs[tIndex].fTiny) { + SkASSERT(!isCanceled(tIndex)); + ++tIndex; + } + return tIndex; +} + +// FIXME: not crazy about this +// when the intersections are performed, the other index is into an +// incomplete array. As the array grows, the indices become incorrect +// while the following fixes the indices up again, it isn't smart about +// skipping segments whose indices are already correct +// assuming we leave the code that wrote the index in the first place +// FIXME: if called after remove, this needs to correct tiny +void SkOpSegment::fixOtherTIndex() { + int iCount = fTs.count(); + for (int i = 0; i < iCount; ++i) { + SkOpSpan& iSpan = fTs[i]; + double oT = iSpan.fOtherT; + SkOpSegment* other = iSpan.fOther; + int oCount = other->fTs.count(); + SkDEBUGCODE(iSpan.fOtherIndex = -1); + for (int o = 0; o < oCount; ++o) { + SkOpSpan& oSpan = other->fTs[o]; + if (oT == oSpan.fT && this == oSpan.fOther && oSpan.fOtherT == iSpan.fT) { + iSpan.fOtherIndex = o; + oSpan.fOtherIndex = i; + break; + } + } + SkASSERT(iSpan.fOtherIndex >= 0); + } +} + +bool SkOpSegment::inCoincidentSpan(double t, const SkOpSegment* other) const { + int foundEnds = 0; + int count = this->count(); + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = this->span(index); + if (span.fCoincident) { + foundEnds |= (span.fOther == other) << ((t > span.fT) + (t >= span.fT)); + } + } + SkASSERT(foundEnds != 7); + return foundEnds == 0x3 || foundEnds == 0x5 || foundEnds == 0x6; // two bits set +} + +bool SkOpSegment::inconsistentAngle(int maxWinding, int sumWinding, int oppMaxWinding, + int oppSumWinding, const SkOpAngle* angle) const { + SkASSERT(angle->segment() == this); + if (UseInnerWinding(maxWinding, sumWinding)) { + maxWinding = sumWinding; + } + if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) { + oppMaxWinding = oppSumWinding; + } + return inconsistentWinding(angle, maxWinding, oppMaxWinding); } -void SkOpSegment::init(SkPoint pts[], SkOpContour* contour, SkPath::Verb verb) { - fContour = contour; - fNext = NULL; +bool SkOpSegment::inconsistentWinding(const SkOpAngle* angle, int winding, + int oppWinding) const { + int index = angle->start(); + int endIndex = angle->end(); + int min = SkMin32(index, endIndex); + int step = SkSign32(endIndex - index); + if (inconsistentWinding(min, winding, oppWinding)) { + return true; + } + const SkOpSegment* other = this; + while ((other = other->nextChase(&index, &step, &min, NULL))) { + if (other->fTs[min].fWindSum != SK_MinS32) { + break; + } + if (fOperand == other->fOperand) { + if (other->inconsistentWinding(min, winding, oppWinding)) { + return true; + } + } else { + if (other->inconsistentWinding(min, oppWinding, winding)) { + return true; + } + } + } + return false; +} + +bool SkOpSegment::inconsistentWinding(int index, int winding, int oppWinding) const { + SkASSERT(winding || oppWinding); + double referenceT = this->span(index).fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + if (inconsistentWinding(__FUNCTION__, lesser, winding, oppWinding)) { + return true; + } + } + do { + if (inconsistentWinding(__FUNCTION__, index, winding, oppWinding)) { + return true; + } + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); + return false; +} + +bool SkOpSegment::inconsistentWinding(const char* funName, int tIndex, int winding, + int oppWinding) const { + const SkOpSpan& span = this->span(tIndex); + if (span.fDone && !span.fSmall) { + return false; + } + return (span.fWindSum != SK_MinS32 && span.fWindSum != winding) + || (span.fOppSum != SK_MinS32 && span.fOppSum != oppWinding); +} + +void SkOpSegment::init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd) { + fDoneSpans = 0; + fOperand = operand; + fXor = evenOdd; fPts = pts; fVerb = verb; - fCount = 0; - fDoneCount = 0; - fVisited = false; - SkOpSpan* zeroSpan = &fHead; - zeroSpan->init(this, NULL, 0, fPts[0]); - SkOpSpanBase* oneSpan = &fTail; - zeroSpan->setNext(oneSpan); - oneSpan->initBase(this, zeroSpan, 1, fPts[SkPathOpsVerbToPoints(fVerb)]); - PATH_OPS_DEBUG_CODE(fID = globalState()->nextSegmentID()); -} - -void SkOpSegment::initWinding(SkOpSpanBase* start, SkOpSpanBase* end, - SkOpAngle::IncludeType angleIncludeType) { - int local = SkOpSegment::SpanSign(start, end); + fLoop = fMultiples = fSmall = fTiny = false; +} + +void SkOpSegment::initWinding(int start, int end, SkOpAngle::IncludeType angleIncludeType) { + int local = spanSign(start, end); SkDEBUGCODE(bool success); if (angleIncludeType == SkOpAngle::kBinarySingle) { - int oppLocal = SkOpSegment::OppSign(start, end); + int oppLocal = oppSign(start, end); SkDEBUGCODE(success =) markAndChaseWinding(start, end, local, oppLocal, NULL); // OPTIMIZATION: the reverse mark and chase could skip the first marking SkDEBUGCODE(success |=) markAndChaseWinding(end, start, local, oppLocal, NULL); @@ -1211,13 +3679,12 @@ If there was a winding, then it may or may not need adjusting. If the span the w from has the same x direction as this span, the winding should change. If the dx is opposite, then the same winding is shared by both. */ -bool SkOpSegment::initWinding(SkOpSpanBase* start, SkOpSpanBase* end, double tHit, - int winding, SkScalar hitDx, int oppWind, SkScalar hitOppDx) { - SkASSERT(this == start->segment()); +bool SkOpSegment::initWinding(int start, int end, double tHit, int winding, SkScalar hitDx, + int oppWind, SkScalar hitOppDx) { SkASSERT(hitDx || !winding); SkScalar dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX; -// SkASSERT(dx); - int windVal = start->starter(end)->windValue(); + SkASSERT(dx); + int windVal = windValue(SkMin32(start, end)); #if DEBUG_WINDING_AT_T SkDebugf("%s id=%d oldWinding=%d hitDx=%c dx=%c windVal=%d", __FUNCTION__, debugID(), winding, hitDx ? hitDx > 0 ? '+' : '-' : '0', dx > 0 ? '+' : '-', windVal); @@ -1226,9 +3693,9 @@ bool SkOpSegment::initWinding(SkOpSpanBase* start, SkOpSpanBase* end, double tHi if (abs(winding) < abs(sideWind)) { winding = sideWind; } - SkDEBUGCODE(int oppLocal = SkOpSegment::OppSign(start, end)); + SkDEBUGCODE(int oppLocal = oppSign(start, end)); SkASSERT(hitOppDx || !oppWind || !oppLocal); - int oppWindVal = start->starter(end)->oppValue(); + int oppWindVal = oppValue(SkMin32(start, end)); if (!oppWind) { oppWind = dx < 0 ? oppWindVal : -oppWindVal; } else if (hitOppDx * dx >= 0) { @@ -1247,57 +3714,149 @@ bool SkOpSegment::initWinding(SkOpSpanBase* start, SkOpSpanBase* end, double tHi return success; } -bool SkOpSegment::isClose(double t, const SkOpSegment* opp) const { - SkDPoint cPt = this->dPtAtT(t); - int pts = SkPathOpsVerbToPoints(this->verb()); - SkDVector dxdy = (*CurveDSlopeAtT[pts])(this->pts(), t); - SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; - SkIntersections i; - int oppPts = SkPathOpsVerbToPoints(opp->verb()); - (*CurveIntersectRay[oppPts])(opp->pts(), perp, &i); - int used = i.used(); - for (int index = 0; index < used; ++index) { - if (cPt.roughlyEqual(i.pt(index))) { - return true; +bool SkOpSegment::inLoop(const SkOpAngle* baseAngle, int spanCount, int* indexPtr) const { + if (!baseAngle->inLoop()) { + return false; + } + int index = *indexPtr; + SkOpAngle* from = fTs[index].fFromAngle; + SkOpAngle* to = fTs[index].fToAngle; + while (++index < spanCount) { + SkOpAngle* nextFrom = fTs[index].fFromAngle; + SkOpAngle* nextTo = fTs[index].fToAngle; + if (from != nextFrom || to != nextTo) { + break; + } + } + *indexPtr = index; + return true; +} + +// OPTIMIZE: successive calls could start were the last leaves off +// or calls could specialize to walk forwards or backwards +bool SkOpSegment::isMissing(double startT, const SkPoint& pt) const { + int tCount = fTs.count(); + for (int index = 0; index < tCount; ++index) { + const SkOpSpan& span = fTs[index]; + if (approximately_zero(startT - span.fT) && pt == span.fPt) { + return false; } } + return true; +} + + +SkOpSegment* SkOpSegment::isSimple(int* end, int* step) { + return nextChase(end, step, NULL, NULL); +} + +bool SkOpSegment::isTiny(const SkOpAngle* angle) const { + int start = angle->start(); + int end = angle->end(); + const SkOpSpan& mSpan = fTs[SkMin32(start, end)]; + return mSpan.fTiny; +} + +bool SkOpSegment::isTiny(int index) const { + return fTs[index].fTiny; +} + +// look pair of active edges going away from coincident edge +// one of them should be the continuation of other +// if both are active, look to see if they both the connect to another coincident pair +// if at least one is a line, then make the pair coincident +// if neither is a line, test for coincidence +bool SkOpSegment::joinCoincidence(SkOpSegment* other, double otherT, const SkPoint& otherPt, + int step, bool cancel) { + int otherTIndex = other->findT(otherT, otherPt, this); + int next = other->nextExactSpan(otherTIndex, step); + int otherMin = SkMin32(otherTIndex, next); + int otherWind = other->span(otherMin).fWindValue; + if (otherWind == 0) { + return false; + } + if (next < 0) { + return false; // can happen if t values were adjusted but coincident ts were not + } + int tIndex = 0; + do { + SkOpSpan* test = &fTs[tIndex]; + SkASSERT(test->fT == 0); + if (test->fOther == other || test->fOtherT != 1) { + continue; + } + SkPoint startPt, endPt; + double endT; + if (findCoincidentMatch(test, other, otherTIndex, next, step, &startPt, &endPt, &endT)) { + SkOpSegment* match = test->fOther; + if (cancel) { + match->addTCancel(startPt, endPt, other); + } else { + if (!match->addTCoincident(startPt, endPt, endT, other)) { + return false; + } + } + return true; + } + } while (fTs[++tIndex].fT == 0); return false; } -bool SkOpSegment::isXor() const { - return fContour->isXor(); +// this span is excluded by the winding rule -- chase the ends +// as long as they are unambiguous to mark connections as done +// and give them the same winding value + +SkOpSpan* SkOpSegment::markAndChaseDoneBinary(int index, int endIndex) { + int step = SkSign32(endIndex - index); + int min = SkMin32(index, endIndex); + markDoneBinary(min); + SkOpSpan* last = NULL; + SkOpSegment* other = this; + while ((other = other->nextChase(&index, &step, &min, &last))) { + if (other->done()) { + SkASSERT(!last); + break; + } + other->markDoneBinary(min); + } + return last; } -SkOpSpanBase* SkOpSegment::markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end) { - int step = start->step(end); - SkOpSpan* minSpan = start->starter(end); - markDone(minSpan); - SkOpSpanBase* last = NULL; +SkOpSpan* SkOpSegment::markAndChaseDoneUnary(int index, int endIndex) { + int step = SkSign32(endIndex - index); + int min = SkMin32(index, endIndex); + markDoneUnary(min); + SkOpSpan* last = NULL; SkOpSegment* other = this; - while ((other = other->nextChase(&start, &step, &minSpan, &last))) { + while ((other = other->nextChase(&index, &step, &min, &last))) { if (other->done()) { SkASSERT(!last); break; } - other->markDone(minSpan); + other->markDoneUnary(min); } return last; } -bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, - SkOpSpanBase** lastPtr) { - SkOpSpan* spanStart = start->starter(end); - int step = start->step(end); - bool success = markWinding(spanStart, winding); - SkOpSpanBase* last = NULL; +bool SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, int winding, SkOpSpan** lastPtr) { + int index = angle->start(); + int endIndex = angle->end(); + return markAndChaseWinding(index, endIndex, winding, lastPtr); +} + +bool SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding, SkOpSpan** lastPtr) { + int min = SkMin32(index, endIndex); + int step = SkSign32(endIndex - index); + bool success = markWinding(min, winding); + SkOpSpan* last = NULL; SkOpSegment* other = this; - while ((other = other->nextChase(&start, &step, &spanStart, &last))) { - if (spanStart->windSum() != SK_MinS32) { - SkASSERT(spanStart->windSum() == winding); + while ((other = other->nextChase(&index, &step, &min, &last))) { + if (other->fTs[min].fWindSum != SK_MinS32) { + SkASSERT(other->fTs[min].fWindSum == winding || other->fTs[min].fLoop); SkASSERT(!last); break; } - (void) other->markWinding(spanStart, winding); + (void) other->markWinding(min, winding); } if (lastPtr) { *lastPtr = last; @@ -1305,32 +3864,37 @@ bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, in return success; } -bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, - int winding, int oppWinding, SkOpSpanBase** lastPtr) { - SkOpSpan* spanStart = start->starter(end); - int step = start->step(end); - bool success = markWinding(spanStart, winding, oppWinding); - SkOpSpanBase* last = NULL; +bool SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding, int oppWinding, + SkOpSpan** lastPtr) { + int min = SkMin32(index, endIndex); + int step = SkSign32(endIndex - index); + bool success = markWinding(min, winding, oppWinding); + SkOpSpan* last = NULL; SkOpSegment* other = this; - while ((other = other->nextChase(&start, &step, &spanStart, &last))) { - if (spanStart->windSum() != SK_MinS32) { - if (this->operand() == other->operand()) { - SkASSERT(spanStart->windSum() == winding); - if (spanStart->oppSum() != oppWinding) { - this->globalState()->setWindingFailed(); - return false; + while ((other = other->nextChase(&index, &step, &min, &last))) { + if (other->fTs[min].fWindSum != SK_MinS32) { +#ifdef SK_DEBUG + if (!other->fTs[min].fLoop) { + if (fOperand == other->fOperand) { +// FIXME: this is probably a bug -- rects4 asserts here +// SkASSERT(other->fTs[min].fWindSum == winding); +// FIXME: this is probably a bug -- rects3 asserts here +// SkASSERT(other->fTs[min].fOppSum == oppWinding); + } else { +// FIXME: this is probably a bug -- issue414409b asserts here +// SkASSERT(other->fTs[min].fWindSum == oppWinding); +// FIXME: this is probably a bug -- skpwww_joomla_org_23 asserts here +// SkASSERT(other->fTs[min].fOppSum == winding); } - } else { - SkASSERT(spanStart->windSum() == oppWinding); - SkASSERT(spanStart->oppSum() == winding); } SkASSERT(!last); +#endif break; } - if (this->operand() == other->operand()) { - (void) other->markWinding(spanStart, winding, oppWinding); + if (fOperand == other->fOperand) { + (void) other->markWinding(min, winding, oppWinding); } else { - (void) other->markWinding(spanStart, oppWinding, winding); + (void) other->markWinding(min, oppWinding, winding); } } if (lastPtr) { @@ -1339,29 +3903,33 @@ bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, return success; } -SkOpSpanBase* SkOpSegment::markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle) { +bool SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding, + SkOpSpan** lastPtr) { + int start = angle->start(); + int end = angle->end(); + return markAndChaseWinding(start, end, winding, oppWinding, lastPtr); +} + +SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle) { SkASSERT(angle->segment() == this); if (UseInnerWinding(maxWinding, sumWinding)) { maxWinding = sumWinding; } - SkOpSpanBase* last; - (void) markAndChaseWinding(angle->start(), angle->end(), maxWinding, &last); + SkOpSpan* last; + SkAssertResult(markAndChaseWinding(angle, maxWinding, &last)); #if DEBUG_WINDING if (last) { - SkDebugf("%s last seg=%d span=%d", __FUNCTION__, - last->segment()->debugID(), last->debugID()); - if (!last->final()) { - SkDebugf(" windSum="); - SkPathOpsDebug::WindingPrintf(last->upCast()->windSum()); - } - SkDebugf("\n"); + SkDebugf("%s last id=%d windSum=", __FUNCTION__, + last->fOther->fTs[last->fOtherIndex].fOther->debugID()); + SkPathOpsDebug::WindingPrintf(last->fWindSum); + SkDebugf(" small=%d\n", last->fSmall); } #endif return last; } -SkOpSpanBase* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding, - int oppSumWinding, const SkOpAngle* angle) { +SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding, + int oppSumWinding, const SkOpAngle* angle) { SkASSERT(angle->segment() == this); if (UseInnerWinding(maxWinding, sumWinding)) { maxWinding = sumWinding; @@ -1369,161 +3937,440 @@ SkOpSpanBase* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxW if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) { oppMaxWinding = oppSumWinding; } - SkOpSpanBase* last = NULL; + SkOpSpan* last; // caller doesn't require that this marks anything - (void) markAndChaseWinding(angle->start(), angle->end(), maxWinding, oppMaxWinding, &last); + (void) markAndChaseWinding(angle, maxWinding, oppMaxWinding, &last); #if DEBUG_WINDING if (last) { - SkDebugf("%s last segment=%d span=%d", __FUNCTION__, - last->segment()->debugID(), last->debugID()); - if (!last->final()) { - SkDebugf(" windSum="); - SkPathOpsDebug::WindingPrintf(last->upCast()->windSum()); - } - SkDebugf(" \n"); + SkDebugf("%s last id=%d windSum=", __FUNCTION__, + last->fOther->fTs[last->fOtherIndex].fOther->debugID()); + SkPathOpsDebug::WindingPrintf(last->fWindSum); + SkDebugf(" small=%d\n", last->fSmall); } #endif return last; } -void SkOpSegment::markDone(SkOpSpan* span) { - SkASSERT(this == span->segment()); - if (span->done()) { - return; +// FIXME: this should also mark spans with equal (x,y) +// This may be called when the segment is already marked done. While this +// wastes time, it shouldn't do any more than spin through the T spans. +// OPTIMIZATION: abort on first done found (assuming that this code is +// always called to mark segments done). +void SkOpSegment::markDone(int index, int winding) { + // SkASSERT(!done()); + SkASSERT(winding); + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneDone(__FUNCTION__, lesser, winding); } -#if DEBUG_MARK_DONE - debugShowNewWinding(__FUNCTION__, span, span->windSum(), span->oppSum()); -#endif - span->setDone(true); - ++fDoneCount; + do { + markOneDone(__FUNCTION__, index, winding); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); debugValidate(); } -bool SkOpSegment::markWinding(SkOpSpan* span, int winding) { - SkASSERT(this == span->segment()); - SkASSERT(winding); - if (span->done()) { +void SkOpSegment::markDoneBinary(int index) { + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneDoneBinary(__FUNCTION__, lesser); + } + do { + markOneDoneBinary(__FUNCTION__, index); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); + debugValidate(); +} + +void SkOpSegment::markDoneFinal(int index) { + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneDoneFinal(__FUNCTION__, lesser); + } + do { + markOneDoneFinal(__FUNCTION__, index); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); + debugValidate(); +} + +void SkOpSegment::markDoneUnary(int index) { + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneDoneUnary(__FUNCTION__, lesser); + } + do { + markOneDoneUnary(__FUNCTION__, index); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); + debugValidate(); +} + +void SkOpSegment::markOneDone(const char* funName, int tIndex, int winding) { + SkOpSpan* span; + (void) markOneWinding(funName, tIndex, winding, &span); // allowed to do nothing + if (span->fDone) { + return; + } + span->fDone = true; + ++fDoneSpans; +} + +void SkOpSegment::markOneDoneFinal(const char* funName, int tIndex) { + SkOpSpan* span = &fTs[tIndex]; + if (span->fDone) { + return; + } + span->fDone = true; + ++fDoneSpans; +} + +void SkOpSegment::markOneDoneBinary(const char* funName, int tIndex) { + SkOpSpan* span = verifyOneWinding(funName, tIndex); + if (!span) { + return; + } + SkASSERT(!span->fDone); + span->fDone = true; + ++fDoneSpans; +} + +void SkOpSegment::markOneDoneUnary(const char* funName, int tIndex) { + SkOpSpan* span = verifyOneWindingU(funName, tIndex); + if (!span) { + return; + } + if (span->fWindSum == SK_MinS32) { + SkDebugf("%s uncomputed\n", __FUNCTION__); + } + SkASSERT(!span->fDone); + span->fDone = true; + ++fDoneSpans; +} + +bool SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding, SkOpSpan** lastPtr) { + SkOpSpan* span = &fTs[tIndex]; + if (lastPtr) { + *lastPtr = span; + } + if (span->fDone && !span->fSmall) { return false; } #if DEBUG_MARK_DONE - debugShowNewWinding(__FUNCTION__, span, winding); + debugShowNewWinding(funName, *span, winding); #endif - span->setWindSum(winding); - debugValidate(); + SkASSERT(span->fWindSum == SK_MinS32 || span->fWindSum == winding); +#if DEBUG_LIMIT_WIND_SUM + SkASSERT(abs(winding) <= DEBUG_LIMIT_WIND_SUM); +#endif + span->fWindSum = winding; return true; } -bool SkOpSegment::markWinding(SkOpSpan* span, int winding, int oppWinding) { - SkASSERT(this == span->segment()); - SkASSERT(winding || oppWinding); - if (span->done()) { +bool SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding, + int oppWinding, SkOpSpan** lastPtr) { + SkOpSpan* span = &fTs[tIndex]; + if (span->fDone && !span->fSmall) { return false; } #if DEBUG_MARK_DONE - debugShowNewWinding(__FUNCTION__, span, winding, oppWinding); + debugShowNewWinding(funName, *span, winding, oppWinding); #endif - span->setWindSum(winding); - span->setOppSum(oppWinding); + SkASSERT(span->fWindSum == SK_MinS32 || span->fWindSum == winding); +#if DEBUG_LIMIT_WIND_SUM + SkASSERT(abs(winding) <= DEBUG_LIMIT_WIND_SUM); +#endif + span->fWindSum = winding; + SkASSERT(span->fOppSum == SK_MinS32 || span->fOppSum == oppWinding); +#if DEBUG_LIMIT_WIND_SUM + SkASSERT(abs(oppWinding) <= DEBUG_LIMIT_WIND_SUM); +#endif + span->fOppSum = oppWinding; debugValidate(); + if (lastPtr) { + *lastPtr = span; + } return true; } -bool SkOpSegment::match(const SkOpPtT* base, const SkOpSegment* testParent, double testT, - const SkPoint& testPt) const { - const SkOpSegment* baseParent = base->segment(); - if (this == baseParent && this == testParent && precisely_equal(base->fT, testT)) { - return true; +// from http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order +bool SkOpSegment::clockwise(int tStart, int tEnd, bool* swap) const { + SkASSERT(fVerb != SkPath::kLine_Verb); + SkPoint edge[4]; + subDivide(tStart, tEnd, edge); + int points = SkPathOpsVerbToPoints(fVerb); + double sum = (edge[0].fX - edge[points].fX) * (edge[0].fY + edge[points].fY); + bool sumSet = false; + if (fVerb == SkPath::kCubic_Verb) { + SkDCubic cubic; + cubic.set(edge); + double inflectionTs[2]; + int inflections = cubic.findInflections(inflectionTs); + // FIXME: this fixes cubicOp114 and breaks cubicOp58d + // the trouble is that cubics with inflections confuse whether the curve breaks towards + // or away, which in turn is used to determine if it is on the far right or left. + // Probably a totally different approach is in order. At one time I tried to project a + // horizontal ray to determine winding, but was confused by how to map the vertically + // oriented winding computation over. + if (0 && inflections) { + double tLo = this->span(tStart).fT; + double tHi = this->span(tEnd).fT; + double tLoStart = tLo; + for (int index = 0; index < inflections; ++index) { + if (between(tLo, inflectionTs[index], tHi)) { + tLo = inflectionTs[index]; + } + } + if (tLo != tLoStart && tLo != tHi) { + SkDPoint sub[2]; + sub[0] = cubic.ptAtT(tLo); + sub[1].set(edge[3]); + SkDPoint ctrl[2]; + SkDCubic::SubDivide(fPts, sub[0], sub[1], tLo, tHi, ctrl); + edge[0] = sub[0].asSkPoint(); + edge[1] = ctrl[0].asSkPoint(); + edge[2] = ctrl[1].asSkPoint(); + sum = (edge[0].fX - edge[3].fX) * (edge[0].fY + edge[3].fY); + } + } + SkScalar lesser = SkTMin<SkScalar>(edge[0].fY, edge[3].fY); + if (edge[1].fY < lesser && edge[2].fY < lesser) { + SkDLine tangent1 = {{ {edge[0].fX, edge[0].fY}, {edge[1].fX, edge[1].fY} }}; + SkDLine tangent2 = {{ {edge[2].fX, edge[2].fY}, {edge[3].fX, edge[3].fY} }}; + if (SkIntersections::Test(tangent1, tangent2)) { + SkPoint topPt = cubic_top(fPts, fTs[tStart].fT, fTs[tEnd].fT); + sum += (topPt.fX - edge[0].fX) * (topPt.fY + edge[0].fY); + sum += (edge[3].fX - topPt.fX) * (edge[3].fY + topPt.fY); + sumSet = true; + } + } } - if (!SkDPoint::ApproximatelyEqual(testPt, base->fPt)) { - return false; + if (!sumSet) { + for (int idx = 0; idx < points; ++idx){ + sum += (edge[idx + 1].fX - edge[idx].fX) * (edge[idx + 1].fY + edge[idx].fY); + } } - return !ptsDisjoint(base->fT, base->fPt, testT, testPt); -} - -static SkOpSegment* set_last(SkOpSpanBase** last, SkOpSpanBase* endSpan) { - if (last) { - *last = endSpan; + if (fVerb == SkPath::kCubic_Verb) { + SkDCubic cubic; + cubic.set(edge); + *swap = sum > 0 && !cubic.monotonicInY() && !cubic.serpentine(); + } else { + SkDQuad quad; + quad.set(edge); + *swap = sum > 0 && !quad.monotonicInY(); } - return NULL; + return sum <= 0; } -bool SkOpSegment::monotonicInY(const SkOpSpanBase* start, const SkOpSpanBase* end) const { +bool SkOpSegment::monotonicInY(int tStart, int tEnd) const { SkASSERT(fVerb != SkPath::kLine_Verb); if (fVerb == SkPath::kQuad_Verb) { - SkDQuad dst = SkDQuad::SubDivide(fPts, start->t(), end->t()); + SkDQuad dst = SkDQuad::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); return dst.monotonicInY(); } SkASSERT(fVerb == SkPath::kCubic_Verb); - SkDCubic dst = SkDCubic::SubDivide(fPts, start->t(), end->t()); + SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); return dst.monotonicInY(); } -bool SkOpSegment::NextCandidate(SkOpSpanBase* span, SkOpSpanBase** start, - SkOpSpanBase** end) { - while (span->final() || span->upCast()->done()) { - if (span->final()) { +bool SkOpSegment::serpentine(int tStart, int tEnd) const { + if (fVerb != SkPath::kCubic_Verb) { + return false; + } + SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); + return dst.serpentine(); +} + +SkOpSpan* SkOpSegment::verifyOneWinding(const char* funName, int tIndex) { + SkOpSpan& span = fTs[tIndex]; + if (span.fDone) { + return NULL; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(funName, span, span.fWindSum, span.fOppSum); +#endif +// If the prior angle in the sort is unorderable, the winding sum may not be computable. +// To enable the assert, the 'prior is unorderable' state could be +// piped down to this test, but not sure it's worth it. +// (Once the sort order is stored in the span, this test may be feasible.) +// SkASSERT(span.fWindSum != SK_MinS32); +// SkASSERT(span.fOppSum != SK_MinS32); + return &span; +} + +SkOpSpan* SkOpSegment::verifyOneWindingU(const char* funName, int tIndex) { + SkOpSpan& span = fTs[tIndex]; + if (span.fDone) { + return NULL; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(funName, span, span.fWindSum); +#endif +// If the prior angle in the sort is unorderable, the winding sum may not be computable. +// To enable the assert, the 'prior is unorderable' state could be +// piped down to this test, but not sure it's worth it. +// (Once the sort order is stored in the span, this test may be feasible.) +// SkASSERT(span.fWindSum != SK_MinS32); + return &span; +} + +bool SkOpSegment::markWinding(int index, int winding) { +// SkASSERT(!done()); + SkASSERT(winding); + double referenceT = fTs[index].fT; + int lesser = index; + bool success = false; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + success |= markOneWinding(__FUNCTION__, lesser, winding, NULL); + } + do { + success |= markOneWinding(__FUNCTION__, index, winding, NULL); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); + debugValidate(); + return success; +} + +bool SkOpSegment::markWinding(int index, int winding, int oppWinding) { +// SkASSERT(!done()); + SkASSERT(winding || oppWinding); + double referenceT = fTs[index].fT; + int lesser = index; + bool success = false; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + success |= markOneWinding(__FUNCTION__, lesser, winding, oppWinding, NULL); + } + do { + success |= markOneWinding(__FUNCTION__, index, winding, oppWinding, NULL); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); + debugValidate(); + return success; +} + +void SkOpSegment::matchWindingValue(int tIndex, double t, bool borrowWind) { + int nextDoorWind = SK_MaxS32; + int nextOppWind = SK_MaxS32; + // prefer exact matches + if (tIndex > 0) { + const SkOpSpan& below = fTs[tIndex - 1]; + if (below.fT == t) { + nextDoorWind = below.fWindValue; + nextOppWind = below.fOppValue; + } + } + if (nextDoorWind == SK_MaxS32 && tIndex + 1 < fTs.count()) { + const SkOpSpan& above = fTs[tIndex + 1]; + if (above.fT == t) { + nextDoorWind = above.fWindValue; + nextOppWind = above.fOppValue; + } + } + if (nextDoorWind == SK_MaxS32 && tIndex > 0) { + const SkOpSpan& below = fTs[tIndex - 1]; + if (approximately_negative(t - below.fT)) { + nextDoorWind = below.fWindValue; + nextOppWind = below.fOppValue; + } + } + if (nextDoorWind == SK_MaxS32 && tIndex + 1 < fTs.count()) { + const SkOpSpan& above = fTs[tIndex + 1]; + if (approximately_negative(above.fT - t)) { + nextDoorWind = above.fWindValue; + nextOppWind = above.fOppValue; + } + } + if (nextDoorWind == SK_MaxS32 && borrowWind && tIndex > 0 && t < 1) { + const SkOpSpan& below = fTs[tIndex - 1]; + nextDoorWind = below.fWindValue; + nextOppWind = below.fOppValue; + } + if (nextDoorWind != SK_MaxS32) { + SkOpSpan& newSpan = fTs[tIndex]; + newSpan.fWindValue = nextDoorWind; + newSpan.fOppValue = nextOppWind; + if (!nextDoorWind && !nextOppWind && !newSpan.fDone) { + newSpan.fDone = true; + ++fDoneSpans; + } + } +} + +bool SkOpSegment::nextCandidate(int* start, int* end) const { + while (fTs[*end].fDone) { + if (fTs[*end].fT == 1) { return false; } - span = span->upCast()->next(); + ++(*end); } - *start = span; - *end = span->upCast()->next(); + *start = *end; + *end = nextExactSpan(*start, 1); return true; } -SkOpSegment* SkOpSegment::nextChase(SkOpSpanBase** startPtr, int* stepPtr, SkOpSpan** minPtr, - SkOpSpanBase** last) const { - SkOpSpanBase* origStart = *startPtr; +static SkOpSegment* set_last(SkOpSpan** last, const SkOpSpan* endSpan) { + if (last && !endSpan->fSmall) { + *last = const_cast<SkOpSpan*>(endSpan); // FIXME: get rid of cast + } + return NULL; +} + +SkOpSegment* SkOpSegment::nextChase(int* indexPtr, int* stepPtr, int* minPtr, + SkOpSpan** last) const { + int origIndex = *indexPtr; int step = *stepPtr; - SkOpSpanBase* endSpan = step > 0 ? origStart->upCast()->next() : origStart->prev(); - SkASSERT(endSpan); - SkOpAngle* angle = step > 0 ? endSpan->fromAngle() : endSpan->upCast()->toAngle(); - SkOpSpanBase* foundSpan; - SkOpSpanBase* otherEnd; + int end = nextExactSpan(origIndex, step); + SkASSERT(end >= 0); + const SkOpSpan& endSpan = this->span(end); + SkOpAngle* angle = step > 0 ? endSpan.fFromAngle : endSpan.fToAngle; + int foundIndex; + int otherEnd; SkOpSegment* other; if (angle == NULL) { - if (endSpan->t() != 0 && endSpan->t() != 1) { + if (endSpan.fT != 0 && endSpan.fT != 1) { return NULL; } - SkOpPtT* otherPtT = endSpan->ptT()->next(); - other = otherPtT->segment(); - foundSpan = otherPtT->span(); - otherEnd = step > 0 ? foundSpan->upCast()->next() : foundSpan->prev(); + other = endSpan.fOther; + foundIndex = endSpan.fOtherIndex; + otherEnd = other->nextExactSpan(foundIndex, step); } else { int loopCount = angle->loopCount(); if (loopCount > 2) { - return set_last(last, endSpan); + return set_last(last, &endSpan); } const SkOpAngle* next = angle->next(); if (NULL == next) { return NULL; } + if (angle->sign() != next->sign()) { #if DEBUG_WINDING - if (angle->sign() != next->sign() && !angle->segment()->contour()->isXor() - && !next->segment()->contour()->isXor()) { SkDebugf("%s mismatched signs\n", __FUNCTION__); - } #endif + // return set_last(last, &endSpan); + } other = next->segment(); - foundSpan = endSpan = next->start(); + foundIndex = end = next->start(); otherEnd = next->end(); } - int foundStep = foundSpan->step(otherEnd); + int foundStep = foundIndex < otherEnd ? 1 : -1; if (*stepPtr != foundStep) { - return set_last(last, endSpan); + return set_last(last, &endSpan); } - SkASSERT(*startPtr); - if (!otherEnd) { + SkASSERT(*indexPtr >= 0); + if (otherEnd < 0) { return NULL; } // SkASSERT(otherEnd >= 0); - SkOpSpan* origMin = step < 0 ? origStart->prev() : origStart->upCast(); - SkOpSpan* foundMin = foundSpan->starter(otherEnd); - if (foundMin->windValue() != origMin->windValue() - || foundMin->oppValue() != origMin->oppValue()) { - return set_last(last, endSpan); +#if 1 + int origMin = origIndex + (step < 0 ? step : 0); + const SkOpSpan& orig = this->span(origMin); +#endif + int foundMin = SkMin32(foundIndex, otherEnd); +#if 1 + const SkOpSpan& found = other->span(foundMin); + if (found.fWindValue != orig.fWindValue || found.fOppValue != orig.fOppValue) { + return set_last(last, &endSpan); } - *startPtr = foundSpan; +#endif + *indexPtr = foundIndex; *stepPtr = foundStep; if (minPtr) { *minPtr = foundMin; @@ -1531,217 +4378,101 @@ SkOpSegment* SkOpSegment::nextChase(SkOpSpanBase** startPtr, int* stepPtr, SkOpS return other; } -static void clear_visited(SkOpSpan* span) { - // reset visited flag back to false - do { - SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; - while ((ptT = ptT->next()) != stopPtT) { - SkOpSegment* opp = ptT->segment(); - opp->resetVisited(); +// This has callers for two different situations: one establishes the end +// of the current span, and one establishes the beginning of the next span +// (thus the name). When this is looking for the end of the current span, +// coincidence is found when the beginning Ts contain -step and the end +// contains step. When it is looking for the beginning of the next, the +// first Ts found can be ignored and the last Ts should contain -step. +// OPTIMIZATION: probably should split into two functions +int SkOpSegment::nextSpan(int from, int step) const { + const SkOpSpan& fromSpan = fTs[from]; + int count = fTs.count(); + int to = from; + while (step > 0 ? ++to < count : --to >= 0) { + const SkOpSpan& span = fTs[to]; + if (approximately_zero(span.fT - fromSpan.fT)) { + continue; } - } while ((span = span->next()->upCastable())); + return to; + } + return -1; } -// look for pairs of undetected coincident curves -// assumes that segments going in have visited flag clear -// curve/curve intersection should now do a pretty good job of finding coincident runs so -// this may be only be necessary for line/curve pairs -- so skip unless this is a line and the -// the opp is not a line -void SkOpSegment::missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) { - if (this->verb() != SkPath::kLine_Verb) { - return; - } - SkOpSpan* prior = NULL; - SkOpSpan* span = &fHead; - do { - SkOpPtT* ptT = span->ptT(), * spanStopPtT = ptT; - SkASSERT(ptT->span() == span); - while ((ptT = ptT->next()) != spanStopPtT) { - SkOpSegment* opp = ptT->span()->segment(); - if (opp->setVisited()) { - continue; - } - if (opp->verb() == SkPath::kLine_Verb) { - continue; - } - if (span->containsCoincidence(opp)) { // FIXME: this assumes that if the opposite - // segment is coincident then no more coincidence - // needs to be detected. This may not be true. +// FIXME +// this returns at any difference in T, vs. a preset minimum. It may be +// that all callers to nextSpan should use this instead. +int SkOpSegment::nextExactSpan(int from, int step) const { + int to = from; + if (step < 0) { + const SkOpSpan& fromSpan = fTs[from]; + while (--to >= 0) { + const SkOpSpan& span = fTs[to]; + if (precisely_negative(fromSpan.fT - span.fT) || span.fTiny) { continue; } - if (span->containsCoinEnd(opp)) { - continue; - } - // if already visited and visited again, check for coin - if (span == &fHead) { - continue; - } - SkOpPtT* priorPtT = NULL, * priorStopPtT; - // find prior span containing opp segment - SkOpSegment* priorOpp = NULL; - prior = span; - while (!priorOpp && (prior = prior->prev())) { - priorStopPtT = priorPtT = prior->ptT(); - while ((priorPtT = priorPtT->next()) != priorStopPtT) { - SkOpSegment* segment = priorPtT->span()->segment(); - if (segment == opp) { - priorOpp = opp; - break; - } - } - } - if (!priorOpp) { - continue; - } - SkOpPtT* oppStart = prior->ptT(); - SkOpPtT* oppEnd = span->ptT(); - bool swapped = priorPtT->fT > ptT->fT; - if (swapped) { - SkTSwap(priorPtT, ptT); - SkTSwap(oppStart, oppEnd); - } - bool flipped = oppStart->fT > oppEnd->fT; - bool coincident; - if (coincidences->contains(priorPtT, ptT, oppStart, oppEnd, flipped)) { - goto swapBack; - } - { - // average t, find mid pt - double midT = (prior->t() + span->t()) / 2; - SkPoint midPt = this->ptAtT(midT); - coincident = true; - // if the mid pt is not near either end pt, project perpendicular through opp seg - if (!SkDPoint::ApproximatelyEqual(priorPtT->fPt, midPt) - && !SkDPoint::ApproximatelyEqual(ptT->fPt, midPt)) { - coincident = false; - SkIntersections i; - int ptCount = SkPathOpsVerbToPoints(this->verb()); - SkVector dxdy = (*CurveSlopeAtT[ptCount])(pts(), midT); - SkDLine ray = {{{midPt.fX, midPt.fY}, - {midPt.fX + dxdy.fY, midPt.fY - dxdy.fX}}}; - int oppPtCount = SkPathOpsVerbToPoints(opp->verb()); - (*CurveIntersectRay[oppPtCount])(opp->pts(), ray, &i); - // measure distance and see if it's small enough to denote coincidence - for (int index = 0; index < i.used(); ++index) { - SkDPoint oppPt = i.pt(index); - if (oppPt.approximatelyEqual(midPt)) { - SkVector oppDxdy = (*CurveSlopeAtT[oppPtCount])(opp->pts(), - i[index][0]); - oppDxdy.normalize(); - dxdy.normalize(); - SkScalar flatness = SkScalarAbs(dxdy.cross(oppDxdy) / FLT_EPSILON); - coincident |= flatness < 5000; // FIXME: replace with tuned value - } - } - } - } - if (coincident) { - // mark coincidence - coincidences->add(priorPtT, ptT, oppStart, oppEnd, allocator); - clear_visited(&fHead); - missingCoincidence(coincidences, allocator); - return; - } - swapBack: - if (swapped) { - SkTSwap(priorPtT, ptT); - } + return to; } - } while ((span = span->next()->upCastable())); - clear_visited(&fHead); -} - -// Move nearby t values and pts so they all hang off the same span. Alignment happens later. -bool SkOpSegment::moveNearby() { - debugValidate(); - SkOpSpanBase* spanS = &fHead; - do { - SkOpSpanBase* test = spanS->upCast()->next(); - SkOpSpanBase* next; - if (spanS->contains(test)) { - if (!test->final()) { - test->upCast()->detach(spanS->ptT()); - continue; - } else if (spanS != &fHead) { - spanS->upCast()->detach(test->ptT()); - spanS = test; + } else { + while (fTs[from].fTiny) { + from++; + } + const SkOpSpan& fromSpan = fTs[from]; + int count = fTs.count(); + while (++to < count) { + const SkOpSpan& span = fTs[to]; + if (precisely_negative(span.fT - fromSpan.fT)) { continue; } + return to; } - do { // iterate through all spans associated with start - SkOpPtT* startBase = spanS->ptT(); - next = test->final() ? NULL : test->upCast()->next(); - do { - SkOpPtT* testBase = test->ptT(); - do { - if (startBase == testBase) { - goto checkNextSpan; - } - if (testBase->duplicate()) { - continue; - } - if (this->match(startBase, testBase->segment(), testBase->fT, testBase->fPt)) { - if (test == &this->fTail) { - if (spanS == &fHead) { - debugValidate(); - return true; // if this span has collapsed, remove it from parent - } - this->fTail.merge(spanS->upCast()); - debugValidate(); - return true; - } - spanS->merge(test->upCast()); - spanS->upCast()->setNext(next); - goto checkNextSpan; - } - } while ((testBase = testBase->next()) != test->ptT()); - } while ((startBase = startBase->next()) != spanS->ptT()); - checkNextSpan: - ; - } while ((test = next)); - spanS = spanS->upCast()->next(); - } while (!spanS->final()); - debugValidate(); - return true; -} - -bool SkOpSegment::operand() const { - return fContour->operand(); + } + return -1; } -bool SkOpSegment::oppXor() const { - return fContour->oppXor(); +void SkOpSegment::pinT(const SkPoint& pt, double* t) { + if (pt == fPts[0]) { + *t = 0; + } + int count = SkPathOpsVerbToPoints(fVerb); + if (pt == fPts[count]) { + *t = 1; + } } -bool SkOpSegment::ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const { - if (fVerb == SkPath::kLine_Verb) { - return false; +bool SkOpSegment::reversePoints(const SkPoint& p1, const SkPoint& p2) const { + SkASSERT(p1 != p2); + int spanCount = count(); + int p1IndexMin = -1; + int p2IndexMax = spanCount; + for (int index = 0; index < spanCount; ++index) { + const SkOpSpan& span = fTs[index]; + if (span.fPt == p1) { + if (p1IndexMin < 0) { + p1IndexMin = index; + } + } else if (span.fPt == p2) { + p2IndexMax = index; + } } - // quads (and cubics) can loop back to nearly a line so that an opposite curve - // hits in two places with very different t values. - // OPTIMIZATION: curves could be preflighted so that, for example, something like - // 'controls contained by ends' could avoid this check for common curves - // 'ends are extremes in x or y' is cheaper to compute and real-world common - // on the other hand, the below check is relatively inexpensive - double midT = (t1 + t2) / 2; - SkPoint midPt = this->ptAtT(midT); - double seDistSq = SkTMax(pt1.distanceToSqd(pt2) * 2, FLT_EPSILON * 2); - return midPt.distanceToSqd(pt1) > seDistSq || midPt.distanceToSqd(pt2) > seDistSq; + return p1IndexMin > p2IndexMax; } -void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, - int* maxWinding, int* sumWinding) { - int deltaSum = SpanSign(start, end); - *maxWinding = *sumMiWinding; - *sumWinding = *sumMiWinding -= deltaSum; - SkASSERT(!DEBUG_LIMIT_WIND_SUM || abs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); +void SkOpSegment::setCoincidentRange(const SkPoint& startPt, const SkPoint& endPt, + SkOpSegment* other) { + int count = this->count(); + for (int index = 0; index < count; ++index) { + SkOpSpan &span = fTs[index]; + if ((startPt == span.fPt || endPt == span.fPt) && other == span.fOther) { + span.fCoincident = true; + } + } } -void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, - int* sumSuWinding, int* maxWinding, int* sumWinding, int* oppMaxWinding, - int* oppSumWinding) { - int deltaSum = SpanSign(start, end); - int oppDeltaSum = OppSign(start, end); +void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding, + int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding) { + int deltaSum = spanSign(index, endIndex); + int oppDeltaSum = oppSign(index, endIndex); if (operand()) { *maxWinding = *sumSuWinding; *sumWinding = *sumSuWinding -= deltaSum; @@ -1753,94 +4484,130 @@ void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sum *oppMaxWinding = *sumSuWinding; *oppSumWinding = *sumSuWinding -= oppDeltaSum; } - SkASSERT(!DEBUG_LIMIT_WIND_SUM || abs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); - SkASSERT(!DEBUG_LIMIT_WIND_SUM || abs(*oppSumWinding) <= DEBUG_LIMIT_WIND_SUM); +#if DEBUG_LIMIT_WIND_SUM + SkASSERT(abs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); + SkASSERT(abs(*oppSumWinding) <= DEBUG_LIMIT_WIND_SUM); +#endif +} + +void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding, + int* maxWinding, int* sumWinding) { + int deltaSum = spanSign(index, endIndex); + *maxWinding = *sumMiWinding; + *sumWinding = *sumMiWinding -= deltaSum; +#if DEBUG_LIMIT_WIND_SUM + SkASSERT(abs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); +#endif } void SkOpSegment::sortAngles() { - SkOpSpanBase* span = &this->fHead; + int spanCount = fTs.count(); + if (spanCount <= 2) { + return; + } + int index = 0; do { - SkOpAngle* fromAngle = span->fromAngle(); - SkOpAngle* toAngle = span->final() ? NULL : span->upCast()->toAngle(); + SkOpAngle* fromAngle = fTs[index].fFromAngle; + SkOpAngle* toAngle = fTs[index].fToAngle; if (!fromAngle && !toAngle) { + index += 1; continue; } + SkOpAngle* baseAngle = NULL; + if (fromAngle) { + baseAngle = fromAngle; + if (inLoop(baseAngle, spanCount, &index)) { + continue; + } + } #if DEBUG_ANGLE bool wroteAfterHeader = false; #endif - SkOpAngle* baseAngle = fromAngle; - if (fromAngle && toAngle) { + if (toAngle) { + if (!baseAngle) { + baseAngle = toAngle; + if (inLoop(baseAngle, spanCount, &index)) { + continue; + } + } else { + SkDEBUGCODE(int newIndex = index); + SkASSERT(!inLoop(baseAngle, spanCount, &newIndex) && newIndex == index); #if DEBUG_ANGLE - SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), span->t(), - span->debugID()); - wroteAfterHeader = true; + SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), fTs[index].fT, + index); + wroteAfterHeader = true; #endif - fromAngle->insert(toAngle); - } else if (!fromAngle) { - baseAngle = toAngle; + baseAngle->insert(toAngle); + } } - SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; + SkOpAngle* nextFrom, * nextTo; + int firstIndex = index; do { - SkOpSpanBase* oSpan = ptT->span(); - if (oSpan == span) { - continue; - } - SkOpAngle* oAngle = oSpan->fromAngle(); + SkOpSpan& span = fTs[index]; + SkOpSegment* other = span.fOther; + SkOpSpan& oSpan = other->fTs[span.fOtherIndex]; + SkOpAngle* oAngle = oSpan.fFromAngle; if (oAngle) { #if DEBUG_ANGLE if (!wroteAfterHeader) { - SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), - span->t(), span->debugID()); + SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), fTs[index].fT, + index); wroteAfterHeader = true; } #endif - if (!oAngle->loopContains(baseAngle)) { + if (!oAngle->loopContains(*baseAngle)) { baseAngle->insert(oAngle); } } - if (!oSpan->final()) { - oAngle = oSpan->upCast()->toAngle(); - if (oAngle) { + oAngle = oSpan.fToAngle; + if (oAngle) { #if DEBUG_ANGLE - if (!wroteAfterHeader) { - SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), - span->t(), span->debugID()); - wroteAfterHeader = true; - } + if (!wroteAfterHeader) { + SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), fTs[index].fT, + index); + wroteAfterHeader = true; + } #endif - if (!oAngle->loopContains(baseAngle)) { - baseAngle->insert(oAngle); - } + if (!oAngle->loopContains(*baseAngle)) { + baseAngle->insert(oAngle); } } - } while ((ptT = ptT->next()) != stopPtT); - if (baseAngle->loopCount() == 1) { - span->setFromAngle(NULL); - if (toAngle) { - span->upCast()->setToAngle(NULL); + if (++index == spanCount) { + break; } + nextFrom = fTs[index].fFromAngle; + nextTo = fTs[index].fToAngle; + } while (fromAngle == nextFrom && toAngle == nextTo); + if (baseAngle && baseAngle->loopCount() == 1) { + index = firstIndex; + do { + SkOpSpan& span = fTs[index]; + span.fFromAngle = span.fToAngle = NULL; + if (++index == spanCount) { + break; + } + nextFrom = fTs[index].fFromAngle; + nextTo = fTs[index].fToAngle; + } while (fromAngle == nextFrom && toAngle == nextTo); baseAngle = NULL; } #if DEBUG_SORT SkASSERT(!baseAngle || baseAngle->loopCount() > 1); #endif - } while (!span->final() && (span = span->upCast()->next())); + } while (index < spanCount); } // return true if midpoints were computed -bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, - SkPoint edge[4]) const { +bool SkOpSegment::subDivide(int start, int end, SkPoint edge[4]) const { SkASSERT(start != end); - const SkOpPtT& startPtT = *start->ptT(); - const SkOpPtT& endPtT = *end->ptT(); - edge[0] = startPtT.fPt; + edge[0] = fTs[start].fPt; int points = SkPathOpsVerbToPoints(fVerb); - edge[points] = endPtT.fPt; + edge[points] = fTs[end].fPt; if (fVerb == SkPath::kLine_Verb) { return false; } - double startT = startPtT.fT; - double endT = endPtT.fT; + double startT = fTs[start].fT; + double endT = fTs[end].fT; if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) { // don't compute midpoints if we already have them if (fVerb == SkPath::kQuad_Verb) { @@ -1870,19 +4637,17 @@ bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, return true; } -bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, - SkDCubic* result) const { +// return true if midpoints were computed +bool SkOpSegment::subDivide(int start, int end, SkDCubic* result) const { SkASSERT(start != end); - const SkOpPtT& startPtT = *start->ptT(); - const SkOpPtT& endPtT = *end->ptT(); - (*result)[0].set(startPtT.fPt); + (*result)[0].set(fTs[start].fPt); int points = SkPathOpsVerbToPoints(fVerb); - (*result)[points].set(endPtT.fPt); + (*result)[points].set(fTs[end].fPt); if (fVerb == SkPath::kLine_Verb) { return false; } - double startT = startPtT.fT; - double endT = endPtT.fT; + double startT = fTs[start].fT; + double endT = fTs[end].fT; if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) { // don't compute midpoints if we already have them if (fVerb == SkPath::kQuad_Verb) { @@ -1890,7 +4655,7 @@ bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, return false; } SkASSERT(fVerb == SkPath::kCubic_Verb); - if (startT == 0) { + if (start < end) { (*result)[1].set(fPts[1]); (*result)[2].set(fPts[2]); return false; @@ -1908,29 +4673,49 @@ bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, return true; } -void SkOpSegment::subDivideBounds(const SkOpSpanBase* start, const SkOpSpanBase* end, - SkPathOpsBounds* bounds) const { +void SkOpSegment::subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const { SkPoint edge[4]; subDivide(start, end, edge); (bounds->*SetCurveBounds[SkPathOpsVerbToPoints(fVerb)])(edge); } -void SkOpSegment::undoneSpan(SkOpSpanBase** start, SkOpSpanBase** end) { - SkOpSpan* span = this->head(); - do { - if (!span->done()) { +void SkOpSegment::TrackOutsidePair(SkTArray<SkPoint, true>* outsidePts, const SkPoint& endPt, + const SkPoint& startPt) { + int outCount = outsidePts->count(); + if (outCount == 0 || endPt != (*outsidePts)[outCount - 2]) { + outsidePts->push_back(endPt); + outsidePts->push_back(startPt); + } +} + +void SkOpSegment::TrackOutside(SkTArray<SkPoint, true>* outsidePts, const SkPoint& startPt) { + int outCount = outsidePts->count(); + if (outCount == 0 || startPt != (*outsidePts)[outCount - 1]) { + outsidePts->push_back(startPt); + } +} + +void SkOpSegment::undoneSpan(int* start, int* end) { + int tCount = fTs.count(); + int index; + for (index = 0; index < tCount; ++index) { + if (!fTs[index].fDone) { break; } - } while ((span = span->next()->upCastable())); - SkASSERT(span); - *start = span; - *end = span->next(); + } + SkASSERT(index < tCount - 1); + *start = index; + double startT = fTs[index].fT; + while (approximately_negative(fTs[++index].fT - startT)) + SkASSERT(index < tCount); + SkASSERT(index < tCount); + *end = index; } -int SkOpSegment::updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const { - const SkOpSpan* lesser = start->starter(end); - int oppWinding = lesser->oppSum(); - int oppSpanWinding = SkOpSegment::OppSign(start, end); +int SkOpSegment::updateOppWinding(int index, int endIndex) const { + int lesser = SkMin32(index, endIndex); + int oppWinding = oppSum(lesser); + int oppSpanWinding = oppSign(index, endIndex); if (oppSpanWinding && UseInnerWinding(oppWinding - oppSpanWinding, oppWinding) && oppWinding != SK_MaxS32) { oppWinding -= oppSpanWinding; @@ -1939,24 +4724,24 @@ int SkOpSegment::updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* } int SkOpSegment::updateOppWinding(const SkOpAngle* angle) const { - const SkOpSpanBase* startSpan = angle->start(); - const SkOpSpanBase* endSpan = angle->end(); - return updateOppWinding(endSpan, startSpan); + int startIndex = angle->start(); + int endIndex = angle->end(); + return updateOppWinding(endIndex, startIndex); } int SkOpSegment::updateOppWindingReverse(const SkOpAngle* angle) const { - const SkOpSpanBase* startSpan = angle->start(); - const SkOpSpanBase* endSpan = angle->end(); - return updateOppWinding(startSpan, endSpan); + int startIndex = angle->start(); + int endIndex = angle->end(); + return updateOppWinding(startIndex, endIndex); } -int SkOpSegment::updateWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const { - const SkOpSpan* lesser = start->starter(end); - int winding = lesser->windSum(); +int SkOpSegment::updateWinding(int index, int endIndex) const { + int lesser = SkMin32(index, endIndex); + int winding = windSum(lesser); if (winding == SK_MinS32) { return winding; } - int spanWinding = SkOpSegment::SpanSign(start, end); + int spanWinding = spanSign(index, endIndex); if (winding && UseInnerWinding(winding - spanWinding, winding) && winding != SK_MaxS32) { winding -= spanWinding; @@ -1965,15 +4750,26 @@ int SkOpSegment::updateWinding(const SkOpSpanBase* start, const SkOpSpanBase* en } int SkOpSegment::updateWinding(const SkOpAngle* angle) const { - const SkOpSpanBase* startSpan = angle->start(); - const SkOpSpanBase* endSpan = angle->end(); - return updateWinding(endSpan, startSpan); + int startIndex = angle->start(); + int endIndex = angle->end(); + return updateWinding(endIndex, startIndex); +} + +int SkOpSegment::updateWindingReverse(int index, int endIndex) const { + int lesser = SkMin32(index, endIndex); + int winding = windSum(lesser); + int spanWinding = spanSign(endIndex, index); + if (winding && UseInnerWindingReverse(winding - spanWinding, winding) + && winding != SK_MaxS32) { + winding -= spanWinding; + } + return winding; } int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) const { - const SkOpSpanBase* startSpan = angle->start(); - const SkOpSpanBase* endSpan = angle->end(); - return updateWinding(startSpan, endSpan); + int startIndex = angle->start(); + int endIndex = angle->end(); + return updateWindingReverse(endIndex, startIndex); } // OPTIMIZATION: does the following also work, and is it any faster? @@ -1988,17 +4784,25 @@ bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) { return result; } -int SkOpSegment::windingAtT(double tHit, const SkOpSpan* span, bool crossOpp, - SkScalar* dx) const { - if (approximately_zero(tHit - span->t())) { // if we hit the end of a span, disregard +bool SkOpSegment::UseInnerWindingReverse(int outerWinding, int innerWinding) { + SkASSERT(outerWinding != SK_MaxS32); + SkASSERT(innerWinding != SK_MaxS32); + int absOut = abs(outerWinding); + int absIn = abs(innerWinding); + bool result = absOut == absIn ? true : absOut < absIn; + return result; +} + +int SkOpSegment::windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const { + if (approximately_zero(tHit - t(tIndex))) { // if we hit the end of a span, disregard return SK_MinS32; } - int winding = crossOpp ? span->oppSum() : span->windSum(); + int winding = crossOpp ? oppSum(tIndex) : windSum(tIndex); SkASSERT(winding != SK_MinS32); - int windVal = crossOpp ? span->oppValue() : span->windValue(); + int windVal = crossOpp ? oppValue(tIndex) : windValue(tIndex); #if DEBUG_WINDING_AT_T SkDebugf("%s id=%d opp=%d tHit=%1.9g t=%1.9g oldWinding=%d windValue=%d", __FUNCTION__, - debugID(), crossOpp, tHit, span->t(), winding, windVal); + debugID(), crossOpp, tHit, t(tIndex), winding, windVal); #endif // see if a + change in T results in a +/- change in X (compute x'(T)) *dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX; @@ -2024,6 +4828,20 @@ int SkOpSegment::windingAtT(double tHit, const SkOpSpan* span, bool crossOpp, } int SkOpSegment::windSum(const SkOpAngle* angle) const { - const SkOpSpan* minSpan = angle->start()->starter(angle->end()); - return minSpan->windSum(); + int start = angle->start(); + int end = angle->end(); + int index = SkMin32(start, end); + return windSum(index); +} + +void SkOpSegment::zeroSpan(SkOpSpan* span) { + SkASSERT(span->fWindValue > 0 || span->fOppValue != 0); + span->fWindValue = 0; + span->fOppValue = 0; + if (span->fTiny || span->fSmall) { + return; + } + SkASSERT(!span->fDone); + span->fDone = true; + ++fDoneSpans; } diff --git a/src/pathops/SkOpSegment.h b/src/pathops/SkOpSegment.h index c1c6e696fe..b4da929d99 100644 --- a/src/pathops/SkOpSegment.h +++ b/src/pathops/SkOpSegment.h @@ -9,261 +9,159 @@ #include "SkOpAngle.h" #include "SkOpSpan.h" -#include "SkOpTAllocator.h" #include "SkPathOpsBounds.h" #include "SkPathOpsCurve.h" +#include "SkTArray.h" +#include "SkTDArray.h" -class SkOpCoincidence; -class SkOpContour; +#if defined(SK_DEBUG) || !FORCE_RELEASE +#include "SkThread.h" +#endif + +struct SkCoincidence; class SkPathWriter; class SkOpSegment { public: - enum AllowAlias { - kAllowAlias, - kNoAlias - }; + SkOpSegment() { +#if defined(SK_DEBUG) || !FORCE_RELEASE + fID = sk_atomic_inc(&SkPathOpsDebug::gSegmentID); +#endif + } bool operator<(const SkOpSegment& rh) const { return fBounds.fTop < rh.fBounds.fTop; } - SkOpAngle* activeAngle(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, - bool* done, bool* sortable); - SkOpAngle* activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr, bool* done, bool* sortable); - SkOpAngle* activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr, bool* done, bool* sortable); - bool activeOp(SkOpSpanBase* start, SkOpSpanBase* end, int xorMiMask, int xorSuMask, - SkPathOp op); - bool activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, SkOpSpanBase* end, SkPathOp op, - int* sumMiWinding, int* sumSuWinding); - - SkPoint activeLeftTop(SkOpSpanBase** firstT); - - bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end); - bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding); + struct AlignedSpan { + double fOldT; + double fT; + SkPoint fOldPt; + SkPoint fPt; + const SkOpSegment* fSegment; + const SkOpSegment* fOther1; + const SkOpSegment* fOther2; + }; - void addCubic(SkPoint pts[4], SkOpContour* parent) { - init(pts, parent, SkPath::kCubic_Verb); - fBounds.setCubicBounds(pts); + const SkPathOpsBounds& bounds() const { + return fBounds; } - void addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, SkPathWriter* path, - bool active) const; - - SkOpAngle* addEndSpan(SkChunkAlloc* allocator) { - SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); - angle->set(&fTail, fTail.prev()); - fTail.setFromAngle(angle); - return angle; + // OPTIMIZE + // when the edges are initially walked, they don't automatically get the prior and next + // edges assigned to positions t=0 and t=1. Doing that would remove the need for this check, + // and would additionally remove the need for similar checks in condition edges. It would + // also allow intersection code to assume end of segment intersections (maybe?) + bool complete() const { + int count = fTs.count(); + return count > 1 && fTs[0].fT == 0 && fTs[--count].fT == 1; } - void addLine(SkPoint pts[2], SkOpContour* parent) { - init(pts, parent, SkPath::kLine_Verb); - fBounds.set(pts, 2); + int count() const { + return fTs.count(); } - SkOpPtT* addMissing(double t, SkOpSegment* opp, SkChunkAlloc* ); - SkOpAngle* addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** , SkChunkAlloc* ); - SkOpAngle* addSingletonAngles(int step, SkChunkAlloc* ); - SkOpAngle* addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** , SkChunkAlloc* ); - - SkOpAngle* addStartSpan(SkChunkAlloc* allocator) { - SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); - angle->set(&fHead, fHead.next()); - fHead.setToAngle(angle); - return angle; + bool done() const { + SkASSERT(fDoneSpans <= fTs.count()); + return fDoneSpans == fTs.count(); } - void addQuad(SkPoint pts[3], SkOpContour* parent) { - init(pts, parent, SkPath::kQuad_Verb); - fBounds.setQuadBounds(pts); + bool done(int min) const { + return fTs[min].fDone; } - SkOpPtT* addT(double t, AllowAlias , SkChunkAlloc* ); - - void align(); - static bool BetweenTs(const SkOpSpanBase* lesser, double testT, const SkOpSpanBase* greater); - - const SkPathOpsBounds& bounds() const { - return fBounds; + bool done(const SkOpAngle* angle) const { + return done(SkMin32(angle->start(), angle->end())); } - void bumpCount() { - ++fCount; + SkDPoint dPtAtT(double mid) const { + return (*CurveDPointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid); } - void calcAngles(SkChunkAlloc*); - void checkAngleCoin(SkOpCoincidence* coincidences, SkChunkAlloc* allocator); - void checkNearCoincidence(SkOpAngle* ); - bool clockwise(const SkOpSpanBase* start, const SkOpSpanBase* end, bool* swap) const; - static void ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, - SkOpAngle::IncludeType ); - static void ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, - SkOpAngle::IncludeType ); - int computeSum(SkOpSpanBase* start, SkOpSpanBase* end, SkOpAngle::IncludeType includeType); - - SkOpContour* contour() const { - return fContour; + SkVector dxdy(int index) const { + return (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, fTs[index].fT); } - int count() const { - return fCount; + SkScalar dy(int index) const { + return dxdy(index).fY; } - SkOpSpan* crossedSpanY(const SkPoint& basePt, double mid, bool opp, bool current, - SkScalar* bestY, double* hitT, bool* hitSomething, bool* vertical); - - void debugAddAngle(double startT, double endT, SkChunkAlloc*); - const SkOpAngle* debugAngle(int id) const; - SkOpContour* debugContour(int id); - - int debugID() const { - return PATH_OPS_DEBUG_RELEASE(fID, -1); + bool hasMultiples() const { + return fMultiples; } -#if DEBUG_SWAP_TOP - int debugInflections(const SkOpSpanBase* start, const SkOpSpanBase* end) const; -#endif - - SkOpAngle* debugLastAngle(); - const SkOpPtT* debugPtT(int id) const; - void debugReset(); - const SkOpSegment* debugSegment(int id) const; - -#if DEBUG_ACTIVE_SPANS - void debugShowActiveSpans() const; -#endif -#if DEBUG_MARK_DONE - void debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding); - void debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding, int oppWinding); -#endif - - const SkOpSpanBase* debugSpan(int id) const; - void debugValidate() const; - void detach(const SkOpSpan* ); - double distSq(double t, SkOpAngle* opp); - - bool done() const { - SkASSERT(fDoneCount <= fCount); - return fDoneCount == fCount; + bool hasSmall() const { + return fSmall; } - bool done(const SkOpAngle* angle) const { - return angle->start()->starter(angle->end())->done(); + bool hasTiny() const { + return fTiny; } - SkDPoint dPtAtT(double mid) const { - return (*CurveDPointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid); + bool intersected() const { + return fTs.count() > 0; } - SkDVector dSlopeAtT(double mid) const { - return (*CurveDSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid); + bool isCanceled(int tIndex) const { + return fTs[tIndex].fWindValue == 0 && fTs[tIndex].fOppValue == 0; } - void dump() const; - void dumpAll() const; - void dumpAngles() const; - void dumpCoin() const; - void dumpPts() const; - - SkOpSegment* findNextOp(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** nextStart, - SkOpSpanBase** nextEnd, bool* unsortable, SkPathOp op, - int xorMiMask, int xorSuMask); - SkOpSegment* findNextWinding(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** nextStart, - SkOpSpanBase** nextEnd, bool* unsortable); - SkOpSegment* findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable); - SkOpSegment* findTop(bool firstPass, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, - bool* unsortable, SkChunkAlloc* ); - SkOpGlobalState* globalState() const; - - const SkOpSpan* head() const { - return &fHead; - } - - SkOpSpan* head() { - return &fHead; - } - - void init(SkPoint pts[], SkOpContour* parent, SkPath::Verb verb); - void initWinding(SkOpSpanBase* start, SkOpSpanBase* end, - SkOpAngle::IncludeType angleIncludeType); - bool initWinding(SkOpSpanBase* start, SkOpSpanBase* end, double tHit, int winding, - SkScalar hitDx, int oppWind, SkScalar hitOppDx); - - SkOpSpan* insert(SkOpSpan* prev, SkChunkAlloc* allocator) { - SkOpSpan* result = SkOpTAllocator<SkOpSpan>::Allocate(allocator); - SkOpSpanBase* next = prev->next(); - result->setPrev(prev); - prev->setNext(result); - SkDEBUGCODE(result->ptT()->fT = 0); - result->setNext(next); - if (next) { - next->setPrev(result); - } - return result; + bool isConnected(int startIndex, int endIndex) const { + return fTs[startIndex].fWindSum != SK_MinS32 || fTs[endIndex].fWindSum != SK_MinS32; } - bool isClose(double t, const SkOpSegment* opp) const; - bool isHorizontal() const { return fBounds.fTop == fBounds.fBottom; } - SkOpSegment* isSimple(SkOpSpanBase** end, int* step) { - return nextChase(end, step, NULL, NULL); - } - bool isVertical() const { return fBounds.fLeft == fBounds.fRight; } - bool isVertical(SkOpSpanBase* start, SkOpSpanBase* end) const { - return (*CurveIsVertical[SkPathOpsVerbToPoints(fVerb)])(fPts, start->t(), end->t()); + bool isVertical(int start, int end) const { + return (*CurveIsVertical[SkPathOpsVerbToPoints(fVerb)])(fPts, start, end); } - bool isXor() const; + bool operand() const { + return fOperand; + } - const SkPoint& lastPt() const { - return fPts[SkPathOpsVerbToPoints(fVerb)]; + int oppSign(const SkOpAngle* angle) const { + SkASSERT(angle->segment() == this); + return oppSign(angle->start(), angle->end()); } - SkOpSpanBase* markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end); - bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, - SkOpSpanBase** lastPtr); - bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, - int oppWinding, SkOpSpanBase** lastPtr); - SkOpSpanBase* markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle); - SkOpSpanBase* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, - const SkOpAngle* angle); - void markDone(SkOpSpan* ); - bool markWinding(SkOpSpan* , int winding); - bool markWinding(SkOpSpan* , int winding, int oppWinding); - bool match(const SkOpPtT* span, const SkOpSegment* parent, double t, const SkPoint& pt) const; - void missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator); - bool monotonicInY(const SkOpSpanBase* start, const SkOpSpanBase* end) const; - bool moveNearby(); + int oppSign(int startIndex, int endIndex) const { + int result = startIndex < endIndex ? -fTs[startIndex].fOppValue : fTs[endIndex].fOppValue; +#if DEBUG_WIND_BUMP + SkDebugf("%s oppSign=%d\n", __FUNCTION__, result); +#endif + return result; + } - SkOpSegment* next() const { - return fNext; + int oppSum(int tIndex) const { + return fTs[tIndex].fOppSum; } - static bool NextCandidate(SkOpSpanBase* span, SkOpSpanBase** start, SkOpSpanBase** end); - SkOpSegment* nextChase(SkOpSpanBase** , int* step, SkOpSpan** , SkOpSpanBase** last) const; - bool operand() const; + int oppSum(const SkOpAngle* angle) const { + int lesser = SkMin32(angle->start(), angle->end()); + return fTs[lesser].fOppSum; + } - static int OppSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { - int result = start->t() < end->t() ? -start->upCast()->oppValue() - : end->upCast()->oppValue(); - return result; + int oppValue(int tIndex) const { + return fTs[tIndex].fOppValue; } - bool oppXor() const; + int oppValue(const SkOpAngle* angle) const { + int lesser = SkMin32(angle->start(), angle->end()); + return fTs[lesser].fOppValue; + } - const SkOpSegment* prev() const { - return fPrev; +#if DEBUG_VALIDATE + bool oppXor() const { + return fOppXor; } +#endif SkPoint ptAtT(double mid) const { return (*CurvePointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid); @@ -273,113 +171,399 @@ public: return fPts; } - bool ptsDisjoint(const SkOpPtT& span, const SkOpPtT& test) const { - return ptsDisjoint(span.fT, span.fPt, test.fT, test.fPt); + void reset() { + init(NULL, (SkPath::Verb) -1, false, false); + fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); + fTs.reset(); } - bool ptsDisjoint(const SkOpPtT& span, double t, const SkPoint& pt) const { - return ptsDisjoint(span.fT, span.fPt, t, pt); + bool reversePoints(const SkPoint& p1, const SkPoint& p2) const; + + void setOppXor(bool isOppXor) { + fOppXor = isOppXor; } - bool ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const; + void setUpWinding(int index, int endIndex, int* maxWinding, int* sumWinding) { + int deltaSum = spanSign(index, endIndex); + *maxWinding = *sumWinding; + *sumWinding -= deltaSum; + } - void resetVisited() { - fVisited = false; + const SkOpSpan& span(int tIndex) const { + return fTs[tIndex]; } - void setContour(SkOpContour* contour) { - fContour = contour; + const SkOpAngle* spanToAngle(int tStart, int tEnd) const { + SkASSERT(tStart != tEnd); + const SkOpSpan& span = fTs[tStart]; + return tStart < tEnd ? span.fToAngle : span.fFromAngle; } - void setNext(SkOpSegment* next) { - fNext = next; + // FIXME: create some sort of macro or template that avoids casting + SkOpAngle* spanToAngle(int tStart, int tEnd) { + const SkOpAngle* cAngle = (const_cast<const SkOpSegment*>(this))->spanToAngle(tStart, tEnd); + return const_cast<SkOpAngle*>(cAngle); } - void setPrev(SkOpSegment* prev) { - fPrev = prev; + int spanSign(const SkOpAngle* angle) const { + SkASSERT(angle->segment() == this); + return spanSign(angle->start(), angle->end()); } - bool setVisited() { - if (fVisited) { - return false; - } - return (fVisited = true); + int spanSign(int startIndex, int endIndex) const { + int result = startIndex < endIndex ? -fTs[startIndex].fWindValue : fTs[endIndex].fWindValue; +#if DEBUG_WIND_BUMP + SkDebugf("%s spanSign=%d\n", __FUNCTION__, result); +#endif + return result; } - void setUpWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* maxWinding, int* sumWinding) { - int deltaSum = SpanSign(start, end); - *maxWinding = *sumWinding; - *sumWinding -= deltaSum; + double t(int tIndex) const { + return fTs[tIndex].fT; } - void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, - int* maxWinding, int* sumWinding); - void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, int* sumSuWinding, - int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); - void sortAngles(); + double tAtMid(int start, int end, double mid) const { + return fTs[start].fT * (1 - mid) + fTs[end].fT * mid; + } - static int SpanSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { - int result = start->t() < end->t() ? -start->upCast()->windValue() - : end->upCast()->windValue(); - return result; + void updatePts(const SkPoint pts[]) { + fPts = pts; } - SkOpAngle* spanToAngle(SkOpSpanBase* start, SkOpSpanBase* end) { - SkASSERT(start != end); - return start->t() < end->t() ? start->upCast()->toAngle() : start->fromAngle(); + SkPath::Verb verb() const { + return fVerb; } - bool subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, SkPoint edge[4]) const; - bool subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, SkDCubic* result) const; - void subDivideBounds(const SkOpSpanBase* start, const SkOpSpanBase* end, - SkPathOpsBounds* bounds) const; + int windSum(int tIndex) const { + return fTs[tIndex].fWindSum; + } - const SkOpSpanBase* tail() const { - return &fTail; + int windValue(int tIndex) const { + return fTs[tIndex].fWindValue; } - SkOpSpanBase* tail() { - return &fTail; +#if defined(SK_DEBUG) || DEBUG_WINDING + SkScalar xAtT(int index) const { + return xAtT(&fTs[index]); } +#endif - static double TAtMid(const SkOpSpanBase* start, const SkOpSpanBase* end, double mid) { - return start->t() * (1 - mid) + end->t() * mid; +#if DEBUG_VALIDATE + bool _xor() const { // FIXME: used only by SkOpAngle::debugValidateLoop() + return fXor; } +#endif - void undoneSpan(SkOpSpanBase** start, SkOpSpanBase** end); - int updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const; - int updateOppWinding(const SkOpAngle* angle) const; - int updateOppWindingReverse(const SkOpAngle* angle) const; - int updateWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const; - int updateWinding(const SkOpAngle* angle) const; - int updateWindingReverse(const SkOpAngle* angle) const; + const SkPoint& xyAtT(const SkOpSpan* span) const { + return span->fPt; + } - static bool UseInnerWinding(int outerWinding, int innerWinding); + const SkPoint& xyAtT(int index) const { + return xyAtT(&fTs[index]); + } - SkPath::Verb verb() const { - return fVerb; +#if defined(SK_DEBUG) || DEBUG_WINDING + SkScalar yAtT(int index) const { + return yAtT(&fTs[index]); } +#endif - int windingAtT(double tHit, const SkOpSpan* span, bool crossOpp, SkScalar* dx) const; + const SkOpAngle* activeAngle(int index, int* start, int* end, bool* done, + bool* sortable) const; + SkPoint activeLeftTop(int* firstT) const; + bool activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op); + bool activeWinding(int index, int endIndex); + void addCubic(const SkPoint pts[4], bool operand, bool evenOdd); + void addCurveTo(int start, int end, SkPathWriter* path, bool active) const; + void addEndSpan(int endIndex); + void addLine(const SkPoint pts[2], bool operand, bool evenOdd); + void addOtherT(int index, double otherT, int otherIndex); + void addQuad(const SkPoint pts[3], bool operand, bool evenOdd); + void addSimpleAngle(int endIndex); + int addSelfT(const SkPoint& pt, double newT); + void addStartSpan(int endIndex); + int addT(SkOpSegment* other, const SkPoint& pt, double newT); + void addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); + bool addTCoincident(const SkPoint& startPt, const SkPoint& endPt, double endT, + SkOpSegment* other); + const SkOpSpan* addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, + const SkPoint& pt); + const SkOpSpan* addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, + const SkPoint& pt, const SkPoint& oPt); + void alignMultiples(SkTDArray<AlignedSpan>* aligned); + bool alignSpan(int index, double thisT, const SkPoint& thisPt); + void alignSpanState(int start, int end); + bool betweenTs(int lesser, double testT, int greater) const; + void blindCancel(const SkCoincidence& coincidence, SkOpSegment* other); + void blindCoincident(const SkCoincidence& coincidence, SkOpSegment* other); + bool calcAngles(); + double calcMissingTEnd(const SkOpSegment* ref, double loEnd, double min, double max, + double hiEnd, const SkOpSegment* other, int thisEnd); + double calcMissingTStart(const SkOpSegment* ref, double loEnd, double min, double max, + double hiEnd, const SkOpSegment* other, int thisEnd); + void checkDuplicates(); + bool checkEnds(); + void checkMultiples(); + void checkSmall(); + bool checkSmall(int index) const; + void checkTiny(); + int computeSum(int startIndex, int endIndex, SkOpAngle::IncludeType includeType); + bool containsPt(const SkPoint& , int index, int endIndex) const; + int crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, bool* hitSomething, + double mid, bool opp, bool current) const; + bool findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* other, int oStart, int oEnd, + int step, SkPoint* startPt, SkPoint* endPt, double* endT) const; + SkOpSegment* findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd, + bool* unsortable, SkPathOp op, int xorMiMask, int xorSuMask); + SkOpSegment* findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd, + bool* unsortable); + SkOpSegment* findNextXor(int* nextStart, int* nextEnd, bool* unsortable); + int findExactT(double t, const SkOpSegment* ) const; + int findOtherT(double t, const SkOpSegment* ) const; + int findT(double t, const SkPoint& , const SkOpSegment* ) const; + SkOpSegment* findTop(int* tIndex, int* endIndex, bool* unsortable, bool firstPass); + void fixOtherTIndex(); + bool inconsistentAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, + const SkOpAngle* angle) const; + void initWinding(int start, int end, SkOpAngle::IncludeType angleIncludeType); + bool initWinding(int start, int end, double tHit, int winding, SkScalar hitDx, int oppWind, + SkScalar hitOppDx); + bool isMissing(double startT, const SkPoint& pt) const; + bool isTiny(const SkOpAngle* angle) const; + bool joinCoincidence(SkOpSegment* other, double otherT, const SkPoint& otherPt, int step, + bool cancel); + SkOpSpan* markAndChaseDoneBinary(int index, int endIndex); + SkOpSpan* markAndChaseDoneUnary(int index, int endIndex); + bool markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding, + SkOpSpan** lastPtr); + SkOpSpan* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, + const SkOpAngle* angle); + void markDone(int index, int winding); + void markDoneBinary(int index); + void markDoneFinal(int index); + void markDoneUnary(int index); + bool nextCandidate(int* start, int* end) const; + int nextSpan(int from, int step) const; + void pinT(const SkPoint& pt, double* t); + void setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding, + int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); + void sortAngles(); + bool subDivide(int start, int end, SkPoint edge[4]) const; + bool subDivide(int start, int end, SkDCubic* result) const; + void undoneSpan(int* start, int* end); + int updateOppWindingReverse(const SkOpAngle* angle) const; + int updateWindingReverse(const SkOpAngle* angle) const; + static bool UseInnerWinding(int outerWinding, int innerWinding); + static bool UseInnerWindingReverse(int outerWinding, int innerWinding); + int windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const; int windSum(const SkOpAngle* angle) const; - - SkPoint* writablePt(bool end) { - return &fPts[end ? SkPathOpsVerbToPoints(fVerb) : 0]; +// available for testing only +#if defined(SK_DEBUG) || !FORCE_RELEASE + int debugID() const { + return fID; + } +#else + int debugID() const { + return -1; } +#endif +#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY + void debugShowActiveSpans() const; +#endif +#if DEBUG_CONCIDENT + void debugShowTs(const char* prefix) const; +#endif +#if DEBUG_SHOW_WINDING + int debugShowWindingValues(int slotCount, int ofInterest) const; +#endif + const SkTDArray<SkOpSpan>& debugSpans() const; + void debugValidate() const; + // available to testing only + const SkOpAngle* debugLastAngle() const; + void dumpAngles() const; + void dumpContour(int firstID, int lastID) const; + void dumpPts() const; + void dumpSpans() const; private: - SkOpSpan fHead; // the head span always has its t set to zero - SkOpSpanBase fTail; // the tail span always has its t set to one - SkOpContour* fContour; - SkOpSegment* fNext; // forward-only linked list used by contour to walk the segments - const SkOpSegment* fPrev; - SkPoint* fPts; // pointer into array of points owned by edge builder that may be tweaked - SkPathOpsBounds fBounds; // tight bounds - int fCount; // number of spans (one for a non-intersecting segment) - int fDoneCount; // number of processed spans (zero initially) + struct MissingSpan { + double fT; + double fEndT; + SkOpSegment* fSegment; + SkOpSegment* fOther; + double fOtherT; + SkPoint fPt; + }; + + const SkOpAngle* activeAngleInner(int index, int* start, int* end, bool* done, + bool* sortable) const; + const SkOpAngle* activeAngleOther(int index, int* start, int* end, bool* done, + bool* sortable) const; + bool activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op, + int* sumMiWinding, int* sumSuWinding); + bool activeWinding(int index, int endIndex, int* sumWinding); + void addCancelOutsides(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); + void addCoinOutsides(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); + SkOpAngle* addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** ); + SkOpAngle* addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** ); + SkOpAngle* addSingletonAngles(int step); + void alignRange(int lower, int upper, const SkOpSegment* other, int oLower, int oUpper); + void alignSpan(const SkPoint& newPt, double newT, const SkOpSegment* other, double otherT, + const SkOpSegment* other2, SkOpSpan* oSpan, SkTDArray<AlignedSpan>* ); + bool betweenPoints(double midT, const SkPoint& pt1, const SkPoint& pt2) const; + void bumpCoincidentBlind(bool binary, int index, int last); + bool bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* index, + SkTArray<SkPoint, true>* outsideTs); + void bumpCoincidentOBlind(int index, int last); + bool bumpCoincidentOther(const SkOpSpan& oTest, int* index, + SkTArray<SkPoint, true>* outsideTs, const SkPoint& endPt); + bool bumpSpan(SkOpSpan* span, int windDelta, int oppDelta); + bool calcLoopSpanCount(const SkOpSpan& thisSpan, int* smallCounts); + bool checkForSmall(const SkOpSpan* span, const SkPoint& pt, double newT, + int* less, int* more) const; + void checkLinks(const SkOpSpan* , + SkTArray<MissingSpan, true>* missingSpans) const; + static void CheckOneLink(const SkOpSpan* test, const SkOpSpan* oSpan, + const SkOpSpan* oFirst, const SkOpSpan* oLast, + const SkOpSpan** missingPtr, + SkTArray<MissingSpan, true>* missingSpans); + int checkSetAngle(int tIndex) const; + void checkSmallCoincidence(const SkOpSpan& span, SkTArray<MissingSpan, true>* ); + bool coincidentSmall(const SkPoint& pt, double t, const SkOpSegment* other) const; + bool clockwise(int tStart, int tEnd, bool* swap) const; + static void ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType ); + static void ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType ); + bool containsT(double t, const SkOpSegment* other, double otherT) const; + bool decrementSpan(SkOpSpan* span); + int findEndSpan(int endIndex) const; + int findStartSpan(int startIndex) const; + int firstActive(int tIndex) const; + const SkOpSpan& firstSpan(const SkOpSpan& thisSpan) const; + void init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd); + bool inCoincidentSpan(double t, const SkOpSegment* other) const; + bool inconsistentWinding(const SkOpAngle* , int maxWinding, int oppMaxWinding) const; + bool inconsistentWinding(int min, int maxWinding, int oppMaxWinding) const; + bool inconsistentWinding(const char* funName, int tIndex, int winding, int oppWinding) const; + bool inLoop(const SkOpAngle* baseAngle, int spanCount, int* indexPtr) const; +#if OLD_CHASE + bool isSimple(int end) const; +#else + SkOpSegment* isSimple(int* end, int* step); +#endif + bool isTiny(int index) const; + const SkOpSpan& lastSpan(const SkOpSpan& thisSpan) const; + void matchWindingValue(int tIndex, double t, bool borrowWind); + SkOpSpan* markAndChaseDone(int index, int endIndex, int winding); + SkOpSpan* markAndChaseDoneBinary(const SkOpAngle* angle, int winding, int oppWinding); + bool markAndChaseWinding(const SkOpAngle* angle, int winding, SkOpSpan** lastPtr); + bool markAndChaseWinding(int index, int endIndex, int winding, SkOpSpan** lastPtr); + bool markAndChaseWinding(int index, int endIndex, int winding, int oppWinding, + SkOpSpan** lastPtr); + SkOpSpan* markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle); + void markDoneBinary(int index, int winding, int oppWinding); + SkOpSpan* markAndChaseDoneUnary(const SkOpAngle* angle, int winding); + void markOneDone(const char* funName, int tIndex, int winding); + void markOneDoneBinary(const char* funName, int tIndex); + void markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding); + void markOneDoneFinal(const char* funName, int tIndex); + void markOneDoneUnary(const char* funName, int tIndex); + bool markOneWinding(const char* funName, int tIndex, int winding, SkOpSpan** lastPtr); + bool markOneWinding(const char* funName, int tIndex, int winding, int oppWinding, + SkOpSpan** lastPtr); + bool markWinding(int index, int winding); + bool markWinding(int index, int winding, int oppWinding); + bool monotonicInY(int tStart, int tEnd) const; + + bool multipleEnds() const { return fTs[count() - 2].fT == 1; } + bool multipleStarts() const { return fTs[1].fT == 0; } + + SkOpSegment* nextChase(int* index, int* step, int* min, SkOpSpan** last) const; + int nextExactSpan(int from, int step) const; + void resetSpanFlags(); + bool serpentine(int tStart, int tEnd) const; + void setCoincidentRange(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other); + void setFromAngle(int endIndex, SkOpAngle* ); + void setSpanFlags(const SkPoint& pt, double newT, SkOpSpan* span); + void setToAngle(int endIndex, SkOpAngle* ); + void setUpWindings(int index, int endIndex, int* sumMiWinding, + int* maxWinding, int* sumWinding); + void subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const; + static void TrackOutsidePair(SkTArray<SkPoint, true>* outsideTs, const SkPoint& endPt, + const SkPoint& startPt); + static void TrackOutside(SkTArray<SkPoint, true>* outsideTs, const SkPoint& startPt); + int updateOppWinding(int index, int endIndex) const; + int updateOppWinding(const SkOpAngle* angle) const; + int updateWinding(int index, int endIndex) const; + int updateWinding(const SkOpAngle* angle) const; + int updateWindingReverse(int index, int endIndex) const; + SkOpSpan* verifyOneWinding(const char* funName, int tIndex); + SkOpSpan* verifyOneWindingU(const char* funName, int tIndex); + + SkScalar xAtT(const SkOpSpan* span) const { + return xyAtT(span).fX; + } + + SkScalar yAtT(const SkOpSpan* span) const { + return xyAtT(span).fY; + } + + void zeroSpan(SkOpSpan* span); + +#if DEBUG_SWAP_TOP + bool controlsContainedByEnds(int tStart, int tEnd) const; +#endif + void debugAddAngle(int start, int end); +#if DEBUG_CONCIDENT + void debugAddTPair(double t, const SkOpSegment& other, double otherT) const; +#endif +#if DEBUG_ANGLE + void debugCheckPointsEqualish(int tStart, int tEnd) const; +#endif +#if DEBUG_SWAP_TOP + int debugInflections(int index, int endIndex) const; +#endif +#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE + void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding); + void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, int oppWinding); +#endif +#if DEBUG_WINDING + static char as_digit(int value) { + return value < 0 ? '?' : value <= 9 ? '0' + value : '+'; + } +#endif + // available to testing only + void debugConstruct(); + void debugConstructCubic(SkPoint shortQuad[4]); + void debugConstructLine(SkPoint shortQuad[2]); + void debugConstructQuad(SkPoint shortQuad[3]); + void debugReset(); + void dumpDPts() const; + void dumpHexPts() const; + void dumpSpan(int index) const; + + const SkPoint* fPts; + SkPathOpsBounds fBounds; + // FIXME: can't convert to SkTArray because it uses insert + SkTDArray<SkOpSpan> fTs; // 2+ (always includes t=0 t=1) -- at least (number of spans) + 1 + SkOpAngleSet fAngles; // empty or 2+ -- (number of non-zero spans) * 2 + // OPTIMIZATION: could pack donespans, verb, operand, xor into 1 int-sized value + int fDoneSpans; // quick check that segment is finished + // OPTIMIZATION: force the following to be byte-sized SkPath::Verb fVerb; - bool fVisited; // used by missing coincidence check - PATH_OPS_DEBUG_CODE(int fID); + bool fLoop; // set if cubic intersects itself + bool fMultiples; // set if curve intersects multiple other curves at one interior point + bool fOperand; + bool fXor; // set if original contour had even-odd fill + bool fOppXor; // set if opposite operand had even-odd fill + bool fSmall; // set if some span is small + bool fTiny; // set if some span is tiny +#if defined(SK_DEBUG) || !FORCE_RELEASE + int fID; +#endif + + friend class PathOpsSegmentTester; }; #endif diff --git a/src/pathops/SkOpSpan.cpp b/src/pathops/SkOpSpan.cpp deleted file mode 100755 index 37d5120019..0000000000 --- a/src/pathops/SkOpSpan.cpp +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2014 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -#include "SkOpCoincidence.h" -#include "SkOpContour.h" -#include "SkOpSegment.h" -#include "SkPathWriter.h" - -bool SkOpPtT::alias() const { - return this->span()->ptT() != this; -} - -SkOpContour* SkOpPtT::contour() const { - return segment()->contour(); -} - -SkOpGlobalState* SkOpPtT::globalState() const { - return contour()->globalState(); -} - -void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplicate) { - fT = t; - fPt = pt; - fSpan = span; - fNext = this; - fDuplicatePt = duplicate; - fDeleted = false; - PATH_OPS_DEBUG_CODE(fID = span->globalState()->nextPtTID()); -} - -bool SkOpPtT::onEnd() const { - const SkOpSpanBase* span = this->span(); - if (span->ptT() != this) { - return false; - } - const SkOpSegment* segment = this->segment(); - return span == segment->head() || span == segment->tail(); -} - -SkOpPtT* SkOpPtT::prev() { - SkOpPtT* result = this; - SkOpPtT* next = this; - while ((next = next->fNext) != this) { - result = next; - } - SkASSERT(result->fNext == this); - return result; -} - -SkOpPtT* SkOpPtT::remove() { - SkOpPtT* prev = this; - do { - SkOpPtT* next = prev->fNext; - if (next == this) { - prev->removeNext(this); - SkASSERT(prev->fNext != prev); - fDeleted = true; - return prev; - } - prev = next; - } while (prev != this); - SkASSERT(0); - return NULL; -} - -void SkOpPtT::removeNext(SkOpPtT* kept) { - SkASSERT(this->fNext); - SkOpPtT* next = this->fNext; - SkASSERT(this != next->fNext); - this->fNext = next->fNext; - SkOpSpanBase* span = next->span(); - next->setDeleted(); - if (span->ptT() == next) { - span->upCast()->detach(kept); - } -} - -const SkOpSegment* SkOpPtT::segment() const { - return span()->segment(); -} - -SkOpSegment* SkOpPtT::segment() { - return span()->segment(); -} - -// find the starting or ending span with an existing loop of angles -// OPTIMIZE? remove the spans pointing to windValue==0 here or earlier? -// FIXME? assert that only one other span has a valid windValue or oppValue -void SkOpSpanBase::addSimpleAngle(bool checkFrom, SkChunkAlloc* allocator) { - SkOpAngle* angle; - if (checkFrom) { - SkASSERT(this->final()); - if (this->fromAngle()) { - SkASSERT(this->fromAngle()->loopCount() == 2); - return; - } - angle = this->segment()->addEndSpan(allocator); - } else { - SkASSERT(this->t() == 0); - SkOpSpan* span = this->upCast(); - if (span->toAngle()) { - SkASSERT(span->toAngle()->loopCount() == 2); - SkASSERT(!span->fromAngle()); - span->setFromAngle(span->toAngle()->next()); - return; - } - angle = this->segment()->addStartSpan(allocator); - } - SkOpPtT* ptT = this->ptT(); - SkOpSpanBase* oSpanBase; - SkOpSpan* oSpan; - SkOpSegment* other; - do { - ptT = ptT->next(); - oSpanBase = ptT->span(); - oSpan = oSpanBase->upCastable(); - other = oSpanBase->segment(); - if (oSpan && oSpan->windValue()) { - break; - } - if (oSpanBase->t() == 0) { - continue; - } - SkOpSpan* oFromSpan = oSpanBase->prev(); - SkASSERT(oFromSpan->t() < 1); - if (oFromSpan->windValue()) { - break; - } - } while (ptT != this->ptT()); - SkOpAngle* oAngle; - if (checkFrom) { - oAngle = other->addStartSpan(allocator); - SkASSERT(oSpan && !oSpan->final()); - SkASSERT(oAngle == oSpan->toAngle()); - } else { - oAngle = other->addEndSpan(allocator); - SkASSERT(oAngle == oSpanBase->fromAngle()); - } - angle->insert(oAngle); -} - -void SkOpSpanBase::align() { - if (this->fAligned) { - return; - } - SkASSERT(!zero_or_one(this->fPtT.fT)); - SkASSERT(this->fPtT.next()); - // if a linked pt/t pair has a t of zero or one, use it as the base for alignment - SkOpPtT* ptT = &this->fPtT, * stopPtT = ptT; - while ((ptT = ptT->next()) != stopPtT) { - if (zero_or_one(ptT->fT)) { - SkOpSegment* segment = ptT->segment(); - SkASSERT(this->segment() != segment); - SkASSERT(segment->head()->ptT() == ptT || segment->tail()->ptT() == ptT); - if (ptT->fT) { - segment->tail()->alignEnd(1, segment->lastPt()); - } else { - segment->head()->alignEnd(0, segment->pts()[0]); - } - return; - } - } - alignInner(); - this->fAligned = true; -} - - -// FIXME: delete spans that collapse -// delete segments that collapse -// delete contours that collapse -void SkOpSpanBase::alignEnd(double t, const SkPoint& pt) { - SkASSERT(zero_or_one(t)); - SkOpSegment* segment = this->segment(); - SkASSERT(t ? segment->lastPt() == pt : segment->pts()[0] == pt); - alignInner(); - *segment->writablePt(!!t) = pt; - SkOpPtT* ptT = &this->fPtT; - SkASSERT(t == ptT->fT); - SkASSERT(pt == ptT->fPt); - SkOpPtT* test = ptT, * stopPtT = ptT; - while ((test = test->next()) != stopPtT) { - SkOpSegment* other = test->segment(); - if (other == this->segment()) { - continue; - } - if (!zero_or_one(test->fT)) { - continue; - } - *other->writablePt(!!test->fT) = pt; - } - this->fAligned = true; -} - -void SkOpSpanBase::alignInner() { - // force the spans to share points and t values - SkOpPtT* ptT = &this->fPtT, * stopPtT = ptT; - const SkPoint& pt = ptT->fPt; - do { - ptT->fPt = pt; - const SkOpSpanBase* span = ptT->span(); - SkOpPtT* test = ptT; - do { - SkOpPtT* prev = test; - if ((test = test->next()) == stopPtT) { - break; - } - if (span == test->span() && !span->segment()->ptsDisjoint(*ptT, *test)) { - // omit aliases that alignment makes redundant - if ((!ptT->alias() || test->alias()) && (ptT->onEnd() || !test->onEnd())) { - SkASSERT(test->alias()); - prev->removeNext(ptT); - test = prev; - } else { - SkASSERT(ptT->alias()); - stopPtT = ptT = ptT->remove(); - break; - } - } - } while (true); - } while ((ptT = ptT->next()) != stopPtT); -} - -bool SkOpSpanBase::contains(const SkOpSpanBase* span) const { - const SkOpPtT* start = &fPtT; - const SkOpPtT* check = &span->fPtT; - SkASSERT(start != check); - const SkOpPtT* walk = start; - while ((walk = walk->next()) != start) { - if (walk == check) { - return true; - } - } - return false; -} - -SkOpPtT* SkOpSpanBase::contains(const SkOpSegment* segment) { - SkOpPtT* start = &fPtT; - SkOpPtT* walk = start; - while ((walk = walk->next()) != start) { - if (walk->segment() == segment) { - return walk; - } - } - return NULL; -} - -bool SkOpSpanBase::containsCoinEnd(const SkOpSegment* segment) const { - SkASSERT(this->segment() != segment); - const SkOpSpanBase* next = this; - while ((next = next->fCoinEnd) != this) { - if (next->segment() == segment) { - return true; - } - } - return false; -} - -SkOpContour* SkOpSpanBase::contour() const { - return segment()->contour(); -} - -SkOpGlobalState* SkOpSpanBase::globalState() const { - return contour()->globalState(); -} - -void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { - fSegment = segment; - fPtT.init(this, t, pt, false); - fCoinEnd = this; - fFromAngle = NULL; - fPrev = prev; - fAligned = true; - fChased = false; - PATH_OPS_DEBUG_CODE(fCount = 1); - PATH_OPS_DEBUG_CODE(fID = globalState()->nextSpanID()); -} - -// this pair of spans share a common t value or point; merge them and eliminate duplicates -// this does not compute the best t or pt value; this merely moves all data into a single list -void SkOpSpanBase::merge(SkOpSpan* span) { - SkOpPtT* spanPtT = span->ptT(); - SkASSERT(this->t() != spanPtT->fT); - SkASSERT(!zero_or_one(spanPtT->fT)); - span->detach(this->ptT()); - SkOpPtT* remainder = spanPtT->next(); - ptT()->insert(spanPtT); - while (remainder != spanPtT) { - SkOpPtT* next = remainder->next(); - SkOpPtT* compare = spanPtT->next(); - while (compare != spanPtT) { - SkOpPtT* nextC = compare->next(); - if (nextC->span() == remainder->span() && nextC->fT == remainder->fT) { - goto tryNextRemainder; - } - compare = nextC; - } - spanPtT->insert(remainder); -tryNextRemainder: - remainder = next; - } -} - -void SkOpSpan::applyCoincidence(SkOpSpan* opp) { - SkASSERT(!final()); - SkASSERT(0); // incomplete -} - -bool SkOpSpan::containsCoincidence(const SkOpSegment* segment) const { - SkASSERT(this->segment() != segment); - const SkOpSpan* next = fCoincident; - do { - if (next->segment() == segment) { - return true; - } - } while ((next = next->fCoincident) != this); - return false; -} - -void SkOpSpan::detach(SkOpPtT* kept) { - SkASSERT(!final()); - SkOpSpan* prev = this->prev(); - SkASSERT(prev); - SkOpSpanBase* next = this->next(); - SkASSERT(next); - prev->setNext(next); - next->setPrev(prev); - this->segment()->detach(this); - this->globalState()->coincidence()->fixUp(this->ptT(), kept); - this->ptT()->setDeleted(); -} - -void SkOpSpan::init(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { - SkASSERT(t != 1); - initBase(segment, prev, t, pt); - fCoincident = this; - fToAngle = NULL; - fWindSum = fOppSum = SK_MinS32; - fWindValue = 1; - fOppValue = 0; - fChased = fDone = false; - segment->bumpCount(); -} - -void SkOpSpan::setOppSum(int oppSum) { - SkASSERT(!final()); - if (fOppSum != SK_MinS32 && fOppSum != oppSum) { - this->globalState()->setWindingFailed(); - return; - } - SkASSERT(!DEBUG_LIMIT_WIND_SUM || abs(oppSum) <= DEBUG_LIMIT_WIND_SUM); - fOppSum = oppSum; -} diff --git a/src/pathops/SkOpSpan.h b/src/pathops/SkOpSpan.h index 9e5939a5e1..d9ce44eb77 100644 --- a/src/pathops/SkOpSpan.h +++ b/src/pathops/SkOpSpan.h @@ -7,460 +7,36 @@ #ifndef SkOpSpan_DEFINED #define SkOpSpan_DEFINED -#include "SkPathOpsDebug.h" #include "SkPoint.h" -class SkChunkAlloc; -struct SkOpAngle; -class SkOpContour; -class SkOpGlobalState; +class SkOpAngle; class SkOpSegment; -class SkOpSpanBase; -class SkOpSpan; -// subset of op span used by terminal span (when t is equal to one) -class SkOpPtT { -public: - enum { - kIsAlias = 1, - kIsDuplicate = 1 - }; - - void addOpp(SkOpPtT* opp) { - // find the fOpp ptr to opp - SkOpPtT* oppPrev = opp->fNext; - if (oppPrev == this) { - return; - } - while (oppPrev->fNext != opp) { - oppPrev = oppPrev->fNext; - if (oppPrev == this) { - return; - } - } - - SkOpPtT* oldNext = this->fNext; - SkASSERT(this != opp); - this->fNext = opp; - SkASSERT(oppPrev != oldNext); - oppPrev->fNext = oldNext; - } - - bool alias() const; - SkOpContour* contour() const; - - int debugID() const { - return PATH_OPS_DEBUG_RELEASE(fID, -1); - } - - const SkOpAngle* debugAngle(int id) const; - SkOpContour* debugContour(int id); - int debugLoopLimit(bool report) const; - bool debugMatchID(int id) const; - const SkOpPtT* debugPtT(int id) const; - const SkOpSegment* debugSegment(int id) const; - const SkOpSpanBase* debugSpan(int id) const; - SkOpGlobalState* globalState() const; - void debugValidate() const; - - bool deleted() const { - return fDeleted; - } - - bool duplicate() const { - return fDuplicatePt; - } - - void dump() const; // available to testing only - void dumpAll() const; - void dumpBase() const; - - void init(SkOpSpanBase* , double t, const SkPoint& , bool dup); - - void insert(SkOpPtT* span) { - SkASSERT(span != this); - span->fNext = fNext; - fNext = span; - } - - const SkOpPtT* next() const { - return fNext; - } - - SkOpPtT* next() { - return fNext; - } - - bool onEnd() const; - SkOpPtT* prev(); - SkOpPtT* remove(); - void removeNext(SkOpPtT* kept); - - const SkOpSegment* segment() const; - SkOpSegment* segment(); - - void setDeleted() { - SkASSERT(!fDeleted); - fDeleted = true; - } - - const SkOpSpanBase* span() const { - return fSpan; - } - - SkOpSpanBase* span() { - return fSpan; - } - - double fT; - SkPoint fPt; // cache of point value at this t -protected: - SkOpSpanBase* fSpan; // contains winding data - SkOpPtT* fNext; // intersection on opposite curve or alias on this curve - bool fDeleted; // set if removed from span list - bool fDuplicatePt; // set if identical pt is somewhere in the next loop - PATH_OPS_DEBUG_CODE(int fID); -}; - -class SkOpSpanBase { -public: - void addSimpleAngle(bool checkFrom , SkChunkAlloc* ); - void align(); - - bool aligned() const { - return fAligned; - } - - void alignEnd(double t, const SkPoint& pt); - - bool chased() const { - return fChased; - } - - void clearCoinEnd() { - SkASSERT(fCoinEnd != this); - fCoinEnd = this; - } - - const SkOpSpanBase* coinEnd() const { - return fCoinEnd; - } - - bool contains(const SkOpSpanBase* ) const; - SkOpPtT* contains(const SkOpSegment* ); - - bool containsCoinEnd(const SkOpSpanBase* coin) const { - SkASSERT(this != coin); - const SkOpSpanBase* next = this; - while ((next = next->fCoinEnd) != this) { - if (next == coin) { - return true; - } - } - return false; - } - - bool containsCoinEnd(const SkOpSegment* ) const; - SkOpContour* contour() const; - - int debugBumpCount() { - return PATH_OPS_DEBUG_RELEASE(++fCount, -1); - } - - int debugID() const { - return PATH_OPS_DEBUG_RELEASE(fID, -1); - } - - const SkOpAngle* debugAngle(int id) const; - bool debugCoinEndLoopCheck() const; - SkOpContour* debugContour(int id); - const SkOpPtT* debugPtT(int id) const; - const SkOpSegment* debugSegment(int id) const; - const SkOpSpanBase* debugSpan(int id) const; - SkOpGlobalState* globalState() const; - void debugValidate() const; - - bool deleted() const { - return fPtT.deleted(); - } - - void dump() const; // available to testing only - void dumpCoin() const; - void dumpAll() const; - void dumpBase() const; - - bool final() const { - return fPtT.fT == 1; - } - - SkOpAngle* fromAngle() const { - return fFromAngle; - } - - void initBase(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); - - void insertCoinEnd(SkOpSpanBase* coin) { - if (containsCoinEnd(coin)) { - SkASSERT(coin->containsCoinEnd(this)); - return; - } - debugValidate(); - SkASSERT(this != coin); - SkOpSpanBase* coinNext = coin->fCoinEnd; - coin->fCoinEnd = this->fCoinEnd; - this->fCoinEnd = coinNext; - debugValidate(); - } - - void merge(SkOpSpan* span); - - SkOpSpan* prev() const { - return fPrev; - } - - const SkPoint& pt() const { - return fPtT.fPt; - } - - const SkOpPtT* ptT() const { - return &fPtT; - } - - SkOpPtT* ptT() { - return &fPtT; - } - - SkOpSegment* segment() const { - return fSegment; - } - - void setChased(bool chased) { - fChased = chased; - } - - SkOpPtT* setCoinEnd(SkOpSpanBase* oldCoinEnd, SkOpSegment* oppSegment); - - void setFromAngle(SkOpAngle* angle) { - fFromAngle = angle; - } - - void setPrev(SkOpSpan* prev) { - fPrev = prev; - } - - bool simple() const { - fPtT.debugValidate(); - return fPtT.next()->next() == &fPtT; - } - - const SkOpSpan* starter(const SkOpSpanBase* end) const { - const SkOpSpanBase* result = t() < end->t() ? this : end; - return result->upCast(); - } - - SkOpSpan* starter(SkOpSpanBase* end) { - SkASSERT(this->segment() == end->segment()); - SkOpSpanBase* result = t() < end->t() ? this : end; - return result->upCast(); - } - - SkOpSpan* starter(SkOpSpanBase** endPtr) { - SkOpSpanBase* end = *endPtr; - SkASSERT(this->segment() == end->segment()); - SkOpSpanBase* result; - if (t() < end->t()) { - result = this; - } else { - result = end; - *endPtr = this; - } - return result->upCast(); - } - - int step(const SkOpSpanBase* end) const { - return t() < end->t() ? 1 : -1; - } - - double t() const { - return fPtT.fT; - } - - void unaligned() { - fAligned = false; - } - - SkOpSpan* upCast() { - SkASSERT(!final()); - return (SkOpSpan*) this; - } - - const SkOpSpan* upCast() const { - SkASSERT(!final()); - return (const SkOpSpan*) this; - } - - SkOpSpan* upCastable() { - return final() ? NULL : upCast(); - } - - const SkOpSpan* upCastable() const { - return final() ? NULL : upCast(); - } - -private: - void alignInner(); - -protected: // no direct access to internals to avoid treating a span base as a span - SkOpPtT fPtT; // list of points and t values associated with the start of this span - SkOpSegment* fSegment; // segment that contains this span - SkOpSpanBase* fCoinEnd; // linked list of coincident spans that end here (may point to itself) - SkOpAngle* fFromAngle; // points to next angle from span start to end - SkOpSpan* fPrev; // previous intersection point - bool fAligned; - bool fChased; // set after span has been added to chase array - PATH_OPS_DEBUG_CODE(int fCount); // number of pt/t pairs added - PATH_OPS_DEBUG_CODE(int fID); -}; - -class SkOpSpan : public SkOpSpanBase { -public: - void applyCoincidence(SkOpSpan* opp); - - bool clearCoincident() { - SkASSERT(!final()); - if (fCoincident == this) { - return false; - } - fCoincident = this; - return true; - } - - bool containsCoincidence(const SkOpSegment* ) const; - - bool containsCoincidence(const SkOpSpan* coin) const { - SkASSERT(this != coin); - const SkOpSpan* next = this; - while ((next = next->fCoincident) != this) { - if (next == coin) { - return true; - } - } - return false; - } - - bool debugCoinLoopCheck() const; - void detach(SkOpPtT* ); - - bool done() const { - SkASSERT(!final()); - return fDone; - } - - void dumpCoin() const; - bool dumpSpan() const; - void init(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); - - void insertCoincidence(SkOpSpan* coin) { - if (containsCoincidence(coin)) { - SkASSERT(coin->containsCoincidence(this)); - return; - } - debugValidate(); - SkASSERT(this != coin); - SkOpSpan* coinNext = coin->fCoincident; - coin->fCoincident = this->fCoincident; - this->fCoincident = coinNext; - debugValidate(); - } - - bool isCanceled() const { - SkASSERT(!final()); - return fWindValue == 0 && fOppValue == 0; - } - - bool isCoincident() const { - SkASSERT(!final()); - return fCoincident != this; - } - - SkOpSpanBase* next() const { - SkASSERT(!final()); - return fNext; - } - - int oppSum() const { - SkASSERT(!final()); - return fOppSum; - } - - int oppValue() const { - SkASSERT(!final()); - return fOppValue; - } - - SkOpPtT* setCoinStart(SkOpSpan* oldCoinStart, SkOpSegment* oppSegment); - - void setDone(bool done) { - SkASSERT(!final()); - fDone = done; - } - - void setNext(SkOpSpanBase* nextT) { - SkASSERT(!final()); - fNext = nextT; - } - - void setOppSum(int oppSum); - - void setOppValue(int oppValue) { - SkASSERT(!final()); - SkASSERT(fOppSum == SK_MinS32); - fOppValue = oppValue; - } - - void setToAngle(SkOpAngle* angle) { - SkASSERT(!final()); - fToAngle = angle; - } - - void setWindSum(int windSum) { - SkASSERT(!final()); - SkASSERT(fWindSum == SK_MinS32 || fWindSum == windSum); - SkASSERT(!DEBUG_LIMIT_WIND_SUM || abs(windSum) <= DEBUG_LIMIT_WIND_SUM); - fWindSum = windSum; - } - - void setWindValue(int windValue) { - SkASSERT(!final()); - SkASSERT(windValue >= 0); - SkASSERT(fWindSum == SK_MinS32); - fWindValue = windValue; - } - - SkOpAngle* toAngle() const { - SkASSERT(!final()); - return fToAngle; - } - - int windSum() const { - SkASSERT(!final()); - return fWindSum; - } - - int windValue() const { - SkASSERT(!final()); - return fWindValue; - } - -private: // no direct access to internals to avoid treating a span base as a span - SkOpSpan* fCoincident; // linked list of spans coincident with this one (may point to itself) - SkOpAngle* fToAngle; // points to next angle from span start to end - SkOpSpanBase* fNext; // next intersection point +struct SkOpSpan { + SkPoint fPt; // computed when the curves are intersected + double fT; + double fOtherT; // value at fOther[fOtherIndex].fT + SkOpSegment* fOther; + SkOpAngle* fFromAngle; // (if t > 0) index into segment's angle array going negative in t + SkOpAngle* fToAngle; // (if t < 1) index into segment's angle array going positive in t + int fOtherIndex; // can't be used during intersection int fWindSum; // accumulated from contours surrounding this one. int fOppSum; // for binary operators: the opposite winding sum int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here + bool fChased; // set after span has been added to chase array + bool fCoincident; // set if span is bumped -- if set additional points aren't inserted bool fDone; // if set, this span to next higher T has been processed + bool fLoop; // set when a cubic loops back to this point + bool fMultiple; // set if this is one of mutiple spans with identical t and pt values + bool fNear; // set if opposite end point is near but not equal to this one + bool fSmall; // if set, consecutive points are almost equal + bool fTiny; // if set, consecutive points are equal but consecutive ts are not precisely equal + + // available to testing only + const SkOpSegment* debugToSegment(ptrdiff_t* ) const; + void dump() const; + void dumpOne() const; }; #endif diff --git a/src/pathops/SkOpTAllocator.h b/src/pathops/SkOpTAllocator.h index e8835f02e6..c80c12f63b 100644 --- a/src/pathops/SkOpTAllocator.h +++ b/src/pathops/SkOpTAllocator.h @@ -19,12 +19,6 @@ public: return record; } - static T* AllocateArray(SkChunkAlloc* allocator, int count) { - void* ptr = allocator->allocThrow(sizeof(T) * count); - T* record = (T*) ptr; - return record; - } - static T* New(SkChunkAlloc* allocator) { return new (Allocate(allocator)) T(); } diff --git a/src/pathops/SkPathOpsCommon.cpp b/src/pathops/SkPathOpsCommon.cpp index b0fd822a9d..1a5bfc1889 100644 --- a/src/pathops/SkPathOpsCommon.cpp +++ b/src/pathops/SkPathOpsCommon.cpp @@ -5,25 +5,47 @@ * found in the LICENSE file. */ #include "SkAddIntersections.h" -#include "SkOpCoincidence.h" #include "SkOpEdgeBuilder.h" #include "SkPathOpsCommon.h" #include "SkPathWriter.h" #include "SkTSort.h" -static int contourRangeCheckY(const SkTDArray<SkOpContour* >& contourList, - SkOpSegment** currentPtr, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, - double* bestHit, SkScalar* bestDx, bool* tryAgain, double* midPtr, bool opp) { - SkOpSpanBase* start = *startPtr; - SkOpSpanBase* end = *endPtr; +static void alignMultiples(SkTArray<SkOpContour*, true>* contourList, + SkTDArray<SkOpSegment::AlignedSpan>* aligned) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + if (contour->hasMultiples()) { + contour->alignMultiples(aligned); + } + } +} + +static void alignCoincidence(SkTArray<SkOpContour*, true>* contourList, + const SkTDArray<SkOpSegment::AlignedSpan>& aligned) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + int count = aligned.count(); + for (int index = 0; index < count; ++index) { + contour->alignCoincidence(aligned[index]); + } + } +} + +static int contourRangeCheckY(const SkTArray<SkOpContour*, true>& contourList, SkOpSegment** currentPtr, + int* indexPtr, int* endIndexPtr, double* bestHit, SkScalar* bestDx, + bool* tryAgain, double* midPtr, bool opp) { + const int index = *indexPtr; + const int endIndex = *endIndexPtr; const double mid = *midPtr; const SkOpSegment* current = *currentPtr; - double tAtMid = SkOpSegment::TAtMid(start, end, mid); + double tAtMid = current->tAtMid(index, endIndex, mid); SkPoint basePt = current->ptAtT(tAtMid); int contourCount = contourList.count(); SkScalar bestY = SK_ScalarMin; SkOpSegment* bestSeg = NULL; - SkOpSpan* bestTSpan = NULL; + int bestTIndex = 0; bool bestOpp; bool hitSomething = false; for (int cTest = 0; cTest < contourCount; ++cTest) { @@ -35,38 +57,37 @@ static int contourRangeCheckY(const SkTDArray<SkOpContour* >& contourList, if (bestY > contour->bounds().fBottom) { continue; } - SkOpSegment* testSeg = contour->first(); - SkASSERT(testSeg); - do { + int segmentCount = contour->segments().count(); + for (int test = 0; test < segmentCount; ++test) { + SkOpSegment* testSeg = &contour->segments()[test]; SkScalar testY = bestY; double testHit; - bool vertical; - SkOpSpan* testTSpan = testSeg->crossedSpanY(basePt, tAtMid, testOpp, - testSeg == current, &testY, &testHit, &hitSomething, &vertical); - if (!testTSpan) { - if (vertical) { + int testTIndex = testSeg->crossedSpanY(basePt, &testY, &testHit, &hitSomething, tAtMid, + testOpp, testSeg == current); + if (testTIndex < 0) { + if (testTIndex == SK_MinS32) { hitSomething = true; bestSeg = NULL; goto abortContours; // vertical encountered, return and try different point } continue; } - if (testSeg == current && SkOpSegment::BetweenTs(start, testHit, end)) { - double baseT = start->t(); - double endT = end->t(); + if (testSeg == current && current->betweenTs(index, testHit, endIndex)) { + double baseT = current->t(index); + double endT = current->t(endIndex); double newMid = (testHit - baseT) / (endT - baseT); #if DEBUG_WINDING - double midT = SkOpSegment::TAtMid(start, end, mid); - SkPoint midXY = current->ptAtT(midT); - double newMidT = SkOpSegment::TAtMid(start, end, newMid); - SkPoint newXY = current->ptAtT(newMidT); + double midT = current->tAtMid(index, endIndex, mid); + SkPoint midXY = current->xyAtT(midT); + double newMidT = current->tAtMid(index, endIndex, newMid); + SkPoint newXY = current->xyAtT(newMidT); SkDebugf("%s [%d] mid=%1.9g->%1.9g s=%1.9g (%1.9g,%1.9g) m=%1.9g (%1.9g,%1.9g)" " n=%1.9g (%1.9g,%1.9g) e=%1.9g (%1.9g,%1.9g)\n", __FUNCTION__, current->debugID(), mid, newMid, - baseT, start->pt().fX, start->pt().fY, + baseT, current->xAtT(index), current->yAtT(index), baseT + mid * (endT - baseT), midXY.fX, midXY.fY, baseT + newMid * (endT - baseT), newXY.fX, newXY.fY, - endT, end->pt().fX, end->pt().fY); + endT, current->xAtT(endIndex), current->yAtT(endIndex)); #endif *midPtr = newMid * 2; // calling loop with divide by 2 before continuing return SK_MinS32; @@ -74,39 +95,38 @@ static int contourRangeCheckY(const SkTDArray<SkOpContour* >& contourList, bestSeg = testSeg; *bestHit = testHit; bestOpp = testOpp; - bestTSpan = testTSpan; + bestTIndex = testTIndex; bestY = testY; - } while ((testSeg = testSeg->next())); + } } abortContours: int result; if (!bestSeg) { result = hitSomething ? SK_MinS32 : 0; } else { - if (bestTSpan->windSum() == SK_MinS32) { + if (bestSeg->windSum(bestTIndex) == SK_MinS32) { *currentPtr = bestSeg; - *startPtr = bestTSpan; - *endPtr = bestTSpan->next(); - SkASSERT(*startPtr != *endPtr && *startPtr && *endPtr); + *indexPtr = bestTIndex; + *endIndexPtr = bestSeg->nextSpan(bestTIndex, 1); + SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0); *tryAgain = true; return 0; } - result = bestSeg->windingAtT(*bestHit, bestTSpan, bestOpp, bestDx); + result = bestSeg->windingAtT(*bestHit, bestTIndex, bestOpp, bestDx); SkASSERT(result == SK_MinS32 || *bestDx); } - double baseT = (*startPtr)->t(); - double endT = (*endPtr)->t(); + double baseT = current->t(index); + double endT = current->t(endIndex); *bestHit = baseT + mid * (endT - baseT); return result; } -SkOpSegment* FindUndone(SkTDArray<SkOpContour* >& contourList, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr) { +SkOpSegment* FindUndone(SkTArray<SkOpContour*, true>& contourList, int* start, int* end) { int contourCount = contourList.count(); SkOpSegment* result; for (int cIndex = 0; cIndex < contourCount; ++cIndex) { SkOpContour* contour = contourList[cIndex]; - result = contour->undoneSegment(startPtr, endPtr); + result = contour->undoneSegment(start, end); if (result) { return result; } @@ -114,23 +134,20 @@ SkOpSegment* FindUndone(SkTDArray<SkOpContour* >& contourList, SkOpSpanBase** st return NULL; } -SkOpSegment* FindChase(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr) { +SkOpSegment* FindChase(SkTDArray<SkOpSpan*>* chase, int* tIndex, int* endIndex) { while (chase->count()) { - SkOpSpanBase* span; + SkOpSpan* span; chase->pop(&span); - SkOpSegment* segment = span->segment(); - *startPtr = span->ptT()->next()->span(); + const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex); + SkOpSegment* segment = backPtr.fOther; + *tIndex = backPtr.fOtherIndex; bool sortable = true; bool done = true; - *endPtr = NULL; - if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done, + *endIndex = -1; + if (const SkOpAngle* last = segment->activeAngle(*tIndex, tIndex, endIndex, &done, &sortable)) { - if (last->unorderable()) { - continue; - } - *startPtr = last->start(); - *endPtr = last->end(); + *tIndex = last->start(); + *endIndex = last->end(); #if TRY_ROTATE *chase->insert(0) = span; #else @@ -145,58 +162,65 @@ SkOpSegment* FindChase(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** startPtr, continue; } // find first angle, initialize winding to computed wind sum - const SkOpAngle* angle = segment->spanToAngle(*startPtr, *endPtr); - if (!angle) { - continue; - } - const SkOpAngle* firstAngle = angle; - bool loop = false; - int winding = SK_MinS32; + const SkOpAngle* angle = segment->spanToAngle(*tIndex, *endIndex); + const SkOpAngle* firstAngle; + SkDEBUGCODE(firstAngle = angle); + SkDEBUGCODE(bool loop = false); + int winding; do { angle = angle->next(); - if (angle == firstAngle && loop) { - break; // if we get here, there's no winding, loop is unorderable - } - loop |= angle == firstAngle; + SkASSERT(angle != firstAngle || !loop); + SkDEBUGCODE(loop |= angle == firstAngle); segment = angle->segment(); winding = segment->windSum(angle); } while (winding == SK_MinS32); - if (winding == SK_MinS32) { - continue; - } - int sumWinding = segment->updateWindingReverse(angle); - SkOpSegment* first = NULL; + int spanWinding = segment->spanSign(angle->start(), angle->end()); + #if DEBUG_WINDING + SkDebugf("%s winding=%d spanWinding=%d\n", __FUNCTION__, winding, spanWinding); + #endif + // turn span winding into contour winding + if (spanWinding * winding < 0) { + winding += spanWinding; + } + // we care about first sign and whether wind sum indicates this + // edge is inside or outside. Maybe need to pass span winding + // or first winding or something into this function? + // advance to first undone angle, then return it and winding + // (to set whether edges are active or not) firstAngle = angle; + winding -= firstAngle->segment()->spanSign(firstAngle); while ((angle = angle->next()) != firstAngle) { segment = angle->segment(); - SkOpSpanBase* start = angle->start(); - SkOpSpanBase* end = angle->end(); - int maxWinding; - segment->setUpWinding(start, end, &maxWinding, &sumWinding); - if (!segment->done(angle)) { - if (!first) { - first = segment; - *startPtr = start; - *endPtr = end; + int maxWinding = winding; + winding -= segment->spanSign(angle); + #if DEBUG_SORT + SkDebugf("%s id=%d maxWinding=%d winding=%d sign=%d\n", __FUNCTION__, + segment->debugID(), maxWinding, winding, angle->sign()); + #endif + *tIndex = angle->start(); + *endIndex = angle->end(); + int lesser = SkMin32(*tIndex, *endIndex); + const SkOpSpan& nextSpan = segment->span(lesser); + if (!nextSpan.fDone) { + // FIXME: this be wrong? assign startWinding if edge is in + // same direction. If the direction is opposite, winding to + // assign is flipped sign or +/- 1? + if (SkOpSegment::UseInnerWinding(maxWinding, winding)) { + maxWinding = winding; } - // OPTIMIZATION: should this also add to the chase? - (void) segment->markAngle(maxWinding, sumWinding, angle); + // allowed to do nothing + (void) segment->markAndChaseWinding(angle, maxWinding, 0, NULL); + break; } } - if (first) { - #if TRY_ROTATE - *chase->insert(0) = span; - #else - *chase->append() = span; - #endif - return first; - } + *chase->insert(0) = span; + return segment; } return NULL; } -#if DEBUG_ACTIVE_SPANS -void DebugShowActiveSpans(SkTDArray<SkOpContour* >& contourList) { +#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY +void DebugShowActiveSpans(SkTArray<SkOpContour*, true>& contourList) { int index; for (index = 0; index < contourList.count(); ++ index) { contourList[index]->debugShowActiveSpans(); @@ -204,12 +228,11 @@ void DebugShowActiveSpans(SkTDArray<SkOpContour* >& contourList) { } #endif -static SkOpSegment* findTopSegment(const SkTDArray<SkOpContour* >& contourList, - bool firstPass, SkOpSpanBase** start, SkOpSpanBase** end, SkPoint* topLeft, - bool* unsortable, bool* done, SkChunkAlloc* allocator) { +static SkOpSegment* findTopSegment(const SkTArray<SkOpContour*, true>& contourList, int* index, + int* endIndex, SkPoint* topLeft, bool* unsortable, bool* done, bool firstPass) { SkOpSegment* result; const SkOpSegment* lastTopStart = NULL; - SkOpSpanBase* lastStart = NULL, * lastEnd = NULL; + int lastIndex = -1, lastEndIndex = -1; do { SkPoint bestXY = {SK_ScalarMax, SK_ScalarMax}; int contourCount = contourList.count(); @@ -238,27 +261,27 @@ static SkOpSegment* findTopSegment(const SkTDArray<SkOpContour* >& contourList, return NULL; } *topLeft = bestXY; - result = topStart->findTop(firstPass, start, end, unsortable, allocator); + result = topStart->findTop(index, endIndex, unsortable, firstPass); if (!result) { - if (lastTopStart == topStart && lastStart == *start && lastEnd == *end) { + if (lastTopStart == topStart && lastIndex == *index && lastEndIndex == *endIndex) { *done = true; return NULL; } lastTopStart = topStart; - lastStart = *start; - lastEnd = *end; + lastIndex = *index; + lastEndIndex = *endIndex; } } while (!result); return result; } -static int rightAngleWinding(const SkTDArray<SkOpContour* >& contourList, - SkOpSegment** currentPtr, SkOpSpanBase** start, SkOpSpanBase** end, double* tHit, +static int rightAngleWinding(const SkTArray<SkOpContour*, true>& contourList, + SkOpSegment** currentPtr, int* indexPtr, int* endIndexPtr, double* tHit, SkScalar* hitDx, bool* tryAgain, bool* onlyVertical, bool opp) { double test = 0.9; int contourWinding; do { - contourWinding = contourRangeCheckY(contourList, currentPtr, start, end, + contourWinding = contourRangeCheckY(contourList, currentPtr, indexPtr, endIndexPtr, tHit, hitDx, tryAgain, &test, opp); if (contourWinding != SK_MinS32 || *tryAgain) { return contourWinding; @@ -273,9 +296,9 @@ static int rightAngleWinding(const SkTDArray<SkOpContour* >& contourList, return contourWinding; } -static void skipVertical(const SkTDArray<SkOpContour* >& contourList, - SkOpSegment** current, SkOpSpanBase** start, SkOpSpanBase** end) { - if (!(*current)->isVertical(*start, *end)) { +static void skipVertical(const SkTArray<SkOpContour*, true>& contourList, + SkOpSegment** current, int* index, int* endIndex) { + if (!(*current)->isVertical(*index, *endIndex)) { return; } int contourCount = contourList.count(); @@ -284,7 +307,7 @@ static void skipVertical(const SkTDArray<SkOpContour* >& contourList, if (contour->done()) { continue; } - SkOpSegment* nonVertical = contour->nonVerticalSegment(start, end); + SkOpSegment* nonVertical = contour->nonVerticalSegment(index, endIndex); if (nonVertical) { *current = nonVertical; return; @@ -293,41 +316,41 @@ static void skipVertical(const SkTDArray<SkOpContour* >& contourList, return; } -struct SortableTop2 { // error if local in pre-C++11 - SkOpSpanBase* fStart; - SkOpSpanBase* fEnd; +struct SortableTop { // error if local in pre-C++11 + SkOpSegment* fSegment; + int fIndex; + int fEndIndex; }; -SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour* >& contourList, bool firstPass, - SkOpAngle::IncludeType angleIncludeType, bool* firstContour, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr, SkPoint* topLeft, bool* unsortable, bool* done, bool* onlyVertical, - SkChunkAlloc* allocator) { - SkOpSegment* current = findTopSegment(contourList, firstPass, startPtr, endPtr, topLeft, - unsortable, done, allocator); +SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& contourList, + SkOpAngle::IncludeType angleIncludeType, bool* firstContour, int* indexPtr, + int* endIndexPtr, SkPoint* topLeft, bool* unsortable, bool* done, bool* onlyVertical, + bool firstPass) { + SkOpSegment* current = findTopSegment(contourList, indexPtr, endIndexPtr, topLeft, unsortable, + done, firstPass); if (!current) { return NULL; } - SkOpSpanBase* start = *startPtr; - SkOpSpanBase* end = *endPtr; - SkASSERT(current == start->segment()); + const int startIndex = *indexPtr; + const int endIndex = *endIndexPtr; if (*firstContour) { - current->initWinding(start, end, angleIncludeType); + current->initWinding(startIndex, endIndex, angleIncludeType); *firstContour = false; return current; } - SkOpSpan* minSpan = start->starter(end); - int sumWinding = minSpan->windSum(); + int minIndex = SkMin32(startIndex, endIndex); + int sumWinding = current->windSum(minIndex); if (sumWinding == SK_MinS32) { - SkOpSpanBase* iSpan = end; - SkOpSpanBase* oSpan = start; - do { - bool checkFrom = oSpan->t() < iSpan->t(); - if ((checkFrom ? iSpan->fromAngle() : iSpan->upCast()->toAngle()) == NULL) { - iSpan->addSimpleAngle(checkFrom, allocator); + int index = endIndex; + int oIndex = startIndex; + do { + const SkOpSpan& span = current->span(index); + if ((oIndex < index ? span.fFromAngle : span.fToAngle) == NULL) { + current->addSimpleAngle(index); } - sumWinding = current->computeSum(oSpan, iSpan, angleIncludeType); - SkTSwap(iSpan, oSpan); - } while (sumWinding == SK_MinS32 && iSpan == start); + sumWinding = current->computeSum(oIndex, index, angleIncludeType); + SkTSwap(index, oIndex); + } while (sumWinding == SK_MinS32 && index == startIndex); } if (sumWinding != SK_MinS32 && sumWinding != SK_NaN32) { return current; @@ -341,28 +364,26 @@ SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour* >& contourList, bool f SkScalar hitDx = 0; SkScalar hitOppDx = 0; // keep track of subsequent returns to detect infinite loops - SkTDArray<SortableTop2> sortableTops; + SkTDArray<SortableTop> sortableTops; do { // if current is vertical, find another candidate which is not // if only remaining candidates are vertical, then they can be marked done - SkASSERT(*startPtr != *endPtr && *startPtr && *endPtr); - SkASSERT(current == (*startPtr)->segment()); - skipVertical(contourList, ¤t, startPtr, endPtr); + SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0); + skipVertical(contourList, ¤t, indexPtr, endIndexPtr); SkASSERT(current); // FIXME: if null, all remaining are vertical - SkASSERT(*startPtr != *endPtr && *startPtr && *endPtr); - SkASSERT(current == (*startPtr)->segment()); + SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0); tryAgain = false; - contourWinding = rightAngleWinding(contourList, ¤t, startPtr, endPtr, &tHit, + contourWinding = rightAngleWinding(contourList, ¤t, indexPtr, endIndexPtr, &tHit, &hitDx, &tryAgain, onlyVertical, false); - SkASSERT(current == (*startPtr)->segment()); if (tryAgain) { bool giveUp = false; int count = sortableTops.count(); for (int index = 0; index < count; ++index) { - const SortableTop2& prev = sortableTops[index]; + const SortableTop& prev = sortableTops[index]; if (giveUp) { - prev.fStart->segment()->markDone(prev.fStart->starter(prev.fEnd)); - } else if (prev.fStart == *startPtr || prev.fEnd == *endPtr) { + prev.fSegment->markDoneFinal(prev.fIndex); + } else if (prev.fSegment == current + && (prev.fIndex == *indexPtr || prev.fEndIndex == *endIndexPtr)) { // remaining edges are non-vertical and cannot have their winding computed // mark them as done and return, and hope that assembly can fill the holes giveUp = true; @@ -374,13 +395,14 @@ SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour* >& contourList, bool f return NULL; } } - SortableTop2* sortableTop = sortableTops.append(); - sortableTop->fStart = *startPtr; - sortableTop->fEnd = *endPtr; + SortableTop* sortableTop = sortableTops.append(); + sortableTop->fSegment = current; + sortableTop->fIndex = *indexPtr; + sortableTop->fEndIndex = *endIndexPtr; #if DEBUG_SORT SkDebugf("%s current=%d index=%d endIndex=%d tHit=%1.9g hitDx=%1.9g try=%d vert=%d\n", - __FUNCTION__, current->debugID(), (*startPtr)->debugID(), (*endPtr)->debugID(), - tHit, hitDx, tryAgain, *onlyVertical); + __FUNCTION__, current->debugID(), *indexPtr, *endIndexPtr, tHit, hitDx, tryAgain, + *onlyVertical); #endif if (*onlyVertical) { return current; @@ -391,35 +413,127 @@ SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour* >& contourList, bool f if (angleIncludeType < SkOpAngle::kBinarySingle) { break; } - oppContourWinding = rightAngleWinding(contourList, ¤t, startPtr, endPtr, &tHit, + oppContourWinding = rightAngleWinding(contourList, ¤t, indexPtr, endIndexPtr, &tHit, &hitOppDx, &tryAgain, NULL, true); - SkASSERT(current == (*startPtr)->segment()); } while (tryAgain); - bool success = current->initWinding(*startPtr, *endPtr, tHit, contourWinding, hitDx, + bool success = current->initWinding(*indexPtr, *endIndexPtr, tHit, contourWinding, hitDx, oppContourWinding, hitOppDx); if (current->done()) { return NULL; } else if (!success) { // check if the span has a valid winding - SkOpSpan* minSpan = (*startPtr)->t() < (*endPtr)->t() ? (*startPtr)->upCast() - : (*endPtr)->upCast(); - if (minSpan->windSum() == SK_MinS32) { + int min = SkTMin(*indexPtr, *endIndexPtr); + const SkOpSpan& span = current->span(min); + if (span.fWindSum == SK_MinS32) { return NULL; } } return current; } -void MakeContourList(SkOpContour* contour, SkTDArray<SkOpContour* >& list, - bool evenOdd, bool oppEvenOdd) { - do { - if (contour->count()) { - contour->setOppXor(contour->operand() ? evenOdd : oppEvenOdd); - *list.append() = contour; +static bool calcAngles(SkTArray<SkOpContour*, true>* contourList) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + if (!contour->calcAngles()) { + return false; } - } while ((contour = contour->next())); - if (list.count() < 2) { + } + return true; +} + +static void checkDuplicates(SkTArray<SkOpContour*, true>* contourList) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + contour->checkDuplicates(); + } +} + +static bool checkEnds(SkTArray<SkOpContour*, true>* contourList) { + // it's hard to determine if the end of a cubic or conic nearly intersects another curve. + // instead, look to see if the connecting curve intersected at that same end. + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + if (!contour->checkEnds()) { + return false; + } + } + return true; +} + +static bool checkMultiples(SkTArray<SkOpContour*, true>* contourList) { + bool hasMultiples = false; + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + contour->checkMultiples(); + hasMultiples |= contour->hasMultiples(); + } + return hasMultiples; +} + +// A small interval of a pair of curves may collapse to lines for each, triggering coincidence +static void checkSmall(SkTArray<SkOpContour*, true>* contourList) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + contour->checkSmall(); + } +} + +// A tiny interval may indicate an undiscovered coincidence. Find and fix. +static void checkTiny(SkTArray<SkOpContour*, true>* contourList) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + contour->checkTiny(); + } +} + +static void fixOtherTIndex(SkTArray<SkOpContour*, true>* contourList) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + contour->fixOtherTIndex(); + } +} + +static void joinCoincidence(SkTArray<SkOpContour*, true>* contourList) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + contour->joinCoincidence(); + } +} + +static void sortAngles(SkTArray<SkOpContour*, true>* contourList) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + contour->sortAngles(); + } +} + +static void sortSegments(SkTArray<SkOpContour*, true>* contourList) { + int contourCount = (*contourList).count(); + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = (*contourList)[cTest]; + contour->sortSegments(); + } +} + +void MakeContourList(SkTArray<SkOpContour>& contours, SkTArray<SkOpContour*, true>& list, + bool evenOdd, bool oppEvenOdd) { + int count = contours.count(); + if (count == 0) { return; } + for (int index = 0; index < count; ++index) { + SkOpContour& contour = contours[index]; + contour.setOppXor(contour.operand() ? evenOdd : oppEvenOdd); + list.push_back(&contour); + } SkTQSort<SkOpContour>(list.begin(), list.end() - 1); } @@ -440,22 +554,19 @@ public: reassemble contour pieces into new path */ void Assemble(const SkPathWriter& path, SkPathWriter* simple) { - SkOpContour contour; - SkOpGlobalState globalState(NULL PATH_OPS_DEBUG_PARAMS(&contour)); #if DEBUG_PATH_CONSTRUCTION SkDebugf("%s\n", __FUNCTION__); #endif - SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune - SkOpEdgeBuilder builder(path, &contour, &allocator, &globalState); - builder.finish(&allocator); - SkTDArray<const SkOpContour* > runs; // indices of partial contours - const SkOpContour* eContour = builder.head(); - do { - if (!eContour->count()) { - continue; - } - const SkPoint& eStart = eContour->start(); - const SkPoint& eEnd = eContour->end(); + SkTArray<SkOpContour> contours; + SkOpEdgeBuilder builder(path, contours); + builder.finish(); + int count = contours.count(); + int outer; + SkTArray<int, true> runs(count); // indices of partial contours + for (outer = 0; outer < count; ++outer) { + const SkOpContour& eContour = contours[outer]; + const SkPoint& eStart = eContour.start(); + const SkPoint& eEnd = eContour.end(); #if DEBUG_ASSEMBLE SkDebugf("%s contour", __FUNCTION__); if (!SkDPoint::ApproximatelyEqual(eStart, eEnd)) { @@ -467,42 +578,44 @@ void Assemble(const SkPathWriter& path, SkPathWriter* simple) { eStart.fX, eStart.fY, eEnd.fX, eEnd.fY); #endif if (SkDPoint::ApproximatelyEqual(eStart, eEnd)) { - eContour->toPath(simple); + eContour.toPath(simple); continue; } - *runs.append() = eContour; - } while ((eContour = eContour->next())); - int count = runs.count(); + runs.push_back(outer); + } + count = runs.count(); if (count == 0) { return; } - SkTDArray<int> sLink, eLink; - sLink.append(count); - eLink.append(count); + SkTArray<int, true> sLink, eLink; + sLink.push_back_n(count); + eLink.push_back_n(count); int rIndex, iIndex; for (rIndex = 0; rIndex < count; ++rIndex) { sLink[rIndex] = eLink[rIndex] = SK_MaxS32; } const int ends = count * 2; // all starts and ends const int entries = (ends - 1) * count; // folded triangle : n * (n - 1) / 2 - SkTDArray<double> distances; - distances.append(entries); + SkTArray<double, true> distances; + distances.push_back_n(entries); for (rIndex = 0; rIndex < ends - 1; ++rIndex) { - const SkOpContour* oContour = runs[rIndex >> 1]; - const SkPoint& oPt = rIndex & 1 ? oContour->end() : oContour->start(); + outer = runs[rIndex >> 1]; + const SkOpContour& oContour = contours[outer]; + const SkPoint& oPt = rIndex & 1 ? oContour.end() : oContour.start(); const int row = rIndex < count - 1 ? rIndex * ends : (ends - rIndex - 2) * ends - rIndex - 1; for (iIndex = rIndex + 1; iIndex < ends; ++iIndex) { - const SkOpContour* iContour = runs[iIndex >> 1]; - const SkPoint& iPt = iIndex & 1 ? iContour->end() : iContour->start(); + int inner = runs[iIndex >> 1]; + const SkOpContour& iContour = contours[inner]; + const SkPoint& iPt = iIndex & 1 ? iContour.end() : iContour.start(); double dx = iPt.fX - oPt.fX; double dy = iPt.fY - oPt.fY; double dist = dx * dx + dy * dy; distances[row + iIndex] = dist; // oStart distance from iStart } } - SkTDArray<int> sortedDist; - sortedDist.append(entries); + SkTArray<int, true> sortedDist; + sortedDist.push_back_n(entries); for (rIndex = 0; rIndex < entries; ++rIndex) { sortedDist[rIndex] = rIndex; } @@ -565,16 +678,17 @@ void Assemble(const SkPathWriter& path, SkPathWriter* simple) { eIndex < 0 ? ~eIndex : eIndex); #endif do { - const SkOpContour* contour = runs[rIndex]; + outer = runs[rIndex]; + const SkOpContour& contour = contours[outer]; if (first) { first = false; - const SkPoint* startPtr = &contour->start(); + const SkPoint* startPtr = &contour.start(); simple->deferredMove(startPtr[0]); } if (forward) { - contour->toPartialForward(simple); + contour.toPartialForward(simple); } else { - contour->toPartialBackward(simple); + contour.toPartialBackward(simple); } #if DEBUG_ASSEMBLE SkDebugf("%s rIndex=%d eIndex=%s%d close=%d\n", __FUNCTION__, rIndex, @@ -628,88 +742,36 @@ void Assemble(const SkPathWriter& path, SkPathWriter* simple) { #endif } -static void align(SkTDArray<SkOpContour* >* contourList) { - int contourCount = (*contourList).count(); - for (int cTest = 0; cTest < contourCount; ++cTest) { - SkOpContour* contour = (*contourList)[cTest]; - contour->align(); - } -} - -static void calcAngles(SkTDArray<SkOpContour* >* contourList, SkChunkAlloc* allocator) { - int contourCount = (*contourList).count(); - for (int cTest = 0; cTest < contourCount; ++cTest) { - SkOpContour* contour = (*contourList)[cTest]; - contour->calcAngles(allocator); - } -} - -static void missingCoincidence(SkTDArray<SkOpContour* >* contourList, - SkOpCoincidence* coincidence, SkChunkAlloc* allocator) { - int contourCount = (*contourList).count(); - for (int cTest = 0; cTest < contourCount; ++cTest) { - SkOpContour* contour = (*contourList)[cTest]; - contour->missingCoincidence(coincidence, allocator); - } -} - -static bool moveNearby(SkTDArray<SkOpContour* >* contourList) { - int contourCount = (*contourList).count(); - for (int cTest = 0; cTest < contourCount; ++cTest) { - SkOpContour* contour = (*contourList)[cTest]; - if (!contour->moveNearby()) { - return false; - } - } - return true; -} - -static void sortAngles(SkTDArray<SkOpContour* >* contourList) { - int contourCount = (*contourList).count(); - for (int cTest = 0; cTest < contourCount; ++cTest) { - SkOpContour* contour = (*contourList)[cTest]; - contour->sortAngles(); - } -} - -static void sortSegments(SkTDArray<SkOpContour* >* contourList) { - int contourCount = (*contourList).count(); - for (int cTest = 0; cTest < contourCount; ++cTest) { - SkOpContour* contour = (*contourList)[cTest]; - contour->sortSegments(); - } -} - -bool HandleCoincidence(SkTDArray<SkOpContour* >* contourList, SkOpCoincidence* coincidence, - SkChunkAlloc* allocator, SkOpGlobalState* globalState) { - // move t values and points together to eliminate small/tiny gaps - if (!moveNearby(contourList)) { +bool HandleCoincidence(SkTArray<SkOpContour*, true>* contourList, int total) { +#if DEBUG_SHOW_WINDING + SkOpContour::debugShowWindingValues(contourList); +#endif + if (!CoincidenceCheck(contourList, total)) { return false; } - align(contourList); // give all span members common values -#if DEBUG_VALIDATE - globalState->setPhase(SkOpGlobalState::kIntersecting); -#endif - coincidence->addMissing(allocator); -#if DEBUG_VALIDATE - globalState->setPhase(SkOpGlobalState::kWalking); +#if DEBUG_SHOW_WINDING + SkOpContour::debugShowWindingValues(contourList); #endif - coincidence->expand(); // check to see if, loosely, coincident ranges may be expanded - coincidence->mark(); // mark spans of coincident segments as coincident - missingCoincidence(contourList, coincidence, allocator); // look for coincidence missed earlier - if (!coincidence->apply()) { // adjust the winding value to account for coincident edges + fixOtherTIndex(contourList); + if (!checkEnds(contourList)) { // check if connecting curve intersected at the same end return false; } + bool hasM = checkMultiples(contourList); // check if intersections agree on t and point values + SkTDArray<SkOpSegment::AlignedSpan> aligned; + if (hasM) { + alignMultiples(contourList, &aligned); // align pairs of identical points + alignCoincidence(contourList, aligned); + } + checkDuplicates(contourList); // check if spans have the same number on the other end + checkTiny(contourList); // if pair have the same end points, mark them as parallel + checkSmall(contourList); // a pair of curves with a small span may turn into coincident lines + joinCoincidence(contourList); // join curves that connect to a coincident pair sortSegments(contourList); - calcAngles(contourList, allocator); - sortAngles(contourList); - if (globalState->angleCoincidence()) { - missingCoincidence(contourList, coincidence, allocator); - if (!coincidence->apply()) { - return false; - } + if (!calcAngles(contourList)) { + return false; } -#if DEBUG_ACTIVE_SPANS + sortAngles(contourList); +#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY DebugShowActiveSpans(*contourList); #endif return true; diff --git a/src/pathops/SkPathOpsCommon.h b/src/pathops/SkPathOpsCommon.h index 1bf17919ad..0d8cfc42f9 100644 --- a/src/pathops/SkPathOpsCommon.h +++ b/src/pathops/SkPathOpsCommon.h @@ -8,28 +8,24 @@ #define SkPathOpsCommon_DEFINED #include "SkOpAngle.h" +#include "SkOpContour.h" #include "SkTDArray.h" -class SkOpCoincidence; -class SkOpContour; class SkPathWriter; void Assemble(const SkPathWriter& path, SkPathWriter* simple); -SkOpSegment* FindChase(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr); -SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour*>& , bool firstPass, - SkOpAngle::IncludeType , bool* firstContour, SkOpSpanBase** index, - SkOpSpanBase** endIndex, SkPoint* topLeft, bool* unsortable, - bool* done, bool* onlyVertical, SkChunkAlloc* ); -SkOpSegment* FindUndone(SkTDArray<SkOpContour*>& contourList, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr); -void MakeContourList(SkOpContour* , SkTDArray<SkOpContour*>& list, +// FIXME: find chase uses insert, so it can't be converted to SkTArray yet +SkOpSegment* FindChase(SkTDArray<SkOpSpan*>* chase, int* tIndex, int* endIndex); +SkOpSegment* FindSortableTop(const SkTArray<SkOpContour*, true>& , SkOpAngle::IncludeType , + bool* firstContour, int* index, int* endIndex, SkPoint* topLeft, + bool* unsortable, bool* done, bool* onlyVertical, bool firstPass); +SkOpSegment* FindUndone(SkTArray<SkOpContour*, true>& contourList, int* start, int* end); +void MakeContourList(SkTArray<SkOpContour>& contours, SkTArray<SkOpContour*, true>& list, bool evenOdd, bool oppEvenOdd); -bool HandleCoincidence(SkTDArray<SkOpContour*>* , SkOpCoincidence* , SkChunkAlloc* , - SkOpGlobalState* ); +bool HandleCoincidence(SkTArray<SkOpContour*, true>* , int ); -#if DEBUG_ACTIVE_SPANS -void DebugShowActiveSpans(SkTDArray<SkOpContour*>& contourList); +#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY +void DebugShowActiveSpans(SkTArray<SkOpContour*, true>& contourList); #endif #endif diff --git a/src/pathops/SkPathOpsCubic.cpp b/src/pathops/SkPathOpsCubic.cpp index d4a5898a1d..9d70d58ec1 100644 --- a/src/pathops/SkPathOpsCubic.cpp +++ b/src/pathops/SkPathOpsCubic.cpp @@ -4,7 +4,6 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ -#include "SkGeometry.h" #include "SkLineParameters.h" #include "SkPathOpsCubic.h" #include "SkPathOpsLine.h" @@ -27,8 +26,8 @@ double SkDCubic::binarySearch(double min, double max, double axisIntercept, double priorT = t - step; SkASSERT(priorT >= min); SkDPoint lessPt = ptAtT(priorT); - if (approximately_equal_half(lessPt.fX, cubicAtT.fX) - && approximately_equal_half(lessPt.fY, cubicAtT.fY)) { + if (approximately_equal(lessPt.fX, cubicAtT.fX) + && approximately_equal(lessPt.fY, cubicAtT.fY)) { return -1; // binary search found no point at this axis intercept } double lessDist = (&lessPt.fX)[xAxis] - axisIntercept; @@ -42,12 +41,10 @@ double SkDCubic::binarySearch(double min, double max, double axisIntercept, t = priorT; } else { double nextT = t + lastStep; - if (nextT > max) { - return -1; - } + SkASSERT(nextT <= max); SkDPoint morePt = ptAtT(nextT); - if (approximately_equal_half(morePt.fX, cubicAtT.fX) - && approximately_equal_half(morePt.fY, cubicAtT.fY)) { + if (approximately_equal(morePt.fX, cubicAtT.fX) + && approximately_equal(morePt.fY, cubicAtT.fY)) { return -1; // binary search found no point at this axis intercept } double moreDist = (&morePt.fX)[xAxis] - axisIntercept; @@ -91,6 +88,35 @@ void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, *C -= 3 * *D; // C = -3*a + 3*b } +bool SkDCubic::controlsContainedByEnds() const { + SkDVector startTan = fPts[1] - fPts[0]; + if (startTan.fX == 0 && startTan.fY == 0) { + startTan = fPts[2] - fPts[0]; + } + SkDVector endTan = fPts[2] - fPts[3]; + if (endTan.fX == 0 && endTan.fY == 0) { + endTan = fPts[1] - fPts[3]; + } + if (startTan.dot(endTan) >= 0) { + return false; + } + SkDLine startEdge = {{fPts[0], fPts[0]}}; + startEdge[1].fX -= startTan.fY; + startEdge[1].fY += startTan.fX; + SkDLine endEdge = {{fPts[3], fPts[3]}}; + endEdge[1].fX -= endTan.fY; + endEdge[1].fY += endTan.fX; + double leftStart1 = startEdge.isLeft(fPts[1]); + if (leftStart1 * startEdge.isLeft(fPts[2]) < 0) { + return false; + } + double leftEnd1 = endEdge.isLeft(fPts[1]); + if (leftEnd1 * endEdge.isLeft(fPts[2]) < 0) { + return false; + } + return leftStart1 * leftEnd1 >= 0; +} + bool SkDCubic::endsAreExtremaInXOrY() const { return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX) && between(fPts[0].fX, fPts[2].fX, fPts[3].fX)) @@ -98,120 +124,17 @@ bool SkDCubic::endsAreExtremaInXOrY() const { && between(fPts[0].fY, fPts[2].fY, fPts[3].fY)); } -// Do a quick reject by rotating all points relative to a line formed by -// a pair of one cubic's points. If the 2nd cubic's points -// are on the line or on the opposite side from the 1st cubic's 'odd man', the -// curves at most intersect at the endpoints. -/* if returning true, check contains true if cubic's hull collapsed, making the cubic linear - if returning false, check contains true if the the cubic pair have only the end point in common -*/ -bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const { - bool linear = true; - char hullOrder[4]; - int hullCount = convexHull(hullOrder); - int end1 = hullOrder[0]; - int hullIndex = 0; - const SkDPoint* endPt[2]; - endPt[0] = &fPts[end1]; - do { - hullIndex = (hullIndex + 1) % hullCount; - int end2 = hullOrder[hullIndex]; - endPt[1] = &fPts[end2]; - double origX = endPt[0]->fX; - double origY = endPt[0]->fY; - double adj = endPt[1]->fX - origX; - double opp = endPt[1]->fY - origY; - int oddManMask = other_two(end1, end2); - int oddMan = end1 ^ oddManMask; - double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp; - int oddMan2 = end2 ^ oddManMask; - double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp; - if (sign * sign2 < 0) { - continue; - } - if (approximately_zero(sign)) { - sign = sign2; - if (approximately_zero(sign)) { - continue; - } - } - linear = false; - bool foundOutlier = false; - for (int n = 0; n < kPointCount; ++n) { - double test = (c2[n].fY - origY) * adj - (c2[n].fX - origX) * opp; - if (test * sign > 0 && !precisely_zero(test)) { - foundOutlier = true; - break; - } - } - if (!foundOutlier) { - return false; - } - endPt[0] = endPt[1]; - end1 = end2; - } while (hullIndex); - *isLinear = linear; - return true; -} - bool SkDCubic::isLinear(int startIndex, int endIndex) const { SkLineParameters lineParameters; lineParameters.cubicEndPoints(*this, startIndex, endIndex); // FIXME: maybe it's possible to avoid this and compare non-normalized lineParameters.normalize(); - double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), - fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY); - double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), - fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY); - largest = SkTMax(largest, -tiniest); double distance = lineParameters.controlPtDistance(*this, 1); - if (!approximately_zero_when_compared_to(distance, largest)) { + if (!approximately_zero(distance)) { return false; } distance = lineParameters.controlPtDistance(*this, 2); - return approximately_zero_when_compared_to(distance, largest); -} - -bool SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) { - SkScalar d[3]; - SkCubicType cubicType = SkClassifyCubic(pointsPtr, d); - if (cubicType == kLoop_SkCubicType) { - // crib code from gpu path utils that finds t values where loop self-intersects - // use it to find mid of t values which should be a friendly place to chop - SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]); - SkScalar ls = d[1] - tempSqrt; - SkScalar lt = 2.f * d[0]; - SkScalar ms = d[1] + tempSqrt; - SkScalar mt = 2.f * d[0]; - if (between(0, ls, lt) || between(0, ms, mt)) { - ls = ls / lt; - ms = ms / mt; - SkScalar smaller = SkTMax(0.f, SkTMin(ls, ms)); - SkScalar larger = SkTMin(1.f, SkTMax(ls, ms)); - *t = (smaller + larger) / 2; - return *t > 0 && *t < 1; - } - } else if (cubicType == kSerpentine_SkCubicType) { - SkDCubic cubic; - cubic.set(pointsPtr); - double inflectionTs[2]; - int infTCount = cubic.findInflections(inflectionTs); - if (infTCount == 2) { - double maxCurvature[3]; - int roots = cubic.findMaxCurvature(maxCurvature); - for (int index = 0; index < roots; ++index) { - if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) { - *t = maxCurvature[index]; - return true; - } - } - } else if (infTCount == 1) { - *t = inflectionTs[0]; - return *t > 0 && *t < 1; - } - return false; - } - return false; + return approximately_zero(distance); } bool SkDCubic::monotonicInY() const { @@ -219,13 +142,6 @@ bool SkDCubic::monotonicInY() const { && between(fPts[0].fY, fPts[2].fY, fPts[3].fY); } -void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const { - int offset = (int) !SkToBool(index); - o1Pts[0] = &fPts[offset]; - o1Pts[1] = &fPts[++offset]; - o1Pts[2] = &fPts[++offset]; -} - int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept, SearchAxis xAxis, double* validRoots) const { extrema += findInflections(&extremeTs[extrema]); @@ -247,6 +163,26 @@ int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept return validCount; } +bool SkDCubic::serpentine() const { +#if 0 // FIXME: enabling this fixes cubicOp114 but breaks cubicOp58d and cubicOp53d + double tValues[2]; + // OPTIMIZATION : another case where caching the present of cubic inflections would be useful + return findInflections(tValues) > 1; +#endif + if (!controlsContainedByEnds()) { + return false; + } + double wiggle = (fPts[0].fX - fPts[2].fX) * (fPts[0].fY + fPts[2].fY); + for (int idx = 0; idx < 2; ++idx) { + wiggle += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY); + } + double waggle = (fPts[1].fX - fPts[3].fX) * (fPts[1].fY + fPts[3].fY); + for (int idx = 1; idx < 3; ++idx) { + waggle += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY); + } + return wiggle * waggle < 0; +} + // cubic roots static const double PI = 3.141592653589793; @@ -569,10 +505,25 @@ void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const { void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint dst[2]) const { SkASSERT(t1 != t2); +#if 0 + double ex = interp_cubic_coords(&fPts[0].fX, (t1 * 2 + t2) / 3); + double ey = interp_cubic_coords(&fPts[0].fY, (t1 * 2 + t2) / 3); + double fx = interp_cubic_coords(&fPts[0].fX, (t1 + t2 * 2) / 3); + double fy = interp_cubic_coords(&fPts[0].fY, (t1 + t2 * 2) / 3); + double mx = ex * 27 - a.fX * 8 - d.fX; + double my = ey * 27 - a.fY * 8 - d.fY; + double nx = fx * 27 - a.fX - d.fX * 8; + double ny = fy * 27 - a.fY - d.fY * 8; + /* bx = */ dst[0].fX = (mx * 2 - nx) / 18; + /* by = */ dst[0].fY = (my * 2 - ny) / 18; + /* cx = */ dst[1].fX = (nx * 2 - mx) / 18; + /* cy = */ dst[1].fY = (ny * 2 - my) / 18; +#else // this approach assumes that the control points computed directly are accurate enough SkDCubic sub = subDivide(t1, t2); dst[0] = sub[1] + (a - sub[0]); dst[1] = sub[2] + (d - sub[3]); +#endif if (t1 == 0 || t2 == 0) { align(0, 1, t1 == 0 ? &dst[0] : &dst[1]); } diff --git a/src/pathops/SkPathOpsCubic.h b/src/pathops/SkPathOpsCubic.h index 9932e1d1bc..1037cae4f7 100644 --- a/src/pathops/SkPathOpsCubic.h +++ b/src/pathops/SkPathOpsCubic.h @@ -10,6 +10,7 @@ #include "SkPath.h" #include "SkPathOpsPoint.h" +#include "SkTArray.h" struct SkDCubicPair { const SkDCubic& first() const { return (const SkDCubic&) pts[0]; } @@ -18,33 +19,13 @@ struct SkDCubicPair { }; struct SkDCubic { - static const int kPointCount = 4; - static const int kPointLast = kPointCount - 1; - static const int kMaxIntersections = 9; - enum SearchAxis { kXAxis, kYAxis }; - bool collapsed() const { - return fPts[0].approximatelyEqual(fPts[1]) && fPts[0].approximatelyEqual(fPts[2]) - && fPts[0].approximatelyEqual(fPts[3]); - } - - bool controlsInside() const { - SkDVector v01 = fPts[0] - fPts[1]; - SkDVector v02 = fPts[0] - fPts[2]; - SkDVector v03 = fPts[0] - fPts[3]; - SkDVector v13 = fPts[1] - fPts[3]; - SkDVector v23 = fPts[2] - fPts[3]; - return v03.dot(v01) > 0 && v03.dot(v02) > 0 && v03.dot(v13) > 0 && v03.dot(v23) > 0; - } - - static bool IsCubic() { return true; } - - const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } - SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 4); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 4); return fPts[n]; } void align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const; double binarySearch(double min, double max, double axisIntercept, SearchAxis xAxis) const; @@ -52,35 +33,30 @@ struct SkDCubic { SkDCubicPair chopAt(double t) const; bool clockwise() const; static void Coefficients(const double* cubic, double* A, double* B, double* C, double* D); - static bool ComplexBreak(const SkPoint pts[4], SkScalar* t); - int convexHull(char order[kPointCount]) const; - void dump() const; // callable from the debugger when the implementation code is linked in - void dumpID(int id) const; - void dumpInner() const; + bool controlsContainedByEnds() const; SkDVector dxdyAtT(double t) const; bool endsAreExtremaInXOrY() const; static int FindExtrema(double a, double b, double c, double d, double tValue[2]); int findInflections(double tValues[2]) const; - static int FindInflections(const SkPoint a[kPointCount], double tValues[2]) { + static int FindInflections(const SkPoint a[4], double tValues[2]) { SkDCubic cubic; cubic.set(a); return cubic.findInflections(tValues); } int findMaxCurvature(double tValues[]) const; - bool hullIntersects(const SkDCubic& c2, bool* isLinear) const; bool isLinear(int startIndex, int endIndex) const; bool monotonicInY() const; - void otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const; SkDPoint ptAtT(double t) const; static int RootsReal(double A, double B, double C, double D, double t[3]); static int RootsValidT(const double A, const double B, const double C, double D, double s[3]); int searchRoots(double extremes[6], int extrema, double axisIntercept, SearchAxis xAxis, double* validRoots) const; + bool serpentine() const; - void set(const SkPoint pts[kPointCount]) { + void set(const SkPoint pts[4]) { fPts[0] = pts[0]; fPts[1] = pts[1]; fPts[2] = pts[2]; @@ -89,7 +65,7 @@ struct SkDCubic { SkDCubic subDivide(double t1, double t2) const; - static SkDCubic SubDivide(const SkPoint a[kPointCount], double t1, double t2) { + static SkDCubic SubDivide(const SkPoint a[4], double t1, double t2) { SkDCubic cubic; cubic.set(a); return cubic.subDivide(t1, t2); @@ -97,7 +73,7 @@ struct SkDCubic { void subDivide(const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint p[2]) const; - static void SubDivide(const SkPoint pts[kPointCount], const SkDPoint& a, const SkDPoint& d, double t1, + static void SubDivide(const SkPoint pts[4], const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint p[2]) { SkDCubic cubic; cubic.set(pts); @@ -105,29 +81,16 @@ struct SkDCubic { } SkDPoint top(double startT, double endT) const; + void toQuadraticTs(double precision, SkTArray<double, true>* ts) const; SkDQuad toQuad() const; + // utilities callable by the user from the debugger when the implementation code is linked in + void dump() const; + void dumpNumber() const; + static const int gPrecisionUnit; - SkDPoint fPts[kPointCount]; + SkDPoint fPts[4]; }; -/* Given the set [0, 1, 2, 3], and two of the four members, compute an XOR mask - that computes the other two. Note that: - - one ^ two == 3 for (0, 3), (1, 2) - one ^ two < 3 for (0, 1), (0, 2), (1, 3), (2, 3) - 3 - (one ^ two) is either 0, 1, or 2 - 1 >> (3 - (one ^ two)) is either 0 or 1 -thus: - returned == 2 for (0, 3), (1, 2) - returned == 3 for (0, 1), (0, 2), (1, 3), (2, 3) -given that: - (0, 3) ^ 2 -> (2, 1) (1, 2) ^ 2 -> (3, 0) - (0, 1) ^ 3 -> (3, 2) (0, 2) ^ 3 -> (3, 1) (1, 3) ^ 3 -> (2, 0) (2, 3) ^ 3 -> (1, 0) -*/ -inline int other_two(int one, int two) { - return 1 >> (3 - (one ^ two)) ^ 3; -} - #endif diff --git a/src/pathops/SkPathOpsCubicSect.h b/src/pathops/SkPathOpsCubicSect.h new file mode 100644 index 0000000000..d7634449b6 --- /dev/null +++ b/src/pathops/SkPathOpsCubicSect.h @@ -0,0 +1,175 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkCubicSpan_DEFINE +#define SkCubicSpan_DEFINE + +#include "SkChunkAlloc.h" +#include "SkPathOpsRect.h" +#include "SkPathOpsCubic.h" +#include "SkTArray.h" + +class SkIntersections; + +class SkCubicCoincident { +public: + bool isCoincident() const { + return fCoincident; + } + + void init() { + fCoincident = false; + SkDEBUGCODE(fPerpPt.fX = fPerpPt.fY = SK_ScalarNaN); + SkDEBUGCODE(fPerpT = SK_ScalarNaN); + } + + void markCoincident() { + if (!fCoincident) { + fPerpT = -1; + } + fCoincident = true; + } + + const SkDPoint& perpPt() const { + return fPerpPt; + } + + double perpT() const { + return fPerpT; + } + + void setPerp(const SkDCubic& cubic1, double t, const SkDPoint& qPt, const SkDCubic& cubic2); + +private: + SkDPoint fPerpPt; + double fPerpT; // perpendicular intersection on opposite Cubic + bool fCoincident; +}; + +class SkCubicSect; // used only by debug id + +class SkCubicSpan { +public: + void init(const SkDCubic& Cubic); + void initBounds(const SkDCubic& Cubic); + + bool contains(double t) const { + return !! const_cast<SkCubicSpan*>(this)->innerFind(t); + } + + bool contains(const SkCubicSpan* span) const; + + SkCubicSpan* find(double t) { + SkCubicSpan* result = innerFind(t); + SkASSERT(result); + return result; + } + + bool intersects(const SkCubicSpan* span) const; + + const SkCubicSpan* next() const { + return fNext; + } + + void reset() { + fBounded.reset(); + } + + bool split(SkCubicSpan* work) { + return splitAt(work, (work->fStartT + work->fEndT) * 0.5); + } + + bool splitAt(SkCubicSpan* work, double t); + bool tightBoundsIntersects(const SkCubicSpan* span) const; + + // implementation is for testing only + void dump() const; + +private: + bool hullIntersects(const SkDCubic& ) const; + SkCubicSpan* innerFind(double t); + bool linearIntersects(const SkDCubic& ) const; + + // implementation is for testing only +#if DEBUG_BINARY_CUBIC + int debugID(const SkCubicSect* ) const { return fDebugID; } +#else + int debugID(const SkCubicSect* ) const; +#endif + void dump(const SkCubicSect* ) const; + void dumpID(const SkCubicSect* ) const; + +#if DEBUG_BINARY_CUBIC + void validate() const; +#endif + + SkDCubic fPart; + SkCubicCoincident fCoinStart; + SkCubicCoincident fCoinEnd; + SkSTArray<4, SkCubicSpan*, true> fBounded; + SkCubicSpan* fPrev; + SkCubicSpan* fNext; + SkDRect fBounds; + double fStartT; + double fEndT; + double fBoundsMax; + bool fCollapsed; + bool fHasPerp; + mutable bool fIsLinear; +#if DEBUG_BINARY_CUBIC + int fDebugID; + bool fDebugDeleted; +#endif + friend class SkCubicSect; +}; + +class SkCubicSect { +public: + SkCubicSect(const SkDCubic& Cubic PATH_OPS_DEBUG_PARAMS(int id)); + static void BinarySearch(SkCubicSect* sect1, SkCubicSect* sect2, SkIntersections* intersections); + + // for testing only + void dumpCubics() const; +private: + SkCubicSpan* addOne(); + bool binarySearchCoin(const SkCubicSect& , double tStart, double tStep, double* t, + double* oppT); + SkCubicSpan* boundsMax() const; + void coincidentCheck(SkCubicSect* sect2); + bool intersects(const SkCubicSpan* span, const SkCubicSect* opp, const SkCubicSpan* oppSpan) const; + void onCurveCheck(SkCubicSect* sect2, SkCubicSpan* first, SkCubicSpan* last); + void recoverCollapsed(); + void removeSpan(SkCubicSpan* span); + void removeOne(const SkCubicSpan* test, SkCubicSpan* span); + void removeSpans(SkCubicSpan* span, SkCubicSect* opp); + void setPerp(const SkDCubic& opp, SkCubicSpan* first, SkCubicSpan* last); + void trim(SkCubicSpan* span, SkCubicSect* opp); + + // for testing only + void dump() const; + void dumpBoth(const SkCubicSect& opp) const; + void dumpBoth(const SkCubicSect* opp) const; + +#if DEBUG_BINARY_CUBIC + int debugID() const { return fDebugID; } + void validate() const; +#else + int debugID() const { return 0; } +#endif + const SkDCubic& fCubic; + SkChunkAlloc fHeap; + SkCubicSpan* fHead; + SkCubicSpan* fDeleted; + int fActiveCount; +#if DEBUG_BINARY_CUBIC + int fDebugID; + int fDebugCount; + int fDebugAllocatedCount; +#endif + friend class SkCubicSpan; // only used by debug id +}; + +#endif diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp index 0331f34e8a..7db93f5e96 100644 --- a/src/pathops/SkPathOpsDebug.cpp +++ b/src/pathops/SkPathOpsDebug.cpp @@ -7,13 +7,6 @@ #include "SkPathOpsDebug.h" #include "SkPath.h" -#if DEBUG_ANGLE -#include "SkString.h" -#endif - -#if DEBUG_VALIDATE -extern bool FLAGS_runFail; -#endif #if defined SK_DEBUG || !FORCE_RELEASE @@ -33,10 +26,10 @@ int SkPathOpsDebug::gSortCount; const char* SkPathOpsDebug::kPathOpStr[] = {"diff", "sect", "union", "xor"}; #endif -bool SkPathOpsDebug::ChaseContains(const SkTDArray<SkOpSpanBase* >& chaseArray, - const SkOpSpanBase* span) { +bool SkPathOpsDebug::ChaseContains(const SkTDArray<SkOpSpan *>& chaseArray, + const SkOpSpan* span) { for (int index = 0; index < chaseArray.count(); ++index) { - const SkOpSpanBase* entry = chaseArray[index]; + const SkOpSpan* entry = chaseArray[index]; if (entry == span) { return true; } @@ -72,8 +65,6 @@ void SkPathOpsDebug::WindingPrintf(int wind) { SkDebugf("%d", wind); } } -#endif // defined SK_DEBUG || !FORCE_RELEASE - #if DEBUG_SHOW_TEST_NAME void* SkPathOpsDebug::CreateNameStr() { @@ -106,368 +97,470 @@ void SkPathOpsDebug::ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, } #endif +#endif // defined SK_DEBUG || !FORCE_RELEASE + #include "SkOpAngle.h" #include "SkOpSegment.h" +#if DEBUG_SORT +void SkOpAngle::debugLoop() const { + const SkOpAngle* first = this; + const SkOpAngle* next = this; + do { + next->dumpOne(true); + SkDebugf("\n"); + next = next->fNext; + } while (next && next != first); +} +#endif + +#if DEBUG_ANGLE +void SkOpAngle::debugSameAs(const SkOpAngle* compare) const { + SK_ALWAYSBREAK(fSegment == compare->fSegment); + const SkOpSpan& startSpan = fSegment->span(fStart); + const SkOpSpan& oStartSpan = fSegment->span(compare->fStart); + SK_ALWAYSBREAK(startSpan.fToAngle == oStartSpan.fToAngle); + SK_ALWAYSBREAK(startSpan.fFromAngle == oStartSpan.fFromAngle); + const SkOpSpan& endSpan = fSegment->span(fEnd); + const SkOpSpan& oEndSpan = fSegment->span(compare->fEnd); + SK_ALWAYSBREAK(endSpan.fToAngle == oEndSpan.fToAngle); + SK_ALWAYSBREAK(endSpan.fFromAngle == oEndSpan.fFromAngle); +} +#endif + +#if DEBUG_VALIDATE +void SkOpAngle::debugValidateNext() const { + const SkOpAngle* first = this; + const SkOpAngle* next = first; + SkTDArray<const SkOpAngle*>(angles); + do { +// SK_ALWAYSBREAK(next->fSegment->debugContains(next)); + angles.push(next); + next = next->next(); + if (next == first) { + break; + } + SK_ALWAYSBREAK(!angles.contains(next)); + if (!next) { + return; + } + } while (true); +} + +void SkOpAngle::debugValidateLoop() const { + const SkOpAngle* first = this; + const SkOpAngle* next = first; + SK_ALWAYSBREAK(first->next() != first); + int signSum = 0; + int oppSum = 0; + bool firstOperand = fSegment->operand(); + bool unorderable = false; + do { + unorderable |= next->fUnorderable; + const SkOpSegment* segment = next->fSegment; + bool operandsMatch = firstOperand == segment->operand(); + signSum += operandsMatch ? segment->spanSign(next) : segment->oppSign(next); + oppSum += operandsMatch ? segment->oppSign(next) : segment->spanSign(next); + const SkOpSpan& span = segment->span(SkMin32(next->fStart, next->fEnd)); + if (segment->_xor()) { +// SK_ALWAYSBREAK(span.fWindValue == 1); +// SK_ALWAYSBREAK(span.fWindSum == SK_MinS32 || span.fWindSum == 1); + } + if (segment->oppXor()) { + SK_ALWAYSBREAK(span.fOppValue == 0 || abs(span.fOppValue) == 1); +// SK_ALWAYSBREAK(span.fOppSum == SK_MinS32 || span.fOppSum == 0 || abs(span.fOppSum) == 1); + } + next = next->next(); + if (!next) { + return; + } + } while (next != first); + if (unorderable) { + return; + } + SK_ALWAYSBREAK(!signSum || fSegment->_xor()); + SK_ALWAYSBREAK(!oppSum || fSegment->oppXor()); + int lastWinding; + int lastOppWinding; + int winding; + int oppWinding; + do { + const SkOpSegment* segment = next->fSegment; + const SkOpSpan& span = segment->span(SkMin32(next->fStart, next->fEnd)); + winding = span.fWindSum; + if (winding != SK_MinS32) { +// SK_ALWAYSBREAK(winding != 0); + SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(winding)); + lastWinding = winding; + int diffWinding = segment->spanSign(next); + if (!segment->_xor()) { + SK_ALWAYSBREAK(diffWinding != 0); + bool sameSign = (winding > 0) == (diffWinding > 0); + winding -= sameSign ? diffWinding : -diffWinding; + SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(winding)); + SK_ALWAYSBREAK(abs(winding) <= abs(lastWinding)); + if (!sameSign) { + SkTSwap(winding, lastWinding); + } + } + lastOppWinding = oppWinding = span.fOppSum; + if (oppWinding != SK_MinS32 && !segment->oppXor()) { + int oppDiffWinding = segment->oppSign(next); +// SK_ALWAYSBREAK(abs(oppDiffWinding) <= abs(diffWinding) || segment->_xor()); + if (oppDiffWinding) { + bool oppSameSign = (oppWinding > 0) == (oppDiffWinding > 0); + oppWinding -= oppSameSign ? oppDiffWinding : -oppDiffWinding; + SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(oppWinding)); + SK_ALWAYSBREAK(abs(oppWinding) <= abs(lastOppWinding)); + if (!oppSameSign) { + SkTSwap(oppWinding, lastOppWinding); + } + } + } + firstOperand = segment->operand(); + break; + } + SK_ALWAYSBREAK(span.fOppSum == SK_MinS32); + next = next->next(); + } while (next != first); + if (winding == SK_MinS32) { + return; + } + SK_ALWAYSBREAK(oppWinding == SK_MinS32 || SkPathOpsDebug::ValidWind(oppWinding)); + first = next; + next = next->next(); + do { + const SkOpSegment* segment = next->fSegment; + lastWinding = winding; + lastOppWinding = oppWinding; + bool operandsMatch = firstOperand == segment->operand(); + if (operandsMatch) { + if (!segment->_xor()) { + winding -= segment->spanSign(next); + SK_ALWAYSBREAK(winding != lastWinding); + SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(winding)); + } + if (!segment->oppXor()) { + int oppDiffWinding = segment->oppSign(next); + if (oppWinding != SK_MinS32) { + oppWinding -= oppDiffWinding; + SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(oppWinding)); + } else { + SK_ALWAYSBREAK(oppDiffWinding == 0); + } + } + } else { + if (!segment->oppXor()) { + winding -= segment->oppSign(next); + SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(winding)); + } + if (!segment->_xor()) { + oppWinding -= segment->spanSign(next); + SK_ALWAYSBREAK(oppWinding != lastOppWinding); + SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(oppWinding)); + } + } + bool useInner = SkOpSegment::UseInnerWinding(lastWinding, winding); + int sumWinding = useInner ? winding : lastWinding; + bool oppUseInner = SkOpSegment::UseInnerWinding(lastOppWinding, oppWinding); + int oppSumWinding = oppUseInner ? oppWinding : lastOppWinding; + if (!operandsMatch) { + SkTSwap(useInner, oppUseInner); + SkTSwap(sumWinding, oppSumWinding); + } + const SkOpSpan& span = segment->span(SkMin32(next->fStart, next->fEnd)); + if (winding == -lastWinding) { + if (span.fWindSum != SK_MinS32) { + SkDebugf("%s useInner=%d spanSign=%d lastWinding=%d winding=%d windSum=%d\n", + __FUNCTION__, + useInner, segment->spanSign(next), lastWinding, winding, span.fWindSum); + } + } + if (oppWinding != SK_MinS32) { + if (span.fOppSum != SK_MinS32) { + SK_ALWAYSBREAK(span.fOppSum == oppSumWinding || segment->oppXor() || segment->_xor()); + } + } else { + SK_ALWAYSBREAK(!firstOperand); + SK_ALWAYSBREAK(!segment->operand()); + SK_ALWAYSBREAK(!span.fOppValue); + } + next = next->next(); + } while (next != first); +} +#endif + #if DEBUG_SWAP_TOP -int SkOpSegment::debugInflections(const SkOpSpanBase* start, const SkOpSpanBase* end) const { +bool SkOpSegment::controlsContainedByEnds(int tStart, int tEnd) const { if (fVerb != SkPath::kCubic_Verb) { return false; } - SkDCubic dst = SkDCubic::SubDivide(fPts, start->t(), end->t()); + SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); + return dst.controlsContainedByEnds(); +} +#endif + +#if DEBUG_CONCIDENT +// SK_ALWAYSBREAK if pair has not already been added +void SkOpSegment::debugAddTPair(double t, const SkOpSegment& other, double otherT) const { + for (int i = 0; i < fTs.count(); ++i) { + if (fTs[i].fT == t && fTs[i].fOther == &other && fTs[i].fOtherT == otherT) { + return; + } + } + SK_ALWAYSBREAK(0); +} +#endif + +#if DEBUG_ANGLE +void SkOpSegment::debugCheckPointsEqualish(int tStart, int tEnd) const { + const SkPoint& basePt = fTs[tStart].fPt; + while (++tStart < tEnd) { + const SkPoint& cmpPt = fTs[tStart].fPt; + SK_ALWAYSBREAK(SkDPoint::ApproximatelyEqual(basePt, cmpPt)); + } +} +#endif + +#if DEBUG_SWAP_TOP +int SkOpSegment::debugInflections(int tStart, int tEnd) const { + if (fVerb != SkPath::kCubic_Verb) { + return false; + } + SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); double inflections[2]; return dst.findInflections(inflections); } #endif -SkOpAngle* SkOpSegment::debugLastAngle() { - SkOpAngle* result = NULL; - SkOpSpan* span = this->head(); - do { - if (span->toAngle()) { +const SkOpAngle* SkOpSegment::debugLastAngle() const { + const SkOpAngle* result = NULL; + for (int index = 0; index < count(); ++index) { + const SkOpSpan& span = this->span(index); + if (span.fToAngle) { SkASSERT(!result); - result = span->toAngle(); + result = span.fToAngle; } - } while ((span = span->next()->upCastable())); + } SkASSERT(result); return result; } void SkOpSegment::debugReset() { - this->init(this->fPts, this->contour(), this->verb()); + fTs.reset(); + fAngles.reset(); +} + +#if DEBUG_CONCIDENT +void SkOpSegment::debugShowTs(const char* prefix) const { + SkDebugf("%s %s id=%d", __FUNCTION__, prefix, fID); + int lastWind = -1; + int lastOpp = -1; + double lastT = -1; + int i; + for (i = 0; i < fTs.count(); ++i) { + bool change = lastT != fTs[i].fT || lastWind != fTs[i].fWindValue + || lastOpp != fTs[i].fOppValue; + if (change && lastWind >= 0) { + SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]", + lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp); + } + if (change) { + SkDebugf(" [o=%d", fTs[i].fOther->fID); + lastWind = fTs[i].fWindValue; + lastOpp = fTs[i].fOppValue; + lastT = fTs[i].fT; + } else { + SkDebugf(",%d", fTs[i].fOther->fID); + } + } + if (i <= 0) { + return; + } + SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]", + lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp); + if (fOperand) { + SkDebugf(" operand"); + } + if (done()) { + SkDebugf(" done"); + } + SkDebugf("\n"); } +#endif -#if DEBUG_ACTIVE_SPANS +#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY void SkOpSegment::debugShowActiveSpans() const { debugValidate(); if (done()) { return; } +#if DEBUG_ACTIVE_SPANS_SHORT_FORM int lastId = -1; double lastT = -1; - const SkOpSpan* span = &fHead; - do { - if (span->done()) { +#endif + for (int i = 0; i < fTs.count(); ++i) { + if (fTs[i].fDone) { continue; } - if (lastId == fID && lastT == span->t()) { + SK_ALWAYSBREAK(i < fTs.count() - 1); +#if DEBUG_ACTIVE_SPANS_SHORT_FORM + if (lastId == fID && lastT == fTs[i].fT) { continue; } lastId = fID; - lastT = span->t(); + lastT = fTs[i].fT; +#endif SkDebugf("%s id=%d", __FUNCTION__, fID); SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); } - const SkOpPtT* ptT = span->ptT(); - SkDebugf(") t=%1.9g (%1.9g,%1.9g)", ptT->fT, ptT->fPt.fX, ptT->fPt.fY); - SkDebugf(" tEnd=%1.9g", span->next()->t()); - SkDebugf(" windSum="); - if (span->windSum() == SK_MinS32) { + const SkOpSpan* span = &fTs[i]; + SkDebugf(") t=%1.9g (%1.9g,%1.9g)", span->fT, xAtT(span), yAtT(span)); + int iEnd = i + 1; + while (fTs[iEnd].fT < 1 && approximately_equal(fTs[i].fT, fTs[iEnd].fT)) { + ++iEnd; + } + SkDebugf(" tEnd=%1.9g", fTs[iEnd].fT); + const SkOpSegment* other = fTs[i].fOther; + SkDebugf(" other=%d otherT=%1.9g otherIndex=%d windSum=", + other->fID, fTs[i].fOtherT, fTs[i].fOtherIndex); + if (fTs[i].fWindSum == SK_MinS32) { SkDebugf("?"); } else { - SkDebugf("%d", span->windSum()); + SkDebugf("%d", fTs[i].fWindSum); } - SkDebugf(" windValue=%d oppValue=%d", span->windValue(), span->oppValue()); - SkDebugf("\n"); - } while ((span = span->next()->upCastable())); + SkDebugf(" windValue=%d oppValue=%d\n", fTs[i].fWindValue, fTs[i].fOppValue); + } } #endif -#if DEBUG_MARK_DONE -void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding) { - const SkPoint& pt = span->ptT()->fPt; +#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE +void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding) { + const SkPoint& pt = xyAtT(&span); SkDebugf("%s id=%d", fun, fID); SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); } - SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=", - span->t(), span->debugID(), pt.fX, pt.fY, span->next()->t()); - if (winding == SK_MinS32) { + SK_ALWAYSBREAK(&span == &span.fOther->fTs[span.fOtherIndex].fOther-> + fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]); + SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=%d windSum=", + span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY, + (&span)[1].fT, winding); + if (span.fWindSum == SK_MinS32) { SkDebugf("?"); } else { - SkDebugf("%d", winding); + SkDebugf("%d", span.fWindSum); } - SkDebugf(" windSum="); - if (span->windSum() == SK_MinS32) { - SkDebugf("?"); - } else { - SkDebugf("%d", span->windSum()); - } - SkDebugf(" windValue=%d\n", span->windValue()); + SkDebugf(" windValue=%d\n", span.fWindValue); } -void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding, +void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, int oppWinding) { - const SkPoint& pt = span->ptT()->fPt; + const SkPoint& pt = xyAtT(&span); SkDebugf("%s id=%d", fun, fID); SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); } - SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=", - span->t(), span->debugID(), pt.fX, pt.fY, span->next()->t(), winding, oppWinding); - if (winding == SK_MinS32) { - SkDebugf("?"); - } else { - SkDebugf("%d", winding); - } - SkDebugf(" newOppSum="); - if (oppWinding == SK_MinS32) { - SkDebugf("?"); - } else { - SkDebugf("%d", oppWinding); - } - SkDebugf(" oppSum="); - if (span->oppSum() == SK_MinS32) { + SK_ALWAYSBREAK(&span == &span.fOther->fTs[span.fOtherIndex].fOther-> + fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]); + SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=%d newOppSum=%d oppSum=", + span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY, + (&span)[1].fT, winding, oppWinding); + if (span.fOppSum == SK_MinS32) { SkDebugf("?"); } else { - SkDebugf("%d", span->oppSum()); + SkDebugf("%d", span.fOppSum); } SkDebugf(" windSum="); - if (span->windSum() == SK_MinS32) { + if (span.fWindSum == SK_MinS32) { SkDebugf("?"); } else { - SkDebugf("%d", span->windSum()); + SkDebugf("%d", span.fWindSum); } - SkDebugf(" windValue=%d oppValue=%d\n", span->windValue(), span->oppValue()); + SkDebugf(" windValue=%d oppValue=%d\n", span.fWindValue, span.fOppValue); } - #endif -#if DEBUG_ANGLE -SkString SkOpAngle::debugPart() const { - SkString result; - switch (this->segment()->verb()) { - case SkPath::kLine_Verb: - result.printf(LINE_DEBUG_STR " id=%d", LINE_DEBUG_DATA(fCurvePart), - this->segment()->debugID()); - break; - case SkPath::kQuad_Verb: - result.printf(QUAD_DEBUG_STR " id=%d", QUAD_DEBUG_DATA(fCurvePart), - this->segment()->debugID()); - break; - case SkPath::kCubic_Verb: - result.printf(CUBIC_DEBUG_STR " id=%d", CUBIC_DEBUG_DATA(fCurvePart), - this->segment()->debugID()); - break; - default: - SkASSERT(0); - } - return result; -} -#endif - -#if DEBUG_SORT -void SkOpAngle::debugLoop() const { - const SkOpAngle* first = this; - const SkOpAngle* next = this; - do { - next->dumpOne(true); - SkDebugf("\n"); - next = next->fNext; - } while (next && next != first); - next = first; - do { - next->debugValidate(); - next = next->fNext; - } while (next && next != first); -} -#endif - -void SkOpAngle::debugValidate() const { -#if DEBUG_VALIDATE - const SkOpAngle* first = this; - const SkOpAngle* next = this; - int wind = 0; - int opp = 0; - int lastXor = -1; - int lastOppXor = -1; - do { - if (next->unorderable()) { - return; - } - const SkOpSpan* minSpan = next->start()->starter(next->end()); - if (minSpan->windValue() == SK_MinS32) { - return; - } - bool op = next->segment()->operand(); - bool isXor = next->segment()->isXor(); - bool oppXor = next->segment()->oppXor(); - SkASSERT(!DEBUG_LIMIT_WIND_SUM || between(0, minSpan->windValue(), DEBUG_LIMIT_WIND_SUM)); - SkASSERT(!DEBUG_LIMIT_WIND_SUM - || between(-DEBUG_LIMIT_WIND_SUM, minSpan->oppValue(), DEBUG_LIMIT_WIND_SUM)); - bool useXor = op ? oppXor : isXor; - SkASSERT(lastXor == -1 || lastXor == (int) useXor); - lastXor = (int) useXor; - wind += next->sign() * (op ? minSpan->oppValue() : minSpan->windValue()); - if (useXor) { - wind &= 1; - } - useXor = op ? isXor : oppXor; - SkASSERT(lastOppXor == -1 || lastOppXor == (int) useXor); - lastOppXor = (int) useXor; - opp += next->sign() * (op ? minSpan->windValue() : minSpan->oppValue()); - if (useXor) { - opp &= 1; - } - next = next->fNext; - } while (next && next != first); - SkASSERT(wind == 0); - SkASSERT(opp == 0 || !FLAGS_runFail); -#endif +#if DEBUG_SHOW_WINDING +int SkOpSegment::debugShowWindingValues(int slotCount, int ofInterest) const { + if (!(1 << fID & ofInterest)) { + return 0; + } + int sum = 0; + SkTArray<char, true> slots(slotCount * 2); + memset(slots.begin(), ' ', slotCount * 2); + for (int i = 0; i < fTs.count(); ++i) { + // if (!(1 << fTs[i].fOther->fID & ofInterest)) { + // continue; + // } + sum += fTs[i].fWindValue; + slots[fTs[i].fOther->fID - 1] = as_digit(fTs[i].fWindValue); + sum += fTs[i].fOppValue; + slots[slotCount + fTs[i].fOther->fID - 1] = as_digit(fTs[i].fOppValue); + } + SkDebugf("%s id=%2d %.*s | %.*s\n", __FUNCTION__, fID, slotCount, slots.begin(), slotCount, + slots.begin() + slotCount); + return sum; } - -void SkOpAngle::debugValidateNext() const { -#if !FORCE_RELEASE - const SkOpAngle* first = this; - const SkOpAngle* next = first; - SkTDArray<const SkOpAngle*>(angles); - do { -// SK_ALWAYSBREAK(next->fSegment->debugContains(next)); - angles.push(next); - next = next->next(); - if (next == first) { - break; - } - SK_ALWAYSBREAK(!angles.contains(next)); - if (!next) { - return; - } - } while (true); #endif -} void SkOpSegment::debugValidate() const { #if DEBUG_VALIDATE - const SkOpSpanBase* span = &fHead; - double lastT = -1; - const SkOpSpanBase* prev = NULL; - int count = 0; + int count = fTs.count(); + SK_ALWAYSBREAK(count >= 2); + SK_ALWAYSBREAK(fTs[0].fT == 0); + SK_ALWAYSBREAK(fTs[count - 1].fT == 1); int done = 0; - do { - if (!span->final()) { - ++count; - done += span->upCast()->done() ? 1 : 0; - } - SkASSERT(span->segment() == this); - SkASSERT(!prev || prev->upCast()->next() == span); - SkASSERT(!prev || prev == span->prev()); - prev = span; - double t = span->ptT()->fT; - SkASSERT(lastT < t); - lastT = t; - span->debugValidate(); - } while (!span->final() && (span = span->upCast()->next())); - SkASSERT(count == fCount); - SkASSERT(done == fDoneCount); - SkASSERT(span->final()); - span->debugValidate(); -#endif -} - -bool SkOpSpanBase::debugCoinEndLoopCheck() const { - int loop = 0; - const SkOpSpanBase* next = this; - SkOpSpanBase* nextCoin; - do { - nextCoin = next->fCoinEnd; - SkASSERT(nextCoin == this || nextCoin->fCoinEnd != nextCoin); - for (int check = 1; check < loop - 1; ++check) { - const SkOpSpanBase* checkCoin = this->fCoinEnd; - const SkOpSpanBase* innerCoin = checkCoin; - for (int inner = check + 1; inner < loop; ++inner) { - innerCoin = innerCoin->fCoinEnd; - if (checkCoin == innerCoin) { - SkDebugf("*** bad coincident end loop ***\n"); - return false; - } - } - } - ++loop; - } while ((next = nextCoin) && next != this); - return true; -} - -void SkOpSpanBase::debugValidate() const { -#if DEBUG_VALIDATE - const SkOpPtT* ptT = &fPtT; - SkASSERT(ptT->span() == this); - do { -// SkASSERT(SkDPoint::RoughlyEqual(fPtT.fPt, ptT->fPt)); - ptT->debugValidate(); - ptT = ptT->next(); - } while (ptT != &fPtT); - SkASSERT(this->debugCoinEndLoopCheck()); - if (!this->final()) { - SkASSERT(this->upCast()->debugCoinLoopCheck()); - } - if (fFromAngle) { - fFromAngle->debugValidate(); - } - if (!this->final() && this->upCast()->toAngle()) { - this->upCast()->toAngle()->debugValidate(); - } + double t = -1; + const SkOpSpan* last = NULL; + bool tinyTFound = false; + bool hasLoop = false; + for (int i = 0; i < count; ++i) { + const SkOpSpan& span = fTs[i]; + SK_ALWAYSBREAK(t <= span.fT); + t = span.fT; + int otherIndex = span.fOtherIndex; + const SkOpSegment* other = span.fOther; + SK_ALWAYSBREAK(other != this || fVerb == SkPath::kCubic_Verb); + const SkOpSpan& otherSpan = other->fTs[otherIndex]; + SK_ALWAYSBREAK(otherSpan.fPt == span.fPt); + SK_ALWAYSBREAK(otherSpan.fOtherT == t); + SK_ALWAYSBREAK(&fTs[i] == &otherSpan.fOther->fTs[otherSpan.fOtherIndex]); + done += span.fDone; + if (last) { + SK_ALWAYSBREAK(last->fT != span.fT || last->fOther != span.fOther); + bool tsEqual = last->fT == span.fT; + bool tsPreciselyEqual = precisely_equal(last->fT, span.fT); + SK_ALWAYSBREAK(!tsEqual || tsPreciselyEqual); + bool pointsEqual = last->fPt == span.fPt; + bool pointsNearlyEqual = AlmostEqualUlps(last->fPt, span.fPt); +#if 0 // bufferOverflow test triggers this + SK_ALWAYSBREAK(!tsPreciselyEqual || pointsNearlyEqual); #endif -} - -bool SkOpSpan::debugCoinLoopCheck() const { - int loop = 0; - const SkOpSpan* next = this; - SkOpSpan* nextCoin; - do { - nextCoin = next->fCoincident; - SkASSERT(nextCoin == this || nextCoin->fCoincident != nextCoin); - for (int check = 1; check < loop - 1; ++check) { - const SkOpSpan* checkCoin = this->fCoincident; - const SkOpSpan* innerCoin = checkCoin; - for (int inner = check + 1; inner < loop; ++inner) { - innerCoin = innerCoin->fCoincident; - if (checkCoin == innerCoin) { - SkDebugf("*** bad coincident loop ***\n"); - return false; - } - } - } - ++loop; - } while ((next = nextCoin) && next != this); - return true; -} - -#include "SkOpContour.h" - -int SkOpPtT::debugLoopLimit(bool report) const { - int loop = 0; - const SkOpPtT* next = this; - do { - for (int check = 1; check < loop - 1; ++check) { - const SkOpPtT* checkPtT = this->fNext; - const SkOpPtT* innerPtT = checkPtT; - for (int inner = check + 1; inner < loop; ++inner) { - innerPtT = innerPtT->fNext; - if (checkPtT == innerPtT) { - if (report) { - SkDebugf("*** bad ptT loop ***\n"); - } - return loop; - } +// SK_ALWAYSBREAK(!last->fTiny || !tsPreciselyEqual || span.fTiny || tinyTFound); + SK_ALWAYSBREAK(last->fTiny || tsPreciselyEqual || !pointsEqual || hasLoop); + SK_ALWAYSBREAK(!last->fTiny || pointsEqual); + SK_ALWAYSBREAK(!last->fTiny || last->fDone); + SK_ALWAYSBREAK(!last->fSmall || pointsNearlyEqual); + SK_ALWAYSBREAK(!last->fSmall || last->fDone); +// SK_ALWAYSBREAK(!last->fSmall || last->fTiny); +// SK_ALWAYSBREAK(last->fTiny || !pointsEqual || last->fDone == span.fDone); + if (last->fTiny) { + tinyTFound |= !tsPreciselyEqual; + } else { + tinyTFound = false; } } - ++loop; - } while ((next = next->fNext) && next != this); - return 0; -} - -void SkOpPtT::debugValidate() const { -#if DEBUG_VALIDATE - if (contour()->globalState()->phase() == SkOpGlobalState::kIntersecting) { - return; + last = &span; + hasLoop |= last->fLoop; } - SkASSERT(fNext); - SkASSERT(fNext != this); - SkASSERT(fNext->fNext); - SkASSERT(debugLoopLimit(false) == 0); + SK_ALWAYSBREAK(done == fDoneSpans); +// if (fAngles.count() ) { +// fAngles.begin()->debugValidateLoop(); +// } #endif } diff --git a/src/pathops/SkPathOpsDebug.h b/src/pathops/SkPathOpsDebug.h index 72a9ea528f..5770aefec5 100644 --- a/src/pathops/SkPathOpsDebug.h +++ b/src/pathops/SkPathOpsDebug.h @@ -39,22 +39,35 @@ #define DEBUG_ACTIVE_OP 0 #define DEBUG_ACTIVE_SPANS 0 +#define DEBUG_ACTIVE_SPANS_FIRST_ONLY 0 +#define DEBUG_ACTIVE_SPANS_SHORT_FORM 1 #define DEBUG_ADD_INTERSECTING_TS 0 -#define DEBUG_ADD_T 0 +#define DEBUG_ADD_T_PAIR 0 #define DEBUG_ANGLE 0 +#define DEBUG_AS_C_CODE 1 #define DEBUG_ASSEMBLE 0 +#define DEBUG_CHECK_ALIGN 0 +#define DEBUG_CHECK_TINY 0 +#define DEBUG_CONCIDENT 0 +#define DEBUG_CROSS 0 #define DEBUG_CUBIC_BINARY_SEARCH 0 +#define DEBUG_DUPLICATES 0 +#define DEBUG_FLAT_QUADS 0 #define DEBUG_FLOW 0 #define DEBUG_LIMIT_WIND_SUM 0 #define DEBUG_MARK_DONE 0 #define DEBUG_PATH_CONSTRUCTION 0 -#define DEBUG_PERP 0 #define DEBUG_SHOW_TEST_NAME 0 +#define DEBUG_SHOW_TEST_PROGRESS 0 +#define DEBUG_SHOW_WINDING 0 #define DEBUG_SORT 0 +#define DEBUG_SORT_COMPACT 0 +#define DEBUG_SORT_RAW 0 +#define DEBUG_SORT_SINGLE 0 #define DEBUG_SWAP_TOP 0 -#define DEBUG_T_SECT 0 -#define DEBUG_T_SECT_DUMP 0 +#define DEBUG_UNSORTABLE 0 #define DEBUG_VALIDATE 0 +#define DEBUG_WIND_BUMP 0 #define DEBUG_WINDING 0 #define DEBUG_WINDING_AT_T 0 @@ -62,56 +75,51 @@ #define DEBUG_ACTIVE_OP 1 #define DEBUG_ACTIVE_SPANS 1 +#define DEBUG_ACTIVE_SPANS_FIRST_ONLY 0 +#define DEBUG_ACTIVE_SPANS_SHORT_FORM 1 #define DEBUG_ADD_INTERSECTING_TS 1 -#define DEBUG_ADD_T 1 +#define DEBUG_ADD_T_PAIR 1 #define DEBUG_ANGLE 1 +#define DEBUG_AS_C_CODE 1 #define DEBUG_ASSEMBLE 1 +#define DEBUG_CHECK_ALIGN 1 +#define DEBUG_CHECK_TINY 1 +#define DEBUG_CONCIDENT 1 +#define DEBUG_CROSS 01 #define DEBUG_CUBIC_BINARY_SEARCH 0 +#define DEBUG_DUPLICATES 1 +#define DEBUG_FLAT_QUADS 0 #define DEBUG_FLOW 1 -#define DEBUG_LIMIT_WIND_SUM 5 +#define DEBUG_LIMIT_WIND_SUM 4 #define DEBUG_MARK_DONE 1 #define DEBUG_PATH_CONSTRUCTION 1 -#define DEBUG_PERP 0 #define DEBUG_SHOW_TEST_NAME 1 +#define DEBUG_SHOW_TEST_PROGRESS 1 +#define DEBUG_SHOW_WINDING 0 #define DEBUG_SORT 1 +#define DEBUG_SORT_COMPACT 0 +#define DEBUG_SORT_RAW 0 +#define DEBUG_SORT_SINGLE 0 #define DEBUG_SWAP_TOP 1 -#define DEBUG_T_SECT 1 -#define DEBUG_T_SECT_DUMP 02 -#define DEBUG_VALIDATE 1 +#define DEBUG_UNSORTABLE 1 +#define DEBUG_VALIDATE 0 +#define DEBUG_WIND_BUMP 0 #define DEBUG_WINDING 1 #define DEBUG_WINDING_AT_T 1 #endif -#ifdef SK_RELEASE - #define PATH_OPS_DEBUG_RELEASE(a, b) b - #define PATH_OPS_DEBUG_CODE(...) - #define PATH_OPS_DEBUG_PARAMS(...) -#else - #define PATH_OPS_DEBUG_RELEASE(a, b) a - #define PATH_OPS_DEBUG_CODE(...) __VA_ARGS__ - #define PATH_OPS_DEBUG_PARAMS(...) , __VA_ARGS__ -#endif - -#if DEBUG_T_SECT == 0 - #define PATH_OPS_DEBUG_T_SECT_RELEASE(a, b) b - #define PATH_OPS_DEBUG_T_SECT_PARAMS(...) - #define PATH_OPS_DEBUG_T_SECT_CODE(...) +#if DEBUG_AS_C_CODE +#define CUBIC_DEBUG_STR "{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}" +#define QUAD_DEBUG_STR "{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}" +#define LINE_DEBUG_STR "{{%1.9g,%1.9g}, {%1.9g,%1.9g}}" +#define PT_DEBUG_STR "{{%1.9g,%1.9g}}" #else - #define PATH_OPS_DEBUG_T_SECT_RELEASE(a, b) a - #define PATH_OPS_DEBUG_T_SECT_PARAMS(...) , __VA_ARGS__ - #define PATH_OPS_DEBUG_T_SECT_CODE(...) __VA_ARGS__ +#define CUBIC_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)" +#define QUAD_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)" +#define LINE_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g)" +#define PT_DEBUG_STR "(%1.9g,%1.9g)" #endif - -#if DEBUG_T_SECT_DUMP > 1 - extern int gDumpTSectNum; -#endif - -#define CUBIC_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" -#define QUAD_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" -#define LINE_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}}}" -#define PT_DEBUG_STR "{{%1.9g,%1.9g}}" - #define T_DEBUG_STR(t, n) #t "[" #n "]=%1.9g" #define TX_DEBUG_STR(t) #t "[%d]=%1.9g" #define CUBIC_DEBUG_DATA(c) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, c[3].fX, c[3].fY @@ -127,6 +135,7 @@ #include "SkTLS.h" #endif +#include "SkTArray.h" #include "SkTDArray.h" class SkPathOpsDebug { @@ -147,6 +156,7 @@ public: static const char* kPathOpStr[]; #endif + static bool ChaseContains(const SkTDArray<struct SkOpSpan *>& , const struct SkOpSpan * ); static void MathematicaIze(char* str, size_t bufferSize); static bool ValidWind(int winding); static void WindingPrintf(int winding); @@ -161,96 +171,66 @@ public: #endif static void ShowOnePath(const SkPath& path, const char* name, bool includeDeclaration); static void ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, const char* name); - - static bool ChaseContains(const SkTDArray<class SkOpSpanBase*>& , const class SkOpSpanBase* ); - - static const struct SkOpAngle* DebugAngleAngle(const struct SkOpAngle*, int id); - static class SkOpContour* DebugAngleContour(struct SkOpAngle*, int id); - static const class SkOpPtT* DebugAnglePtT(const struct SkOpAngle*, int id); - static const class SkOpSegment* DebugAngleSegment(const struct SkOpAngle*, int id); - static const class SkOpSpanBase* DebugAngleSpan(const struct SkOpAngle*, int id); - - static const struct SkOpAngle* DebugContourAngle(class SkOpContour*, int id); - static class SkOpContour* DebugContourContour(class SkOpContour*, int id); - static const class SkOpPtT* DebugContourPtT(class SkOpContour*, int id); - static const class SkOpSegment* DebugContourSegment(class SkOpContour*, int id); - static const class SkOpSpanBase* DebugContourSpan(class SkOpContour*, int id); - - static const struct SkOpAngle* DebugPtTAngle(const class SkOpPtT*, int id); - static class SkOpContour* DebugPtTContour(class SkOpPtT*, int id); - static const class SkOpPtT* DebugPtTPtT(const class SkOpPtT*, int id); - static const class SkOpSegment* DebugPtTSegment(const class SkOpPtT*, int id); - static const class SkOpSpanBase* DebugPtTSpan(const class SkOpPtT*, int id); - - static const struct SkOpAngle* DebugSegmentAngle(const class SkOpSegment*, int id); - static class SkOpContour* DebugSegmentContour(class SkOpSegment*, int id); - static const class SkOpPtT* DebugSegmentPtT(const class SkOpSegment*, int id); - static const class SkOpSegment* DebugSegmentSegment(const class SkOpSegment*, int id); - static const class SkOpSpanBase* DebugSegmentSpan(const class SkOpSegment*, int id); - - static const struct SkOpAngle* DebugSpanAngle(const class SkOpSpanBase*, int id); - static class SkOpContour* DebugSpanContour(class SkOpSpanBase*, int id); - static const class SkOpPtT* DebugSpanPtT(const class SkOpSpanBase*, int id); - static const class SkOpSegment* DebugSpanSegment(const class SkOpSpanBase*, int id); - static const class SkOpSpanBase* DebugSpanSpan(const class SkOpSpanBase*, int id); - - static void DumpContours(SkTDArray<class SkOpContour* >* contours); - static void DumpContoursAll(SkTDArray<class SkOpContour* >* contours); - static void DumpContoursAngles(const SkTDArray<class SkOpContour* >* contours); - static void DumpContoursPt(const SkTDArray<class SkOpContour* >* contours, int id); - static void DumpContoursPts(const SkTDArray<class SkOpContour* >* contours); - static void DumpContoursSegment(const SkTDArray<class SkOpContour* >* contours, int id); - static void DumpContoursSpan(const SkTDArray<class SkOpContour* >* contours, int id); - static void DumpContoursSpans(const SkTDArray<class SkOpContour* >* contours); + static void DumpCoincidence(const SkTArray<class SkOpContour, true>& contours); + static void DumpCoincidence(const SkTArray<class SkOpContour* , true>& contours); + static void DumpContours(const SkTArray<class SkOpContour, true>& contours); + static void DumpContours(const SkTArray<class SkOpContour* , true>& contours); + static void DumpContourAngles(const SkTArray<class SkOpContour, true>& contours); + static void DumpContourAngles(const SkTArray<class SkOpContour* , true>& contours); + static void DumpContourPt(const SkTArray<class SkOpContour, true>& contours, int id); + static void DumpContourPt(const SkTArray<class SkOpContour* , true>& contours, int id); + static void DumpContourPts(const SkTArray<class SkOpContour, true>& contours); + static void DumpContourPts(const SkTArray<class SkOpContour* , true>& contours); + static void DumpContourSpan(const SkTArray<class SkOpContour, true>& contours, int id); + static void DumpContourSpan(const SkTArray<class SkOpContour* , true>& contours, int id); + static void DumpContourSpans(const SkTArray<class SkOpContour, true>& contours); + static void DumpContourSpans(const SkTArray<class SkOpContour* , true>& contours); + static void DumpSpans(const SkTDArray<struct SkOpSpan *>& ); + static void DumpSpans(const SkTDArray<struct SkOpSpan *>* ); }; // shorthand for calling from debugger -template<typename TCurve> class SkTSect; -template<typename TCurve> class SkTSpan; - -struct SkDQuad; -struct SkDCubic; - -const SkTSpan<SkDCubic>* DebugSpan(const SkTSect<SkDCubic>* , int id); -const SkTSpan<SkDQuad>* DebugSpan(const SkTSect<SkDQuad>* , int id); -const SkTSpan<SkDCubic>* DebugT(const SkTSect<SkDCubic>* , double t); -const SkTSpan<SkDQuad>* DebugT(const SkTSect<SkDQuad>* , double t); - -const SkTSpan<SkDCubic>* DebugSpan(const SkTSpan<SkDCubic>* , int id); -const SkTSpan<SkDQuad>* DebugSpan(const SkTSpan<SkDQuad>* , int id); -const SkTSpan<SkDCubic>* DebugT(const SkTSpan<SkDCubic>* , double t); -const SkTSpan<SkDQuad>* DebugT(const SkTSpan<SkDQuad>* , double t); - -void Dump(const SkTSect<SkDCubic>* ); -void Dump(const SkTSect<SkDQuad>* ); -void Dump(const SkTSpan<SkDCubic>* , const SkTSect<SkDCubic>* = NULL); -void Dump(const SkTSpan<SkDQuad>* , const SkTSect<SkDQuad>* = NULL); -void DumpBoth(SkTSect<SkDCubic>* sect1, SkTSect<SkDCubic>* sect2); -void DumpBoth(SkTSect<SkDQuad>* sect1, SkTSect<SkDQuad>* sect2); -void DumpCoin(SkTSect<SkDCubic>* sect1); -void DumpCoin(SkTSect<SkDQuad>* sect1); -void DumpCoinCurves(SkTSect<SkDCubic>* sect1); -void DumpCoinCurves(SkTSect<SkDQuad>* sect1); -void DumpCurves(const SkTSpan<SkDCubic>* ); -void DumpCurves(const SkTSpan<SkDQuad>* ); +void Dump(const SkTArray<class SkOpContour, true>& contours); +void Dump(const SkTArray<class SkOpContour* , true>& contours); +void Dump(const SkTArray<class SkOpContour, true>* contours); +void Dump(const SkTArray<class SkOpContour* , true>* contours); + +void Dump(const SkTDArray<SkOpSpan* >& chase); +void Dump(const SkTDArray<SkOpSpan* >* chase); + +void DumpAngles(const SkTArray<class SkOpContour, true>& contours); +void DumpAngles(const SkTArray<class SkOpContour* , true>& contours); +void DumpAngles(const SkTArray<class SkOpContour, true>* contours); +void DumpAngles(const SkTArray<class SkOpContour* , true>* contours); + +void DumpCoin(const SkTArray<class SkOpContour, true>& contours); +void DumpCoin(const SkTArray<class SkOpContour* , true>& contours); +void DumpCoin(const SkTArray<class SkOpContour, true>* contours); +void DumpCoin(const SkTArray<class SkOpContour* , true>* contours); + +void DumpPts(const SkTArray<class SkOpContour, true>& contours); +void DumpPts(const SkTArray<class SkOpContour* , true>& contours); +void DumpPts(const SkTArray<class SkOpContour, true>* contours); +void DumpPts(const SkTArray<class SkOpContour* , true>* contours); + +void DumpPt(const SkTArray<class SkOpContour, true>& contours, int segmentID); +void DumpPt(const SkTArray<class SkOpContour* , true>& contours, int segmentID); +void DumpPt(const SkTArray<class SkOpContour, true>* contours, int segmentID); +void DumpPt(const SkTArray<class SkOpContour* , true>* contours, int segmentID); + +void DumpSpans(const SkTArray<class SkOpContour, true>& contours); +void DumpSpans(const SkTArray<class SkOpContour* , true>& contours); +void DumpSpans(const SkTArray<class SkOpContour, true>* contours); +void DumpSpans(const SkTArray<class SkOpContour* , true>* contours); + +void DumpSpan(const SkTArray<class SkOpContour, true>& contours, int segmentID); +void DumpSpan(const SkTArray<class SkOpContour* , true>& contours, int segmentID); +void DumpSpan(const SkTArray<class SkOpContour, true>* contours, int segmentID); +void DumpSpan(const SkTArray<class SkOpContour* , true>* contours, int segmentID); // generates tools/path_sorter.htm and path_visualizer.htm compatible data -void DumpQ(const SkDQuad& quad1, const SkDQuad& quad2, int testNo); -void DumpT(const SkDQuad& quad, double t); +void DumpQ(const struct SkDQuad& quad1, const struct SkDQuad& quad2, int testNo); -const struct SkOpAngle* DebugAngle(const SkTDArray<class SkOpContour* >* contours, int id); -class SkOpContour* DebugContour(const SkTDArray<class SkOpContour* >* contours, int id); -const class SkOpPtT* DebugPtT(const SkTDArray<class SkOpContour* >* contours, int id); -const class SkOpSegment* DebugSegment(const SkTDArray<class SkOpContour* >* contours, int id); -const class SkOpSpanBase* DebugSpan(const SkTDArray<class SkOpContour* >* contours, int id); +void DumpT(const struct SkDQuad& quad, double t); -void Dump(const SkTDArray<class SkOpContour* >* contours); -void DumpAll(SkTDArray<class SkOpContour* >* contours); -void DumpAngles(const SkTDArray<class SkOpContour* >* contours); -void DumpCoin(const SkTDArray<class SkOpContour* >* contours); -void DumpPt(const SkTDArray<class SkOpContour* >* contours, int segmentID); -void DumpPts(const SkTDArray<class SkOpContour* >* contours); -void DumpSegment(const SkTDArray<class SkOpContour* >* contours, int segmentID); -void DumpSpan(const SkTDArray<class SkOpContour* >* contours, int spanID); -void DumpSpans(const SkTDArray<class SkOpContour* >* contours); #endif diff --git a/src/pathops/SkPathOpsLine.cpp b/src/pathops/SkPathOpsLine.cpp index 70f2e12472..e4fc97bc61 100644 --- a/src/pathops/SkPathOpsLine.cpp +++ b/src/pathops/SkPathOpsLine.cpp @@ -6,6 +6,14 @@ */ #include "SkPathOpsLine.h" +SkDLine SkDLine::subDivide(double t1, double t2) const { + SkDVector delta = tangent(); + SkDLine dst = {{{ + fPts[0].fX - t1 * delta.fX, fPts[0].fY - t1 * delta.fY}, { + fPts[0].fX - t2 * delta.fX, fPts[0].fY - t2 * delta.fY}}}; + return dst; +} + // may have this below somewhere else already: // copying here because I thought it was clever @@ -20,7 +28,6 @@ // Point with coordinates {float x, y;} //=================================================================== -// (only used by testing) // isLeft(): tests if a point is Left|On|Right of an infinite line. // Input: three points P0, P1, and P2 // Return: >0 for P2 left of the line through P0 and P1 @@ -103,6 +110,19 @@ bool SkDLine::nearRay(const SkDPoint& xy) const { return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? } +// Returns true if a ray from (0,0) to (x1,y1) is coincident with a ray (0,0) to (x2,y2) +// OPTIMIZE: a specialty routine could speed this up -- may not be called very often though +bool SkDLine::NearRay(double x1, double y1, double x2, double y2) { + double denom1 = x1 * x1 + y1 * y1; + double denom2 = x2 * x2 + y2 * y2; + SkDLine line = {{{0, 0}, {x1, y1}}}; + SkDPoint pt = {x2, y2}; + if (denom2 > denom1) { + SkTSwap(line[1], pt); + } + return line.nearRay(pt); +} + double SkDLine::ExactPointH(const SkDPoint& xy, double left, double right, double y) { if (xy.fY == y) { if (xy.fX == left) { diff --git a/src/pathops/SkPathOpsLine.h b/src/pathops/SkPathOpsLine.h index bb25162860..74eb615348 100644 --- a/src/pathops/SkPathOpsLine.h +++ b/src/pathops/SkPathOpsLine.h @@ -20,20 +20,27 @@ struct SkDLine { fPts[1] = pts[1]; } + static SkDLine SubDivide(const SkPoint a[2], double t1, double t2) { + SkDLine line; + line.set(a); + return line.subDivide(t1, t2); + } + double exactPoint(const SkDPoint& xy) const; static double ExactPointH(const SkDPoint& xy, double left, double right, double y); static double ExactPointV(const SkDPoint& xy, double top, double bottom, double x); - - // only used by testing double isLeft(const SkDPoint& pt) const; - double nearPoint(const SkDPoint& xy, bool* unequal) const; bool nearRay(const SkDPoint& xy) const; static double NearPointH(const SkDPoint& xy, double left, double right, double y); static double NearPointV(const SkDPoint& xy, double top, double bottom, double x); + static bool NearRay(double dx1, double dy1, double dx2, double dy2); SkDPoint ptAtT(double t) const; + SkDLine subDivide(double t1, double t2) const; void dump() const; +private: + SkDVector tangent() const { return fPts[0] - fPts[1]; } }; #endif diff --git a/src/pathops/SkPathOpsOp.cpp b/src/pathops/SkPathOpsOp.cpp index 77ae2de778..f2b25c04ec 100644 --- a/src/pathops/SkPathOpsOp.cpp +++ b/src/pathops/SkPathOpsOp.cpp @@ -5,29 +5,27 @@ * found in the LICENSE file. */ #include "SkAddIntersections.h" -#include "SkOpCoincidence.h" #include "SkOpEdgeBuilder.h" #include "SkPathOpsCommon.h" #include "SkPathWriter.h" -static SkOpSegment* findChaseOp(SkTDArray<SkOpSpanBase*>& chase, SkOpSpanBase** startPtr, - SkOpSpanBase** endPtr) { +static SkOpSegment* findChaseOp(SkTDArray<SkOpSpan*>& chase, int* tIndex, int* endIndex) { while (chase.count()) { - SkOpSpanBase* span; + SkOpSpan* span; chase.pop(&span); - // OPTIMIZE: prev makes this compatible with old code -- but is it necessary? - *startPtr = span->ptT()->prev()->span(); - SkOpSegment* segment = (*startPtr)->segment(); + const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex); + SkOpSegment* segment = backPtr.fOther; + *tIndex = backPtr.fOtherIndex; bool sortable = true; bool done = true; - *endPtr = NULL; - if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done, + *endIndex = -1; + if (const SkOpAngle* last = segment->activeAngle(*tIndex, tIndex, endIndex, &done, &sortable)) { if (last->unorderable()) { continue; } - *startPtr = last->start(); - *endPtr = last->end(); + *tIndex = last->start(); + *endIndex = last->end(); #if TRY_ROTATE *chase.insert(0) = span; #else @@ -42,7 +40,7 @@ static SkOpSegment* findChaseOp(SkTDArray<SkOpSpanBase*>& chase, SkOpSpanBase** continue; } // find first angle, initialize winding to computed fWindSum - const SkOpAngle* angle = segment->spanToAngle(*startPtr, *endPtr); + const SkOpAngle* angle = segment->spanToAngle(*tIndex, *endIndex); if (!angle) { continue; } @@ -67,25 +65,33 @@ static SkOpSegment* findChaseOp(SkTDArray<SkOpSpanBase*>& chase, SkOpSpanBase** SkTSwap<int>(sumMiWinding, sumSuWinding); } SkOpSegment* first = NULL; - firstAngle = angle; - while ((angle = angle->next()) != firstAngle) { + bool badData = false; + while ((angle = angle->next()) != firstAngle && !badData) { segment = angle->segment(); - SkOpSpanBase* start = angle->start(); - SkOpSpanBase* end = angle->end(); + int start = angle->start(); + int end = angle->end(); int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); if (!segment->done(angle)) { if (!first) { first = segment; - *startPtr = start; - *endPtr = end; + *tIndex = start; + *endIndex = end; + } + if (segment->inconsistentAngle(maxWinding, sumWinding, oppMaxWinding, + oppSumWinding, angle)) { + badData = true; + break; } // OPTIMIZATION: should this also add to the chase? (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, angle); } } + if (badData) { + continue; + } if (first) { #if TRY_ROTATE *chase.insert(0) = span; @@ -98,8 +104,36 @@ static SkOpSegment* findChaseOp(SkTDArray<SkOpSpanBase*>& chase, SkOpSpanBase** return NULL; } -static bool bridgeOp(SkTDArray<SkOpContour* >& contourList, const SkPathOp op, - const int xorMask, const int xorOpMask, SkPathWriter* simple, SkChunkAlloc* allocator) { +/* +static bool windingIsActive(int winding, int oppWinding, int spanWinding, int oppSpanWinding, + bool windingIsOp, PathOp op) { + bool active = windingIsActive(winding, spanWinding); + if (!active) { + return false; + } + if (oppSpanWinding && windingIsActive(oppWinding, oppSpanWinding)) { + switch (op) { + case kIntersect_Op: + case kUnion_Op: + return true; + case kDifference_Op: { + int absSpan = abs(spanWinding); + int absOpp = abs(oppSpanWinding); + return windingIsOp ? absSpan < absOpp : absSpan > absOpp; + } + case kXor_Op: + return spanWinding != oppSpanWinding; + default: + SkASSERT(0); + } + } + bool opActive = oppWinding != 0; + return gOpLookup[op][opActive][windingIsOp]; +} +*/ + +static bool bridgeOp(SkTArray<SkOpContour*, true>& contourList, const SkPathOp op, + const int xorMask, const int xorOpMask, SkPathWriter* simple) { bool firstContour = true; bool unsortable = false; bool topUnsortable = false; @@ -107,14 +141,12 @@ static bool bridgeOp(SkTDArray<SkOpContour* >& contourList, const SkPathOp op, SkPoint lastTopLeft; SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin}; do { - SkOpSpanBase* start; - SkOpSpanBase* end; + int index, endIndex; bool topDone; bool onlyVertical = false; lastTopLeft = topLeft; - SkOpSegment* current = FindSortableTop(contourList, firstPass, SkOpAngle::kBinarySingle, - &firstContour, &start, &end, &topLeft, &topUnsortable, &topDone, &onlyVertical, - allocator); + SkOpSegment* current = FindSortableTop(contourList, SkOpAngle::kBinarySingle, &firstContour, + &index, &endIndex, &topLeft, &topUnsortable, &topDone, &onlyVertical, firstPass); if (!current) { if ((!topUnsortable || firstPass) && !topDone) { SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin); @@ -133,65 +165,69 @@ static bool bridgeOp(SkTDArray<SkOpContour* >& contourList, const SkPathOp op, break; } firstPass = !topUnsortable || lastTopLeft != topLeft; - SkTDArray<SkOpSpanBase*> chase; + SkTDArray<SkOpSpan*> chase; do { - if (current->activeOp(start, end, xorMask, xorOpMask, op)) { + if (current->activeOp(index, endIndex, xorMask, xorOpMask, op)) { do { if (!unsortable && current->done()) { break; } SkASSERT(unsortable || !current->done()); - SkOpSpanBase* nextStart = start; - SkOpSpanBase* nextEnd = end; + int nextStart = index; + int nextEnd = endIndex; SkOpSegment* next = current->findNextOp(&chase, &nextStart, &nextEnd, &unsortable, op, xorMask, xorOpMask); if (!next) { if (!unsortable && simple->hasMove() && current->verb() != SkPath::kLine_Verb && !simple->isClosed()) { - current->addCurveTo(start, end, simple, true); + current->addCurveTo(index, endIndex, simple, true); #if DEBUG_ACTIVE_SPANS if (!simple->isClosed()) { DebugShowActiveSpans(contourList); } #endif +// SkASSERT(simple->isClosed()); } break; } #if DEBUG_FLOW - SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, - current->debugID(), start->pt().fX, start->pt().fY, - end->pt().fX, end->pt().fY); + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY, + current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY); #endif - current->addCurveTo(start, end, simple, true); + current->addCurveTo(index, endIndex, simple, true); current = next; - start = nextStart; - end = nextEnd; - } while (!simple->isClosed() && (!unsortable || !start->starter(end)->done())); - if (current->activeWinding(start, end) && !simple->isClosed()) { - SkOpSpan* spanStart = start->starter(end); - if (!spanStart->done()) { - current->addCurveTo(start, end, simple, true); - current->markDone(spanStart); + index = nextStart; + endIndex = nextEnd; + } while (!simple->isClosed() && (!unsortable + || !current->done(SkMin32(index, endIndex)))); + if (current->activeWinding(index, endIndex) && !simple->isClosed()) { + // FIXME : add to simplify, xor cpaths + int min = SkMin32(index, endIndex); + if (!unsortable && !simple->isEmpty()) { + unsortable = current->checkSmall(min); + } + if (!current->done(min)) { + current->addCurveTo(index, endIndex, simple, true); + current->markDoneBinary(min); } } simple->close(); } else { - SkOpSpanBase* last = current->markAndChaseDone(start, end); - if (last && !last->chased()) { - last->setChased(true); + SkOpSpan* last = current->markAndChaseDoneBinary(index, endIndex); + if (last && !last->fChased && !last->fLoop) { + last->fChased = true; SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); *chase.append() = last; #if DEBUG_WINDING - SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); - if (!last->final()) { - SkDebugf(" windSum=%d", last->upCast()->windSum()); - } - SkDebugf("\n"); + SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__, + last->fOther->span(last->fOtherIndex).fOther->debugID(), last->fWindSum, + last->fSmall); #endif } } - current = findChaseOp(chase, &start, &end); + current = findChaseOp(chase, &index, &endIndex); #if DEBUG_ACTIVE_SPANS DebugShowActiveSpans(contourList); #endif @@ -255,19 +291,16 @@ static void dump_op(const SkPath& one, const SkPath& two, SkPathOp op) { dump_path(file, two, false, true); fprintf(file, " SkPath path2(path);\n"); fprintf(file, " testPathOp(reporter, path1, path2, (SkPathOp) %d, filename);\n", op); - fprintf(file, "}\n"); + fprintf(file, "}\n"); fclose(file); } #endif bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) { - SkOpContour contour; - SkOpCoincidence coincidence; - SkOpGlobalState globalState(&coincidence PATH_OPS_DEBUG_PARAMS(&contour)); #if DEBUGGING_PATHOPS_FROM_HOST dump_op(one, two, op); -#endif -#if 0 && DEBUG_SHOW_TEST_NAME +#endif +#if DEBUG_SHOW_TEST_NAME char* debugName = DEBUG_FILENAME_STRING; if (debugName && debugName[0]) { SkPathOpsDebug::BumpTestName(debugName); @@ -288,54 +321,53 @@ bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) { SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; #endif // turn path into list of segments - SkChunkAlloc allocator(4096); // FIXME: add a constant expression here, tune - SkOpEdgeBuilder builder(*minuend, &contour, &allocator, &globalState); + SkTArray<SkOpContour> contours; + // FIXME: add self-intersecting cubics' T values to segment + SkOpEdgeBuilder builder(*minuend, contours); if (builder.unparseable()) { return false; } const int xorMask = builder.xorMask(); builder.addOperand(*subtrahend); - if (!builder.finish(&allocator)) { + if (!builder.finish()) { return false; } -#if !FORCE_RELEASE - contour.dumpSegments(op); -#endif - result->reset(); result->setFillType(fillType); const int xorOpMask = builder.xorMask(); - SkTDArray<SkOpContour* > contourList; - MakeContourList(&contour, contourList, xorMask == kEvenOdd_PathOpsMask, + SkTArray<SkOpContour*, true> contourList; + MakeContourList(contours, contourList, xorMask == kEvenOdd_PathOpsMask, xorOpMask == kEvenOdd_PathOpsMask); SkOpContour** currentPtr = contourList.begin(); if (!currentPtr) { return true; } - if ((*currentPtr)->count() == 0) { - SkASSERT((*currentPtr)->next() == NULL); - return true; - } SkOpContour** listEnd = contourList.end(); // find all intersections between segments do { SkOpContour** nextPtr = currentPtr; SkOpContour* current = *currentPtr++; + if (current->containsCubics()) { + AddSelfIntersectTs(current); + } SkOpContour* next; do { next = *nextPtr++; - } while (AddIntersectTs(current, next, &coincidence, &allocator) && nextPtr != listEnd); + } while (AddIntersectTs(current, next) && nextPtr != listEnd); } while (currentPtr != listEnd); -#if DEBUG_VALIDATE - globalState.setPhase(SkOpGlobalState::kWalking); -#endif // eat through coincident edges - if (!HandleCoincidence(&contourList, &coincidence, &allocator, &globalState)) { + + int total = 0; + int index; + for (index = 0; index < contourList.count(); ++index) { + total += contourList[index]->segments().count(); + } + if (!HandleCoincidence(&contourList, total)) { return false; } // construct closed contours SkPathWriter wrapper(*result); - bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper, &allocator); + bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper); { // if some edges could not be resolved, assemble remaining fragments SkPath temp; temp.setFillType(fillType); diff --git a/src/pathops/SkPathOpsPoint.h b/src/pathops/SkPathOpsPoint.h index 2d07427783..7ddfbfb5d1 100644 --- a/src/pathops/SkPathOpsPoint.h +++ b/src/pathops/SkPathOpsPoint.h @@ -25,25 +25,21 @@ struct SkDVector { friend SkDPoint operator+(const SkDPoint& a, const SkDVector& b); - // only used by testing void operator+=(const SkDVector& v) { fX += v.fX; fY += v.fY; } - // only called by nearestT, which is currently only used by testing void operator-=(const SkDVector& v) { fX -= v.fX; fY -= v.fY; } - // only used by testing void operator/=(const double s) { fX /= s; fY /= s; } - // only used by testing void operator*=(const double s) { fX *= s; fY *= s; @@ -54,7 +50,6 @@ struct SkDVector { return v; } - // only used by testing double cross(const SkDVector& a) const { return fX * a.fY - fY * a.fX; } @@ -103,13 +98,11 @@ struct SkDPoint { fY = pt.fY; } - // only used by testing void operator+=(const SkDVector& v) { fX += v.fX; fY += v.fY; } - // only used by testing void operator-=(const SkDVector& v) { fX -= v.fX; fY -= v.fY; @@ -129,7 +122,7 @@ struct SkDPoint { double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); largest = SkTMax(largest, -tiniest); - return AlmostPequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? + return AlmostBequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? } bool approximatelyEqual(const SkPoint& a) const { @@ -152,10 +145,44 @@ struct SkDPoint { float tiniest = SkTMin(SkTMin(SkTMin(a.fX, b.fX), a.fY), b.fY); float largest = SkTMax(SkTMax(SkTMax(a.fX, b.fX), a.fY), b.fY); largest = SkTMax(largest, -tiniest); - return AlmostPequalUlps((double) largest, largest + dist); // is dist within ULPS tolerance? + return AlmostBequalUlps((double) largest, largest + dist); // is dist within ULPS tolerance? + } + + static bool RoughlyEqual(const SkPoint& a, const SkPoint& b) { + if (approximately_equal(a.fX, b.fX) && approximately_equal(a.fY, b.fY)) { + return true; + } + return RoughlyEqualUlps(a.fX, b.fX) && RoughlyEqualUlps(a.fY, b.fY); + } + + bool approximatelyPEqual(const SkDPoint& a) const { + if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { + return true; + } + if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) { + return false; + } + double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); + double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); + largest = SkTMax(largest, -tiniest); + return AlmostPequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? + } + + bool approximatelyDEqual(const SkDPoint& a) const { + if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { + return true; + } + if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) { + return false; + } + double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); + double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); + largest = SkTMax(largest, -tiniest); + return AlmostDequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? } - // only used by testing bool approximatelyZero() const { return approximately_zero(fX) && approximately_zero(fY); } @@ -182,7 +209,7 @@ struct SkDPoint { return result; } - bool roughlyEqual(const SkDPoint& a) const { + bool moreRoughlyEqual(const SkDPoint& a) const { if (roughly_equal(fX, a.fX) && roughly_equal(fY, a.fY)) { return true; } @@ -193,6 +220,10 @@ struct SkDPoint { return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? } + bool roughlyEqual(const SkDPoint& a) const { + return roughly_equal(a.fY, fY) && roughly_equal(a.fX, fX); + } + // utilities callable by the user from the debugger when the implementation code is linked in void dump() const; static void Dump(const SkPoint& pt); diff --git a/src/pathops/SkPathOpsPostSect.cpp b/src/pathops/SkPathOpsPostSect.cpp index eb2d1ab3ab..15a1900ce3 100755..100644 --- a/src/pathops/SkPathOpsPostSect.cpp +++ b/src/pathops/SkPathOpsPostSect.cpp @@ -17,8 +17,8 @@ SkOpContour* SkOpPtT::contour() const { return segment()->contour(); } -SkOpGlobalState* SkOpPtT::globalState() const { - return PATH_OPS_DEBUG_RELEASE(contour()->globalState(), NULL); +SkOpDebugState* SkOpPtT::debugState() const { + return PATH_OPS_DEBUG_RELEASE(contour()->debugState(), NULL); } void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplicate) { @@ -28,7 +28,7 @@ void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplica fNext = this; fDuplicatePt = duplicate; fDeleted = false; - PATH_OPS_DEBUG_CODE(fID = ++span->globalState()->fPtTID); + PATH_OPS_DEBUG_CODE(fID = ++span->debugState()->fPtTID); } bool SkOpPtT::onEnd() const { @@ -45,7 +45,7 @@ SkOpPtT* SkOpPtT::remove() { do { SkOpPtT* next = prev->fNext; if (next == this) { - prev->removeNext(this); + prev->removeNext(); fDeleted = true; return prev; } @@ -55,14 +55,14 @@ SkOpPtT* SkOpPtT::remove() { return NULL; } -void SkOpPtT::removeNext(SkOpPtT* kept) { +void SkOpPtT::removeNext() { SkASSERT(this->fNext); SkOpPtT* next = this->fNext; this->fNext = next->fNext; SkOpSpanBase* span = next->span(); next->setDeleted(); if (span->ptT() == next) { - span->upCast()->detach(kept); + span->upCast()->detach(); } } @@ -199,7 +199,7 @@ void SkOpSpanBase::alignInner() { // omit aliases that alignment makes redundant if ((!ptT->alias() || test->alias()) && (ptT->onEnd() || !test->onEnd())) { SkASSERT(test->alias()); - prev->removeNext(ptT); + prev->removeNext(); test = prev; } else { SkASSERT(ptT->alias()); @@ -239,8 +239,8 @@ SkOpContour* SkOpSpanBase::contour() const { return segment()->contour(); } -SkOpGlobalState* SkOpSpanBase::globalState() const { - return PATH_OPS_DEBUG_RELEASE(contour()->globalState(), NULL); +SkOpDebugState* SkOpSpanBase::debugState() const { + return PATH_OPS_DEBUG_RELEASE(contour()->debugState(), NULL); } void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { @@ -252,7 +252,7 @@ void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, cons fAligned = true; fChased = false; PATH_OPS_DEBUG_CODE(fCount = 1); - PATH_OPS_DEBUG_CODE(fID = ++globalState()->fSpanID); + PATH_OPS_DEBUG_CODE(fID = ++debugState()->fSpanID); } // this pair of spans share a common t value or point; merge them and eliminate duplicates @@ -261,7 +261,7 @@ void SkOpSpanBase::merge(SkOpSpan* span) { SkOpPtT* spanPtT = span->ptT(); SkASSERT(this->t() != spanPtT->fT); SkASSERT(!zero_or_one(spanPtT->fT)); - span->detach(this->ptT()); + span->detach(); SkOpPtT* remainder = spanPtT->next(); ptT()->insert(spanPtT); while (remainder != spanPtT) { @@ -304,7 +304,7 @@ bool SkOpSpan::containsCoincidence(const SkOpSegment* segment) const { return false; } -void SkOpSpan::detach(SkOpPtT* kept) { +void SkOpSpan::detach() { SkASSERT(!final()); SkOpSpan* prev = this->prev(); SkASSERT(prev); @@ -313,9 +313,6 @@ void SkOpSpan::detach(SkOpPtT* kept) { prev->setNext(next); next->setPrev(prev); this->segment()->detach(this); - if (this->coincident()) { - this->globalState()->fCoincidence->fixUp(this->ptT(), kept); - } this->ptT()->setDeleted(); } diff --git a/src/pathops/SkPathOpsQuad.cpp b/src/pathops/SkPathOpsQuad.cpp index 4913c9f9f3..c1d068af34 100644 --- a/src/pathops/SkPathOpsQuad.cpp +++ b/src/pathops/SkPathOpsQuad.cpp @@ -8,61 +8,7 @@ #include "SkLineParameters.h" #include "SkPathOpsCubic.h" #include "SkPathOpsQuad.h" - -/* started with at_most_end_pts_in_common from SkDQuadIntersection.cpp */ -// Do a quick reject by rotating all points relative to a line formed by -// a pair of one quad's points. If the 2nd quad's points -// are on the line or on the opposite side from the 1st quad's 'odd man', the -// curves at most intersect at the endpoints. -/* if returning true, check contains true if quad's hull collapsed, making the cubic linear - if returning false, check contains true if the the quad pair have only the end point in common -*/ -bool SkDQuad::hullIntersects(const SkDQuad& q2, bool* isLinear) const { - bool linear = true; - for (int oddMan = 0; oddMan < kPointCount; ++oddMan) { - const SkDPoint* endPt[2]; - this->otherPts(oddMan, endPt); - double origX = endPt[0]->fX; - double origY = endPt[0]->fY; - double adj = endPt[1]->fX - origX; - double opp = endPt[1]->fY - origY; - double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp; - if (approximately_zero(sign)) { - continue; - } - linear = false; - bool foundOutlier = false; - for (int n = 0; n < kPointCount; ++n) { - double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; - if (test * sign > 0 && !precisely_zero(test)) { - foundOutlier = true; - break; - } - } - if (!foundOutlier) { - return false; - } - } - *isLinear = linear; - return true; -} - -/* bit twiddling for finding the off curve index (x&~m is the pair in [0,1,2] excluding oddMan) -oddMan opp x=oddMan^opp x=x-oddMan m=x>>2 x&~m - 0 1 1 1 0 1 - 2 2 2 0 2 - 1 1 0 -1 -1 0 - 2 3 2 0 2 - 2 1 3 1 0 1 - 2 0 -2 -1 0 -*/ -void SkDQuad::otherPts(int oddMan, const SkDPoint* endPt[2]) const { - for (int opp = 1; opp < kPointCount; ++opp) { - int end = (oddMan ^ opp) - oddMan; // choose a value not equal to oddMan - end &= ~(end >> 2); // if the value went negative, set it to zero - endPt[opp - 1] = &fPts[end]; - } -} +#include "SkPathOpsTriangle.h" // from http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html // (currently only used by testing) @@ -97,6 +43,10 @@ double SkDQuad::nearestT(const SkDPoint& pt) const { return d0 < d2 ? 0 : 1; } +bool SkDQuad::pointInHull(const SkDPoint& pt) const { + return ((const SkDTriangle&) fPts).contains(pt); +} + SkDPoint SkDQuad::top(double startT, double endT) const { SkDQuad sub = subDivide(startT, endT); SkDPoint topPt = sub[0]; @@ -190,12 +140,7 @@ bool SkDQuad::isLinear(int startIndex, int endIndex) const { // FIXME: maybe it's possible to avoid this and compare non-normalized lineParameters.normalize(); double distance = lineParameters.controlPtDistance(*this); - double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), - fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY); - double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), - fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY); - largest = SkTMax(largest, -tiniest); - return approximately_zero_when_compared_to(distance, largest); + return approximately_zero(distance); } SkDCubic SkDQuad::toCubic() const { @@ -295,6 +240,13 @@ void SkDQuad::align(int endIndex, SkDPoint* dstPt) const { SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const { SkASSERT(t1 != t2); SkDPoint b; +#if 0 + // this approach assumes that the control point computed directly is accurate enough + double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2); + double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2); + b.fX = 2 * dx - (a.fX + c.fX) / 2; + b.fY = 2 * dy - (a.fY + c.fY) / 2; +#else SkDQuad sub = subDivide(t1, t2); SkDLine b0 = {{a, sub[1] + (a - sub[0])}}; SkDLine b1 = {{c, sub[1] + (c - sub[2])}}; @@ -306,6 +258,7 @@ SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, dou SkASSERT(i.used() <= 2); b = SkDPoint::Mid(b0[1], b1[1]); } +#endif if (t1 == 0 || t2 == 0) { align(0, &b); } diff --git a/src/pathops/SkPathOpsQuad.h b/src/pathops/SkPathOpsQuad.h index 81638cf0bc..932c5fbe75 100644 --- a/src/pathops/SkPathOpsQuad.h +++ b/src/pathops/SkPathOpsQuad.h @@ -17,61 +17,43 @@ struct SkDQuadPair { }; struct SkDQuad { - static const int kPointCount = 3; - static const int kPointLast = kPointCount - 1; - static const int kMaxIntersections = 4; - - SkDPoint fPts[kPointCount]; - - bool collapsed() const { - return fPts[0].approximatelyEqual(fPts[1]) && fPts[0].approximatelyEqual(fPts[2]); - } - - bool controlsInside() const { - SkDVector v01 = fPts[0] - fPts[1]; - SkDVector v02 = fPts[0] - fPts[2]; - SkDVector v12 = fPts[1] - fPts[2]; - return v02.dot(v01) > 0 && v02.dot(v12) > 0; - } + SkDPoint fPts[3]; SkDQuad flip() const { SkDQuad result = {{fPts[2], fPts[1], fPts[0]}}; return result; } - static bool IsCubic() { return false; } - - void set(const SkPoint pts[kPointCount]) { + void set(const SkPoint pts[3]) { fPts[0] = pts[0]; fPts[1] = pts[1]; fPts[2] = pts[2]; } - const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } - SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 3); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 3); return fPts[n]; } static int AddValidTs(double s[], int realRoots, double* t); void align(int endIndex, SkDPoint* dstPt) const; SkDQuadPair chopAt(double t) const; SkDVector dxdyAtT(double t) const; static int FindExtrema(double a, double b, double c, double tValue[1]); - bool hullIntersects(const SkDQuad& , bool* isLinear) const; bool isLinear(int startIndex, int endIndex) const; bool monotonicInY() const; double nearestT(const SkDPoint&) const; - void otherPts(int oddMan, const SkDPoint* endPt[2]) const; + bool pointInHull(const SkDPoint&) const; SkDPoint ptAtT(double t) const; static int RootsReal(double A, double B, double C, double t[2]); static int RootsValidT(const double A, const double B, const double C, double s[2]); static void SetABC(const double* quad, double* a, double* b, double* c); SkDQuad subDivide(double t1, double t2) const; - static SkDQuad SubDivide(const SkPoint a[kPointCount], double t1, double t2) { + static SkDQuad SubDivide(const SkPoint a[3], double t1, double t2) { SkDQuad quad; quad.set(a); return quad.subDivide(t1, t2); } SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const; - static SkDPoint SubDivide(const SkPoint pts[kPointCount], const SkDPoint& a, const SkDPoint& c, + static SkDPoint SubDivide(const SkPoint pts[3], const SkDPoint& a, const SkDPoint& c, double t1, double t2) { SkDQuad quad; quad.set(pts); @@ -82,8 +64,7 @@ struct SkDQuad { // utilities callable by the user from the debugger when the implementation code is linked in void dump() const; - void dumpID(int id) const; - void dumpInner() const; + void dumpComma(const char*) const; private: // static double Tangent(const double* quadratic, double t); // uncalled diff --git a/src/pathops/SkPathOpsQuadSect.h b/src/pathops/SkPathOpsQuadSect.h new file mode 100644 index 0000000000..57f1aa08d8 --- /dev/null +++ b/src/pathops/SkPathOpsQuadSect.h @@ -0,0 +1,175 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkQuadSpan_DEFINE +#define SkQuadSpan_DEFINE + +#include "SkChunkAlloc.h" +#include "SkPathOpsRect.h" +#include "SkPathOpsQuad.h" +#include "SkTArray.h" + +class SkIntersections; + +class SkQuadCoincident { +public: + bool isCoincident() const { + return fCoincident; + } + + void init() { + fCoincident = false; + SkDEBUGCODE(fPerpPt.fX = fPerpPt.fY = SK_ScalarNaN); + SkDEBUGCODE(fPerpT = SK_ScalarNaN); + } + + void markCoincident() { + if (!fCoincident) { + fPerpT = -1; + } + fCoincident = true; + } + + const SkDPoint& perpPt() const { + return fPerpPt; + } + + double perpT() const { + return fPerpT; + } + + void setPerp(const SkDQuad& quad1, double t, const SkDPoint& qPt, const SkDQuad& quad2); + +private: + SkDPoint fPerpPt; + double fPerpT; // perpendicular intersection on opposite quad + bool fCoincident; +}; + +class SkQuadSect; // used only by debug id + +class SkQuadSpan { +public: + void init(const SkDQuad& quad); + void initBounds(const SkDQuad& quad); + + bool contains(double t) const { + return !! const_cast<SkQuadSpan*>(this)->innerFind(t); + } + + bool contains(const SkQuadSpan* span) const; + + SkQuadSpan* find(double t) { + SkQuadSpan* result = innerFind(t); + SkASSERT(result); + return result; + } + + bool intersects(const SkQuadSpan* span) const; + + const SkQuadSpan* next() const { + return fNext; + } + + void reset() { + fBounded.reset(); + } + + bool split(SkQuadSpan* work) { + return splitAt(work, (work->fStartT + work->fEndT) * 0.5); + } + + bool splitAt(SkQuadSpan* work, double t); + bool tightBoundsIntersects(const SkQuadSpan* span) const; + + // implementation is for testing only + void dump() const; + +private: + bool hullIntersects(const SkDQuad& q2) const; + SkQuadSpan* innerFind(double t); + bool linearIntersects(const SkDQuad& q2) const; + + // implementation is for testing only +#if DEBUG_BINARY_QUAD + int debugID(const SkQuadSect* ) const { return fDebugID; } +#else + int debugID(const SkQuadSect* ) const; +#endif + void dump(const SkQuadSect* ) const; + void dumpID(const SkQuadSect* ) const; + +#if DEBUG_BINARY_QUAD + void validate() const; +#endif + + SkDQuad fPart; + SkQuadCoincident fCoinStart; + SkQuadCoincident fCoinEnd; + SkSTArray<4, SkQuadSpan*, true> fBounded; + SkQuadSpan* fPrev; + SkQuadSpan* fNext; + SkDRect fBounds; + double fStartT; + double fEndT; + double fBoundsMax; + bool fCollapsed; + bool fHasPerp; + mutable bool fIsLinear; +#if DEBUG_BINARY_QUAD + int fDebugID; + bool fDebugDeleted; +#endif + friend class SkQuadSect; +}; + +class SkQuadSect { +public: + SkQuadSect(const SkDQuad& quad PATH_OPS_DEBUG_PARAMS(int id)); + static void BinarySearch(SkQuadSect* sect1, SkQuadSect* sect2, SkIntersections* intersections); + + // for testing only + void dumpQuads() const; +private: + SkQuadSpan* addOne(); + bool binarySearchCoin(const SkQuadSect& , double tStart, double tStep, double* t, double* oppT); + SkQuadSpan* boundsMax() const; + void coincidentCheck(SkQuadSect* sect2); + bool intersects(const SkQuadSpan* span, const SkQuadSect* opp, const SkQuadSpan* oppSpan) const; + void onCurveCheck(SkQuadSect* sect2, SkQuadSpan* first, SkQuadSpan* last); + void recoverCollapsed(); + void removeSpan(SkQuadSpan* span); + void removeOne(const SkQuadSpan* test, SkQuadSpan* span); + void removeSpans(SkQuadSpan* span, SkQuadSect* opp); + void setPerp(const SkDQuad& opp, SkQuadSpan* first, SkQuadSpan* last); + const SkQuadSpan* tail() const; + void trim(SkQuadSpan* span, SkQuadSect* opp); + + // for testing only + void dump() const; + void dumpBoth(const SkQuadSect& opp) const; + void dumpBoth(const SkQuadSect* opp) const; + +#if DEBUG_BINARY_QUAD + int debugID() const { return fDebugID; } + void validate() const; +#else + int debugID() const { return 0; } +#endif + const SkDQuad& fQuad; + SkChunkAlloc fHeap; + SkQuadSpan* fHead; + SkQuadSpan* fDeleted; + int fActiveCount; +#if DEBUG_BINARY_QUAD + int fDebugID; + int fDebugCount; + int fDebugAllocatedCount; +#endif + friend class SkQuadSpan; // only used by debug id +}; + +#endif diff --git a/src/pathops/SkPathOpsRect.cpp b/src/pathops/SkPathOpsRect.cpp index 5dd3d8def5..2ceed32900 100644 --- a/src/pathops/SkPathOpsRect.cpp +++ b/src/pathops/SkPathOpsRect.cpp @@ -9,6 +9,11 @@ #include "SkPathOpsQuad.h" #include "SkPathOpsRect.h" +void SkDRect::setBounds(const SkDLine& line) { + set(line[0]); + add(line[1]); +} + void SkDRect::setBounds(const SkDQuad& quad) { set(quad[0]); add(quad[2]); @@ -25,6 +30,13 @@ void SkDRect::setBounds(const SkDQuad& quad) { } } +void SkDRect::setRawBounds(const SkDQuad& quad) { + set(quad[0]); + for (int x = 1; x < 3; ++x) { + add(quad[x]); + } +} + static bool is_bounded_by_end_points(double a, double b, double c, double d) { return between(a, b, d) && between(a, c, d); } @@ -44,3 +56,10 @@ void SkDRect::setBounds(const SkDCubic& c) { add(c.ptAtT(tValues[x])); } } + +void SkDRect::setRawBounds(const SkDCubic& cubic) { + set(cubic[0]); + for (int x = 1; x < 4; ++x) { + add(cubic[x]); + } +} diff --git a/src/pathops/SkPathOpsRect.h b/src/pathops/SkPathOpsRect.h index 2b37a5f098..2c47f43b88 100644 --- a/src/pathops/SkPathOpsRect.h +++ b/src/pathops/SkPathOpsRect.h @@ -13,10 +13,18 @@ struct SkDRect { double fLeft, fTop, fRight, fBottom; void add(const SkDPoint& pt) { - fLeft = SkTMin(fLeft, pt.fX); - fTop = SkTMin(fTop, pt.fY); - fRight = SkTMax(fRight, pt.fX); - fBottom = SkTMax(fBottom, pt.fY); + if (fLeft > pt.fX) { + fLeft = pt.fX; + } + if (fTop > pt.fY) { + fTop = pt.fY; + } + if (fRight < pt.fX) { + fRight = pt.fX; + } + if (fBottom < pt.fY) { + fBottom = pt.fY; + } } bool contains(const SkDPoint& pt) const { @@ -24,15 +32,12 @@ struct SkDRect { && approximately_between(fTop, pt.fY, fBottom); } - bool intersects(const SkDRect& r) const { - if (fLeft > fRight) { - SkDebugf("!"); - } + bool intersects(SkDRect* r) const { SkASSERT(fLeft <= fRight); SkASSERT(fTop <= fBottom); - SkASSERT(r.fLeft <= r.fRight); - SkASSERT(r.fTop <= r.fBottom); - return r.fLeft <= fRight && fLeft <= r.fRight && r.fTop <= fBottom && fTop <= r.fBottom; + SkASSERT(r->fLeft <= r->fRight); + SkASSERT(r->fTop <= r->fBottom); + return r->fLeft <= fRight && fLeft <= r->fRight && r->fTop <= fBottom && fTop <= r->fBottom; } void set(const SkDPoint& pt) { @@ -48,8 +53,11 @@ struct SkDRect { return fBottom - fTop; } + void setBounds(const SkDLine&); void setBounds(const SkDCubic&); void setBounds(const SkDQuad&); + void setRawBounds(const SkDCubic&); + void setRawBounds(const SkDQuad&); }; #endif diff --git a/src/pathops/SkPathOpsSimplify.cpp b/src/pathops/SkPathOpsSimplify.cpp index 7a234ecb01..57090ac423 100644 --- a/src/pathops/SkPathOpsSimplify.cpp +++ b/src/pathops/SkPathOpsSimplify.cpp @@ -5,13 +5,11 @@ * found in the LICENSE file. */ #include "SkAddIntersections.h" -#include "SkOpCoincidence.h" #include "SkOpEdgeBuilder.h" #include "SkPathOpsCommon.h" #include "SkPathWriter.h" -static bool bridgeWinding(SkTDArray<SkOpContour* >& contourList, SkPathWriter* simple, - SkChunkAlloc* allocator) { +static bool bridgeWinding(SkTArray<SkOpContour*, true>& contourList, SkPathWriter* simple) { bool firstContour = true; bool unsortable = false; bool topUnsortable = false; @@ -19,24 +17,15 @@ static bool bridgeWinding(SkTDArray<SkOpContour* >& contourList, SkPathWriter* s SkPoint lastTopLeft; SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin}; do { - SkOpSpanBase* start; - SkOpSpanBase* end; + int index, endIndex; bool topDone; bool onlyVertical = false; lastTopLeft = topLeft; - SkOpSegment* current = FindSortableTop(contourList, firstPass, SkOpAngle::kUnaryWinding, - &firstContour, &start, &end, &topLeft, &topUnsortable, &topDone, &onlyVertical, - allocator); + SkOpSegment* current = FindSortableTop(contourList, SkOpAngle::kUnaryWinding, &firstContour, + &index, &endIndex, &topLeft, &topUnsortable, &topDone, &onlyVertical, firstPass); if (!current) { if ((!topUnsortable || firstPass) && !topDone) { SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin); - if (lastTopLeft.fX == SK_ScalarMin && lastTopLeft.fY == SK_ScalarMin) { - if (firstPass) { - firstPass = false; - } else { - break; - } - } topLeft.fX = topLeft.fY = SK_ScalarMin; continue; } @@ -45,66 +34,62 @@ static bool bridgeWinding(SkTDArray<SkOpContour* >& contourList, SkPathWriter* s break; } firstPass = !topUnsortable || lastTopLeft != topLeft; - SkTDArray<SkOpSpanBase*> chase; + SkTDArray<SkOpSpan*> chase; do { - if (current->activeWinding(start, end)) { + if (current->activeWinding(index, endIndex)) { do { if (!unsortable && current->done()) { break; } SkASSERT(unsortable || !current->done()); - SkOpSpanBase* nextStart = start; - SkOpSpanBase* nextEnd = end; + int nextStart = index; + int nextEnd = endIndex; SkOpSegment* next = current->findNextWinding(&chase, &nextStart, &nextEnd, &unsortable); if (!next) { if (!unsortable && simple->hasMove() && current->verb() != SkPath::kLine_Verb && !simple->isClosed()) { - current->addCurveTo(start, end, simple, true); - #if DEBUG_ACTIVE_SPANS - if (!simple->isClosed()) { - DebugShowActiveSpans(contourList); - } - #endif + current->addCurveTo(index, endIndex, simple, true); + SkASSERT(simple->isClosed()); } break; } #if DEBUG_FLOW SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, - current->debugID(), start->pt().fX, start->pt().fY, - end->pt().fX, end->pt().fY); + current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY, + current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY); #endif - current->addCurveTo(start, end, simple, true); + current->addCurveTo(index, endIndex, simple, true); current = next; - start = nextStart; - end = nextEnd; - } while (!simple->isClosed() && (!unsortable || !start->starter(end)->done())); - if (current->activeWinding(start, end) && !simple->isClosed()) { - SkOpSpan* spanStart = start->starter(end); - if (!spanStart->done()) { - current->addCurveTo(start, end, simple, true); - current->markDone(spanStart); + index = nextStart; + endIndex = nextEnd; + } while (!simple->isClosed() && (!unsortable + || !current->done(SkMin32(index, endIndex)))); + if (current->activeWinding(index, endIndex) && !simple->isClosed()) { +// SkASSERT(unsortable || simple->isEmpty()); + int min = SkMin32(index, endIndex); + if (!current->done(min)) { + current->addCurveTo(index, endIndex, simple, true); + current->markDoneUnary(min); } } simple->close(); } else { - SkOpSpanBase* last = current->markAndChaseDone(start, end); - if (last && !last->chased()) { - last->setChased(true); + SkOpSpan* last = current->markAndChaseDoneUnary(index, endIndex); + if (last && !last->fChased && !last->fLoop) { + last->fChased = true; SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); // assert that last isn't already in array *chase.append() = last; #if DEBUG_WINDING - SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); - if (!last->final()) { - SkDebugf(" windSum=%d", last->upCast()->windSum()); - } - SkDebugf("\n"); + SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__, + last->fOther->span(last->fOtherIndex).fOther->debugID(), last->fWindSum, + last->fSmall); #endif } } - current = FindChase(&chase, &start, &end); + current = FindChase(&chase, &index, &endIndex); #if DEBUG_ACTIVE_SPANS DebugShowActiveSpans(contourList); #endif @@ -117,11 +102,9 @@ static bool bridgeWinding(SkTDArray<SkOpContour* >& contourList, SkPathWriter* s } // returns true if all edges were processed -static bool bridgeXor(SkTDArray<SkOpContour* >& contourList, SkPathWriter* simple, - SkChunkAlloc* allocator) { +static bool bridgeXor(SkTArray<SkOpContour*, true>& contourList, SkPathWriter* simple) { SkOpSegment* current; - SkOpSpanBase* start; - SkOpSpanBase* end; + int start, end; bool unsortable = false; bool closable = true; while ((current = FindUndone(contourList, &start, &end))) { @@ -132,38 +115,34 @@ static bool bridgeXor(SkTDArray<SkOpContour* >& contourList, SkPathWriter* simpl } #endif SkASSERT(unsortable || !current->done()); - SkOpSpanBase* nextStart = start; - SkOpSpanBase* nextEnd = end; + int nextStart = start; + int nextEnd = end; SkOpSegment* next = current->findNextXor(&nextStart, &nextEnd, &unsortable); if (!next) { if (!unsortable && simple->hasMove() && current->verb() != SkPath::kLine_Verb && !simple->isClosed()) { current->addCurveTo(start, end, simple, true); - #if DEBUG_ACTIVE_SPANS - if (!simple->isClosed()) { - DebugShowActiveSpans(contourList); - } - #endif + SkASSERT(simple->isClosed()); } break; } #if DEBUG_FLOW SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, - current->debugID(), start->pt().fX, start->pt().fY, - end->pt().fX, end->pt().fY); + current->debugID(), current->xyAtT(start).fX, current->xyAtT(start).fY, + current->xyAtT(end).fX, current->xyAtT(end).fY); #endif current->addCurveTo(start, end, simple, true); current = next; start = nextStart; end = nextEnd; - } while (!simple->isClosed() && (!unsortable || !start->starter(end)->done())); + } while (!simple->isClosed() && (!unsortable || !current->done(SkMin32(start, end)))); if (!simple->isClosed()) { SkASSERT(unsortable); - SkOpSpan* spanStart = start->starter(end); - if (!spanStart->done()) { + int min = SkMin32(start, end); + if (!current->done(min)) { current->addCurveTo(start, end, simple, true); - current->markDone(spanStart); + current->markDone(min, 1); } closable = false; } @@ -177,68 +156,52 @@ static bool bridgeXor(SkTDArray<SkOpContour* >& contourList, SkPathWriter* simpl // FIXME : add this as a member of SkPath bool Simplify(const SkPath& path, SkPath* result) { +#if DEBUG_SORT || DEBUG_SWAP_TOP + SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; +#endif // returns 1 for evenodd, -1 for winding, regardless of inverse-ness SkPath::FillType fillType = path.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType; - if (path.isConvex()) { - if (result != &path) { - *result = path; - } - result->setFillType(fillType); - return true; - } + // turn path into list of segments - SkOpCoincidence coincidence; - SkOpContour contour; - SkOpGlobalState globalState(&coincidence PATH_OPS_DEBUG_PARAMS(&contour)); -#if DEBUG_SORT || DEBUG_SWAP_TOP - SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; -#endif - SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune - SkOpEdgeBuilder builder(path, &contour, &allocator, &globalState); - if (!builder.finish(&allocator)) { + SkTArray<SkOpContour> contours; + SkOpEdgeBuilder builder(path, contours); + if (!builder.finish()) { return false; } -#if !FORCE_RELEASE - contour.dumpSegments((SkPathOp) -1); -#endif + SkTArray<SkOpContour*, true> contourList; + MakeContourList(contours, contourList, false, false); + SkOpContour** currentPtr = contourList.begin(); result->reset(); result->setFillType(fillType); - SkTDArray<SkOpContour* > contourList; - MakeContourList(&contour, contourList, false, false); - SkOpContour** currentPtr = contourList.begin(); if (!currentPtr) { return true; } - if ((*currentPtr)->count() == 0) { - SkASSERT((*currentPtr)->next() == NULL); - return true; - } - SkOpContour** listEnd2 = contourList.end(); + SkOpContour** listEnd = contourList.end(); // find all intersections between segments do { SkOpContour** nextPtr = currentPtr; SkOpContour* current = *currentPtr++; + if (current->containsCubics()) { + AddSelfIntersectTs(current); + } SkOpContour* next; do { next = *nextPtr++; - } while (AddIntersectTs(current, next, &coincidence, &allocator) && nextPtr != listEnd2); - } while (currentPtr != listEnd2); -#if DEBUG_VALIDATE - globalState.setPhase(SkOpGlobalState::kWalking); -#endif - if (!HandleCoincidence(&contourList, &coincidence, &allocator, &globalState)) { + } while (AddIntersectTs(current, next) && nextPtr != listEnd); + } while (currentPtr != listEnd); + if (!HandleCoincidence(&contourList, 0)) { return false; } // construct closed contours - SkPathWriter wrapper(*result); - if (builder.xorMask() == kWinding_PathOpsMask ? bridgeWinding(contourList, &wrapper, &allocator) - : !bridgeXor(contourList, &wrapper, &allocator)) + SkPathWriter simple(*result); + if (builder.xorMask() == kWinding_PathOpsMask ? bridgeWinding(contourList, &simple) + : !bridgeXor(contourList, &simple)) { // if some edges could not be resolved, assemble remaining fragments SkPath temp; temp.setFillType(fillType); SkPathWriter assembled(temp); - Assemble(wrapper, &assembled); + Assemble(simple, &assembled); *result = *assembled.nativePath(); result->setFillType(fillType); } diff --git a/src/pathops/SkPathOpsTCubicSect.cpp b/src/pathops/SkPathOpsTCubicSect.cpp index 10a84a3843..0b3ddd7d0d 100644 --- a/src/pathops/SkPathOpsTCubicSect.cpp +++ b/src/pathops/SkPathOpsTCubicSect.cpp @@ -7,9 +7,9 @@ #include "SkPathOpsTSect.h" -int SkIntersections::intersect(const SkDCubic& cubic1, const SkDCubic& cubic2) { - SkTSect<SkDCubic> sect1(cubic1 PATH_OPS_DEBUG_T_SECT_PARAMS(1)); - SkTSect<SkDCubic> sect2(cubic2 PATH_OPS_DEBUG_T_SECT_PARAMS(2)); +int SkIntersections::intersectB(const SkDCubic& cubic1, const SkDCubic& cubic2) { + SkTSect<SkDCubic> sect1(cubic1 PATH_OPS_DEBUG_PARAMS(1)); + SkTSect<SkDCubic> sect2(cubic2 PATH_OPS_DEBUG_PARAMS(2)); SkTSect<SkDCubic>::BinarySearch(§1, §2, this); return used(); } diff --git a/src/pathops/SkPathOpsTQuadSect.cpp b/src/pathops/SkPathOpsTQuadSect.cpp index 06b5f2f8e9..46ce5cfdbb 100644 --- a/src/pathops/SkPathOpsTQuadSect.cpp +++ b/src/pathops/SkPathOpsTQuadSect.cpp @@ -7,9 +7,9 @@ #include "SkPathOpsTSect.h" -int SkIntersections::intersect(const SkDQuad& quad1, const SkDQuad& quad2) { - SkTSect<SkDQuad> sect1(quad1 PATH_OPS_DEBUG_T_SECT_PARAMS(1)); - SkTSect<SkDQuad> sect2(quad2 PATH_OPS_DEBUG_T_SECT_PARAMS(2)); +int SkIntersections::intersectB(const SkDQuad& quad1, const SkDQuad& quad2) { + SkTSect<SkDQuad> sect1(quad1 PATH_OPS_DEBUG_PARAMS(1)); + SkTSect<SkDQuad> sect2(quad2 PATH_OPS_DEBUG_PARAMS(2)); SkTSect<SkDQuad>::BinarySearch(§1, §2, this); return used(); } diff --git a/src/pathops/SkPathOpsTSect.h b/src/pathops/SkPathOpsTSect.h index 5c76da7e83..4e7d3b1795 100644 --- a/src/pathops/SkPathOpsTSect.h +++ b/src/pathops/SkPathOpsTSect.h @@ -6,25 +6,15 @@ */ #include "SkChunkAlloc.h" -#include "SkPathOpsBounds.h" #include "SkPathOpsRect.h" #include "SkPathOpsQuad.h" #include "SkIntersections.h" -#include "SkTSort.h" +#include "SkTArray.h" /* TCurve is either SkDQuadratic or SkDCubic */ template<typename TCurve> class SkTCoincident { public: - SkTCoincident() - : fCoincident(false) { - } - - void clear() { - fPerpT = -1; - fCoincident = false; - } - bool isCoincident() const { return fCoincident; } @@ -64,73 +54,41 @@ template<typename TCurve> class SkTSect; template<typename TCurve> class SkTSpan { public: - void addBounded(SkTSpan* ); + void init(const TCurve& ); + void initBounds(const TCurve& ); + double closestBoundedT(const SkDPoint& pt) const; - bool contains(double t) const; - const SkTSect<TCurve>* debugOpp() const; - const SkTSpan* debugSpan(int ) const; - const SkTSpan* debugT(double t) const; -#ifdef SK_DEBUG - bool debugIsBefore(const SkTSpan* span) const; -#endif - void dump() const; - void dumpBounds(int id) const; + bool contains(double t) const { + return !! const_cast<SkTSpan*>(this)->innerFind(t); + } + + bool contains(const SkTSpan* span) const; double endT() const { return fEndT; } - SkTSpan* findOppSpan(const SkTSpan* opp) const; - - SkTSpan* findOppT(double t) const { - SkTSpan* result = oppT(t); + SkTSpan* find(double t) { + SkTSpan* result = innerFind(t); SkASSERT(result); return result; } - bool hasOppT(double t) const { - return SkToBool(oppT(t)); - } - - int hullsIntersect(SkTSpan* span, bool* start, bool* oppStart); - void init(const TCurve& ); - void initBounds(const TCurve& ); - - bool isBounded() const { - return fBounded.count() > 0; - } - - bool linearsIntersect(SkTSpan* span); - double linearT(const SkDPoint& ) const; - - void markCoincident() { - fCoinStart.markCoincident(); - fCoinEnd.markCoincident(); - } + bool intersects(const SkTSpan* span, bool* check); const SkTSpan* next() const { return fNext; } - bool onlyEndPointsInCommon(const SkTSpan* opp, bool* start, bool* oppStart, bool* ptsInCommon); - const TCurve& part() const { return fPart; } - bool removeAllBounded(); - bool removeBounded(const SkTSpan* opp); - void reset() { fBounded.reset(); } - void resetBounds(const TCurve& curve) { - fIsLinear = fIsLine = false; - initBounds(curve); - } - bool split(SkTSpan* work) { return splitAt(work, (work->fStartT + work->fEndT) * 0.5); } @@ -141,23 +99,29 @@ public: return fStartT; } -private: + bool tightBoundsIntersects(const SkTSpan* span) const; // implementation is for testing only - int debugID() const { - return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1); + void dump() const { + dump(NULL); } - void dumpID() const; +private: + SkTSpan* innerFind(double t); + bool linearIntersects(const TCurve& ) const; - int hullCheck(const SkTSpan* opp, bool* start, bool* oppStart); - int linearIntersects(const TCurve& ) const; - SkTSpan* oppT(double t) const; + // implementation is for testing only +#if DEBUG_T_SECT + int debugID(const SkTSect<TCurve>* ) const { return fDebugID; } +#else + int debugID(const SkTSect<TCurve>* ) const; +#endif + void dump(const SkTSect<TCurve>* ) const; + void dumpID(const SkTSect<TCurve>* ) const; +#if DEBUG_T_SECT void validate() const; - void validateBounded() const; - void validatePerpT(double oppT) const; - void validatePerpPt(double t, const SkDPoint& ) const; +#endif TCurve fPart; SkTCoincident<TCurve> fCoinStart; @@ -172,33 +136,23 @@ private: bool fCollapsed; bool fHasPerp; bool fIsLinear; - bool fIsLine; - bool fDeleted; - PATH_OPS_DEBUG_CODE(SkTSect<TCurve>* fDebugSect); - PATH_OPS_DEBUG_T_SECT_CODE(int fID); +#if DEBUG_T_SECT + int fDebugID; + bool fDebugDeleted; +#endif friend class SkTSect<TCurve>; }; template<typename TCurve> class SkTSect { public: - SkTSect(const TCurve& c PATH_OPS_DEBUG_T_SECT_PARAMS(int id)); + SkTSect(const TCurve& c PATH_OPS_DEBUG_PARAMS(int id)); static void BinarySearch(SkTSect* sect1, SkTSect* sect2, SkIntersections* intersections); // for testing only - bool debugHasBounded(const SkTSpan<TCurve>* ) const; - - const SkTSect* debugOpp() const { - return PATH_OPS_DEBUG_RELEASE(fOppSect, NULL); - } - - const SkTSpan<TCurve>* debugSpan(int id) const; - const SkTSpan<TCurve>* debugT(double t) const; void dump() const; - void dumpBoth(SkTSect* ) const; - void dumpBounds(int id) const; - void dumpCoin() const; - void dumpCoinCurves() const; + void dumpBoth(const SkTSect& opp) const; + void dumpBoth(const SkTSect* opp) const; void dumpCurves() const; private: @@ -209,72 +163,36 @@ private: kOneS2Set = 8 }; - SkTSpan<TCurve>* addFollowing(SkTSpan<TCurve>* prior); - void addForPerp(SkTSpan<TCurve>* span, double t); SkTSpan<TCurve>* addOne(); - - SkTSpan<TCurve>* addSplitAt(SkTSpan<TCurve>* span, double t) { - SkTSpan<TCurve>* result = this->addOne(); - result->splitAt(span, t); - result->initBounds(fCurve); - span->initBounds(fCurve); - return result; - } - - bool binarySearchCoin(SkTSect* , double tStart, double tStep, double* t, double* oppT); + bool binarySearchCoin(const SkTSect& , double tStart, double tStep, double* t, double* oppT); SkTSpan<TCurve>* boundsMax() const; void coincidentCheck(SkTSect* sect2); - bool coincidentHasT(double t); - void computePerpendiculars(SkTSect* sect2, SkTSpan<TCurve>* first, SkTSpan<TCurve>* last); - int countConsecutiveSpans(SkTSpan<TCurve>* first, SkTSpan<TCurve>** last) const; - - int debugID() const { - return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1); - } - - void deleteEmptySpans(); - void dumpCommon(const SkTSpan<TCurve>* ) const; - void dumpCommonCurves(const SkTSpan<TCurve>* ) const; static int EndsEqual(const SkTSect* sect1, const SkTSect* sect2, SkIntersections* ); - SkTSpan<TCurve>* extractCoincident(SkTSect* sect2, SkTSpan<TCurve>* first, - SkTSpan<TCurve>* last); - SkTSpan<TCurve>* findCoincidentRun(SkTSpan<TCurve>* first, SkTSpan<TCurve>** lastPtr, - const SkTSect* sect2); - int intersects(SkTSpan<TCurve>* span, const SkTSect* opp, - SkTSpan<TCurve>* oppSpan, int* oppResult) const; - int linesIntersect(const SkTSpan<TCurve>* span, const SkTSect* opp, - const SkTSpan<TCurve>* oppSpan, SkIntersections* ) const; - void markSpanGone(SkTSpan<TCurve>* span); - bool matchedDirection(double t, const SkTSect* sect2, double t2) const; - void matchedDirCheck(double t, const SkTSect* sect2, double t2, - bool* calcMatched, bool* oppMatched) const; - void mergeCoincidence(SkTSect* sect2); - SkTSpan<TCurve>* prev(SkTSpan<TCurve>* ) const; - void removeByPerpendicular(SkTSect* opp); + bool intersects(SkTSpan<TCurve>* span, const SkTSect* opp, + const SkTSpan<TCurve>* oppSpan) const; + void onCurveCheck(SkTSect* sect2, SkTSpan<TCurve>* first, SkTSpan<TCurve>* last); void recoverCollapsed(); - void removeCoincident(SkTSpan<TCurve>* span, bool isBetween); - void removeAllBut(const SkTSpan<TCurve>* keep, SkTSpan<TCurve>* span, SkTSect* opp); void removeSpan(SkTSpan<TCurve>* span); - void removeSpanRange(SkTSpan<TCurve>* first, SkTSpan<TCurve>* last); + void removeOne(const SkTSpan<TCurve>* test, SkTSpan<TCurve>* span); void removeSpans(SkTSpan<TCurve>* span, SkTSect* opp); - SkTSpan<TCurve>* spanAtT(double t, SkTSpan<TCurve>** priorSpan); - SkTSpan<TCurve>* tail(); + void setPerp(const TCurve& opp, SkTSpan<TCurve>* first, SkTSpan<TCurve>* last); + const SkTSpan<TCurve>* tail() const; void trim(SkTSpan<TCurve>* span, SkTSect* opp); - void unlinkSpan(SkTSpan<TCurve>* span); - bool updateBounded(SkTSpan<TCurve>* first, SkTSpan<TCurve>* last, SkTSpan<TCurve>* oppFirst); - void validate() const; - void validateBounded() const; +#if DEBUG_T_SECT + int debugID() const { return fDebugID; } + void validate() const; +#else + int debugID() const { return 0; } +#endif const TCurve& fCurve; SkChunkAlloc fHeap; SkTSpan<TCurve>* fHead; - SkTSpan<TCurve>* fCoincident; SkTSpan<TCurve>* fDeleted; int fActiveCount; - PATH_OPS_DEBUG_CODE(SkTSect* fOppSect); - PATH_OPS_DEBUG_T_SECT_CODE(int fID); - PATH_OPS_DEBUG_T_SECT_CODE(int fDebugCount); #if DEBUG_T_SECT + int fDebugID; + int fDebugCount; int fDebugAllocatedCount; #endif friend class SkTSpan<TCurve>; // only used by debug id @@ -290,8 +208,8 @@ void SkTCoincident<TCurve>::setPerp(const TCurve& c1, double t, SkIntersections i; int used = i.intersectRay(c2, perp); // only keep closest - if (used == 0 || used == 3) { - this->clear(); + if (used == 0) { + fPerpT = -1; return; } fPerpT = i[0][0]; @@ -305,10 +223,6 @@ void SkTCoincident<TCurve>::setPerp(const TCurve& c1, double t, fPerpPt = i.pt(1); } } -#if DEBUG_T_SECT - SkDebugf("%s cPt=(%1.9g,%1.9g) %s fPerpPt=(%1.9g,%1.9g)\n", __FUNCTION__, cPt.fX, cPt.fY, - cPt.approximatelyEqual(fPerpPt) ? "==" : "!=", fPerpPt.fX, fPerpPt.fY); -#endif fCoincident = cPt.approximatelyEqual(fPerpPt); #if DEBUG_T_SECT if (fCoincident) { @@ -318,55 +232,29 @@ void SkTCoincident<TCurve>::setPerp(const TCurve& c1, double t, } template<typename TCurve> -void SkTSpan<TCurve>::addBounded(SkTSpan* span) { - if (this->findOppSpan(span)) { - return; - } - fBounded.push_back() = span; +void SkTSpan<TCurve>::init(const TCurve& c) { + fPrev = fNext = NULL; + fIsLinear = false; + fStartT = 0; + fEndT = 1; + initBounds(c); } template<typename TCurve> -SkTSpan<TCurve>* SkTSect<TCurve>::addFollowing(SkTSpan<TCurve>* prior) { - SkTSpan<TCurve>* result = this->addOne(); - result->fStartT = prior ? prior->fEndT : 0; - SkTSpan<TCurve>* next = prior ? prior->fNext : fHead; - result->fEndT = next ? next->fStartT : 1; - result->fPrev = prior; - result->fNext = next; - if (prior) { - prior->fNext = result; - } else { - fHead = result; - } - if (next) { - next->fPrev = result; +void SkTSpan<TCurve>::initBounds(const TCurve& c) { + fPart = c.subDivide(fStartT, fEndT); + fBounds.setBounds(fPart); + fCoinStart.init(); + fCoinEnd.init(); + fBoundsMax = SkTMax(fBounds.width(), fBounds.height()); + fCollapsed = fPart.collapsed(); + fHasPerp = false; +#if DEBUG_T_SECT + fDebugDeleted = false; + if (fCollapsed) { + SkDebugf(""); // for convenient breakpoints } - result->resetBounds(fCurve); - return result; -} - -template<typename TCurve> -void SkTSect<TCurve>::addForPerp(SkTSpan<TCurve>* span, double t) { - if (!span->hasOppT(t)) { - SkTSpan<TCurve>* priorSpan; - SkTSpan<TCurve>* opp = this->spanAtT(t, &priorSpan); - if (!opp) { - opp = this->addFollowing(priorSpan); -#if DEBUG_PERP - SkDebugf("%s priorSpan=%d t=%1.9g opp=%d\n", __FUNCTION__, priorSpan->debugID(), t, - opp->debugID()); #endif - } -#if DEBUG_PERP - opp->dump(); SkDebugf("\n"); - SkDebugf("%s fBounded.push_back span=%d opp=%d\n", __FUNCTION__, priorSpan->debugID(), - opp->debugID()); -#endif - opp->fBounded.push_back(span); - span->fBounded.push_back(opp); - } - this->validate(); - span->validatePerpT(t); } template<typename TCurve> @@ -391,143 +279,55 @@ double SkTSpan<TCurve>::closestBoundedT(const SkDPoint& pt) const { return result; } -#ifdef SK_DEBUG template<typename TCurve> -bool SkTSpan<TCurve>::debugIsBefore(const SkTSpan* span) const { - const SkTSpan* work = this; - do { - if (span == work) { +bool SkTSpan<TCurve>::contains(const SkTSpan* span) const { + int count = fBounded.count(); + for (int index = 0; index < count; ++index) { + const SkTSpan* test = fBounded[index]; + if (span == test) { return true; } - } while ((work = work->fNext)); + } return false; } -#endif template<typename TCurve> -bool SkTSpan<TCurve>::contains(double t) const { - const SkTSpan* work = this; +SkTSpan<TCurve>* SkTSpan<TCurve>::innerFind(double t) { + SkTSpan* work = this; do { if (between(work->fStartT, t, work->fEndT)) { - return true; + return work; } } while ((work = work->fNext)); - return false; -} - -template<typename TCurve> -const SkTSect<TCurve>* SkTSpan<TCurve>::debugOpp() const { - return PATH_OPS_DEBUG_RELEASE(fDebugSect->debugOpp(), NULL); -} - -template<typename TCurve> -SkTSpan<TCurve>* SkTSpan<TCurve>::findOppSpan(const SkTSpan* opp) const { - int count = fBounded.count(); - for (int index = 0; index < count; ++index) { - SkTSpan* test = fBounded[index]; - if (opp == test) { - return test; - } - } return NULL; } -// returns 0 if no hull intersection -// 1 if hulls intersect -// 2 if hulls only share a common endpoint -// -1 if linear and further checking is required -template<typename TCurve> -int SkTSpan<TCurve>::hullCheck(const SkTSpan* opp, bool* start, bool* oppStart) { - if (fIsLinear) { - return -1; - } - bool ptsInCommon; - if (onlyEndPointsInCommon(opp, start, oppStart, &ptsInCommon)) { - SkASSERT(ptsInCommon); - return 2; - } - bool linear; - if (fPart.hullIntersects(opp->fPart, &linear)) { - if (!linear) { // check set true if linear - return 1; - } - fIsLinear = true; - fIsLine = fPart.controlsInside(); - return ptsInCommon ? 2 : -1; - } else { // hull is not linear; check set true if intersected at the end points - return ((int) ptsInCommon) << 1; // 0 or 2 - } - return 0; -} - // OPTIMIZE ? If at_most_end_pts_in_common detects that one quad is near linear, // use line intersection to guess a better split than 0.5 // OPTIMIZE Once at_most_end_pts_in_common detects linear, mark span so all future splits are linear template<typename TCurve> -int SkTSpan<TCurve>::hullsIntersect(SkTSpan* opp, bool* start, bool* oppStart) { - if (!fBounds.intersects(opp->fBounds)) { - return 0; +bool SkTSpan<TCurve>::intersects(const SkTSpan* span, bool* check) { + if (!fBounds.intersects(span->fBounds)) { + *check = false; // no need to check to see if the bounds have end points in common + return false; } - int hullSect = this->hullCheck(opp, start, oppStart); - if (hullSect >= 0) { - return hullSect; + if (!fIsLinear && fPart.hullIntersects(span->fPart, check)) { + if (!*check) { + return true; + } + fIsLinear = true; } - hullSect = opp->hullCheck(this, oppStart, start); - if (hullSect >= 0) { - return hullSect; + if (fIsLinear) { + *check = false; + return linearIntersects(span->fPart); } - return -1; + return *check; } template<typename TCurve> -void SkTSpan<TCurve>::init(const TCurve& c) { - fPrev = fNext = NULL; - fStartT = 0; - fEndT = 1; - resetBounds(c); -} - -template<typename TCurve> -void SkTSpan<TCurve>::initBounds(const TCurve& c) { - fPart = c.subDivide(fStartT, fEndT); - fBounds.setBounds(fPart); - fCoinStart.init(); - fCoinEnd.init(); - fBoundsMax = SkTMax(fBounds.width(), fBounds.height()); - fCollapsed = fPart.collapsed(); - fHasPerp = false; - fDeleted = false; -#if DEBUG_T_SECT - if (fCollapsed) { - SkDebugf(""); // for convenient breakpoints - } -#endif -} - -template<typename TCurve> -bool SkTSpan<TCurve>::linearsIntersect(SkTSpan* span) { - int result = this->linearIntersects(span->fPart); - if (result <= 1) { - return SkToBool(result); - } - SkASSERT(span->fIsLinear); - result = span->linearIntersects(this->fPart); -// SkASSERT(result <= 1); - return SkToBool(result); -} - -template<typename TCurve> -double SkTSpan<TCurve>::linearT(const SkDPoint& pt) const { - SkDVector len = fPart[TCurve::kPointLast] - fPart[0]; - return fabs(len.fX) > fabs(len.fY) - ? (pt.fX - fPart[0].fX) / len.fX - : (pt.fY - fPart[0].fY) / len.fY; -} - -template<typename TCurve> -int SkTSpan<TCurve>::linearIntersects(const TCurve& q2) const { +bool SkTSpan<TCurve>::linearIntersects(const TCurve& q2) const { // looks like q1 is near-linear - int start = 0, end = TCurve::kPointLast; // the outside points are usually the extremes + int start = 0, end = TCurve::kPointCount - 1; // the outside points are usually the extremes if (!fPart.controlsInside()) { double dist = 0; // if there's any question, compute distance to find best outsiders for (int outer = 0; outer < TCurve::kPointCount - 1; ++outer) { @@ -547,116 +347,20 @@ int SkTSpan<TCurve>::linearIntersects(const TCurve& q2) const { double origY = fPart[start].fY; double adj = fPart[end].fX - origX; double opp = fPart[end].fY - origY; - double maxPart = SkTMax(fabs(adj), fabs(opp)); - double sign = 0; // initialization to shut up warning in release build + double sign; for (int n = 0; n < TCurve::kPointCount; ++n) { - double dx = q2[n].fY - origY; - double dy = q2[n].fX - origX; - double maxVal = SkTMax(maxPart, SkTMax(fabs(dx), fabs(dy))); double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; - if (precisely_zero_when_compared_to(test, maxVal)) { - return 1; - } - if (approximately_zero_when_compared_to(test, maxVal)) { - return 3; + if (precisely_zero(test)) { + return true; } if (n == 0) { sign = test; continue; } if (test * sign < 0) { - return 1; - } - } - return 0; -} - -template<typename TCurve> -bool SkTSpan<TCurve>::onlyEndPointsInCommon(const SkTSpan* opp, bool* start, bool* oppStart, - bool* ptsInCommon) { - if (opp->fPart[0] == fPart[0]) { - *start = *oppStart = true; - } else if (opp->fPart[0] == fPart[TCurve::kPointLast]) { - *start = false; - *oppStart = true; - } else if (opp->fPart[TCurve::kPointLast] == fPart[0]) { - *start = true; - *oppStart = false; - } else if (opp->fPart[TCurve::kPointLast] == fPart[TCurve::kPointLast]) { - *start = *oppStart = false; - } else { - *ptsInCommon = false; - return false; - } - *ptsInCommon = true; - const SkDPoint* o1Pts[TCurve::kPointCount - 1], * o2Pts[TCurve::kPointCount - 1]; - int baseIndex = *start ? 0 : TCurve::kPointLast; - fPart.otherPts(baseIndex, o1Pts); - opp->fPart.otherPts(*oppStart ? 0 : TCurve::kPointLast, o2Pts); - const SkDPoint& base = fPart[baseIndex]; - for (int o1 = 0; o1 < (int) SK_ARRAY_COUNT(o1Pts); ++o1) { - SkDVector v1 = *o1Pts[o1] - base; - for (int o2 = 0; o2 < (int) SK_ARRAY_COUNT(o2Pts); ++o2) { - SkDVector v2 = *o2Pts[o2] - base; - if (v2.dot(v1) >= 0) { - return false; - } - } - } - return true; -} - -template<typename TCurve> -SkTSpan<TCurve>* SkTSpan<TCurve>::oppT(double t) const { - int count = fBounded.count(); - for (int index = 0; index < count; ++index) { - SkTSpan* test = fBounded[index]; - if (between(test->fStartT, t, test->fEndT)) { - return test; - } - } - return NULL; -} - -template<typename TCurve> -bool SkTSpan<TCurve>::removeAllBounded() { - bool deleteSpan = false; - int count = fBounded.count(); - for (int index = 0; index < count; ++index) { - SkTSpan* opp = fBounded[index]; - deleteSpan |= opp->removeBounded(this); - } - return deleteSpan; -} - -template<typename TCurve> -bool SkTSpan<TCurve>::removeBounded(const SkTSpan* opp) { - int count = fBounded.count(); - if (fHasPerp) { - bool foundStart = false; - bool foundEnd = false; - for (int index = 0; index < count; ++index) { - const SkTSpan* test = fBounded[index]; - if (opp == test) { - continue; - } - foundStart |= between(test->fStartT, fCoinStart.perpT(), test->fEndT); - foundEnd |= between(test->fStartT, fCoinEnd.perpT(), test->fEndT); - } - if (!foundStart || !foundEnd) { - fHasPerp = false; - fCoinStart.init(); - fCoinEnd.init(); - } - } - for (int index = 0; index < count; ++index) { - if (opp == fBounded[index]) { - fBounded.removeShuffle(index); - SkASSERT((count == 1) == (fBounded.count() == 0)); - return count == 1; + return true; } } - SkASSERT(0); return false; } @@ -676,8 +380,6 @@ bool SkTSpan<TCurve>::splitAt(SkTSpan* work, double t) { fPrev = work; fNext = work->fNext; fIsLinear = work->fIsLinear; - fIsLine = work->fIsLine; - work->fNext = this; if (fNext) { fNext->fPrev = this; @@ -691,74 +393,102 @@ bool SkTSpan<TCurve>::splitAt(SkTSpan* work, double t) { } template<typename TCurve> -void SkTSpan<TCurve>::validate() const { -#if DEBUG_T_SECT - SkASSERT(fNext == NULL || fNext != fPrev); - SkASSERT(fNext == NULL || this == fNext->fPrev); - SkASSERT(fPrev == NULL || this == fPrev->fNext); - SkASSERT(fBounds.width() || fBounds.height() || fCollapsed); - SkASSERT(fBoundsMax == SkTMax(fBounds.width(), fBounds.height())); - SkASSERT(0 <= fStartT); - SkASSERT(fEndT <= 1); - SkASSERT(fStartT <= fEndT); - SkASSERT(fBounded.count() > 0); - this->validateBounded(); - if (fHasPerp) { - if (fCoinStart.isCoincident()) { - validatePerpT(fCoinStart.perpT()); - validatePerpPt(fCoinStart.perpT(), fCoinStart.perpPt()); +bool SkTSpan<TCurve>::tightBoundsIntersects(const SkTSpan* span) const { + // skew all to an axis + SkDVector v2_0 = fPart[TCurve::kPointLast] - fPart[0]; + bool skewToXAxis = fabs(v2_0.fX) > fabs(v2_0.fY); + double ratio = skewToXAxis ? v2_0.fY / v2_0.fX : v2_0.fX / v2_0.fY; + TCurve r1 = fPart; + if (skewToXAxis) { + r1[1].fY -= (fPart[1].fX - r1[0].fX) * ratio; + if (TCurve::IsCubic()) { + r1[2].fY -= (fPart[2].fX - r1[0].fX) * ratio; + r1[3].fY = r1[0].fY; + } else { + r1[2].fY = r1[0].fY; } - if (fCoinEnd.isCoincident()) { - validatePerpT(fCoinEnd.perpT()); - validatePerpPt(fCoinEnd.perpT(), fCoinEnd.perpPt()); + } else { + r1[1].fX -= (fPart[1].fY - r1[0].fY) * ratio; + if (TCurve::IsCubic()) { + r1[2].fX -= (fPart[2].fY - r1[0].fY) * ratio; + r1[3].fX = r1[0].fX; + } else { + r1[2].fX = r1[0].fX; + } + } + // compute the tight skewed bounds + SkDRect bounds; + bounds.setBounds(r1); + // see if opposite ends are within range of tight skewed bounds + TCurve r2 = span->fPart; + for (int i = 0; i < TCurve::kPointCount; i += 2) { + if (skewToXAxis) { + r2[i].fY -= (r2[i].fX - r1[0].fX) * ratio; + if (between(bounds.fTop, r2[i].fY, bounds.fBottom)) { + return true; + } + } else { + r2[i].fX -= (r2[i].fY - r1[0].fY) * ratio; + if (between(bounds.fLeft, r2[i].fX, bounds.fRight)) { + return true; + } } } -#endif -} - -template<typename TCurve> -void SkTSpan<TCurve>::validateBounded() const { -#if DEBUG_VALIDATE - for (int index = 0; index < fBounded.count(); ++index) { - const SkTSpan* overlap = fBounded[index]; - SkASSERT(!overlap->fDeleted); - SkASSERT(((this->debugID() ^ overlap->debugID()) & 1) == 1); - SkASSERT(overlap->findOppSpan(this)); + // see if opposite ends are on either side of tight skewed bounds + if ((skewToXAxis ? (r2[0].fY - r1[0].fY) * (r2[TCurve::kPointLast].fY - r1[0].fY) + : (r2[0].fX - r1[0].fX) * (r2[TCurve::kPointLast].fX - r1[0].fX)) < 0) { + return true; + } + // compute opposite tight skewed bounds + if (skewToXAxis) { + r2[1].fY -= (r2[1].fX - r1[0].fX) * ratio; + if (TCurve::IsCubic()) { + r2[2].fY -= (r2[2].fX - r1[0].fX) * ratio; + } + } else { + r2[1].fX -= (r2[1].fY - r1[0].fY) * ratio; + if (TCurve::IsCubic()) { + r2[2].fX -= (r2[2].fY - r1[0].fY) * ratio; + } + } + SkDRect sBounds; + sBounds.setBounds(r2); + // see if tight bounds overlap + if (skewToXAxis) { + return bounds.fTop <= sBounds.fBottom && sBounds.fTop <= bounds.fBottom; + } else { + return bounds.fLeft <= sBounds.fRight && sBounds.fLeft <= bounds.fRight; } -#endif } +#if DEBUG_T_SECT template<typename TCurve> -void SkTSpan<TCurve>::validatePerpT(double oppT) const { -#if DEBUG_VALIDATE +void SkTSpan<TCurve>::validate() const { + SkASSERT(fNext == NULL || fNext != fPrev); + SkASSERT(fNext == NULL || this == fNext->fPrev); + SkASSERT(fBounds.width() || fBounds.height()); + SkASSERT(fBoundsMax == SkTMax(fBounds.width(), fBounds.height())); + SkASSERT(0 <= fStartT); + SkASSERT(fEndT <= 1); + SkASSERT(fStartT < fEndT); + SkASSERT(fBounded.count() > 0); for (int index = 0; index < fBounded.count(); ++index) { const SkTSpan* overlap = fBounded[index]; - if (between(overlap->fStartT, oppT, overlap->fEndT)) { - return; - } + SkASSERT(((fDebugID ^ overlap->fDebugID) & 1) == 1); + SkASSERT(overlap->contains(this)); } - SkASSERT(0); -#endif } - -template<typename TCurve> -void SkTSpan<TCurve>::validatePerpPt(double t, const SkDPoint& pt) const { -#if DEBUG_T_SECT - PATH_OPS_DEBUG_CODE(SkASSERT(fDebugSect->fOppSect->fCurve.ptAtT(t) == pt)); #endif -} - template<typename TCurve> -SkTSect<TCurve>::SkTSect(const TCurve& c PATH_OPS_DEBUG_T_SECT_PARAMS(int id)) +SkTSect<TCurve>::SkTSect(const TCurve& c PATH_OPS_DEBUG_PARAMS(int id)) : fCurve(c) , fHeap(sizeof(SkTSpan<TCurve>) * 4) - , fCoincident(NULL) , fDeleted(NULL) , fActiveCount(0) - PATH_OPS_DEBUG_T_SECT_PARAMS(fID(id)) - PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugCount(0)) - PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugAllocatedCount(0)) + PATH_OPS_DEBUG_PARAMS(fDebugID(id)) + PATH_OPS_DEBUG_PARAMS(fDebugCount(0)) + PATH_OPS_DEBUG_PARAMS(fDebugAllocatedCount(0)) { fHead = addOne(); fHead->init(c); @@ -778,22 +508,22 @@ SkTSpan<TCurve>* SkTSect<TCurve>::addOne() { #endif } ++fActiveCount; - PATH_OPS_DEBUG_T_SECT_CODE(result->fID = fDebugCount++ * 2 + fID); - PATH_OPS_DEBUG_CODE(result->fDebugSect = this); +#if DEBUG_T_SECT + result->fDebugID = fDebugCount++ * 2 + fDebugID; +#endif return result; } template<typename TCurve> -bool SkTSect<TCurve>::binarySearchCoin(SkTSect* sect2, double tStart, double tStep, +bool SkTSect<TCurve>::binarySearchCoin(const SkTSect& sect2, double tStart, double tStep, double* resultT, double* oppT) { SkTSpan<TCurve> work; double result = work.fStartT = work.fEndT = tStart; - PATH_OPS_DEBUG_CODE(work.fDebugSect = this); SkDPoint last = fCurve.ptAtT(tStart); SkDPoint oppPt; bool flip = false; SkDEBUGCODE(bool down = tStep < 0); - const TCurve& opp = sect2->fCurve; + const TCurve& opp = sect2.fCurve; do { tStep *= 0.5; work.fStartT += tStep; @@ -811,11 +541,8 @@ bool SkTSect<TCurve>::binarySearchCoin(SkTSect* sect2, double tStart, double tSt last = work.fPart[0]; work.fCoinStart.setPerp(fCurve, work.fStartT, last, opp); if (work.fCoinStart.isCoincident()) { -#if DEBUG_T_SECT - work.validatePerpPt(work.fCoinStart.perpT(), work.fCoinStart.perpPt()); -#endif double oppTTest = work.fCoinStart.perpT(); - if (sect2->fHead->contains(oppTTest)) { + if (sect2.fHead->contains(oppTTest)) { *oppT = oppTTest; oppPt = work.fCoinStart.perpPt(); SkASSERT(down ? result > work.fStartT : result < work.fStartT); @@ -847,472 +574,194 @@ template<typename TCurve> SkTSpan<TCurve>* SkTSect<TCurve>::boundsMax() const { SkTSpan<TCurve>* test = fHead; SkTSpan<TCurve>* largest = fHead; - bool lCollapsed = largest->fCollapsed; + bool largestCoin = largest->fCoinStart.isCoincident() && largest->fCoinEnd.isCoincident(); while ((test = test->fNext)) { - bool tCollapsed = test->fCollapsed; - if ((lCollapsed && !tCollapsed) || (lCollapsed == tCollapsed && - largest->fBoundsMax < test->fBoundsMax)) { + bool testCoin = test->fCoinStart.isCoincident() || test->fCoinEnd.isCoincident(); + if ((largestCoin && !testCoin) || (largestCoin == testCoin + && (largest->fBoundsMax < test->fBoundsMax + || (largest->fCollapsed && !test->fCollapsed)))) { largest = test; + largestCoin = testCoin; } } - return largest; + return largestCoin ? NULL : largest; } template<typename TCurve> void SkTSect<TCurve>::coincidentCheck(SkTSect* sect2) { SkTSpan<TCurve>* first = fHead; - SkTSpan<TCurve>* last, * next; + SkTSpan<TCurve>* next; do { - int consecutive = this->countConsecutiveSpans(first, &last); - next = last->fNext; + int consecutive = 1; + SkTSpan<TCurve>* last = first; + do { + next = last->fNext; + if (!next) { + break; + } + if (next->fStartT > last->fEndT) { + break; + } + ++consecutive; + last = next; + } while (true); if (consecutive < COINCIDENT_SPAN_COUNT) { continue; } - this->validate(); - sect2->validate(); - this->computePerpendiculars(sect2, first, last); - this->validate(); - sect2->validate(); + setPerp(sect2->fCurve, first, last); // check to see if a range of points are on the curve - SkTSpan<TCurve>* coinStart = first; - do { - coinStart = this->extractCoincident(sect2, coinStart, last); - } while (coinStart && !last->fDeleted); - } while ((first = next)); -} - -template<typename TCurve> -bool SkTSect<TCurve>::coincidentHasT(double t) { - SkTSpan<TCurve>* test = fCoincident; - while (test) { - if (between(test->fStartT, t, test->fEndT)) { - return true; + onCurveCheck(sect2, first, last); + SkTSpan<TCurve>* removalCandidate = NULL; + if (!first->fCoinStart.isCoincident()) { + SkTSpan<TCurve>* firstCoin = first->fNext; + removalCandidate = first; + first = firstCoin; + } + if (!first->fCoinStart.isCoincident()) { + continue; } - test = test->fNext; - } - return false; -} - -template<typename TCurve> -void SkTSect<TCurve>::computePerpendiculars(SkTSect* sect2, SkTSpan<TCurve>* first, - SkTSpan<TCurve>* last) { - const TCurve& opp = sect2->fCurve; - SkTSpan<TCurve>* work = first; - SkTSpan<TCurve>* prior = NULL; - do { - if (!work->fHasPerp && !work->fCollapsed) { - if (prior) { - work->fCoinStart = prior->fCoinEnd; - } else { - work->fCoinStart.setPerp(fCurve, work->fStartT, work->fPart[0], opp); - } - if (work->fCoinStart.isCoincident()) { - double perpT = work->fCoinStart.perpT(); - if (sect2->coincidentHasT(perpT)) { - work->fCoinStart.clear(); - } else { - sect2->addForPerp(work, perpT); - } - } - work->fCoinEnd.setPerp(fCurve, work->fEndT, work->fPart[TCurve::kPointLast], opp); - if (work->fCoinEnd.isCoincident()) { - double perpT = work->fCoinEnd.perpT(); - if (sect2->coincidentHasT(perpT)) { - work->fCoinEnd.clear(); - } else { - sect2->addForPerp(work, perpT); - } + if (removalCandidate) { + removeSpans(removalCandidate, sect2); + } + if (!last->fCoinStart.isCoincident()) { + continue; + } + if (!last->fCoinEnd.isCoincident()) { + if (--consecutive < COINCIDENT_SPAN_COUNT) { + continue; } - work->fHasPerp = true; + last = last->fPrev; + SkASSERT(last->fCoinStart.isCoincident()); + SkASSERT(last->fCoinEnd.isCoincident()); } - if (work == last) { - break; + SkASSERT(between(0, first->fCoinStart.perpT(), 1) || first->fCoinStart.perpT() == -1); + if (first->fCoinStart.perpT() < 0) { + first->fCoinStart.setPerp(fCurve, first->fStartT, first->fPart[0], sect2->fCurve); } - prior = work; - work = work->fNext; - SkASSERT(work); - } while (true); -} - -template<typename TCurve> -int SkTSect<TCurve>::countConsecutiveSpans(SkTSpan<TCurve>* first, - SkTSpan<TCurve>** lastPtr) const { - int consecutive = 1; - SkTSpan<TCurve>* last = first; - do { - SkTSpan<TCurve>* next = last->fNext; - if (!next) { - break; + SkASSERT(between(0, last->fCoinEnd.perpT(), 1) || last->fCoinEnd.perpT() == -1); + if (last->fCoinEnd.perpT() < 0) { + last->fCoinEnd.setPerp(fCurve, last->fEndT, last->fPart[TCurve::kPointLast], + sect2->fCurve); } - if (next->fStartT > last->fEndT) { - break; + SkTSpan<TCurve>* removeMe = first->fNext; + while (removeMe != last) { + SkTSpan<TCurve>* removeNext = removeMe->fNext; + removeSpans(removeMe, sect2); + removeMe = removeNext; } - ++consecutive; - last = next; - } while (true); - *lastPtr = last; - return consecutive; + } while ((first = next)); } template<typename TCurve> -bool SkTSect<TCurve>::debugHasBounded(const SkTSpan<TCurve>* span) const { - const SkTSpan<TCurve>* test = fHead; - if (!test) { +bool SkTSect<TCurve>::intersects(SkTSpan<TCurve>* span, const SkTSect* opp, + const SkTSpan<TCurve>* oppSpan) const { + bool check; // we ignore whether the end points are in common or not + if (!span->intersects(oppSpan, &check)) { return false; } - do { - if (test->findOppSpan(span)) { - return true; - } - } while ((test = test->next())); - return false; -} - -template<typename TCurve> -void SkTSect<TCurve>::deleteEmptySpans() { - SkTSpan<TCurve>* test; - SkTSpan<TCurve>* next = fHead; - while ((test = next)) { - next = test->fNext; - if (test->fBounded.count() == 0) { - this->removeSpan(test); - } - } -} - -template<typename TCurve> -SkTSpan<TCurve>* SkTSect<TCurve>::extractCoincident(SkTSect* sect2, SkTSpan<TCurve>* first, - SkTSpan<TCurve>* last) { - first = findCoincidentRun(first, &last, sect2); - if (!first) { - return NULL; + if (fActiveCount < COINCIDENT_SPAN_COUNT || opp->fActiveCount < COINCIDENT_SPAN_COUNT) { + return true; } - // march outwards to find limit of coincidence from here to previous and next spans - double startT = first->fStartT; - double oppStartT; - double oppEndT SK_INIT_TO_AVOID_WARNING; - SkTSpan<TCurve>* prev = first->fPrev; - SkASSERT(first->fCoinStart.isCoincident()); - SkTSpan<TCurve>* oppFirst = first->findOppT(first->fCoinStart.perpT()); - SkASSERT(last->fCoinEnd.isCoincident()); - bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); - double coinStart; - SkDEBUGCODE(double coinEnd); - if (prev && prev->fEndT == startT - && this->binarySearchCoin(sect2, startT, prev->fStartT - startT, &coinStart, - &oppStartT) - && prev->fStartT < coinStart && coinStart < startT) { - oppFirst = prev->findOppT(oppStartT); // find opp start before splitting prev - SkASSERT(oppFirst); - first = this->addSplitAt(prev, coinStart); - first->markCoincident(); - prev->fCoinEnd.markCoincident(); - if (oppFirst->fStartT < oppStartT && oppStartT < oppFirst->fEndT) { - SkTSpan<TCurve>* oppHalf = sect2->addSplitAt(oppFirst, oppStartT); - if (oppMatched) { - oppFirst->fCoinEnd.markCoincident(); - oppHalf->markCoincident(); - oppFirst = oppHalf; - } else { - oppFirst->markCoincident(); - oppHalf->fCoinStart.markCoincident(); - } - } - } else { - SkDEBUGCODE(coinStart = first->fStartT); - SkDEBUGCODE(oppStartT = oppMatched ? oppFirst->fStartT : oppFirst->fEndT); - } - SkTSpan<TCurve>* oppLast; - SkASSERT(last->fCoinEnd.isCoincident()); - oppLast = last->findOppT(last->fCoinEnd.perpT()); - SkDEBUGCODE(coinEnd = last->fEndT); - SkDEBUGCODE(oppEndT = oppMatched ? oppLast->fEndT : oppLast->fStartT); - if (!oppMatched) { - SkTSwap(oppFirst, oppLast); - SkTSwap(oppStartT, oppEndT); - } - SkASSERT(oppStartT < oppEndT); - SkASSERT(coinStart == first->fStartT); - SkASSERT(coinEnd == last->fEndT); - SkASSERT(oppStartT == oppFirst->fStartT); - SkASSERT(oppEndT == oppLast->fEndT); - // reduce coincident runs to single entries - this->validate(); - sect2->validate(); - bool deleteThisSpan = this->updateBounded(first, last, oppFirst); - bool deleteSect2Span = sect2->updateBounded(oppFirst, oppLast, first); - this->removeSpanRange(first, last); - sect2->removeSpanRange(oppFirst, oppLast); - first->fEndT = last->fEndT; - first->resetBounds(this->fCurve); - first->fCoinStart.setPerp(fCurve, first->fStartT, first->fPart[0], sect2->fCurve); - first->fCoinEnd.setPerp(fCurve, first->fEndT, first->fPart[TCurve::kPointLast], sect2->fCurve); - oppStartT = first->fCoinStart.perpT(); - oppEndT = first->fCoinEnd.perpT(); - if (between(0, oppStartT, 1) && between(0, oppEndT, 1)) { - if (!oppMatched) { - SkTSwap(oppStartT, oppEndT); - } - oppFirst->fStartT = oppStartT; - oppFirst->fEndT = oppEndT; - oppFirst->resetBounds(sect2->fCurve); - } - this->validateBounded(); - sect2->validateBounded(); - last = first->fNext; - this->removeCoincident(first, false); - sect2->removeCoincident(oppFirst, true); - if (deleteThisSpan) { - this->deleteEmptySpans(); - } - if (deleteSect2Span) { - sect2->deleteEmptySpans(); - } - this->validate(); - sect2->validate(); - return last && !last->fDeleted ? last : NULL; + return span->tightBoundsIntersects(oppSpan); } template<typename TCurve> -SkTSpan<TCurve>* SkTSect<TCurve>::findCoincidentRun(SkTSpan<TCurve>* first, - SkTSpan<TCurve>** lastPtr, const SkTSect* sect2) { +void SkTSect<TCurve>::onCurveCheck(SkTSect* sect2, SkTSpan<TCurve>* first, SkTSpan<TCurve>* last) { SkTSpan<TCurve>* work = first; - SkTSpan<TCurve>* lastCandidate = NULL; first = NULL; - // find the first fully coincident span do { if (work->fCoinStart.isCoincident()) { - work->validatePerpT(work->fCoinStart.perpT()); - work->validatePerpPt(work->fCoinStart.perpT(), work->fCoinStart.perpPt()); - SkASSERT(work->hasOppT(work->fCoinStart.perpT())); - if (!work->fCoinEnd.isCoincident()) { - break; - } - lastCandidate = work; if (!first) { first = work; } - } else { - lastCandidate = NULL; - SkASSERT(!first); + } else if (first) { + break; } - if (work == *lastPtr) { - return first; + if (work == last) { + break; } work = work->fNext; SkASSERT(work); } while (true); - if (lastCandidate) { - *lastPtr = lastCandidate; + if (!first) { + return; } - return first; -} - -template<typename TCurve> -int SkTSect<TCurve>::intersects(SkTSpan<TCurve>* span, const SkTSect* opp, - SkTSpan<TCurve>* oppSpan, int* oppResult) const { - bool spanStart, oppStart; - int hullResult = span->hullsIntersect(oppSpan, &spanStart, &oppStart); - if (hullResult >= 0) { - if (hullResult == 2) { // hulls have one point in common - if (span->fBounded.count() <= 1) { - SkASSERT(span->fBounded.count() == 0 || span->fBounded[0] == oppSpan); - if (spanStart) { - span->fEndT = span->fStartT; - } else { - span->fStartT = span->fEndT; - } - } else { - hullResult = 1; - } - if (oppSpan->fBounded.count() <= 1) { - SkASSERT(span->fBounded.count() == 0 || oppSpan->fBounded[0] == span); - if (oppStart) { - oppSpan->fEndT = oppSpan->fStartT; + // march outwards to find limit of coincidence from here to previous and next spans + double startT = first->fStartT; + double oppT; + SkTSpan<TCurve>* prev = first->fPrev; + if (prev) { + double coinStart; + if (binarySearchCoin(*sect2, startT, prev->fStartT - startT, &coinStart, &oppT)) { + if (coinStart < startT) { + SkASSERT(prev->fStartT < coinStart && coinStart < prev->fEndT); + SkTSpan<TCurve>* oppStart = sect2->fHead->find(oppT); + if (oppStart->fStartT < oppT && oppT < oppStart->fEndT) { + // split prev at coinStart if needed + SkTSpan<TCurve>* half2 = addOne(); + half2->splitAt(prev, coinStart); + half2->initBounds(fCurve); + prev->initBounds(fCurve); + prev->fCoinEnd.markCoincident(); + half2->fCoinStart.markCoincident(); + half2->fCoinEnd.markCoincident(); + // find span containing opposite t, and split that too + SkTSpan<TCurve>* oppHalf = sect2->addOne(); + oppHalf->splitAt(oppStart, oppT); + oppHalf->initBounds(sect2->fCurve); + oppStart->initBounds(sect2->fCurve); } else { - oppSpan->fStartT = oppSpan->fEndT; + SkASSERT(oppStart->fStartT == oppT || oppT == oppStart->fEndT); + first->fStartT = coinStart; + prev->fEndT = coinStart; + first->initBounds(fCurve); + prev->initBounds(fCurve); + first->fCoinStart.markCoincident(); + first->fCoinEnd.markCoincident(); } - *oppResult = 2; - } else { - *oppResult = 1; } - } else { - *oppResult = 1; - } - return hullResult; - } - if (span->fIsLine && oppSpan->fIsLine) { - SkIntersections i; - int sects = this->linesIntersect(span, opp, oppSpan, &i); - if (!sects) { - return -1; } - span->fStartT = span->fEndT = i[0][0]; - oppSpan->fStartT = oppSpan->fEndT = i[1][0]; - return *oppResult = 2; - } - if (span->fIsLinear || oppSpan->fIsLinear) { - return *oppResult = (int) span->linearsIntersect(oppSpan); } - return *oppResult = 1; -} - -// while the intersection points are sufficiently far apart: -// construct the tangent lines from the intersections -// find the point where the tangent line intersects the opposite curve -template<typename TCurve> -int SkTSect<TCurve>::linesIntersect(const SkTSpan<TCurve>* span, const SkTSect* opp, - const SkTSpan<TCurve>* oppSpan, SkIntersections* i) const { - SkIntersections thisRayI, oppRayI; - SkDLine thisLine = {{ span->fPart[0], span->fPart[TCurve::kPointLast] }}; - SkDLine oppLine = {{ oppSpan->fPart[0], oppSpan->fPart[TCurve::kPointLast] }}; - int loopCount = 0; - double bestDistSq = DBL_MAX; - do { - if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { - return 0; - } - if (!oppRayI.intersectRay(this->fCurve, oppLine)) { - return 0; - } - // pick the closest pair of points - double closest = DBL_MAX; - int closeIndex SK_INIT_TO_AVOID_WARNING; - int oppCloseIndex SK_INIT_TO_AVOID_WARNING; - for (int index = 0; index < oppRayI.used(); ++index) { - if (!roughly_between(span->fStartT, oppRayI[0][index], span->fEndT)) { - continue; - } - for (int oIndex = 0; oIndex < thisRayI.used(); ++oIndex) { - if (!roughly_between(oppSpan->fStartT, thisRayI[0][oIndex], oppSpan->fEndT)) { - continue; - } - double distSq = thisRayI.pt(index).distanceSquared(oppRayI.pt(oIndex)); - if (closest > distSq) { - closest = distSq; - closeIndex = index; - oppCloseIndex = oIndex; + if (!work->fCoinEnd.isCoincident()) { + if (work->fEndT == 1) { + SkDebugf("!"); + } +// SkASSERT(work->fEndT < 1); + startT = work->fStartT; + double coinEnd; + if (binarySearchCoin(*sect2, startT, work->fEndT - startT, &coinEnd, &oppT)) { + if (coinEnd > startT) { + SkTSpan<TCurve>* oppStart = sect2->fHead->find(oppT); + if (oppStart->fStartT < oppT && oppT < oppStart->fEndT) { + SkASSERT(coinEnd < work->fEndT); + // split prev at coinEnd if needed + SkTSpan<TCurve>* half2 = addOne(); + half2->splitAt(work, coinEnd); + half2->initBounds(fCurve); + work->initBounds(fCurve); + work->fCoinStart.markCoincident(); + work->fCoinEnd.markCoincident(); + half2->fCoinStart.markCoincident(); + SkTSpan<TCurve>* oppHalf = sect2->addOne(); + oppHalf->splitAt(oppStart, oppT); + oppHalf->initBounds(sect2->fCurve); + oppStart->initBounds(sect2->fCurve); + } else { + SkASSERT(oppStart->fStartT == oppT || oppT == oppStart->fEndT); + SkTSpan<TCurve>* next = work->fNext; + bool hasNext = next && work->fEndT == next->fStartT; + work->fEndT = coinEnd; + work->initBounds(fCurve); + work->fCoinStart.markCoincident(); + work->fCoinEnd.markCoincident(); + if (hasNext) { + next->fStartT = coinEnd; + next->initBounds(fCurve); + } } } } - if (closest == DBL_MAX) { - return 0; - } - const SkDPoint& oppIPt = thisRayI.pt(oppCloseIndex); - const SkDPoint& iPt = oppRayI.pt(closeIndex); - if (between(span->fStartT, oppRayI[0][closeIndex], span->fEndT) - && between(oppSpan->fStartT, thisRayI[0][oppCloseIndex], oppSpan->fEndT) - && oppIPt.approximatelyEqual(iPt)) { - i->merge(oppRayI, closeIndex, thisRayI, oppCloseIndex); - return i->used(); - } - double distSq = oppIPt.distanceSquared(iPt); - if (bestDistSq < distSq || ++loopCount > 5) { - break; - } - bestDistSq = distSq; - thisLine[0] = fCurve.ptAtT(oppRayI[0][closeIndex]); - thisLine[1] = thisLine[0] + fCurve.dxdyAtT(oppRayI[0][closeIndex]); - oppLine[0] = opp->fCurve.ptAtT(thisRayI[0][oppCloseIndex]); - oppLine[1] = oppLine[0] + opp->fCurve.dxdyAtT(thisRayI[0][oppCloseIndex]); - } while (true); - return false; -} - - -template<typename TCurve> -void SkTSect<TCurve>::markSpanGone(SkTSpan<TCurve>* span) { - --fActiveCount; - span->fNext = fDeleted; - fDeleted = span; - SkASSERT(!span->fDeleted); - span->fDeleted = true; -} - -template<typename TCurve> -bool SkTSect<TCurve>::matchedDirection(double t, const SkTSect* sect2, double t2) const { - SkDVector dxdy = this->fCurve.dxdyAtT(t); - SkDVector dxdy2 = sect2->fCurve.dxdyAtT(t2); - return dxdy.dot(dxdy2) >= 0; -} - -template<typename TCurve> -void SkTSect<TCurve>::matchedDirCheck(double t, const SkTSect* sect2, double t2, - bool* calcMatched, bool* oppMatched) const { - if (*calcMatched) { - SkASSERT(*oppMatched == this->matchedDirection(t, sect2, t2)); - } else { - *oppMatched = this->matchedDirection(t, sect2, t2); - *calcMatched = true; - } -} - -template<typename TCurve> -void SkTSect<TCurve>::mergeCoincidence(SkTSect* sect2) { - double smallLimit = 0; - do { - // find the smallest unprocessed span - SkTSpan<TCurve>* smaller = NULL; - SkTSpan<TCurve>* test = fCoincident; - do { - if (test->fStartT < smallLimit) { - continue; - } - if (smaller && smaller->fEndT < test->fStartT) { - continue; - } - smaller = test; - } while ((test = test->fNext)); - if (!smaller) { - return; - } - smallLimit = smaller->fEndT; - // find next larger span - SkTSpan<TCurve>* prior = NULL; - SkTSpan<TCurve>* larger = NULL; - SkTSpan<TCurve>* largerPrior = NULL; - test = fCoincident; - do { - if (test->fStartT < smaller->fEndT) { - continue; - } - SkASSERT(test->fStartT != smaller->fEndT); - if (larger && larger->fStartT < test->fStartT) { - continue; - } - largerPrior = prior; - larger = test; - } while ((prior = test), (test = test->fNext)); - if (!larger) { - continue; - } - // check middle t value to see if it is coincident as well - double midT = (smaller->fEndT + larger->fStartT) / 2; - SkDPoint midPt = fCurve.ptAtT(midT); - SkTCoincident<TCurve> coin; - coin.setPerp(fCurve, midT, midPt, sect2->fCurve); - if (coin.isCoincident()) { - smaller->fEndT = larger->fEndT; - smaller->fCoinEnd = larger->fCoinEnd; - if (largerPrior) { - largerPrior->fNext = larger->fNext; - } else { - fCoincident = larger->fNext; - } - } - } while (true); -} - -template<typename TCurve> -SkTSpan<TCurve>* SkTSect<TCurve>::prev(SkTSpan<TCurve>* span) const { - SkTSpan<TCurve>* result = NULL; - SkTSpan<TCurve>* test = fHead; - while (span != test) { - result = test; - test = test->fNext; - SkASSERT(test); } - return result; } template<typename TCurve> @@ -1333,84 +782,41 @@ void SkTSect<TCurve>::recoverCollapsed() { } template<typename TCurve> -void SkTSect<TCurve>::removeAllBut(const SkTSpan<TCurve>* keep, SkTSpan<TCurve>* span, - SkTSect* opp) { - int count = span->fBounded.count(); - for (int index = 0; index < count; ++index) { - SkTSpan<TCurve>* bounded = span->fBounded[0]; - if (bounded == keep) { - continue; - } - if (bounded->fDeleted) { // may have been deleted when opp did 'remove all but' - continue; +void SkTSect<TCurve>::removeSpan(SkTSpan<TCurve>* span) { + SkTSpan<TCurve>* prev = span->fPrev; + SkTSpan<TCurve>* next = span->fNext; + if (prev) { + prev->fNext = next; + if (next) { + next->fPrev = prev; } - SkAssertResult(SkDEBUGCODE(!) span->removeBounded(bounded)); - if (bounded->removeBounded(span)) { - opp->removeSpan(bounded); + } else { + fHead = next; + if (next) { + next->fPrev = NULL; } } - SkASSERT(!span->fDeleted); - SkASSERT(span->findOppSpan(keep)); - SkASSERT(keep->findOppSpan(span)); -} - -template<typename TCurve> -void SkTSect<TCurve>::removeByPerpendicular(SkTSect<TCurve>* opp) { - SkTSpan<TCurve>* test = fHead; - SkTSpan<TCurve>* next; - do { - next = test->fNext; - if (test->fCoinStart.perpT() < 0 || test->fCoinEnd.perpT() < 0) { - continue; - } - SkDVector startV = test->fCoinStart.perpPt() - test->fPart[0]; - SkDVector endV = test->fCoinEnd.perpPt() - test->fPart[TCurve::kPointLast]; + --fActiveCount; + span->fNext = fDeleted; + fDeleted = span; #if DEBUG_T_SECT - SkDebugf("%s startV=(%1.9g,%1.9g) endV=(%1.9g,%1.9g) dot=%1.9g\n", __FUNCTION__, - startV.fX, startV.fY, endV.fX, endV.fY, startV.dot(endV)); + SkASSERT(!span->fDebugDeleted); + span->fDebugDeleted = true; #endif - if (startV.dot(endV) <= 0) { - continue; - } - this->removeSpans(test, opp); - } while ((test = next)); } template<typename TCurve> -void SkTSect<TCurve>::removeCoincident(SkTSpan<TCurve>* span, bool isBetween) { - this->unlinkSpan(span); - if (isBetween || between(0, span->fCoinStart.perpT(), 1)) { - --fActiveCount; - span->fNext = fCoincident; - fCoincident = span; - } else { - this->markSpanGone(span); - } -} - -template<typename TCurve> -void SkTSect<TCurve>::removeSpan(SkTSpan<TCurve>* span) { - this->unlinkSpan(span); - this->markSpanGone(span); -} - -template<typename TCurve> -void SkTSect<TCurve>::removeSpanRange(SkTSpan<TCurve>* first, SkTSpan<TCurve>* last) { - if (first == last) { - return; - } - SkTSpan<TCurve>* span = first; - SkASSERT(span); - SkTSpan<TCurve>* final = last->fNext; - SkTSpan<TCurve>* next = span->fNext; - while ((span = next) && span != final) { - next = span->fNext; - this->markSpanGone(span); - } - if (final) { - final->fPrev = first; +void SkTSect<TCurve>::removeOne(const SkTSpan<TCurve>* test, SkTSpan<TCurve>* span) { + int last = span->fBounded.count() - 1; + for (int index = 0; index <= last; ++index) { + if (span->fBounded[index] == test) { + span->fBounded.removeShuffle(index); + if (!last) { + removeSpan(span); + } + return; + } } - first->fNext = final; } template<typename TCurve> @@ -1418,33 +824,38 @@ void SkTSect<TCurve>::removeSpans(SkTSpan<TCurve>* span, SkTSect<TCurve>* opp) { int count = span->fBounded.count(); for (int index = 0; index < count; ++index) { SkTSpan<TCurve>* bounded = span->fBounded[0]; - if (span->removeBounded(bounded)) { // shuffles last into position 0 - this->removeSpan(span); - } - if (bounded->removeBounded(span)) { - opp->removeSpan(bounded); - } - SkASSERT(!span->fDeleted || !opp->debugHasBounded(span)); - + removeOne(bounded, span); // shuffles last into position 0 + opp->removeOne(span, bounded); } } template<typename TCurve> -SkTSpan<TCurve>* SkTSect<TCurve>::spanAtT(double t, SkTSpan<TCurve>** priorSpan) { - SkTSpan<TCurve>* test = fHead; - SkTSpan<TCurve>* prev = NULL; - while (test && test->fEndT < t) { - prev = test; - test = test->fNext; +void SkTSect<TCurve>::setPerp(const TCurve& opp, SkTSpan<TCurve>* first, SkTSpan<TCurve>* last) { + SkTSpan<TCurve>* work = first; + if (!work->fHasPerp) { + work->fCoinStart.setPerp(fCurve, work->fStartT, work->fPart[0], opp); } - *priorSpan = prev; - return test && test->fStartT <= t ? test : NULL; + do { + if (!work->fHasPerp) { + work->fCoinEnd.setPerp(fCurve, work->fEndT, work->fPart[TCurve::kPointLast], opp); + work->fHasPerp = true; + } + if (work == last) { + break; + } + SkTSpan<TCurve>* last = work; + work = work->fNext; + SkASSERT(work); + if (!work->fHasPerp) { + work->fCoinStart = last->fCoinEnd; + } + } while (true); } template<typename TCurve> -SkTSpan<TCurve>* SkTSect<TCurve>::tail() { - SkTSpan<TCurve>* result = fHead; - SkTSpan<TCurve>* next = fHead; +const SkTSpan<TCurve>* SkTSect<TCurve>::tail() const { + const SkTSpan<TCurve>* result = fHead; + const SkTSpan<TCurve>* next = fHead; while ((next = next->fNext)) { if (next->fEndT > result->fEndT) { result = next; @@ -1458,75 +869,32 @@ SkTSpan<TCurve>* SkTSect<TCurve>::tail() { template<typename TCurve> void SkTSect<TCurve>::trim(SkTSpan<TCurve>* span, SkTSect* opp) { span->initBounds(fCurve); - for (int index = 0; index < span->fBounded.count(); ) { + int count = span->fBounded.count(); + for (int index = 0; index < count; ) { SkTSpan<TCurve>* test = span->fBounded[index]; - int oppSects, sects = this->intersects(span, opp, test, &oppSects); - if (sects >= 1) { - if (sects == 2) { - span->initBounds(fCurve); - this->removeAllBut(test, span, opp); - } - if (oppSects == 2) { - test->initBounds(opp->fCurve); - opp->removeAllBut(span, test, this); - } + bool sects = intersects(span, opp, test); + if (sects) { ++index; } else { - if (span->removeBounded(test)) { - this->removeSpan(span); - } - if (test->removeBounded(span)) { - opp->removeSpan(test); - } + removeOne(test, span); + opp->removeOne(span, test); + --count; } } } -template<typename TCurve> -void SkTSect<TCurve>::unlinkSpan(SkTSpan<TCurve>* span) { - SkTSpan<TCurve>* prev = span->fPrev; - SkTSpan<TCurve>* next = span->fNext; - if (prev) { - prev->fNext = next; - if (next) { - next->fPrev = prev; - } - } else { - fHead = next; - if (next) { - next->fPrev = NULL; - } - } -} - -template<typename TCurve> -bool SkTSect<TCurve>::updateBounded(SkTSpan<TCurve>* first, SkTSpan<TCurve>* last, - SkTSpan<TCurve>* oppFirst) { - SkTSpan<TCurve>* test = first; - const SkTSpan<TCurve>* final = last->next(); - bool deleteSpan = false; - do { - deleteSpan |= test->removeAllBounded(); - } while ((test = test->fNext) != final); - first->fBounded.resize_back(1); - first->fBounded[0] = oppFirst; - // cannot call validate until remove span range is called - return deleteSpan; -} - - +#if DEBUG_T_SECT template<typename TCurve> void SkTSect<TCurve>::validate() const { -#if DEBUG_T_SECT int count = 0; if (fHead) { const SkTSpan<TCurve>* span = fHead; SkASSERT(!span->fPrev); - SkDEBUGCODE(double last = 0); + double last = 0; do { span->validate(); SkASSERT(span->fStartT >= last); - SkDEBUGCODE(last = span->fEndT); + last = span->fEndT; ++count; } while ((span = span->fNext) != NULL); } @@ -1538,81 +906,54 @@ void SkTSect<TCurve>::validate() const { ++deletedCount; deleted = deleted->fNext; } - const SkTSpan<TCurve>* coincident = fCoincident; - while (coincident) { - ++deletedCount; - coincident = coincident->fNext; - } SkASSERT(fActiveCount + deletedCount == fDebugAllocatedCount); -#endif } - -template<typename TCurve> -void SkTSect<TCurve>::validateBounded() const { -#if DEBUG_T_SECT - if (!fHead) { - return; - } - const SkTSpan<TCurve>* span = fHead; - do { - span->validateBounded(); - } while ((span = span->fNext) != NULL); #endif -} template<typename TCurve> int SkTSect<TCurve>::EndsEqual(const SkTSect* sect1, const SkTSect* sect2, SkIntersections* intersections) { int zeroOneSet = 0; - if (sect1->fCurve[0] == sect2->fCurve[0]) { - zeroOneSet |= kZeroS1Set | kZeroS2Set; - intersections->insert(0, 0, sect1->fCurve[0]); - } - if (sect1->fCurve[0] == sect2->fCurve[TCurve::kPointLast]) { - zeroOneSet |= kZeroS1Set | kOneS2Set; - intersections->insert(0, 1, sect1->fCurve[0]); - } - if (sect1->fCurve[TCurve::kPointLast] == sect2->fCurve[0]) { - zeroOneSet |= kOneS1Set | kZeroS2Set; - intersections->insert(1, 0, sect1->fCurve[TCurve::kPointLast]); - } - if (sect1->fCurve[TCurve::kPointLast] == sect2->fCurve[TCurve::kPointLast]) { - zeroOneSet |= kOneS1Set | kOneS2Set; - intersections->insert(1, 1, sect1->fCurve[TCurve::kPointLast]); - } // check for zero - if (!(zeroOneSet & (kZeroS1Set | kZeroS2Set)) - && sect1->fCurve[0].approximatelyEqual(sect2->fCurve[0])) { + if (sect1->fCurve[0].approximatelyEqual(sect2->fCurve[0])) { zeroOneSet |= kZeroS1Set | kZeroS2Set; - intersections->insertNear(0, 0, sect1->fCurve[0], sect2->fCurve[0]); - } - if (!(zeroOneSet & (kZeroS1Set | kOneS2Set)) - && sect1->fCurve[0].approximatelyEqual(sect2->fCurve[TCurve::kPointLast])) { + if (sect1->fCurve[0] != sect2->fCurve[0]) { + intersections->insertNear(0, 0, sect1->fCurve[0], sect2->fCurve[0]); + } else { + intersections->insert(0, 0, sect1->fCurve[0]); + } + } + if (sect1->fCurve[0].approximatelyEqual(sect2->fCurve[TCurve::kPointLast])) { zeroOneSet |= kZeroS1Set | kOneS2Set; - intersections->insertNear(0, 1, sect1->fCurve[0], sect2->fCurve[TCurve::kPointLast]); - } + if (sect1->fCurve[0] != sect2->fCurve[TCurve::kPointLast]) { + intersections->insertNear(0, 1, sect1->fCurve[0], sect2->fCurve[TCurve::kPointLast]); + } else { + intersections->insert(0, 1, sect1->fCurve[0]); + } + } // check for one - if (!(zeroOneSet & (kOneS1Set | kZeroS2Set)) - && sect1->fCurve[TCurve::kPointLast].approximatelyEqual(sect2->fCurve[0])) { + if (sect1->fCurve[TCurve::kPointLast].approximatelyEqual(sect2->fCurve[0])) { zeroOneSet |= kOneS1Set | kZeroS2Set; - intersections->insertNear(1, 0, sect1->fCurve[TCurve::kPointLast], sect2->fCurve[0]); - } - if (!(zeroOneSet & (kOneS1Set | kOneS2Set)) - && sect1->fCurve[TCurve::kPointLast].approximatelyEqual(sect2->fCurve[ - TCurve::kPointLast])) { + if (sect1->fCurve[TCurve::kPointLast] != sect2->fCurve[0]) { + intersections->insertNear(1, 0, sect1->fCurve[TCurve::kPointLast], sect2->fCurve[0]); + } else { + intersections->insert(1, 0, sect1->fCurve[TCurve::kPointLast]); + } + } + if (sect1->fCurve[TCurve::kPointLast].approximatelyEqual(sect2->fCurve[TCurve::kPointLast])) { zeroOneSet |= kOneS1Set | kOneS2Set; - intersections->insertNear(1, 1, sect1->fCurve[TCurve::kPointLast], - sect2->fCurve[TCurve::kPointLast]); + if (sect1->fCurve[TCurve::kPointLast] != sect2->fCurve[TCurve::kPointLast]) { + intersections->insertNear(1, 1, sect1->fCurve[TCurve::kPointLast], + sect2->fCurve[TCurve::kPointLast]); + } else { + intersections->insert(1, 1, sect1->fCurve[TCurve::kPointLast]); + } } return zeroOneSet; } template<typename TCurve> struct SkClosestRecord { - bool operator<(const SkClosestRecord& rh) const { - return fClosest < rh.fClosest; - } - void addIntersection(SkIntersections* intersections) const { double r1t = fC1Index ? fC1Span->endT() : fC1Span->startT(); double r2t = fC2Index ? fC2Span->endT() : fC2Span->startT(); @@ -1692,14 +1033,14 @@ struct SkClosestSect { fClosest.push_back().reset(); } - bool find(const SkTSpan<TCurve>* span1, const SkTSpan<TCurve>* span2) { + void find(const SkTSpan<TCurve>* span1, const SkTSpan<TCurve>* span2) { SkClosestRecord<TCurve>* record = &fClosest[fUsed]; record->findEnd(span1, span2, 0, 0); record->findEnd(span1, span2, 0, TCurve::kPointLast); record->findEnd(span1, span2, TCurve::kPointLast, 0); record->findEnd(span1, span2, TCurve::kPointLast, TCurve::kPointLast); if (record->fClosest == FLT_MAX) { - return false; + return; } for (int index = 0; index < fUsed; ++index) { SkClosestRecord<TCurve>* test = &fClosest[index]; @@ -1709,46 +1050,37 @@ struct SkClosestSect { } test->update(*record); record->reset(); - return false; + return; } } ++fUsed; fClosest.push_back().reset(); - return true; } void finish(SkIntersections* intersections) const { - SkSTArray<TCurve::kMaxIntersections * 2, const SkClosestRecord<TCurve>*, true> closestPtrs; - for (int index = 0; index < fUsed; ++index) { - closestPtrs.push_back(&fClosest[index]); - } - SkTQSort<const SkClosestRecord<TCurve> >(closestPtrs.begin(), closestPtrs.end() - 1); for (int index = 0; index < fUsed; ++index) { - const SkClosestRecord<TCurve>* test = closestPtrs[index]; - test->addIntersection(intersections); + const SkClosestRecord<TCurve>& test = fClosest[index]; + test.addIntersection(intersections); } } - // this is oversized so that an extra records can merge into final one - SkSTArray<TCurve::kMaxIntersections * 2, SkClosestRecord<TCurve>, true> fClosest; + // this is oversized by one so that an extra record can merge into final one + SkSTArray<TCurve::kMaxIntersections + 1, SkClosestRecord<TCurve>, true> fClosest; int fUsed; }; // returns true if the rect is too small to consider template<typename TCurve> void SkTSect<TCurve>::BinarySearch(SkTSect* sect1, SkTSect* sect2, SkIntersections* intersections) { - PATH_OPS_DEBUG_CODE(sect1->fOppSect = sect2); - PATH_OPS_DEBUG_CODE(sect2->fOppSect = sect1); intersections->reset(); - intersections->setMax(TCurve::kMaxIntersections * 2); // give extra for slop + intersections->setMax(TCurve::kMaxIntersections); SkTSpan<TCurve>* span1 = sect1->fHead; SkTSpan<TCurve>* span2 = sect2->fHead; - int oppSect, sect = sect1->intersects(span1, sect2, span2, &oppSect); -// SkASSERT(between(0, sect, 2)); - if (!sect) { + bool check; + if (!span1->intersects(span2, &check)) { return; } - if (sect == 2 && oppSect == 2) { + if (check) { (void) EndsEqual(sect1, sect2, intersections); return; } @@ -1764,12 +1096,12 @@ void SkTSect<TCurve>::BinarySearch(SkTSect* sect1, SkTSect* sect2, SkIntersectio bool split1 = !largest2 || (largest1 && (largest1->fBoundsMax > largest2->fBoundsMax || (!largest1->fCollapsed && largest2->fCollapsed))); // split it + SkTSect* splitSect = split1 ? sect1 : sect2; SkTSpan<TCurve>* half1 = split1 ? largest1 : largest2; SkASSERT(half1); if (half1->fCollapsed) { break; } - SkTSect* splitSect = split1 ? sect1 : sect2; // trim parts that don't intersect the opposite SkTSpan<TCurve>* half2 = splitSect->addOne(); SkTSect* unsplitSect = split1 ? sect2 : sect1; @@ -1778,52 +1110,50 @@ void SkTSect<TCurve>::BinarySearch(SkTSect* sect1, SkTSect* sect2, SkIntersectio } splitSect->trim(half1, unsplitSect); splitSect->trim(half2, unsplitSect); - sect1->validate(); - sect2->validate(); // if there are 9 or more continuous spans on both sects, suspect coincidence if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { sect1->coincidentCheck(sect2); - sect1->validate(); - sect2->validate(); } - if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT - && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { - sect1->computePerpendiculars(sect2, sect1->fHead, sect1->tail()); - sect2->computePerpendiculars(sect1, sect2->fHead, sect2->tail()); - sect1->removeByPerpendicular(sect2); - sect1->validate(); - sect2->validate(); - } -#if DEBUG_T_SECT_DUMP - sect1->dumpBoth(sect2); +#if DEBUG_T_SECT + sect1->validate(); + sect2->validate(); +#endif +#if DEBUG_T_SECT_DUMP > 1 + sect1->dumpBoth(*sect2); #endif if (!sect1->fHead || !sect2->fHead) { - break; + return; } } while (true); - SkTSpan<TCurve>* coincident = sect1->fCoincident; - if (coincident) { - // if there is more than one coincident span, check loosely to see if they should be joined - if (coincident->fNext) { - sect1->mergeCoincidence(sect2); - coincident = sect1->fCoincident; - } - SkASSERT(sect2->fCoincident); // courtesy check : coincidence only looks at sect 1 + if (sect1->fActiveCount >= 2 && sect2->fActiveCount >= 2) { + // check for coincidence + SkTSpan<TCurve>* first = sect1->fHead; do { - SkASSERT(coincident->fCoinStart.isCoincident()); - SkASSERT(coincident->fCoinEnd.isCoincident()); - int index = intersections->insertCoincident(coincident->fStartT, - coincident->fCoinStart.perpT(), coincident->fPart[0]); - if ((intersections->insertCoincident(coincident->fEndT, - coincident->fCoinEnd.perpT(), - coincident->fPart[TCurve::kPointLast]) < 0) && index >= 0) { + if (!first->fCoinStart.isCoincident()) { + continue; + } + int spanCount = 1; + SkTSpan<TCurve>* last = first; + while (last->fCoinEnd.isCoincident()) { + SkTSpan<TCurve>* next = last->fNext; + if (!next || !next->fCoinEnd.isCoincident()) { + break; + } + last = next; + ++spanCount; + } + if (spanCount < 2) { + first = last; + continue; + } + int index = intersections->insertCoincident(first->fStartT, first->fCoinStart.perpT(), + first->fPart[0]); + if (intersections->insertCoincident(last->fEndT, last->fCoinEnd.perpT(), + last->fPart[TCurve::kPointLast]) < 0) { intersections->clearCoincidence(index); } - } while ((coincident = coincident->fNext)); - } - if (!sect1->fHead || !sect2->fHead) { - return; + } while ((first = first->fNext)); } int zeroOneSet = EndsEqual(sect1, sect2, intersections); sect1->recoverCollapsed(); @@ -1833,41 +1163,33 @@ void SkTSect<TCurve>::BinarySearch(SkTSect* sect1, SkTSect* sect2, SkIntersectio const SkTSpan<TCurve>* head1 = result1; if (!(zeroOneSet & kZeroS1Set) && approximately_less_than_zero(head1->fStartT)) { const SkDPoint& start1 = sect1->fCurve[0]; - if (head1->isBounded()) { - double t = head1->closestBoundedT(start1); - if (sect2->fCurve.ptAtT(t).approximatelyEqual(start1)) { - intersections->insert(0, t, start1); - } + double t = head1->closestBoundedT(start1); + if (sect2->fCurve.ptAtT(t).approximatelyEqual(start1)) { + intersections->insert(0, t, start1); } } const SkTSpan<TCurve>* head2 = sect2->fHead; if (!(zeroOneSet & kZeroS2Set) && approximately_less_than_zero(head2->fStartT)) { const SkDPoint& start2 = sect2->fCurve[0]; - if (head2->isBounded()) { - double t = head2->closestBoundedT(start2); - if (sect1->fCurve.ptAtT(t).approximatelyEqual(start2)) { - intersections->insert(t, 0, start2); - } + double t = head2->closestBoundedT(start2); + if (sect1->fCurve.ptAtT(t).approximatelyEqual(start2)) { + intersections->insert(t, 0, start2); } } const SkTSpan<TCurve>* tail1 = sect1->tail(); if (!(zeroOneSet & kOneS1Set) && approximately_greater_than_one(tail1->fEndT)) { const SkDPoint& end1 = sect1->fCurve[TCurve::kPointLast]; - if (tail1->isBounded()) { - double t = tail1->closestBoundedT(end1); - if (sect2->fCurve.ptAtT(t).approximatelyEqual(end1)) { - intersections->insert(1, t, end1); - } + double t = tail1->closestBoundedT(end1); + if (sect2->fCurve.ptAtT(t).approximatelyEqual(end1)) { + intersections->insert(1, t, end1); } } const SkTSpan<TCurve>* tail2 = sect2->tail(); if (!(zeroOneSet & kOneS2Set) && approximately_greater_than_one(tail2->fEndT)) { const SkDPoint& end2 = sect2->fCurve[TCurve::kPointLast]; - if (tail2->isBounded()) { - double t = tail2->closestBoundedT(end2); - if (sect1->fCurve.ptAtT(t).approximatelyEqual(end2)) { - intersections->insert(t, 1, end2); - } + double t = tail2->closestBoundedT(end2); + if (sect1->fCurve.ptAtT(t).approximatelyEqual(end2)) { + intersections->insert(t, 1, end2); } } SkClosestSect<TCurve> closest; @@ -1879,39 +1201,11 @@ void SkTSect<TCurve>::BinarySearch(SkTSect* sect1, SkTSect* sect2, SkIntersectio break; } SkTSpan<TCurve>* result2 = sect2->fHead; - bool found = false; while (result2) { - found |= closest.find(result1, result2); + closest.find(result1, result2); result2 = result2->fNext; } + } while ((result1 = result1->fNext)); closest.finish(intersections); - // if there is more than one intersection and it isn't already coincident, check - int last = intersections->used() - 1; - for (int index = 0; index < last; ) { - if (intersections->isCoincident(index) && intersections->isCoincident(index + 1)) { - ++index; - continue; - } - double midT = ((*intersections)[0][index] + (*intersections)[0][index + 1]) / 2; - SkDPoint midPt = sect1->fCurve.ptAtT(midT); - // intersect perpendicular with opposite curve - SkTCoincident<TCurve> perp; - perp.setPerp(sect1->fCurve, midT, midPt, sect2->fCurve); - if (!perp.isCoincident()) { - ++index; - continue; - } - if (intersections->isCoincident(index)) { - intersections->removeOne(index); - --last; - } else if (intersections->isCoincident(index + 1)) { - intersections->removeOne(index + 1); - --last; - } else { - intersections->setCoincident(index++); - } - intersections->setCoincident(index); - } - SkASSERT(intersections->used() <= TCurve::kMaxIntersections); } diff --git a/src/pathops/SkPathOpsTightBounds.cpp b/src/pathops/SkPathOpsTightBounds.cpp index d03efeb173..0f63f396e7 100644 --- a/src/pathops/SkPathOpsTightBounds.cpp +++ b/src/pathops/SkPathOpsTightBounds.cpp @@ -8,16 +8,14 @@ #include "SkPathOpsCommon.h" bool TightBounds(const SkPath& path, SkRect* result) { - SkOpContour contour; - SkOpGlobalState globalState( NULL PATH_OPS_DEBUG_PARAMS(&contour)); // turn path into list of segments - SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune - SkOpEdgeBuilder builder(path, &contour, &allocator, &globalState); - if (!builder.finish(&allocator)) { + SkTArray<SkOpContour> contours; + SkOpEdgeBuilder builder(path, contours); + if (!builder.finish()) { return false; } - SkTDArray<SkOpContour* > contourList; - MakeContourList(&contour, contourList, false, false); + SkTArray<SkOpContour*, true> contourList; + MakeContourList(contours, contourList, false, false); SkOpContour** currentPtr = contourList.begin(); result->setEmpty(); if (!currentPtr) { diff --git a/src/pathops/SkPathOpsTriangle.cpp b/src/pathops/SkPathOpsTriangle.cpp new file mode 100644 index 0000000000..77845e0673 --- /dev/null +++ b/src/pathops/SkPathOpsTriangle.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPathOpsTriangle.h" + +// http://www.blackpawn.com/texts/pointinpoly/default.html +// return true if pt is inside triangle; false if outside or on the line +bool SkDTriangle::contains(const SkDPoint& pt) const { +// Compute vectors + SkDVector v0 = fPts[2] - fPts[0]; + SkDVector v1 = fPts[1] - fPts[0]; + SkDVector v2 = pt - fPts[0]; + +// Compute dot products + double dot00 = v0.dot(v0); + double dot01 = v0.dot(v1); + double dot02 = v0.dot(v2); + double dot11 = v1.dot(v1); + double dot12 = v1.dot(v2); + +// original code doesn't handle degenerate input; isn't symmetric with inclusion of corner pts; +// introduces error with divide; doesn't short circuit on early answer +#if 0 +// Compute barycentric coordinates + double invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + double u = (dot11 * dot02 - dot01 * dot12) * invDenom; + double v = (dot00 * dot12 - dot01 * dot02) * invDenom; + +// Check if point is in triangle + return (u >= 0) && (v >= 0) && (u + v <= 1); +#else + double w = dot00 * dot11 - dot01 * dot01; + if (w == 0) { + return false; + } + double wSign = w < 0 ? -1 : 1; + double u = (dot11 * dot02 - dot01 * dot12) * wSign; + if (u <= 0) { + return false; + } + double v = (dot00 * dot12 - dot01 * dot02) * wSign; + if (v <= 0) { + return false; + } + return u + v < w * wSign; +#endif +} diff --git a/src/pathops/SkPathOpsTriangle.h b/src/pathops/SkPathOpsTriangle.h new file mode 100644 index 0000000000..8cc8c6d3b5 --- /dev/null +++ b/src/pathops/SkPathOpsTriangle.h @@ -0,0 +1,20 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsTriangle_DEFINED +#define SkPathOpsTriangle_DEFINED + +#include "SkPathOpsPoint.h" + +struct SkDTriangle { + SkDPoint fPts[3]; + + bool contains(const SkDPoint& pt) const; + +}; + +#endif diff --git a/src/pathops/SkPathOpsTypes.h b/src/pathops/SkPathOpsTypes.h index 0248e7115a..01fec0d0b6 100644 --- a/src/pathops/SkPathOpsTypes.h +++ b/src/pathops/SkPathOpsTypes.h @@ -22,111 +22,6 @@ enum SkPathOpsMask { kEvenOdd_PathOpsMask = 1 }; -class SkOpCoincidence; -class SkOpContour; - -class SkOpGlobalState { -public: - SkOpGlobalState(SkOpCoincidence* coincidence PATH_OPS_DEBUG_PARAMS(SkOpContour* head)) - : fCoincidence(coincidence) - , fWindingFailed(false) - , fAngleCoincidence(false) -#if DEBUG_VALIDATE - , fPhase(kIntersecting) -#endif - PATH_OPS_DEBUG_PARAMS(fHead(head)) - PATH_OPS_DEBUG_PARAMS(fAngleID(0)) - PATH_OPS_DEBUG_PARAMS(fContourID(0)) - PATH_OPS_DEBUG_PARAMS(fPtTID(0)) - PATH_OPS_DEBUG_PARAMS(fSegmentID(0)) - PATH_OPS_DEBUG_PARAMS(fSpanID(0)) { - } - -#if DEBUG_VALIDATE - enum Phase { - kIntersecting, - kWalking - }; -#endif - - bool angleCoincidence() { - return fAngleCoincidence; - } - - SkOpCoincidence* coincidence() { - return fCoincidence; - } - -#ifdef SK_DEBUG - const struct SkOpAngle* debugAngle(int id) const; - SkOpContour* debugContour(int id); - const class SkOpPtT* debugPtT(int id) const; - const class SkOpSegment* debugSegment(int id) const; - const class SkOpSpanBase* debugSpan(int id) const; - - int nextAngleID() { - return ++fAngleID; - } - - int nextContourID() { - return ++fContourID; - } - int nextPtTID() { - return ++fPtTID; - } - - int nextSegmentID() { - return ++fSegmentID; - } - - int nextSpanID() { - return ++fSpanID; - } -#endif - -#if DEBUG_VALIDATE - Phase phase() const { - return fPhase; - } -#endif - - void setAngleCoincidence() { - fAngleCoincidence = true; - } - -#if DEBUG_VALIDATE - void setPhase(Phase phase) { - SkASSERT(fPhase != phase); - fPhase = phase; - } -#endif - - // called in very rare cases where angles are sorted incorrectly -- signfies op will fail - void setWindingFailed() { - fWindingFailed = true; - } - - bool windingFailed() const { - return fWindingFailed; - } - -private: - SkOpCoincidence* fCoincidence; - bool fWindingFailed; - bool fAngleCoincidence; -#if DEBUG_VALIDATE - Phase fPhase; -#endif -#ifdef SK_DEBUG - SkOpContour* fHead; - int fAngleID; - int fContourID; - int fPtTID; - int fSegmentID; - int fSpanID; -#endif -}; - // Use Almost Equal when comparing coordinates. Use epsilon to compare T values. bool AlmostEqualUlps(float a, float b); inline bool AlmostEqualUlps(double a, double b) { @@ -197,7 +92,6 @@ const double DBL_EPSILON_SUBDIVIDE_ERR = DBL_EPSILON * 16; const double ROUGH_EPSILON = FLT_EPSILON * 64; const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256; const double WAY_ROUGH_EPSILON = FLT_EPSILON * 2048; -const double BUMP_EPSILON = FLT_EPSILON * 4096; inline bool zero_or_one(double x) { return x == 0 || x == 1; @@ -247,6 +141,12 @@ inline bool roughly_zero(double x) { return fabs(x) < ROUGH_EPSILON; } +#if 0 // unused for now +inline bool way_roughly_zero(double x) { + return fabs(x) < WAY_ROUGH_EPSILON; +} +#endif + inline bool approximately_zero_inverse(double x) { return fabs(x) > FLT_EPSILON_INVERSE; } @@ -256,10 +156,6 @@ inline bool approximately_zero_when_compared_to(double x, double y) { return x == 0 || fabs(x) < fabs(y * FLT_EPSILON); } -inline bool precisely_zero_when_compared_to(double x, double y) { - return x == 0 || fabs(x) < fabs(y * DBL_EPSILON); -} - // Use this for comparing Ts in the range of 0 to 1. For general numbers (larger and smaller) use // AlmostEqualUlps instead. inline bool approximately_equal(double x, double y) { @@ -408,8 +304,7 @@ inline bool precisely_between(double a, double b, double c) { // returns true if (a <= b <= c) || (a >= b >= c) inline bool between(double a, double b, double c) { - SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0) - || (precisely_zero(a) && precisely_zero(b) && precisely_zero(c))); + SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)); return (a - b) * (c - b) <= 0; } @@ -417,15 +312,6 @@ inline bool roughly_equal(double x, double y) { return fabs(x - y) < ROUGH_EPSILON; } -inline bool roughly_negative(double x) { - return x < ROUGH_EPSILON; -} - -inline bool roughly_between(double a, double b, double c) { - return a <= c ? roughly_negative(a - b) && roughly_negative(b - c) - : roughly_negative(b - a) && roughly_negative(c - b); -} - inline bool more_roughly_equal(double x, double y) { return fabs(x - y) < MORE_ROUGH_EPSILON; } @@ -438,6 +324,7 @@ struct SkDPoint; struct SkDVector; struct SkDLine; struct SkDQuad; +struct SkDTriangle; struct SkDCubic; struct SkDRect; diff --git a/src/pathops/SkQuarticRoot.cpp b/src/pathops/SkQuarticRoot.cpp new file mode 100644 index 0000000000..f9a7bf5179 --- /dev/null +++ b/src/pathops/SkQuarticRoot.cpp @@ -0,0 +1,168 @@ +// from http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c +/* + * Roots3And4.c + * + * Utility functions to find cubic and quartic roots, + * coefficients are passed like this: + * + * c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 + c[4]*x^4 = 0 + * + * The functions return the number of non-complex roots and + * put the values into the s array. + * + * Author: Jochen Schwarze (schwarze@isa.de) + * + * Jan 26, 1990 Version for Graphics Gems + * Oct 11, 1990 Fixed sign problem for negative q's in SolveQuartic + * (reported by Mark Podlipec), + * Old-style function definitions, + * IsZero() as a macro + * Nov 23, 1990 Some systems do not declare acos() and cbrt() in + * <math.h>, though the functions exist in the library. + * If large coefficients are used, EQN_EPS should be + * reduced considerably (e.g. to 1E-30), results will be + * correct but multiple roots might be reported more + * than once. + */ + +#include "SkPathOpsCubic.h" +#include "SkPathOpsQuad.h" +#include "SkQuarticRoot.h" + +int SkReducedQuarticRoots(const double t4, const double t3, const double t2, const double t1, + const double t0, const bool oneHint, double roots[4]) { +#ifdef SK_DEBUG + // create a string mathematica understands + // GDB set print repe 15 # if repeated digits is a bother + // set print elements 400 # if line doesn't fit + char str[1024]; + sk_bzero(str, sizeof(str)); + SK_SNPRINTF(str, sizeof(str), + "Solve[%1.19g x^4 + %1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]", + t4, t3, t2, t1, t0); + SkPathOpsDebug::MathematicaIze(str, sizeof(str)); +#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA + SkDebugf("%s\n", str); +#endif +#endif + if (approximately_zero_when_compared_to(t4, t0) // 0 is one root + && approximately_zero_when_compared_to(t4, t1) + && approximately_zero_when_compared_to(t4, t2)) { + if (approximately_zero_when_compared_to(t3, t0) + && approximately_zero_when_compared_to(t3, t1) + && approximately_zero_when_compared_to(t3, t2)) { + return SkDQuad::RootsReal(t2, t1, t0, roots); + } + if (approximately_zero_when_compared_to(t4, t3)) { + return SkDCubic::RootsReal(t3, t2, t1, t0, roots); + } + } + if ((approximately_zero_when_compared_to(t0, t1) || approximately_zero(t1)) // 0 is one root + // && approximately_zero_when_compared_to(t0, t2) + && approximately_zero_when_compared_to(t0, t3) + && approximately_zero_when_compared_to(t0, t4)) { + int num = SkDCubic::RootsReal(t4, t3, t2, t1, roots); + for (int i = 0; i < num; ++i) { + if (approximately_zero(roots[i])) { + return num; + } + } + roots[num++] = 0; + return num; + } + if (oneHint) { + SkASSERT(approximately_zero_double(t4 + t3 + t2 + t1 + t0) || + approximately_zero_when_compared_to(t4 + t3 + t2 + t1 + t0, // 1 is one root + SkTMax(fabs(t4), SkTMax(fabs(t3), SkTMax(fabs(t2), SkTMax(fabs(t1), fabs(t0))))))); + // note that -C == A + B + D + E + int num = SkDCubic::RootsReal(t4, t4 + t3, -(t1 + t0), -t0, roots); + for (int i = 0; i < num; ++i) { + if (approximately_equal(roots[i], 1)) { + return num; + } + } + roots[num++] = 1; + return num; + } + return -1; +} + +int SkQuarticRootsReal(int firstCubicRoot, const double A, const double B, const double C, + const double D, const double E, double s[4]) { + double u, v; + /* normal form: x^4 + Ax^3 + Bx^2 + Cx + D = 0 */ + const double invA = 1 / A; + const double a = B * invA; + const double b = C * invA; + const double c = D * invA; + const double d = E * invA; + /* substitute x = y - a/4 to eliminate cubic term: + x^4 + px^2 + qx + r = 0 */ + const double a2 = a * a; + const double p = -3 * a2 / 8 + b; + const double q = a2 * a / 8 - a * b / 2 + c; + const double r = -3 * a2 * a2 / 256 + a2 * b / 16 - a * c / 4 + d; + int num; + double largest = SkTMax(fabs(p), fabs(q)); + if (approximately_zero_when_compared_to(r, largest)) { + /* no absolute term: y(y^3 + py + q) = 0 */ + num = SkDCubic::RootsReal(1, 0, p, q, s); + s[num++] = 0; + } else { + /* solve the resolvent cubic ... */ + double cubicRoots[3]; + int roots = SkDCubic::RootsReal(1, -p / 2, -r, r * p / 2 - q * q / 8, cubicRoots); + int index; + /* ... and take one real solution ... */ + double z; + num = 0; + int num2 = 0; + for (index = firstCubicRoot; index < roots; ++index) { + z = cubicRoots[index]; + /* ... to build two quadric equations */ + u = z * z - r; + v = 2 * z - p; + if (approximately_zero_squared(u)) { + u = 0; + } else if (u > 0) { + u = sqrt(u); + } else { + continue; + } + if (approximately_zero_squared(v)) { + v = 0; + } else if (v > 0) { + v = sqrt(v); + } else { + continue; + } + num = SkDQuad::RootsReal(1, q < 0 ? -v : v, z - u, s); + num2 = SkDQuad::RootsReal(1, q < 0 ? v : -v, z + u, s + num); + if (!((num | num2) & 1)) { + break; // prefer solutions without single quad roots + } + } + num += num2; + if (!num) { + return 0; // no valid cubic root + } + } + /* resubstitute */ + const double sub = a / 4; + for (int i = 0; i < num; ++i) { + s[i] -= sub; + } + // eliminate duplicates + for (int i = 0; i < num - 1; ++i) { + for (int j = i + 1; j < num; ) { + if (AlmostDequalUlps(s[i], s[j])) { + if (j < --num) { + s[j] = s[num]; + } + } else { + ++j; + } + } + } + return num; +} diff --git a/src/pathops/SkQuarticRoot.h b/src/pathops/SkQuarticRoot.h new file mode 100644 index 0000000000..6ce08671e2 --- /dev/null +++ b/src/pathops/SkQuarticRoot.h @@ -0,0 +1,16 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkDQuarticRoot_DEFINED +#define SkDQuarticRoot_DEFINED + +int SkReducedQuarticRoots(const double t4, const double t3, const double t2, const double t1, + const double t0, const bool oneHint, double s[4]); + +int SkQuarticRootsReal(int firstCubicRoot, const double A, const double B, const double C, + const double D, const double E, double s[4]); + +#endif diff --git a/src/pathops/SkReduceOrder.cpp b/src/pathops/SkReduceOrder.cpp index c19cd3db4b..6f06447a47 100644 --- a/src/pathops/SkReduceOrder.cpp +++ b/src/pathops/SkReduceOrder.cpp @@ -272,11 +272,6 @@ SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkPoint* reducePts) { } SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkPoint* reducePts) { - if (SkDPoint::ApproximatelyEqual(a[0], a[1]) && SkDPoint::ApproximatelyEqual(a[0], a[2]) - && SkDPoint::ApproximatelyEqual(a[0], a[3])) { - reducePts[0] = a[0]; - return SkPath::kMove_Verb; - } SkDCubic cubic; cubic.set(a); SkReduceOrder reducer; diff --git a/src/pathops/SkReduceOrder.h b/src/pathops/SkReduceOrder.h index 397b58d927..4ff9a1d127 100644 --- a/src/pathops/SkReduceOrder.h +++ b/src/pathops/SkReduceOrder.h @@ -7,9 +7,11 @@ #ifndef SkReduceOrder_DEFINED #define SkReduceOrder_DEFINED +#include "SkPath.h" #include "SkPathOpsCubic.h" #include "SkPathOpsLine.h" #include "SkPathOpsQuad.h" +#include "SkTArray.h" union SkReduceOrder { enum Quadratics { |