diff options
Diffstat (limited to 'src/pathops')
37 files changed, 3756 insertions, 1785 deletions
diff --git a/src/pathops/SkAddIntersections.cpp b/src/pathops/SkAddIntersections.cpp index 07666e796d..1ca56b8f23 100644 --- a/src/pathops/SkAddIntersections.cpp +++ b/src/pathops/SkAddIntersections.cpp @@ -254,8 +254,7 @@ static void debugShowCubicIntersection(int , const SkIntersectionHelper& , } #endif -bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence, - SkChunkAlloc* allocator) { +bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence) { if (test != next) { if (AlmostLessUlps(test->bounds().fBottom, next->bounds().fTop)) { return false; @@ -506,12 +505,14 @@ bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coinc 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); + SkOpPtT* testTAt = wt.segment()->addT(ts[swap][pt], SkOpSegment::kAllowAliasMatch, + nullptr); wn.segment()->debugValidate(); - SkOpPtT* nextTAt = wn.segment()->addT(ts[!swap][pt], SkOpSegment::kAllowAlias, - allocator); - testTAt->addOpp(nextTAt); + SkOpPtT* nextTAt = wn.segment()->addT(ts[!swap][pt], SkOpSegment::kAllowAliasMatch, + nullptr); + if (testTAt->addOpp(nextTAt)) { + testTAt->span()->checkForCollapsedCoincidence(); + } if (testTAt->fPt != nextTAt->fPt) { testTAt->span()->unaligned(); nextTAt->span()->unaligned(); @@ -540,7 +541,7 @@ bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coinc SkTSwap(testTAt, nextTAt); } SkASSERT(coinPtT[0]->span()->t() < testTAt->span()->t()); - coincidence->add(coinPtT[0], testTAt, coinPtT[1], nextTAt, allocator); + coincidence->add(coinPtT[0], testTAt, coinPtT[1], nextTAt); wt.segment()->debugValidate(); wn.segment()->debugValidate(); coinIndex = -1; diff --git a/src/pathops/SkAddIntersections.h b/src/pathops/SkAddIntersections.h index 23654e5e91..ca409800cd 100644 --- a/src/pathops/SkAddIntersections.h +++ b/src/pathops/SkAddIntersections.h @@ -12,7 +12,6 @@ class SkOpCoincidence; -bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence, - SkChunkAlloc* allocator); +bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence); #endif diff --git a/src/pathops/SkDConicLineIntersection.cpp b/src/pathops/SkDConicLineIntersection.cpp index 710647236c..620e99c8ae 100644 --- a/src/pathops/SkDConicLineIntersection.cpp +++ b/src/pathops/SkDConicLineIntersection.cpp @@ -6,6 +6,7 @@ */ #include "SkIntersections.h" #include "SkPathOpsConic.h" +#include "SkPathOpsCurve.h" #include "SkPathOpsLine.h" class LineConicIntersections { @@ -199,7 +200,22 @@ protected: } fIntersections->insert(conicT, lineT, fConic[cIndex]); } - // FIXME: see if line end is nearly on conic + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double conicT = ((SkDCurve*) &fConic)->nearPoint(SkPath::kConic_Verb, + (*fLine)[lIndex], (*fLine)[!lIndex]); + if (conicT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, (*fLine)[lIndex]); + } } void addExactHorizontalEndPoints(double left, double right, double y) { @@ -225,7 +241,7 @@ protected: } fIntersections->insert(conicT, lineT, fConic[cIndex]); } - // FIXME: see if line end is nearly on conic + this->addLineNearEndPoints(); } void addExactVerticalEndPoints(double top, double bottom, double x) { @@ -251,7 +267,7 @@ protected: } fIntersections->insert(conicT, lineT, fConic[cIndex]); } - // FIXME: see if line end is nearly on conic + this->addLineNearEndPoints(); } double findLineT(double t) { diff --git a/src/pathops/SkDCubicLineIntersection.cpp b/src/pathops/SkDCubicLineIntersection.cpp index cbdce7789a..045acd9e4f 100644 --- a/src/pathops/SkDCubicLineIntersection.cpp +++ b/src/pathops/SkDCubicLineIntersection.cpp @@ -6,6 +6,7 @@ */ #include "SkIntersections.h" #include "SkPathOpsCubic.h" +#include "SkPathOpsCurve.h" #include "SkPathOpsLine.h" /* @@ -291,6 +292,22 @@ public: } fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double cubicT = ((SkDCurve*) &fCubic)->nearPoint(SkPath::kCubic_Verb, + fLine[lIndex], fLine[!lIndex]); + if (cubicT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fLine[lIndex]); + } } void addExactHorizontalEndPoints(double left, double right, double y) { @@ -316,7 +333,7 @@ public: } fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } - // FIXME: see if line end is nearly on cubic + this->addLineNearEndPoints(); } void addExactVerticalEndPoints(double top, double bottom, double x) { @@ -342,7 +359,7 @@ public: } fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } - // FIXME: see if line end is nearly on cubic + this->addLineNearEndPoints(); } double findLineT(double t) { diff --git a/src/pathops/SkDQuadLineIntersection.cpp b/src/pathops/SkDQuadLineIntersection.cpp index a3d10bc9ff..dc237f57a5 100644 --- a/src/pathops/SkDQuadLineIntersection.cpp +++ b/src/pathops/SkDQuadLineIntersection.cpp @@ -5,6 +5,7 @@ * found in the LICENSE file. */ #include "SkIntersections.h" +#include "SkPathOpsCurve.h" #include "SkPathOpsLine.h" #include "SkPathOpsQuad.h" @@ -98,11 +99,11 @@ public: , fLine(&l) , fIntersections(i) , fAllowNear(true) { - i->setMax(3); // allow short partial coincidence plus discrete intersection + i->setMax(4); // allow short partial coincidence plus discrete intersections } LineQuadraticIntersections(const SkDQuad& q) - : fQuad(q) + : fQuad(q) SkDEBUGPARAMS(fLine(nullptr)) SkDEBUGPARAMS(fIntersections(nullptr)) SkDEBUGPARAMS(fAllowNear(false)) { @@ -297,7 +298,22 @@ protected: } fIntersections->insert(quadT, lineT, fQuad[qIndex]); } - // FIXME: see if line end is nearly on quad + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double quadT = ((SkDCurve*) &fQuad)->nearPoint(SkPath::kQuad_Verb, + (*fLine)[lIndex], (*fLine)[!lIndex]); + if (quadT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, (*fLine)[lIndex]); + } } void addExactHorizontalEndPoints(double left, double right, double y) { @@ -323,7 +339,7 @@ protected: } fIntersections->insert(quadT, lineT, fQuad[qIndex]); } - // FIXME: see if line end is nearly on quad + this->addLineNearEndPoints(); } void addExactVerticalEndPoints(double top, double bottom, double x) { @@ -349,7 +365,7 @@ protected: } fIntersections->insert(quadT, lineT, fQuad[qIndex]); } - // FIXME: see if line end is nearly on quad + this->addLineNearEndPoints(); } double findLineT(double t) { diff --git a/src/pathops/SkIntersections.h b/src/pathops/SkIntersections.h index 474142b269..abc10e19dd 100644 --- a/src/pathops/SkIntersections.h +++ b/src/pathops/SkIntersections.h @@ -112,6 +112,11 @@ public: return fUsed > 0 && (t == 0 ? fT[0][0] == 0 : fT[0][fUsed - 1] == 1); } + bool hasOppT(double t) const { + SkASSERT(t == 0 || t == 1); + return fUsed > 0 && (fT[1][0] == t || fT[1][fUsed - 1] == t); + } + int insertSwap(double one, double two, const SkDPoint& pt) { if (fSwap) { return insert(two, one, pt); @@ -180,7 +185,6 @@ public: quad.set(a); SkDLine line; line.set(b); - fMax = 3; // 2; permit small coincident segment + non-coincident intersection return intersect(quad, line); } @@ -207,7 +211,7 @@ public: bool swapped() const { return fSwap; } - + int used() const { return fUsed; } diff --git a/src/pathops/SkOpAngle.cpp b/src/pathops/SkOpAngle.cpp index c2eb0c9326..479a6f673d 100644 --- a/src/pathops/SkOpAngle.cpp +++ b/src/pathops/SkOpAngle.cpp @@ -62,6 +62,12 @@ bool SkOpAngle::after(SkOpAngle* test) { SkOpAngle* lh = test; SkOpAngle* rh = lh->fNext; SkASSERT(lh != rh); + fCurvePart = fOriginalCurvePart; + lh->fCurvePart = lh->fOriginalCurvePart; + lh->fCurvePart.offset(lh->segment()->verb(), fCurvePart[0] - lh->fCurvePart[0]); + rh->fCurvePart = rh->fOriginalCurvePart; + rh->fCurvePart.offset(rh->segment()->verb(), fCurvePart[0] - rh->fCurvePart[0]); + #if DEBUG_ANGLE SkString bugOut; bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" @@ -150,9 +156,7 @@ bool SkOpAngle::after(SkOpAngle* test) { return COMPARE_RESULT(8, ltOpposite); } else if (ltOrder == 1 && trOrder == 0) { SkASSERT(lrOrder < 0); - 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); @@ -175,15 +179,8 @@ bool SkOpAngle::after(SkOpAngle* test) { int SkOpAngle::allOnOneSide(const SkOpAngle* test) { SkASSERT(!fIsCurve); 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; - line = linePts[lineStart ^ 1] - linePts[lineStart]; - } else { - line = (fCurvePart[1] - fCurvePart[0]).asSkVector(); - } + SkDPoint origin = fCurvePart[0]; + SkDVector line = fCurvePart[1] - origin; float crosses[3]; SkPath::Verb testVerb = test->segment()->verb(); int iMax = SkPathOpsVerbToPoints(testVerb); @@ -244,8 +241,7 @@ bool SkOpAngle::checkParallel(SkOpAngle* rh) { // 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 (!fCurvePart[SkPathOpsVerbToPoints(this->segment()->verb())].approximatelyEqual( - rh->fCurvePart[SkPathOpsVerbToPoints(rh->segment()->verb())])) { + if (!fEnd->contains(rh->fEnd)) { if (this->endToSide(rh, &inside)) { return inside; } @@ -280,7 +276,7 @@ bool SkOpAngle::computeSector() { } fComputedSector = true; bool stepUp = fStart->t() < fEnd->t(); - const SkOpSpanBase* checkEnd = fEnd; + SkOpSpanBase* checkEnd = fEnd; if (checkEnd->final() && stepUp) { fUnorderable = true; return false; @@ -306,7 +302,7 @@ bool SkOpAngle::computeSector() { : checkEnd->prev(); } while (checkEnd); recomputeSector: - SkOpSpanBase* computedEnd = stepUp ? checkEnd ? checkEnd->prev() : fEnd->segment()->head() + SkOpSpanBase* computedEnd = stepUp ? checkEnd ? checkEnd->prev() : fEnd->segment()->head() : checkEnd ? checkEnd->upCast()->next() : fEnd->segment()->tail(); if (checkEnd == fEnd || computedEnd == fEnd || computedEnd == fStart) { fUnorderable = true; @@ -398,7 +394,7 @@ bool SkOpAngle::endsIntersect(SkOpAngle* rh) { int rPts = SkPathOpsVerbToPoints(rVerb); SkDLine rays[] = {{{this->fCurvePart[0], rh->fCurvePart[rPts]}}, {{this->fCurvePart[0], this->fCurvePart[lPts]}}}; - if (rays[0][1] == rays[1][1]) { + if (this->fEnd->contains(rh->fEnd)) { return checkParallel(rh); } double smallTs[2] = {-1, -1}; @@ -538,14 +534,14 @@ bool SkOpAngle::endToSide(const SkOpAngle* rh, bool* inside) const { } double maxWidth = SkTMax(maxX - minX, maxY - minY); endDist /= maxWidth; - if (endDist < 5e-11) { // empirically found + if (endDist < 5e-12) { // 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); + double dir = vLeft.crossNoNormalCheck(vRight); if (!dir) { return false; } @@ -785,7 +781,7 @@ bool SkOpAngle::orderable(SkOpAngle* rh) { SkASSERT(x_ry != rx_y); // indicates an undetected coincidence -- worth finding earlier return x_ry < rx_y; } - if ((result = allOnOneSide(rh)) >= 0) { + if ((result = this->allOnOneSide(rh)) >= 0) { return result; } if (fUnorderable || approximately_zero(rh->fSide)) { @@ -798,11 +794,10 @@ bool SkOpAngle::orderable(SkOpAngle* rh) { if (rh->fUnorderable || approximately_zero(fSide)) { goto unorderable; } - } - if ((result = convexHullOverlaps(rh)) >= 0) { + } else if ((result = this->convexHullOverlaps(rh)) >= 0) { return result; } - return endsIntersect(rh); + return this->endsIntersect(rh); unorderable: fUnorderable = true; rh->fUnorderable = true; @@ -846,8 +841,17 @@ void SkOpAngle::setCurveHullSweep() { return; } fSweep[1] = fCurvePart[2] - fCurvePart[0]; + // OPTIMIZE: I do the following float check a lot -- probably need a + // central place for this val-is-small-compared-to-curve check + double maxVal = 0; + for (int index = 0; index < SkPathOpsVerbToPoints(segment->verb()); ++index) { + maxVal = SkTMax(maxVal, SkTMax(SkTAbs(fCurvePart[index].fX), + SkTAbs(fCurvePart[index].fY))); + } + if (SkPath::kCubic_Verb != segment->verb()) { - if (!fSweep[0].fX && !fSweep[0].fY) { + if (roughly_zero_when_compared_to(fSweep[0].fX, maxVal) + && roughly_zero_when_compared_to(fSweep[0].fY, maxVal)) { fSweep[0] = fSweep[1]; } return; @@ -856,7 +860,8 @@ void SkOpAngle::setCurveHullSweep() { if (fSweep[0].fX == 0 && fSweep[0].fY == 0) { fSweep[0] = fSweep[1]; fSweep[1] = thirdSweep; - if (fSweep[0].fX == 0 && fSweep[0].fY == 0) { + if (roughly_zero_when_compared_to(fSweep[0].fX, maxVal) + && roughly_zero_when_compared_to(fSweep[0].fY, maxVal)) { fSweep[0] = fSweep[1]; fCurvePart[1] = fCurvePart[3]; fIsCurve = false; @@ -894,6 +899,7 @@ void SkOpAngle::setSpans() { = SK_ScalarNaN); SkDEBUGCODE(fCurvePart.fVerb = segment->verb()); segment->subDivide(fStart, fEnd, &fCurvePart); + fOriginalCurvePart = fCurvePart; setCurveHullSweep(); const SkPath::Verb verb = segment->verb(); if (verb != SkPath::kLine_Verb @@ -1049,5 +1055,5 @@ bool SkOpAngle::tangentsDiverge(const SkOpAngle* rh, double s0xt0) const { double tDist = tweep[0].length() * m; bool useS = fabs(sDist) < fabs(tDist); double mFactor = fabs(useS ? this->distEndRatio(sDist) : rh->distEndRatio(tDist)); - return mFactor < 2400; // empirically found limit + return mFactor < 50; // empirically found limit } diff --git a/src/pathops/SkOpAngle.h b/src/pathops/SkOpAngle.h index 4b209de32d..a1ead1ecfd 100644 --- a/src/pathops/SkOpAngle.h +++ b/src/pathops/SkOpAngle.h @@ -19,7 +19,8 @@ class SkOpSegment; class SkOpSpanBase; class SkOpSpan; -struct SkOpAngle { +class SkOpAngle { +public: enum IncludeType { kUnaryWinding, kUnaryXor, @@ -27,14 +28,8 @@ struct SkOpAngle { kBinaryOpp, }; - bool after(SkOpAngle* test); - int allOnOneSide(const SkOpAngle* test); - bool checkCrossesZero() const; - bool checkParallel(SkOpAngle* ); - bool computeSector(); - int convexHullOverlaps(const SkOpAngle* ) const; - const SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; SkOpContour* debugContour(int id); int debugID() const { @@ -46,6 +41,7 @@ struct SkOpAngle { #endif #if DEBUG_ANGLE + bool debugCheckCoincidence() const { return fCheckCoincidence; } void debugCheckNearCoincidence() const; SkString debugPart() const; #endif @@ -53,7 +49,7 @@ struct SkOpAngle { const SkOpSegment* debugSegment(int id) const; int debugSign() const; const SkOpSpanBase* debugSpan(int id) const; - void debugValidate() 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 @@ -68,62 +64,56 @@ struct SkOpAngle { return fEnd; } - bool endsIntersect(SkOpAngle* ); - bool endToSide(const SkOpAngle* rh, bool* inside) const; - int findSector(SkPath::Verb verb, double x, double y) const; - SkOpGlobalState* globalState() const; void insert(SkOpAngle* ); SkOpSpanBase* lastMarked() const; bool loopContains(const SkOpAngle* ) const; int loopCount() const; - 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 { - return fSectorEnd; - } - - int sectorStart() const { - return fSectorStart; - } - SkOpSegment* segment() const; - void set(SkOpSpanBase* start, SkOpSpanBase* end); - void setCurveHullSweep(); - - void setID(int id) { - SkDEBUGCODE(fID = id); - } void setLastMarked(SkOpSpanBase* marked) { fLastMarked = marked; } - void setSector(); - void setSpans(); - SkOpSpanBase* start() const { return fStart; } SkOpSpan* starter(); - bool tangentsDiverge(const SkOpAngle* rh, double s0xt0) const; bool unorderable() const { return fUnorderable; } - SkDCurve fCurvePart; // the curve from start to end +private: + bool after(SkOpAngle* test); + int allOnOneSide(const SkOpAngle* test); + bool checkCrossesZero() const; + bool checkParallel(SkOpAngle* ); + bool computeSector(); + int convexHullOverlaps(const SkOpAngle* ) const; + bool endToSide(const SkOpAngle* rh, bool* inside) const; + bool endsIntersect(SkOpAngle* ); + int findSector(SkPath::Verb verb, double x, double y) const; + SkOpGlobalState* globalState() const; + bool merge(SkOpAngle* ); + double midT() const; + bool midToSide(const SkOpAngle* rh, bool* inside) const; + bool oppositePlanes(const SkOpAngle* rh) const; + bool orderable(SkOpAngle* rh); // false == this < rh ; true == this > rh + void setCurveHullSweep(); + void setSector(); + void setSpans(); + bool tangentsDiverge(const SkOpAngle* rh, double s0xt0) const; + + SkDCurve fOriginalCurvePart; // the curve from start to end + SkDCurve fCurvePart; // the curve from start to end offset as needed double fSide; SkLineParameters fTangentHalf; // used only to sort a pair of lines or line-like sections SkOpAngle* fNext; @@ -143,6 +133,7 @@ struct SkOpAngle { bool fCheckCoincidence; SkDEBUGCODE(int fID); + friend class PathOpsAngleTester; }; diff --git a/src/pathops/SkOpBuilder.cpp b/src/pathops/SkOpBuilder.cpp index 67aa92fe26..6652c10d52 100644 --- a/src/pathops/SkOpBuilder.cpp +++ b/src/pathops/SkOpBuilder.cpp @@ -44,13 +44,11 @@ bool FixWinding(SkPath* path) { } SkChunkAlloc allocator(4096); SkOpContourHead contourHead; - SkOpGlobalState globalState(nullptr, &contourHead SkDEBUGPARAMS(false) + SkOpGlobalState globalState(&contourHead, &allocator SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); - SkOpEdgeBuilder builder(*path, &contourHead, &allocator, &globalState); - builder.finish(&allocator); - if (!contourHead.next()) { - return false; - } + SkOpEdgeBuilder builder(*path, &contourHead, &globalState); + builder.finish(); + SkASSERT(contourHead.next()); contourHead.resetReverse(); bool writePath = false; SkOpSpan* topSpan; @@ -67,7 +65,7 @@ bool FixWinding(SkPath* path) { topContour->setReverse(); writePath = true; } - topContour->markDone(); + topContour->markAllDone(); globalState.clearNested(); } if (!writePath) { diff --git a/src/pathops/SkOpCoincidence.cpp b/src/pathops/SkOpCoincidence.cpp index 4b46e685ee..af330dfe1e 100755 --- a/src/pathops/SkOpCoincidence.cpp +++ b/src/pathops/SkOpCoincidence.cpp @@ -8,237 +8,725 @@ #include "SkOpSegment.h" #include "SkPathOpsTSect.h" -bool SkOpCoincidence::extend(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, - SkOpPtT* oppPtTEnd) { - // if there is an existing pair that overlaps the addition, extend it +#if DEBUG_COINCIDENCE +#define FAIL_IF(cond) SkASSERT(!(cond)) +#else +#define FAIL_IF(cond) do { if (cond) return false; } while (false) +#endif + +// returns true if coincident span's start and end are the same +bool SkCoincidentSpans::collapsed(const SkOpPtT* test) const { + return (fCoinPtTStart == test && fCoinPtTEnd->contains(test)) + || (fCoinPtTEnd == test && fCoinPtTStart->contains(test)) + || (fOppPtTStart == test && fOppPtTEnd->contains(test)) + || (fOppPtTEnd == test && fOppPtTStart->contains(test)); +} + +// sets the span's end to the ptT referenced by the previous-next +void SkCoincidentSpans::correctOneEnd( + const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::*setEnd)(const SkOpPtT* ptT) ) { + const SkOpPtT* origPtT = (this->*getEnd)(); + const SkOpSpanBase* origSpan = origPtT->span(); + const SkOpSpan* prev = origSpan->prev(); + const SkOpPtT* testPtT = prev ? prev->next()->ptT() + : origSpan->upCast()->next()->prev()->ptT(); + if (origPtT != testPtT) { + (this->*setEnd)(testPtT); + } +} + +// FIXME: member pointers have fallen out of favor and can be replaced with +// an alternative approach. +// makes all span ends agree with the segment's spans that define them +void SkCoincidentSpans::correctEnds() { + this->correctOneEnd(&SkCoincidentSpans::coinPtTStart, &SkCoincidentSpans::setCoinPtTStart); + this->correctOneEnd(&SkCoincidentSpans::coinPtTEnd, &SkCoincidentSpans::setCoinPtTEnd); + this->correctOneEnd(&SkCoincidentSpans::oppPtTStart, &SkCoincidentSpans::setOppPtTStart); + this->correctOneEnd(&SkCoincidentSpans::oppPtTEnd, &SkCoincidentSpans::setOppPtTEnd); +} + +/* Please keep this in sync with debugExpand */ +// expand the range by checking adjacent spans for coincidence +bool SkCoincidentSpans::expand() { + bool expanded = false; + const SkOpSegment* segment = coinPtTStart()->segment(); + const SkOpSegment* oppSegment = oppPtTStart()->segment(); + do { + const SkOpSpan* start = coinPtTStart()->span()->upCast(); + const SkOpSpan* prev = start->prev(); + const SkOpPtT* oppPtT; + if (!prev || !(oppPtT = prev->contains(oppSegment))) { + break; + } + double midT = (prev->t() + start->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + setStarts(prev->ptT(), oppPtT); + expanded = true; + } while (true); + do { + const SkOpSpanBase* end = coinPtTEnd()->span(); + SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); + const SkOpPtT* oppPtT; + if (!next || !(oppPtT = next->contains(oppSegment))) { + break; + } + double midT = (end->t() + next->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + setEnds(next->ptT(), oppPtT); + expanded = true; + } while (true); + return expanded; +} + +// increase the range of this span +bool SkCoincidentSpans::extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + bool result = false; + if (fCoinPtTStart->fT > coinPtTStart->fT || (this->flipped() + ? fOppPtTStart->fT < oppPtTStart->fT : fOppPtTStart->fT > oppPtTStart->fT)) { + this->setStarts(coinPtTStart, oppPtTStart); + result = true; + } + if (fCoinPtTEnd->fT < coinPtTEnd->fT || (this->flipped() + ? fOppPtTEnd->fT > oppPtTEnd->fT : fOppPtTEnd->fT < oppPtTEnd->fT)) { + this->setEnds(coinPtTEnd, oppPtTEnd); + result = true; + } + return result; +} + +// set the range of this span +void SkCoincidentSpans::set(SkCoincidentSpans* next, const SkOpPtT* coinPtTStart, + const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd + SkDEBUGPARAMS(int id)) { + SkASSERT(SkOpCoincidence::Ordered(coinPtTStart, oppPtTStart)); + fNext = next; + this->setStarts(coinPtTStart, oppPtTStart); + this->setEnds(coinPtTEnd, oppPtTEnd); + SkDEBUGCODE(fID = id); +} + +// returns true if both points are inside this +bool SkCoincidentSpans::contains(const SkOpPtT* s, const SkOpPtT* e) const { + if (s->fT > e->fT) { + SkTSwap(s, e); + } + if (s->segment() == fCoinPtTStart->segment()) { + return fCoinPtTStart->fT <= s->fT && e->fT <= fCoinPtTEnd->fT; + } else { + SkASSERT(s->segment() == fOppPtTStart->segment()); + double oppTs = fOppPtTStart->fT; + double oppTe = fOppPtTEnd->fT; + if (oppTs > oppTe) { + SkTSwap(oppTs, oppTe); + } + return oppTs <= s->fT && e->fT <= oppTe; + } +} + +// returns the number of segment span's contained by this, or -1 if inconsistent +int SkCoincidentSpans::spanCount() const { + // most commonly, concidence are one span long; check for that first + const SkOpSpanBase* start = coinPtTStart()->span(); + const SkOpSpanBase* end = coinPtTEnd()->span(); + int coinIntervals = 0; + while (start != end) { + coinIntervals++; + start = start->upCast()->next(); + } + const SkOpSpanBase* oppStart = (flipped() ? oppPtTEnd() : oppPtTStart())->span(); + const SkOpSpanBase* oppEnd = (flipped() ? oppPtTStart() : oppPtTEnd())->span(); + int oppIntervals = 0; + while (oppStart != oppEnd) { + oppIntervals++; + oppStart = oppStart->upCast()->next(); + } + return coinIntervals == oppIntervals ? coinIntervals : -1; +} + +// returns true if the point is on a coincident edge, and if it is the start of that edge +bool SkOpCoincidence::edge(const SkOpPtT* test, bool* start) const { SkCoincidentSpans* coinRec = fHead; - if (coinRec) { - do { - if (coinRec->fCoinPtTStart->segment() != coinPtTStart->segment()) { - continue; - } - if (coinRec->fOppPtTStart->segment() != oppPtTStart->segment()) { + if (!coinRec) { + return false; + } + do { + if (coinRec->coinPtTStart() == test) { + *start = true; + return true; + } + if (coinRec->coinPtTEnd() == test) { + *start = false; + return true; + } + if (coinRec->oppPtTStart() == test) { + *start = !coinRec->flipped(); + return true; + } + if (coinRec->coinPtTEnd() == test) { + *start = coinRec->flipped(); + return true; + } + } while ((coinRec = coinRec->next())); + return false; +} + +// if there is an existing pair that overlaps the addition, extend it +bool SkOpCoincidence::extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + SkCoincidentSpans* test = fHead; + if (!test) { + return false; + } + const SkOpSegment* coinSeg = coinPtTStart->segment(); + const SkOpSegment* oppSeg = oppPtTStart->segment(); + if (!Ordered(coinPtTStart, oppPtTStart)) { + SkTSwap(coinSeg, oppSeg); + SkTSwap(coinPtTStart, oppPtTStart); + SkTSwap(coinPtTEnd, oppPtTEnd); + if (coinPtTStart->fT > coinPtTEnd->fT) { + SkTSwap(coinPtTStart, coinPtTEnd); + SkTSwap(oppPtTStart, oppPtTEnd); + } + } + double oppMinT = SkTMin(oppPtTStart->fT, oppPtTEnd->fT); + SkDEBUGCODE(double oppMaxT = SkTMax(oppPtTStart->fT, oppPtTEnd->fT)); + do { + if (coinSeg != test->coinPtTStart()->segment()) { + continue; + } + if (oppSeg != test->oppPtTStart()->segment()) { + continue; + } + double oTestMinT = SkTMin(test->oppPtTStart()->fT, test->oppPtTEnd()->fT); + double oTestMaxT = SkTMax(test->oppPtTStart()->fT, test->oppPtTEnd()->fT); + // if debug check triggers, caller failed to check if extended already exists + SkASSERT(test->coinPtTStart()->fT > coinPtTStart->fT + || coinPtTEnd->fT > test->coinPtTEnd()->fT + || oTestMinT > oppMinT || oppMaxT > oTestMaxT); + if ((test->coinPtTStart()->fT <= coinPtTEnd->fT + && coinPtTStart->fT <= test->coinPtTEnd()->fT) + || (oTestMinT <= oTestMaxT && oppMinT <= oTestMaxT)) { + test->extend(coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + return true; + } + } while ((test = test->next())); + return false; +} + +// verifies that the coincidence hasn't already been added +static void DebugCheckAdd(const SkCoincidentSpans* check, const SkOpPtT* coinPtTStart, + const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { +#if DEBUG_COINCIDENCE + while (check) { + SkASSERT(check->coinPtTStart() != coinPtTStart || check->coinPtTEnd() != coinPtTEnd + || check->oppPtTStart() != oppPtTStart || check->oppPtTEnd() != oppPtTEnd); + SkASSERT(check->coinPtTStart() != oppPtTStart || check->coinPtTEnd() != oppPtTEnd + || check->oppPtTStart() != coinPtTStart || check->oppPtTEnd() != coinPtTEnd); + check = check->next(); + } +#endif +} + +// adds a new coincident pair +void SkOpCoincidence::add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, + SkOpPtT* oppPtTEnd) { + // OPTIMIZE: caller should have already sorted + if (!Ordered(coinPtTStart, oppPtTStart)) { + if (oppPtTStart->fT < oppPtTEnd->fT) { + this->add(oppPtTStart, oppPtTEnd, coinPtTStart, coinPtTEnd); + } else { + this->add(oppPtTEnd, oppPtTStart, coinPtTEnd, coinPtTStart); + } + return; + } + SkASSERT(Ordered(coinPtTStart, oppPtTStart)); + // choose the ptT at the front of the list to track + coinPtTStart = coinPtTStart->span()->ptT(); + coinPtTEnd = coinPtTEnd->span()->ptT(); + oppPtTStart = oppPtTStart->span()->ptT(); + oppPtTEnd = oppPtTEnd->span()->ptT(); + SkASSERT(coinPtTStart->fT < coinPtTEnd->fT); + SkASSERT(oppPtTStart->fT != oppPtTEnd->fT); + SkASSERT(!coinPtTStart->deleted()); + SkASSERT(!coinPtTEnd->deleted()); + SkASSERT(!oppPtTStart->deleted()); + SkASSERT(!oppPtTEnd->deleted()); + DebugCheckAdd(fHead, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + DebugCheckAdd(fTop, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + SkCoincidentSpans* coinRec = SkOpTAllocator<SkCoincidentSpans>::Allocate( + this->globalState()->allocator()); + coinRec->init(); + coinRec->set(this->fHead, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd + SkDEBUGPARAMS(fGlobalState->nextCoinID())); + fHead = coinRec; +} + +// description below +void SkOpCoincidence::addEndMovedSpans(const SkOpSpan* base, const SkOpSpanBase* testSpan) { + const SkOpPtT* testPtT = testSpan->ptT(); + const SkOpPtT* stopPtT = testPtT; + const SkOpSegment* baseSeg = base->segment(); + while ((testPtT = testPtT->next()) != stopPtT) { + const SkOpSegment* testSeg = testPtT->segment(); + if (testPtT->deleted()) { + continue; + } + if (testSeg == baseSeg) { + continue; + } + if (this->contains(baseSeg, testSeg, testPtT->fT)) { + continue; + } + // intersect perp with base->ptT() with testPtT->segment() + SkDVector dxdy = baseSeg->dSlopeAtT(base->t()); + const SkPoint& pt = base->pt(); + SkDLine ray = {{{pt.fX, pt.fY}, {pt.fX + dxdy.fY, pt.fY - dxdy.fX}}}; + SkIntersections i; + (*CurveIntersectRay[testSeg->verb()])(testSeg->pts(), testSeg->weight(), ray, &i); + for (int index = 0; index < i.used(); ++index) { + double t = i[0][index]; + if (!between(0, t, 1)) { continue; } - if (coinRec->fCoinPtTStart->fT > coinPtTEnd->fT) { + SkDPoint oppPt = i.pt(index); + if (!oppPt.approximatelyEqual(pt)) { continue; } - if (coinRec->fCoinPtTEnd->fT < coinPtTStart->fT) { + SkOpSegment* writableSeg = const_cast<SkOpSegment*>(testSeg); + SkOpPtT* oppStart = writableSeg->addT(t, SkOpSegment::kAllowAliasMatch, nullptr); + SkOpSpan* writableBase = const_cast<SkOpSpan*>(base); + oppStart->span()->addOppAndMerge(writableBase); + if (oppStart->deleted()) { continue; } - if (coinRec->fCoinPtTStart->fT > coinPtTStart->fT) { - coinRec->fCoinPtTStart = coinPtTStart; - coinRec->fOppPtTStart = oppPtTStart; + SkOpSegment* coinSeg = base->segment(); + SkOpSegment* oppSeg = oppStart->segment(); + double coinTs, coinTe, oppTs, oppTe; + if (coinSeg < oppSeg) { + coinTs = base->t(); + coinTe = testSpan->t(); + oppTs = oppStart->fT; + oppTe = testPtT->fT; + } else { + SkTSwap(coinSeg, oppSeg); + coinTs = oppStart->fT; + coinTe = testPtT->fT; + oppTs = base->t(); + oppTe = testSpan->t(); } - if (coinRec->fCoinPtTEnd->fT < coinPtTEnd->fT) { - coinRec->fCoinPtTEnd = coinPtTEnd; - coinRec->fOppPtTEnd = oppPtTEnd; + if (coinTs > coinTe) { + SkTSwap(coinTs, coinTe); + SkTSwap(oppTs, oppTe); } - return true; - } while ((coinRec = coinRec->fNext)); + (void) this->addOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe); + } } - return false; } -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; - SkDEBUGCODE(coinRec->fID = fDebugState->nextCoinID()); - - this->fHead = coinRec; +// description below +bool SkOpCoincidence::addEndMovedSpans(const SkOpPtT* ptT) { + const SkOpSpan* base = ptT->span()->upCast(); + const SkOpSpan* prev = base->prev(); + if (!prev) { + return false; + } + if (!prev->isCanceled()) { + this->addEndMovedSpans(base, base->prev()); + } + if (!base->isCanceled()) { + this->addEndMovedSpans(base, base->next()); + } + return true; } -static void t_range(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; +/* If A is coincident with B and B includes an endpoint, and A's matching point + is not the endpoint (i.e., there's an implied line connecting B-end and A) + then assume that the same implied line may intersect another curve close to B. + Since we only care about coincidence that was undetected, look at the + ptT list on B-segment adjacent to the B-end/A ptT loop (not in the loop, but + next door) and see if the A matching point is close enough to form another + coincident pair. If so, check for a new coincident span between B-end/A ptT loop + and the adjacent ptT loop. +*/ +bool SkOpCoincidence::addEndMovedSpans() { + SkCoincidentSpans* span = fHead; + if (!span) { + return true; + } + fTop = span; + fHead = nullptr; + do { + if (span->coinPtTStart()->fPt != span->oppPtTStart()->fPt) { + if (1 == span->coinPtTStart()->fT) { + return false; + } + bool onEnd = span->coinPtTStart()->fT == 0; + bool oOnEnd = zero_or_one(span->oppPtTStart()->fT); + if (onEnd) { + if (!oOnEnd) { // if both are on end, any nearby intersect was already found + if (!this->addEndMovedSpans(span->oppPtTStart())) { + return false; + } + } + } else if (oOnEnd) { + if (!this->addEndMovedSpans(span->coinPtTStart())) { + return false; + } + } + } + if (span->coinPtTEnd()->fPt != span->oppPtTEnd()->fPt) { + bool onEnd = span->coinPtTEnd()->fT == 1; + bool oOnEnd = zero_or_one(span->oppPtTEnd()->fT); + if (onEnd) { + if (!oOnEnd) { + if (!this->addEndMovedSpans(span->oppPtTEnd())) { + return false; + } + } + } else if (oOnEnd) { + if (!this->addEndMovedSpans(span->coinPtTEnd())) { + return false; + } + } + } + } while ((span = span->next())); + this->restoreHead(); + return true; } -bool SkOpCoincidence::addExpanded(SkChunkAlloc* allocator - PATH_OPS_DEBUG_VALIDATE_PARAMS(SkOpGlobalState* globalState)) { -#if DEBUG_VALIDATE - globalState->setPhase(SkOpGlobalState::kIntersecting); -#endif - // for each coincident pair, match the spans - // if the spans don't match, add the mssing pt to the segment and loop it in the opposite span +/* Please keep this in sync with debugAddExpanded */ +// for each coincident pair, match the spans +// if the spans don't match, add the missing pt to the segment and loop it in the opposite span +bool SkOpCoincidence::addExpanded() { SkCoincidentSpans* coin = this->fHead; - SkASSERT(coin); + if (!coin) { + return true; + } do { - SkOpPtT* startPtT = coin->fCoinPtTStart; - SkOpPtT* oStartPtT = coin->fOppPtTStart; + const SkOpPtT* startPtT = coin->coinPtTStart(); + const SkOpPtT* oStartPtT = coin->oppPtTStart(); SkASSERT(startPtT->contains(oStartPtT)); - SkASSERT(coin->fCoinPtTEnd->contains(coin->fOppPtTEnd)); - SkOpSpanBase* start = startPtT->span(); - SkOpSpanBase* oStart = oStartPtT->span(); - const SkOpSpanBase* end = coin->fCoinPtTEnd->span(); - const SkOpSpanBase* oEnd = coin->fOppPtTEnd->span(); - if (oEnd->deleted()) { - return false; - } - SkOpSpanBase* test = start->upCast()->next(); - SkOpSpanBase* oTest = coin->fFlipped ? oStart->prev() : oStart->upCast()->next(); + SkASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); + const SkOpSpanBase* start = startPtT->span(); + const SkOpSpanBase* oStart = oStartPtT->span(); + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); + const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span(); + FAIL_IF(oEnd->deleted()); + const SkOpSpanBase* test = start->upCast()->next(); + const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next(); if (!oTest) { return false; } while (test != end || oTest != oEnd) { - if (!test->ptT()->contains(oTest->ptT())) { + if (!test->ptT()->contains(oStart->segment()) + || !oTest->ptT()->contains(start->segment())) { // use t ranges to guess which one is missing - double startRange = coin->fCoinPtTEnd->fT - startPtT->fT; - if (!startRange) { - return false; - } + double startRange = coin->coinPtTEnd()->fT - startPtT->fT; + FAIL_IF(!startRange); double startPart = (test->t() - startPtT->fT) / startRange; - double oStartRange = coin->fOppPtTEnd->fT - oStartPtT->fT; - if (!oStartRange) { - return false; - } + double oStartRange = coin->oppPtTEnd()->fT - oStartPtT->fT; + FAIL_IF(!oStartRange); double oStartPart = (oTest->t() - oStartPtT->fT) / oStartRange; - if (startPart == oStartPart) { + FAIL_IF(startPart == oStartPart); + bool startOver = false; + bool success = startPart < oStartPart + ? oStart->segment()->addExpanded( + oStartPtT->fT + oStartRange * startPart, test, &startOver) + : start->segment()->addExpanded( + startPtT->fT + startRange * oStartPart, oTest, &startOver); + if (!success) { + SkASSERT(0); return false; } - SkOpPtT* newPt; - if (startPart < oStartPart) { - double newT = oStartPtT->fT + oStartRange * startPart; - newPt = oStart->segment()->addT(newT, SkOpSegment::kAllowAlias, allocator); - if (!newPt) { - return false; - } - newPt->fPt = test->pt(); - test->ptT()->addOpp(newPt); - } else { - double newT = startPtT->fT + startRange * oStartPart; - newPt = start->segment()->addT(newT, SkOpSegment::kAllowAlias, allocator); - if (!newPt) { - return false; - } - newPt->fPt = oTest->pt(); - oTest->ptT()->addOpp(newPt); + if (startOver) { + test = start; + oTest = oStart; } - // start over - test = start; - oTest = oStart; } if (test != end) { test = test->upCast()->next(); } if (oTest != oEnd) { - oTest = coin->fFlipped ? oTest->prev() : oTest->upCast()->next(); + oTest = coin->flipped() ? oTest->prev() : oTest->upCast()->next(); if (!oTest) { return false; } } } - } while ((coin = coin->fNext)); -#if DEBUG_VALIDATE - globalState->setPhase(SkOpGlobalState::kWalking); -#endif + } while ((coin = coin->next())); return true; } +// checks to see if coincidence has already been found +bool SkOpCoincidence::alreadyAdded(const SkCoincidentSpans* check, const SkCoincidentSpans* outer, + const SkOpPtT* over1s, const SkOpPtT* over1e) const { + do { + if (check->oppPtTStart() == outer->coinPtTStart() && check->coinPtTStart() == over1s + && check->oppPtTEnd() == outer->coinPtTEnd() && check->coinPtTEnd() == over1e) { + return true; + } + if (check->coinPtTStart() == outer->coinPtTStart() && check->oppPtTStart() == over1s + && check->coinPtTEnd() == outer->coinPtTEnd() && check->oppPtTEnd() == over1e) { + return true; + } + if (check->startEquals(outer->oppPtTStart()->span(), over1s->span())) { + SkDEBUGCODE(check->debugStartCheck(outer->oppPtTEnd()->span(), over1e->span(), + fGlobalState)); + return true; + } + if (check->startEquals(over1s->span(), outer->coinPtTStart()->span())) { + SkDEBUGCODE(check->debugStartCheck(over1e->span(), outer->oppPtTEnd()->span(), + fGlobalState)); + return true; + } + } while ((check = check->next())); + return false; +} + + /* Please keep this in sync with debugAddIfMissing() */ bool SkOpCoincidence::addIfMissing(const SkCoincidentSpans* outer, SkOpPtT* over1s, - SkOpPtT* over1e, SkChunkAlloc* allocator) { - SkCoincidentSpans* check = this->fTop; + SkOpPtT* over1e) { + SkASSERT(fTop); + if (this->alreadyAdded(fTop, outer, over1s, over1e)) { + return false; + } + if (fHead && this->alreadyAdded(fHead, outer, over1s, over1e)) { + return false; + } + this->add(outer->coinPtTStart(), outer->coinPtTEnd(), over1s, over1e); + this->debugValidate(); + return true; +} + +// given a t span, map the same range on the coincident span +void SkOpCoincidence::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; +} + +// return true if span overlaps existing and needs to adjust the coincident list +bool SkOpCoincidence::checkOverlap(SkCoincidentSpans* check, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + SkTDArray<SkCoincidentSpans*>* overlaps) const { + if (!Ordered(coinSeg, oppSeg)) { + if (oppTs < oppTe) { + return this->checkOverlap(check, oppSeg, coinSeg, oppTs, oppTe, coinTs, coinTe, + overlaps); + } + return this->checkOverlap(check, oppSeg, coinSeg, oppTe, oppTs, coinTe, coinTs, overlaps); + } + bool swapOpp = oppTs > oppTe; + if (swapOpp) { + SkTSwap(oppTs, oppTe); + } do { - if (check->fCoinPtTStart->span() == over1s->span() - && check->fOppPtTStart->span() == outer->fOppPtTStart->span()) { - SkASSERT(check->fCoinPtTEnd->span() == over1e->span() - || !fDebugState->debugRunFail()); - SkASSERT(check->fOppPtTEnd->span() == outer->fOppPtTEnd->span() - || !fDebugState->debugRunFail()); - return false; + if (check->coinPtTStart()->segment() != coinSeg) { + continue; } - if (check->fCoinPtTStart->span() == outer->fCoinPtTStart->span() - && check->fOppPtTStart->span() == over1s->span()) { - SkASSERT(check->fCoinPtTEnd->span() == outer->fCoinPtTEnd->span() - || !fDebugState->debugRunFail()); - SkASSERT(check->fOppPtTEnd->span() == over1e->span() - || !fDebugState->debugRunFail()); - return false; + if (check->oppPtTStart()->segment() != oppSeg) { + continue; } - } while ((check = check->fNext)); - this->add(outer->fCoinPtTStart, outer->fCoinPtTEnd, over1s, over1e, allocator); -#if 0 - // FIXME: up to four flavors could be added -- do we need only one? -#endif + double checkTs = check->coinPtTStart()->fT; + double checkTe = check->coinPtTEnd()->fT; + bool coinOutside = coinTe < checkTs || coinTs > checkTe; + double oCheckTs = check->oppPtTStart()->fT; + double oCheckTe = check->oppPtTEnd()->fT; + if (swapOpp) { + if (oCheckTs <= oCheckTe) { + return false; + } + SkTSwap(oCheckTs, oCheckTe); + } + bool oppOutside = oppTe < oCheckTs || oppTs > oCheckTe; + if (coinOutside && oppOutside) { + continue; + } + bool coinInside = coinTe <= checkTe && coinTs >= checkTs; + bool oppInside = oppTe <= oCheckTe && oppTs >= oCheckTs; + if (coinInside && oppInside) { + return false; // complete overlap, already included, do nothing + } + *overlaps->append() = check; // partial overlap, extend existing entry + } while ((check = check->next())); return true; } +/* Please keep this in sync with debugAddIfMissing() */ bool SkOpCoincidence::addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over1e, - const SkOpPtT* over2s, const SkOpPtT* over2e, double tStart, double tEnd, + const SkOpPtT* over2s, const SkOpPtT* over2e, double tStart, double tEnd, SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, - SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd, SkChunkAlloc* allocator) { + SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { double coinTs, coinTe, oppTs, oppTe; - t_range(over1s, over1e, tStart, tEnd, coinPtTStart, coinPtTEnd, &coinTs, &coinTe); - t_range(over2s, over2e, tStart, tEnd, oppPtTStart, oppPtTEnd, &oppTs, &oppTe); + TRange(over1s, over1e, tStart, tEnd, coinPtTStart, coinPtTEnd, &coinTs, &coinTe); + TRange(over2s, over2e, tStart, tEnd, oppPtTStart, oppPtTEnd, &oppTs, &oppTe); + bool swap = coinTs > coinTe; + if (swap) { + SkTSwap(coinTs, coinTe); + } + if ((over1s->fT < over1e->fT) != (over2s->fT < over2e->fT)) { + SkTSwap(oppTs, oppTe); + } + if (swap) { + SkTSwap(oppTs, oppTe); + } SkOpSegment* coinSeg = coinPtTStart->segment(); SkOpSegment* oppSeg = oppPtTStart->segment(); - SkASSERT(coinSeg != oppSeg); - SkCoincidentSpans* check = this->fTop; - do { - const SkOpSegment* checkCoinSeg = check->fCoinPtTStart->segment(); - if (checkCoinSeg != coinSeg && checkCoinSeg != oppSeg) { - continue; + if (coinSeg == oppSeg) { + return false; + } + return this->addOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe); +} + +/* Please keep this in sync with debugAddOrOverlap() */ +bool SkOpCoincidence::addOrOverlap(SkOpSegment* coinSeg, SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe) { + SkTDArray<SkCoincidentSpans*> overlaps; + SkASSERT(fTop); + if (!this->checkOverlap(fTop, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &overlaps)) { + return false; + } + if (fHead && !this->checkOverlap(fHead, coinSeg, oppSeg, coinTs, + coinTe, oppTs, oppTe, &overlaps)) { + return false; + } + SkCoincidentSpans* overlap = overlaps.count() ? overlaps[0] : nullptr; + for (int index = 1; index < overlaps.count(); ++index) { // combine overlaps before continuing + SkCoincidentSpans* test = overlaps[index]; + if (overlap->coinPtTStart()->fT > test->coinPtTStart()->fT) { + overlap->setCoinPtTStart(test->coinPtTStart()); } - const SkOpSegment* checkOppSeg = check->fOppPtTStart->segment(); - if (checkOppSeg != coinSeg && checkOppSeg != oppSeg) { - continue; + if (overlap->coinPtTEnd()->fT < test->coinPtTEnd()->fT) { + overlap->setCoinPtTEnd(test->coinPtTEnd()); + } + if (overlap->flipped() + ? overlap->oppPtTStart()->fT < test->oppPtTStart()->fT + : overlap->oppPtTStart()->fT > test->oppPtTStart()->fT) { + overlap->setOppPtTStart(test->oppPtTStart()); } - 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) { + if (overlap->flipped() + ? overlap->oppPtTEnd()->fT > test->oppPtTEnd()->fT + : overlap->oppPtTEnd()->fT < test->oppPtTEnd()->fT) { + overlap->setOppPtTEnd(test->oppPtTEnd()); + } + if (!fHead || !this->release(fHead, test)) { + SkAssertResult(this->release(fTop, test)); + } + } + const SkOpPtT* cs = coinSeg->existing(coinTs, oppSeg); + const SkOpPtT* ce = coinSeg->existing(coinTe, oppSeg); + if (overlap && cs && ce && overlap->contains(cs, ce)) { + return false; + } + SkASSERT(cs != ce || !cs); + const SkOpPtT* os = oppSeg->existing(oppTs, coinSeg); + const SkOpPtT* oe = oppSeg->existing(oppTe, coinSeg); + if (overlap && os && oe && overlap->contains(os, oe)) { + return false; + } + SkASSERT(!cs || !cs->deleted()); + SkASSERT(!os || !os->deleted()); + SkASSERT(!ce || !ce->deleted()); + SkASSERT(!oe || !oe->deleted()); + const SkOpPtT* csExisting = !cs ? coinSeg->existing(coinTs, nullptr) : nullptr; + const SkOpPtT* ceExisting = !ce ? coinSeg->existing(coinTe, nullptr) : nullptr; + if (csExisting && csExisting == ceExisting) { + return false; + } + if (csExisting && (csExisting == ce || csExisting->contains(ceExisting ? ceExisting : ce))) { + return false; + } + if (ceExisting && (ceExisting == cs || ceExisting->contains(csExisting ? csExisting : cs))) { + return false; + } + const SkOpPtT* osExisting = !os ? oppSeg->existing(oppTs, nullptr) : nullptr; + const SkOpPtT* oeExisting = !oe ? oppSeg->existing(oppTe, nullptr) : nullptr; + if (osExisting && osExisting == oeExisting) { + return false; + } + if (osExisting && (osExisting == oe || osExisting->contains(oeExisting ? oeExisting : oe))) { + return false; + } + if (oeExisting && (oeExisting == os || oeExisting->contains(osExisting ? osExisting : os))) { + return false; + } + // extra line in debug code + this->debugValidate(); + if (!cs || !os) { + SkOpPtT* csWritable = cs ? const_cast<SkOpPtT*>(cs) + : coinSeg->addT(coinTs, SkOpSegment::kNoAliasMatch, nullptr); + SkOpPtT* osWritable = os ? const_cast<SkOpPtT*>(os) + : oppSeg->addT(oppTs, SkOpSegment::kNoAliasMatch, nullptr); + csWritable->span()->addOppAndMerge(osWritable->span()); + cs = csWritable; + os = osWritable; + if ((ce && ce->deleted()) || (oe && oe->deleted())) { return false; } - } 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); + if (!ce || !oe) { + SkOpPtT* ceWritable = ce ? const_cast<SkOpPtT*>(ce) + : coinSeg->addT(coinTe, SkOpSegment::kNoAliasMatch, nullptr); + SkOpPtT* oeWritable = oe ? const_cast<SkOpPtT*>(oe) + : oppSeg->addT(oppTe, SkOpSegment::kNoAliasMatch, nullptr); + ceWritable->span()->addOppAndMerge(oeWritable->span()); + ce = ceWritable; + oe = oeWritable; } - SkOpPtT* cs = coinSeg->addMissing(coinTs, oppSeg, allocator); - SkOpPtT* ce = coinSeg->addMissing(coinTe, oppSeg, allocator); - SkASSERT(cs != ce); - 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; + this->debugValidate(); + if (cs->deleted() || os->deleted() || ce->deleted() || oe->deleted()) { + return false; + } + if (cs->contains(ce) || os->contains(oe)) { + return false; + } + bool result = true; + if (overlap) { + if (overlap->coinPtTStart()->segment() == coinSeg) { + result = overlap->extend(cs, ce, os, oe); + } else { + if (os->fT > oe->fT) { + SkTSwap(cs, ce); + SkTSwap(os, oe); + } + result = overlap->extend(os, oe, cs, ce); + } +#if DEBUG_COINCIDENCE_VERBOSE + if (result) { + overlaps[0]->debugShow(); + } +#endif + } else { + this->add(cs, ce, os, oe); +#if DEBUG_COINCIDENCE_VERBOSE + fHead->debugShow(); +#endif + } + this->debugValidate(); + return result; } +// Please keep this in sync with debugAddMissing() /* detects overlaps of different coincident runs on same segment */ /* does not detect overlaps for pairs without any segments in common */ -bool SkOpCoincidence::addMissing(SkChunkAlloc* allocator) { +// returns true if caller should loop again +bool SkOpCoincidence::addMissing() { SkCoincidentSpans* outer = fHead; if (!outer) { - return true; + return false; } bool added = false; fTop = outer; @@ -247,107 +735,116 @@ bool SkOpCoincidence::addMissing(SkChunkAlloc* allocator) { // addifmissing can modify the list that this is walking // save head so that walker can iterate over old data unperturbed // addifmissing adds to head freely then add saved head in the end - const SkOpSegment* outerCoin = outer->fCoinPtTStart->segment(); - SkASSERT(outerCoin == outer->fCoinPtTEnd->segment()); - const SkOpSegment* outerOpp = outer->fOppPtTStart->segment(); - SkASSERT(outerOpp == outer->fOppPtTEnd->segment()); + const SkOpSegment* outerCoin = outer->coinPtTStart()->segment(); + const SkOpSegment* outerOpp = outer->oppPtTStart()->segment(); + if (outerCoin->done() || outerOpp->done()) { + continue; + } SkCoincidentSpans* inner = outer; - while ((inner = inner->fNext)) { + while ((inner = inner->next())) { + this->debugValidate(); double overS, overE; - const SkOpSegment* innerCoin = inner->fCoinPtTStart->segment(); - SkASSERT(innerCoin == inner->fCoinPtTEnd->segment()); - const SkOpSegment* innerOpp = inner->fOppPtTStart->segment(); - SkASSERT(innerOpp == inner->fOppPtTEnd->segment()); - if (outerCoin == innerCoin - && this->overlap(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, &overS, &overE)) { - added |= this->addIfMissing(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, overS, overE, - outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, allocator); - } else if (outerCoin == innerOpp - && this->overlap(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, &overS, &overE)) { - added |= this->addIfMissing(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, overS, overE, - outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, allocator); - } else if (outerOpp == innerCoin - && this->overlap(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, &overS, &overE)) { - added |= this->addIfMissing(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, overS, overE, - outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, allocator); - } else if (outerOpp == innerOpp - && this->overlap(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, &overS, &overE)) { - added |= this->addIfMissing(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, overS, overE, - outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, allocator); - } else if (outerCoin != innerCoin) { - // check to see if outer span overlaps the inner span - // look for inner segment in pt-t list - // if present, and if t values are in coincident range - // add two pairs of new coincidence - SkOpPtT* testS = outer->fCoinPtTStart->contains(innerCoin); - SkOpPtT* testE = outer->fCoinPtTEnd->contains(innerCoin); - if (testS && testS->fT >= inner->fCoinPtTStart->fT - && testE && testE->fT <= inner->fCoinPtTEnd->fT - && this->testForCoincidence(outer, testS, testE)) { - added |= this->addIfMissing(outer, testS, testE, allocator); - } else { - testS = inner->fCoinPtTStart->contains(outerCoin); - testE = inner->fCoinPtTEnd->contains(outerCoin); - if (testS && testS->fT >= outer->fCoinPtTStart->fT - && testE && testE->fT <= outer->fCoinPtTEnd->fT - && this->testForCoincidence(inner, testS, testE)) { - added |= this->addIfMissing(inner, testS, testE, allocator); - } + const SkOpSegment* innerCoin = inner->coinPtTStart()->segment(); + const SkOpSegment* innerOpp = inner->oppPtTStart()->segment(); + if (innerCoin->done() || innerOpp->done()) { + continue; + } + if (outerCoin == innerCoin) { + if (outerOpp != innerOpp + && this->overlap(outer->coinPtTStart(), outer->coinPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), &overS, &overE)) { + added |= this->addIfMissing(outer->coinPtTStart(), outer->coinPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), overS, overE, + outer->oppPtTStart(), outer->oppPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd()); + } + } else if (outerCoin == innerOpp) { + if (outerOpp != innerCoin + && this->overlap(outer->coinPtTStart(), outer->coinPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), &overS, &overE)) { + added |= this->addIfMissing(outer->coinPtTStart(), outer->coinPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), overS, overE, + outer->oppPtTStart(), outer->oppPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd()); + } + } else if (outerOpp == innerCoin) { + SkASSERT(outerCoin != innerOpp); + if (this->overlap(outer->oppPtTStart(), outer->oppPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), &overS, &overE)) { + added |= this->addIfMissing(outer->oppPtTStart(), outer->oppPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), overS, overE, + outer->coinPtTStart(), outer->coinPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd()); + } + } else if (outerOpp == innerOpp) { + SkASSERT(outerCoin != innerCoin); + if (this->overlap(outer->oppPtTStart(), outer->oppPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), &overS, &overE)) { + added |= this->addIfMissing(outer->oppPtTStart(), outer->oppPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), overS, overE, + outer->coinPtTStart(), outer->coinPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd()); } } -#if 0 && DEBUG_COINCIDENCE - SkString miss; - miss.printf("addMissing inner=%d outer=%d", inner->debugID(), outer->debugID()); - DEBUG_COINCIDENCE_HEALTH(fDebugState->contourHead(), miss.c_str()); -#endif + this->debugValidate(); } - } while ((outer = outer->fNext)); - SkCoincidentSpans** headPtr = &fHead; - while (*headPtr) { - SkCoincidentSpans** headNext = &(*headPtr)->fNext; - if (*headNext) { - break; - } - headPtr = headNext; - } - *headPtr = fTop; + } while ((outer = outer->next())); + this->restoreHead(); return added; } -bool SkOpCoincidence::addOverlap(SkOpSegment* seg1, SkOpSegment* seg1o, SkOpSegment* seg2, - SkOpSegment* seg2o, SkOpPtT* overS, SkOpPtT* overE, SkChunkAlloc* allocator) { - SkOpPtT* s1 = overS->find(seg1); - SkOpPtT* e1 = overE->find(seg1); +bool SkOpCoincidence::addOverlap(const SkOpSegment* seg1, const SkOpSegment* seg1o, + const SkOpSegment* seg2, const SkOpSegment* seg2o, + const SkOpPtT* overS, const SkOpPtT* overE) { + const SkOpPtT* s1, * e1, * s2, * e2; + if (!(s1 = overS->find(seg1))) { + return true; + } + if (!(e1 = overE->find(seg1))) { + return true; + } + if (s1 == e1) { + return true; + } if (approximately_equal_half(s1->fT, e1->fT)) { return false; } if (!s1->starter(e1)->span()->upCast()->windValue()) { - s1 = overS->find(seg1o); - e1 = overE->find(seg1o); + if (!(s1 = overS->find(seg1o))) { + return true; + } + if (!(e1 = overE->find(seg1o))) { + return true; + } + if (s1 == e1) { + return true; + } if (!s1->starter(e1)->span()->upCast()->windValue()) { return true; } } - SkOpPtT* s2 = overS->find(seg2); - SkOpPtT* e2 = overE->find(seg2); + if (!(s2 = overS->find(seg2))) { + return true; + } + if (!(e2 = overE->find(seg2))) { + return true; + } + if (s2 == e2) { + return true; + } if (approximately_equal_half(s2->fT, e2->fT)) { return false; } if (!s2->starter(e2)->span()->upCast()->windValue()) { - s2 = overS->find(seg2o); - e2 = overE->find(seg2o); + if (!(s2 = overS->find(seg2o))) { + return true; + } + if (!(e2 = overE->find(seg2o))) { + return true; + } + if (s2 == e2) { + return true; + } if (!s2->starter(e2)->span()->upCast()->windValue()) { return true; } @@ -359,26 +856,144 @@ bool SkOpCoincidence::addOverlap(SkOpSegment* seg1, SkOpSegment* seg1o, SkOpSegm SkTSwap(s1, e1); SkTSwap(s2, e2); } - this->add(s1, e1, s2, e2, allocator); + this->add(s1, e1, s2, e2); return true; } -bool SkOpCoincidence::contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, - const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd, bool flipped) const { - const SkCoincidentSpans* coin = fHead; - if (!coin) { +/* look for pairs of coincidence with no common segments + if there's no existing coincidence found that matches up the segments, and + if the pt-t list for one contains the other, create coincident pairs for what's left */ +bool SkOpCoincidence::addUncommon() { + SkCoincidentSpans* outer = fHead; + if (!outer) { return false; } + bool added = false; + fTop = outer; + fHead = nullptr; do { - if (coin->fCoinPtTStart == coinPtTStart && coin->fCoinPtTEnd == coinPtTEnd - && coin->fOppPtTStart == oppPtTStart && coin->fOppPtTEnd == oppPtTEnd - && coin->fFlipped == flipped) { + // addifmissing can modify the list that this is walking + // save head so that walker can iterate over old data unperturbed + // addifmissing adds to head freely then add saved head in the end + const SkOpSegment* outerCoin = outer->coinPtTStart()->segment(); + const SkOpSegment* outerOpp = outer->oppPtTStart()->segment(); + if (outerCoin->done() || outerOpp->done()) { + continue; + } + SkCoincidentSpans* inner = outer; + while ((inner = inner->next())) { + this->debugValidate(); + const SkOpSegment* innerCoin = inner->coinPtTStart()->segment(); + const SkOpSegment* innerOpp = inner->oppPtTStart()->segment(); + if (innerCoin->done() || innerOpp->done()) { + continue; + } + // check to see if outer span overlaps the inner span + // look for inner segment in pt-t list + // if present, and if t values are in coincident range + // add two pairs of new coincidence + const SkOpPtT* testS = outer->coinPtTStart()->contains(innerCoin); + const SkOpPtT* testE = outer->coinPtTEnd()->contains(innerCoin); + if (testS && testS->fT >= inner->coinPtTStart()->fT + && testE && testE->fT <= inner->coinPtTEnd()->fT + && this->testForCoincidence(outer, testS, testE)) { + added |= this->addIfMissing(outer, testS, testE); + } else { + testS = inner->coinPtTStart()->contains(outerCoin); + testE = inner->coinPtTEnd()->contains(outerCoin); + if (testS && testS->fT >= outer->coinPtTStart()->fT + && testE && testE->fT <= outer->coinPtTEnd()->fT + && this->testForCoincidence(inner, testS, testE)) { + added |= this->addIfMissing(inner, testS, testE); + } + } + } + } while ((outer = outer->next())); + this->restoreHead(); + return added; +} + +bool SkOpCoincidence::contains(const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const { + if (this->contains(fHead, seg, opp, oppT)) { + return true; + } + if (this->contains(fTop, seg, opp, oppT)) { + return true; + } + return false; +} + +bool SkOpCoincidence::contains(const SkCoincidentSpans* coin, const SkOpSegment* seg, + const SkOpSegment* opp, double oppT) const { + if (!coin) { + return false; + } + do { + if (coin->coinPtTStart()->segment() == seg && coin->oppPtTStart()->segment() == opp + && between(coin->oppPtTStart()->fT, oppT, coin->oppPtTEnd()->fT)) { return true; } - } while ((coin = coin->fNext)); + if (coin->oppPtTStart()->segment() == seg && coin->coinPtTStart()->segment() == opp + && between(coin->coinPtTStart()->fT, oppT, coin->coinPtTEnd()->fT)) { + return true; + } + } while ((coin = coin->next())); + return false; +} + +bool SkOpCoincidence::contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const { + const SkCoincidentSpans* test = fHead; + if (!test) { + return false; + } + const SkOpSegment* coinSeg = coinPtTStart->segment(); + const SkOpSegment* oppSeg = oppPtTStart->segment(); + if (!Ordered(coinPtTStart, oppPtTStart)) { + SkTSwap(coinSeg, oppSeg); + SkTSwap(coinPtTStart, oppPtTStart); + SkTSwap(coinPtTEnd, oppPtTEnd); + if (coinPtTStart->fT > coinPtTEnd->fT) { + SkTSwap(coinPtTStart, coinPtTEnd); + SkTSwap(oppPtTStart, oppPtTEnd); + } + } + double oppMinT = SkTMin(oppPtTStart->fT, oppPtTEnd->fT); + double oppMaxT = SkTMax(oppPtTStart->fT, oppPtTEnd->fT); + do { + if (coinSeg != test->coinPtTStart()->segment()) { + continue; + } + if (coinPtTStart->fT < test->coinPtTStart()->fT) { + continue; + } + if (coinPtTEnd->fT > test->coinPtTEnd()->fT) { + continue; + } + if (oppSeg != test->oppPtTStart()->segment()) { + continue; + } + if (oppMinT < SkTMin(test->oppPtTStart()->fT, test->oppPtTEnd()->fT)) { + continue; + } + if (oppMaxT > SkTMax(test->oppPtTStart()->fT, test->oppPtTEnd()->fT)) { + continue; + } + return true; + } while ((test = test->next())); return false; } +void SkOpCoincidence::correctEnds() { + SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + coin->correctEnds(); + } while ((coin = coin->next())); +} + // walk span sets in parallel, moving winding from one to the other bool SkOpCoincidence::apply() { SkCoincidentSpans* coin = fHead; @@ -386,18 +1001,19 @@ bool SkOpCoincidence::apply() { return true; } do { - SkOpSpan* start = coin->fCoinPtTStart->span()->upCast(); + SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast(); if (start->deleted()) { continue; } - SkOpSpanBase* end = coin->fCoinPtTEnd->span(); + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); SkASSERT(start == start->starter(end)); - bool flipped = coin->fFlipped; - SkOpSpan* oStart = (flipped ? coin->fOppPtTEnd : coin->fOppPtTStart)->span()->upCast(); + bool flipped = coin->flipped(); + SkOpSpan* oStart = (flipped ? coin->oppPtTEndWritable() + : coin->oppPtTStartWritable())->span()->upCast(); if (oStart->deleted()) { continue; } - SkOpSpanBase* oEnd = (flipped ? coin->fOppPtTStart : coin->fOppPtTEnd)->span(); + const SkOpSpanBase* oEnd = (flipped ? coin->oppPtTStart() : coin->oppPtTEnd())->span(); SkASSERT(oStart == oStart->starter(oEnd)); SkOpSegment* segment = start->segment(); SkOpSegment* oSegment = oStart->segment(); @@ -427,8 +1043,12 @@ bool SkOpCoincidence::apply() { windDiff = -windDiff; oWindDiff = -oWindDiff; } - if (windValue && (windValue > windDiff || (windValue == windDiff - && oWindValue <= oWindDiff))) { + bool addToStart = windValue && (windValue > windDiff || (windValue == windDiff + && oWindValue <= oWindDiff)); + if (addToStart ? start->done() : oStart->done()) { + addToStart ^= true; + } + if (addToStart) { if (operandSwap) { SkTSwap(oWindValue, oOppValue); } @@ -465,6 +1085,12 @@ bool SkOpCoincidence::apply() { } windValue = oppValue = 0; } +#if DEBUG_COINCIDENCE + SkDebugf("seg=%d span=%d windValue=%d oppValue=%d\n", segment->debugID(), + start->debugID(), windValue, oppValue); + SkDebugf("seg=%d span=%d windValue=%d oppValue=%d\n", oSegment->debugID(), + oStart->debugID(), oWindValue, oOppValue); +#endif start->setWindValue(windValue); start->setOppValue(oppValue); oStart->setWindValue(oWindValue); @@ -490,29 +1116,87 @@ bool SkOpCoincidence::apply() { } oStart = oNext->upCast(); } while (true); - } while ((coin = coin->fNext)); + } while ((coin = coin->next())); return true; } -void SkOpCoincidence::release(SkCoincidentSpans* remove) { - SkCoincidentSpans* coin = fHead; +// Please keep this in sync with debugRelease() +bool SkOpCoincidence::release(SkCoincidentSpans* coin, SkCoincidentSpans* remove) { + SkCoincidentSpans* head = coin; SkCoincidentSpans* prev = nullptr; SkCoincidentSpans* next; do { - next = coin->fNext; + next = coin->next(); if (coin == remove) { if (prev) { - prev->fNext = next; - } else { + prev->setNext(next); + } else if (head == fHead) { fHead = next; + } else { + fTop = next; } break; } prev = coin; } while ((coin = next)); - SkASSERT(coin); + return coin != nullptr; +} + +// Please keep this in sync with debugReorder() +// iterate through all coincident pairs, looking for ranges greater than 1 +// if found, see if the opposite pair can match it -- which may require +// reordering the ptT pairs +bool SkOpCoincidence::reorder() { + SkCoincidentSpans* coin = fHead; + if (!coin) { + return true; + } + do { + // most commonly, concidence are one span long; check for that first + int intervals = coin->spanCount(); + if (intervals <= 0) { + return false; + } + if (1 == intervals) { +#if DEBUG_COINCIDENCE_VERBOSE + SkASSERT(!coin->debugExpand(nullptr, nullptr)); +#endif + continue; + } + coin->expand(); // be all that you can be + if (coin->spanCount() <= 0) { + return false; + } + // check to see if every span in coin has a mate in opp + const SkOpSpan* start = coin->coinPtTStart()->span()->upCast(); + bool flipped = coin->flipped(); + const SkOpSpanBase* oppStartBase = coin->oppPtTStart()->span(); + const SkOpSpan* oppStart = flipped ? oppStartBase->prev() : oppStartBase->upCast(); + SkDebugf("", start, oppStart); + } while ((coin = coin->next())); + return true; } +void SkOpCoincidence::restoreHead() { + SkCoincidentSpans** headPtr = &fHead; + while (*headPtr) { + headPtr = (*headPtr)->nextPtr(); + } + *headPtr = fTop; + fTop = nullptr; + // segments may have collapsed in the meantime; remove empty referenced segments + headPtr = &fHead; + while (*headPtr) { + SkCoincidentSpans* test = *headPtr; + if (test->coinPtTStart()->segment()->done() || test->oppPtTStart()->segment()->done()) { + *headPtr = test->next(); + continue; + } + headPtr = (*headPtr)->nextPtr(); + } +} + +// Please keep this in sync with debugExpand() bool SkOpCoincidence::expand() { SkCoincidentSpans* coin = fHead; if (!coin) { @@ -520,142 +1204,132 @@ bool SkOpCoincidence::expand() { } bool expanded = false; 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; - expanded = true; - } - } - SkOpSpanBase* next = end->final() ? nullptr : 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; - expanded = true; - } + if (coin->expand()) { + // check to see if multiple spans expanded so they are now identical + SkCoincidentSpans* test = fHead; + do { + if (coin == test) { + continue; + } + if (coin->coinPtTStart() == test->coinPtTStart() + && coin->oppPtTStart() == test->oppPtTStart()) { + this->release(fHead, test); + break; + } + } while ((test = test->next())); + expanded = true; } - } while ((coin = coin->fNext)); + } while ((coin = coin->next())); return expanded; } -bool SkOpCoincidence::findOverlaps(SkOpCoincidence* overlaps, SkChunkAlloc* allocator) const { +bool SkOpCoincidence::findOverlaps(SkOpCoincidence* overlaps) const { overlaps->fHead = overlaps->fTop = nullptr; - SkDEBUGCODE_(overlaps->debugSetGlobalState(fDebugState)); SkCoincidentSpans* outer = fHead; while (outer) { - SkOpSegment* outerCoin = outer->fCoinPtTStart->segment(); - SkOpSegment* outerOpp = outer->fOppPtTStart->segment(); + const SkOpSegment* outerCoin = outer->coinPtTStart()->segment(); + const SkOpSegment* outerOpp = outer->oppPtTStart()->segment(); SkCoincidentSpans* inner = outer; - while ((inner = inner->fNext)) { - SkOpSegment* innerCoin = inner->fCoinPtTStart->segment(); + while ((inner = inner->next())) { + const SkOpSegment* innerCoin = inner->coinPtTStart()->segment(); if (outerCoin == innerCoin) { continue; // both winners are the same segment, so there's no additional overlap } - SkOpSegment* innerOpp = inner->fOppPtTStart->segment(); - SkOpPtT* overlapS, * overlapE; - if ((outerOpp == innerCoin && SkOpPtT::Overlaps(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, &overlapS, &overlapE)) - || (outerCoin == innerOpp && SkOpPtT::Overlaps(outer->fCoinPtTStart, - outer->fCoinPtTEnd, inner->fOppPtTStart, inner->fOppPtTEnd, + const SkOpSegment* innerOpp = inner->oppPtTStart()->segment(); + const SkOpPtT* overlapS; + const SkOpPtT* overlapE; + if ((outerOpp == innerCoin && SkOpPtT::Overlaps(outer->oppPtTStart(), + outer->oppPtTEnd(),inner->coinPtTStart(), inner->coinPtTEnd(), &overlapS, + &overlapE)) + || (outerCoin == innerOpp && SkOpPtT::Overlaps(outer->coinPtTStart(), + outer->coinPtTEnd(), inner->oppPtTStart(), inner->oppPtTEnd(), &overlapS, &overlapE)) - || (outerOpp == innerOpp && SkOpPtT::Overlaps(outer->fOppPtTStart, - outer->fOppPtTEnd, inner->fOppPtTStart, inner->fOppPtTEnd, + || (outerOpp == innerOpp && SkOpPtT::Overlaps(outer->oppPtTStart(), + outer->oppPtTEnd(), inner->oppPtTStart(), inner->oppPtTEnd(), &overlapS, &overlapE))) { if (!overlaps->addOverlap(outerCoin, outerOpp, innerCoin, innerOpp, - overlapS, overlapE, allocator)) { + overlapS, overlapE)) { return false; } } } - outer = outer->fNext; + outer = outer->next(); } return true; } -bool SkOpCoincidence::fixAligned() { +// Please keep this in sync with debugRemoveCollapsed() +bool SkOpCoincidence::removeCollapsed() { SkCoincidentSpans* coin = fHead; if (!coin) { return true; } - do { - if (coin->fCoinPtTStart->deleted()) { - coin->fCoinPtTStart = coin->fCoinPtTStart->doppelganger(); - } - if (coin->fCoinPtTEnd->deleted()) { - coin->fCoinPtTEnd = coin->fCoinPtTEnd->doppelganger(); - } - if (coin->fOppPtTStart->deleted()) { - coin->fOppPtTStart = coin->fOppPtTStart->doppelganger(); - } - if (coin->fOppPtTEnd->deleted()) { - coin->fOppPtTEnd = coin->fOppPtTEnd->doppelganger(); - } - } while ((coin = coin->fNext)); - coin = fHead; SkCoincidentSpans** priorPtr = &fHead; do { - if (coin->fCoinPtTStart == coin->fCoinPtTEnd) { + if (coin->coinPtTStart() == coin->coinPtTEnd()) { return false; } - if (coin->fOppPtTStart == coin->fOppPtTEnd) { + if (coin->oppPtTStart() == coin->oppPtTEnd()) { return false; } - if (coin->fCoinPtTStart->collapsed(coin->fCoinPtTEnd) - || coin->fOppPtTStart->collapsed(coin->fOppPtTEnd)) { - *priorPtr = coin->fNext; + if (coin->coinPtTStart()->collapsed(coin->coinPtTEnd())) { + *priorPtr = coin->next(); continue; } - priorPtr = &coin->fNext; - } while ((coin = coin->fNext)); + if (coin->oppPtTStart()->collapsed(coin->oppPtTEnd())) { + *priorPtr = coin->next(); + continue; + } + priorPtr = coin->nextPtr(); + } while ((coin = coin->next())); return true; } -void SkOpCoincidence::fixUp(SkOpPtT* deleted, SkOpPtT* kept) { - SkCoincidentSpans* coin = fHead; - if (!coin) { - return; +void SkOpCoincidence::fixUp(SkOpPtT* deleted, const SkOpPtT* kept) { + SkASSERT(deleted != kept); + if (fHead) { + this->fixUp(fHead, deleted, kept); + } + if (fTop) { + this->fixUp(fTop, deleted, kept); } +} + +void SkOpCoincidence::fixUp(SkCoincidentSpans* coin, SkOpPtT* deleted, const SkOpPtT* kept) { + SkCoincidentSpans* head = coin; do { - if (coin->fCoinPtTStart == deleted) { - if (coin->fCoinPtTEnd->span() == kept->span()) { - this->release(coin); + if (coin->coinPtTStart() == deleted) { + if (coin->coinPtTEnd()->span() == kept->span()) { + this->release(head, coin); continue; } - coin->fCoinPtTStart = kept; + coin->setCoinPtTStart(kept); } - if (coin->fCoinPtTEnd == deleted) { - if (coin->fCoinPtTStart->span() == kept->span()) { - this->release(coin); + if (coin->coinPtTEnd() == deleted) { + if (coin->coinPtTStart()->span() == kept->span()) { + this->release(head, coin); continue; } - coin->fCoinPtTEnd = kept; - } - if (coin->fOppPtTStart == deleted) { - if (coin->fOppPtTEnd->span() == kept->span()) { - this->release(coin); + coin->setCoinPtTEnd(kept); + } + if (coin->oppPtTStart() == deleted) { + if (coin->oppPtTEnd()->span() == kept->span()) { + this->release(head, coin); continue; } - coin->fOppPtTStart = kept; + coin->setOppPtTStart(kept); } - if (coin->fOppPtTEnd == deleted) { - if (coin->fOppPtTStart->span() == kept->span()) { - this->release(coin); + if (coin->oppPtTEnd() == deleted) { + if (coin->oppPtTStart()->span() == kept->span()) { + this->release(head, coin); continue; } - coin->fOppPtTEnd = kept; + coin->setOppPtTEnd(kept); } - } while ((coin = coin->fNext)); + } while ((coin = coin->next())); } +// Please keep this in sync with debugMark() /* this sets up the coincidence links in the segments when the coincidence crosses multiple spans */ bool SkOpCoincidence::mark() { SkCoincidentSpans* coin = fHead; @@ -663,44 +1337,87 @@ bool SkOpCoincidence::mark() { return true; } do { - SkOpSpanBase* end = coin->fCoinPtTEnd->span(); - if (end->deleted()) { - return false; - } - SkOpSpanBase* oldEnd = end; - SkOpSpan* start = coin->fCoinPtTStart->span()->starter(&end); - SkOpSpanBase* oEnd = coin->fOppPtTEnd->span(); - if (oEnd->deleted()) { - return false; - } - SkOpSpanBase* oOldEnd = oEnd; - SkOpSpanBase* oStart = coin->fOppPtTStart->span()->starter(&oEnd); - bool flipped = (end == oldEnd) != (oEnd == oOldEnd); + SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast(); + SkASSERT(!start->deleted()); + SkOpSpanBase* end = coin->coinPtTEndWritable()->span(); + SkASSERT(!end->deleted()); + SkOpSpanBase* oStart = coin->oppPtTStartWritable()->span(); + SkASSERT(!oStart->deleted()); + SkOpSpanBase* oEnd = coin->oppPtTEndWritable()->span(); + SkASSERT(!oEnd->deleted()); + bool flipped = coin->flipped(); if (flipped) { SkTSwap(oStart, oEnd); } + /* coin and opp spans may not match up. Mark the ends, and then let the interior + get marked as many times as the spans allow */ + start->insertCoincidence(oStart->upCast()); + end->insertCoinEnd(oEnd); + const SkOpSegment* segment = start->segment(); + const SkOpSegment* oSegment = oStart->segment(); SkOpSpanBase* next = start; SkOpSpanBase* oNext = oStart; - do { - next = next->upCast()->next(); - oNext = flipped ? oNext->prev() : oNext->upCast()->next(); - if (next == end || oNext == oEnd) { - break; + while ((next = next->upCast()->next()) != end) { + if (!next->upCast()->insertCoincidence(oSegment, flipped)) { + return false; } - if (!next->containsCoinEnd(oNext)) { - next->insertCoinEnd(oNext); + } + while ((oNext = oNext->upCast()->next()) != oEnd) { + if (!oNext->upCast()->insertCoincidence(segment, flipped)) { + return false; } - SkOpSpan* nextSpan = next->upCast(); - SkOpSpan* oNextSpan = oNext->upCast(); - if (!nextSpan->containsCoincidence(oNextSpan)) { - nextSpan->insertCoincidence(oNextSpan); + } + } while ((coin = coin->next())); + return true; +} + +// Please keep in sync with debugMarkCollapsed() +void SkOpCoincidence::markCollapsed(SkCoincidentSpans* coin, SkOpPtT* test) { + SkCoincidentSpans* head = coin; + while (coin) { + if (coin->collapsed(test)) { + if (zero_or_one(coin->coinPtTStart()->fT) && zero_or_one(coin->coinPtTEnd()->fT)) { + coin->coinPtTStartWritable()->segment()->markAllDone(); } - } while (true); - } while ((coin = coin->fNext)); + if (zero_or_one(coin->oppPtTStart()->fT) && zero_or_one(coin->oppPtTEnd()->fT)) { + coin->oppPtTStartWritable()->segment()->markAllDone(); + } + this->release(head, coin); + } + coin = coin->next(); + } +} + +// Please keep in sync with debugMarkCollapsed() +void SkOpCoincidence::markCollapsed(SkOpPtT* test) { + markCollapsed(fHead, test); + markCollapsed(fTop, test); +} + +bool SkOpCoincidence::Ordered(const SkOpSegment* coinSeg, const SkOpSegment* oppSeg) { + if (coinSeg->verb() < oppSeg->verb()) { + return true; + } + if (coinSeg->verb() > oppSeg->verb()) { + return false; + } + int count = (SkPathOpsVerbToPoints(coinSeg->verb()) + 1) * 2; + const SkScalar* cPt = &coinSeg->pts()[0].fX; + const SkScalar* oPt = &oppSeg->pts()[0].fX; + for (int index = 0; index < count; ++index) { + if (*cPt < *oPt) { + return true; + } + if (*cPt > *oPt) { + return false; + } + ++cPt; + ++oPt; + } return true; } -bool SkOpCoincidence::overlap(const SkOpPtT* coin1s, const SkOpPtT* coin1e, +bool SkOpCoincidence::overlap(const SkOpPtT* coin1s, const SkOpPtT* coin1e, const SkOpPtT* coin2s, const SkOpPtT* coin2e, double* overS, double* overE) const { SkASSERT(coin1s->segment() == coin2s->segment()); *overS = SkTMax(SkTMin(coin1s->fT, coin1e->fT), SkTMin(coin2s->fT, coin2e->fT)); @@ -708,8 +1425,24 @@ bool SkOpCoincidence::overlap(const SkOpPtT* coin1s, const SkOpPtT* coin1e, return *overS < *overE; } +// Commented-out lines keep this in sync with debugRelease() +void SkOpCoincidence::release(const SkOpSegment* deleted) { + SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + if (coin->coinPtTStart()->segment() == deleted + || coin->coinPtTEnd()->segment() == deleted + || coin->oppPtTStart()->segment() == deleted + || coin->oppPtTEnd()->segment() == deleted) { + this->release(fHead, coin); + } + } while ((coin = coin->next())); +} + bool SkOpCoincidence::testForCoincidence(const SkCoincidentSpans* outer, const SkOpPtT* testS, const SkOpPtT* testE) const { return testS->segment()->testForCoincidence(testS, testE, testS->span(), - testE->span(), outer->fCoinPtTStart->segment(), 120000); // FIXME: replace with tuned + testE->span(), outer->coinPtTStart()->segment()); } diff --git a/src/pathops/SkOpCoincidence.h b/src/pathops/SkOpCoincidence.h index 1efe6c0613..c64d148c24 100644 --- a/src/pathops/SkOpCoincidence.h +++ b/src/pathops/SkOpCoincidence.h @@ -7,120 +7,300 @@ #ifndef SkOpCoincidence_DEFINED #define SkOpCoincidence_DEFINED +#include "SkTDArray.h" #include "SkOpTAllocator.h" #include "SkOpSpan.h" #include "SkPathOpsTypes.h" class SkOpPtT; +class SkOpSpanBase; -struct SkCoincidentSpans { - SkCoincidentSpans* fNext; - SkOpPtT* fCoinPtTStart; - SkOpPtT* fCoinPtTEnd; - SkOpPtT* fOppPtTStart; - SkOpPtT* fOppPtTEnd; - bool fFlipped; - SkDEBUGCODE(int fID); +class SkCoincidentSpans { +public: + const SkOpPtT* coinPtTEnd() const { return fCoinPtTEnd; } + const SkOpPtT* coinPtTStart() const { return fCoinPtTStart; } + + // These return non-const pointers so that, as copies, they can be added + // to a new span pair + SkOpPtT* coinPtTEndWritable() const { return const_cast<SkOpPtT*>(fCoinPtTEnd); } + SkOpPtT* coinPtTStartWritable() const { return const_cast<SkOpPtT*>(fCoinPtTStart); } + + bool collapsed(const SkOpPtT* ) const; + bool contains(const SkOpPtT* s, const SkOpPtT* e) const; + void correctEnds(); + void correctOneEnd(const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::* setEnd)(const SkOpPtT* ptT) ); + +#if DEBUG_COINCIDENCE_VERBOSE + bool debugExpand(const char* id, SkPathOpsDebug::GlitchLog* log) const; +#endif int debugID() const { return SkDEBUGRELEASE(fID, -1); } + void debugShow() const; +#ifdef SK_DEBUG + void debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, + const SkOpGlobalState* debugState) const; +#endif void dump() const; + bool expand(); + bool extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); + bool flipped() const { return fOppPtTStart->fT > fOppPtTEnd->fT; } + void init() { sk_bzero(this, sizeof(*this)); } + const SkOpPtT* oppPtTStart() const { return fOppPtTStart; } + const SkOpPtT* oppPtTEnd() const { return fOppPtTEnd; } + // These return non-const pointers so that, as copies, they can be added + // to a new span pair + SkOpPtT* oppPtTStartWritable() const { return const_cast<SkOpPtT*>(fOppPtTStart); } + SkOpPtT* oppPtTEndWritable() const { return const_cast<SkOpPtT*>(fOppPtTEnd); } + SkCoincidentSpans* next() { return fNext; } + const SkCoincidentSpans* next() const { return fNext; } + SkCoincidentSpans** nextPtr() { return &fNext; } + int spanCount() const; + + void set(SkCoincidentSpans* next, const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd + SkDEBUGPARAMS(int id)); + + void setCoinPtTEnd(const SkOpPtT* ptT) { + SkASSERT(ptT == ptT->span()->ptT()) + SkASSERT(!fCoinPtTStart || ptT->fT != fCoinPtTStart->fT); + SkASSERT(!fCoinPtTStart || fCoinPtTStart->segment() == ptT->segment()); + fCoinPtTEnd = ptT; + ptT->setCoincident(); + } + + void setCoinPtTStart(const SkOpPtT* ptT) { + SkASSERT(ptT == ptT->span()->ptT()) + SkASSERT(!fCoinPtTEnd || ptT->fT != fCoinPtTEnd->fT); + SkASSERT(!fCoinPtTEnd || fCoinPtTEnd->segment() == ptT->segment()); + fCoinPtTStart = ptT; + ptT->setCoincident(); + } + + void setEnds(const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTEnd) { + this->setCoinPtTEnd(coinPtTEnd); + this->setOppPtTEnd(oppPtTEnd); + } + + void setOppPtTEnd(const SkOpPtT* ptT) { + SkASSERT(ptT == ptT->span()->ptT()) + SkASSERT(!fOppPtTStart || ptT->fT != fOppPtTStart->fT); + SkASSERT(!fOppPtTStart || fOppPtTStart->segment() == ptT->segment()); + fOppPtTEnd = ptT; + ptT->setCoincident(); + } + + void setOppPtTStart(const SkOpPtT* ptT) { + SkASSERT(ptT == ptT->span()->ptT()) + SkASSERT(!fOppPtTEnd || ptT->fT != fOppPtTEnd->fT); + SkASSERT(!fOppPtTEnd || fOppPtTEnd->segment() == ptT->segment()); + fOppPtTStart = ptT; + ptT->setCoincident(); + } + + void setStarts(const SkOpPtT* coinPtTStart, const SkOpPtT* oppPtTStart) { + this->setCoinPtTStart(coinPtTStart); + this->setOppPtTStart(oppPtTStart); + } + + void setNext(SkCoincidentSpans* next) { fNext = next; } + + bool startEquals(const SkOpSpanBase* outer, const SkOpSpanBase* over) const { + return fCoinPtTStart->span() == over && fOppPtTStart->span() == outer; + } +private: + SkCoincidentSpans* fNext; + const SkOpPtT* fCoinPtTStart; + const SkOpPtT* fCoinPtTEnd; + const SkOpPtT* fOppPtTStart; + const SkOpPtT* fOppPtTEnd; + SkDEBUGCODE(int fID); }; class SkOpCoincidence { public: - SkOpCoincidence() + SkOpCoincidence(SkOpGlobalState* globalState) : fHead(nullptr) , fTop(nullptr) - SkDEBUGPARAMS(fDebugState(nullptr)) - { + , fGlobalState(globalState) + , fContinue(false) + , fSpanDeleted(false) + , fPtAllocated(false) + , fCoinExtended(false) + , fSpanMerged(false) { + globalState->setCoincidence(this); } void add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, - SkOpPtT* oppPtTEnd, SkChunkAlloc* allocator); - bool addExpanded(SkChunkAlloc* allocator PATH_OPS_DEBUG_VALIDATE_PARAMS(SkOpGlobalState* )); - bool addMissing(SkChunkAlloc* allocator); + SkOpPtT* oppPtTEnd); + bool addEndMovedSpans(); + bool addExpanded(); + bool addMissing(); + bool addUncommon(); bool apply(); bool contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, - const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd, bool flipped) const; + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const; + void correctEnds(); +#if DEBUG_COINCIDENCE_VERBOSE void debugAddExpanded(const char* id, SkPathOpsDebug::GlitchLog* ) const; void debugAddMissing(const char* id, SkPathOpsDebug::GlitchLog* ) const; + void debugAddOrOverlap(const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + const char* id, SkPathOpsDebug::GlitchLog* log) const; +#endif const SkOpAngle* debugAngle(int id) const { - return SkDEBUGRELEASE(fDebugState->debugAngle(id), nullptr); + return SkDEBUGRELEASE(fGlobalState->debugAngle(id), nullptr); } +#if DEBUG_COINCIDENCE_VERBOSE + void debugCheckOverlap(const char* id, SkPathOpsDebug::GlitchLog* log) const; + void debugCheckValid(const char* id, SkPathOpsDebug::GlitchLog* log) const; +#endif + SkOpContour* debugContour(int id) { - return SkDEBUGRELEASE(fDebugState->debugContour(id), nullptr); + return SkDEBUGRELEASE(fGlobalState->debugContour(id), nullptr); } +#if DEBUG_COINCIDENCE_VERBOSE bool debugExpand(const char* id, SkPathOpsDebug::GlitchLog* ) const; void debugMark(const char* id, SkPathOpsDebug::GlitchLog* ) const; + void debugMarkCollapsed(const char* id, SkPathOpsDebug::GlitchLog* , + const SkCoincidentSpans* coin, const SkOpPtT* test) const; + void debugMarkCollapsed(const char* id, SkPathOpsDebug::GlitchLog* , const SkOpPtT* test) const; +#endif const SkOpPtT* debugPtT(int id) const { - return SkDEBUGRELEASE(fDebugState->debugPtT(id), nullptr); + return SkDEBUGRELEASE(fGlobalState->debugPtT(id), nullptr); } const SkOpSegment* debugSegment(int id) const { - return SkDEBUGRELEASE(fDebugState->debugSegment(id), nullptr); + return SkDEBUGRELEASE(fGlobalState->debugSegment(id), nullptr); } - void debugSetGlobalState(SkOpGlobalState* debugState) { - SkDEBUGCODE(fDebugState = debugState); - } - - void debugFixAligned(const char* id, SkPathOpsDebug::GlitchLog* ) const; +#if DEBUG_COINCIDENCE_VERBOSE + void debugRemoveCollapsed(const char* id, SkPathOpsDebug::GlitchLog* ) const; + void debugReorder(const char* id, SkPathOpsDebug::GlitchLog* ) const; + void debugRelease(const char* id, SkPathOpsDebug::GlitchLog* , const SkOpSegment* ) const; +#endif void debugShowCoincidence() const; const SkOpSpanBase* debugSpan(int id) const { - return SkDEBUGRELEASE(fDebugState->debugSpan(id), nullptr); + return SkDEBUGRELEASE(fGlobalState->debugSpan(id), nullptr); } - void release(SkCoincidentSpans* ); + void debugValidate() const; void dump() const; + bool edge(const SkOpPtT* , bool* start) const; bool expand(); - bool extend(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, - SkOpPtT* oppPtTEnd); - bool findOverlaps(SkOpCoincidence* , SkChunkAlloc* allocator) const; - bool fixAligned(); - void fixUp(SkOpPtT* deleted, SkOpPtT* kept); + bool extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, + const SkOpPtT* oppPtTEnd); + bool findOverlaps(SkOpCoincidence* ) const; + void fixUp(SkOpPtT* deleted, const SkOpPtT* kept); + + SkOpGlobalState* globalState() { + return fGlobalState; + } bool isEmpty() const { - return !fHead; + return !fHead && !fTop; } bool mark(); + void markCollapsed(SkOpPtT* ); + + static bool Ordered(const SkOpPtT* coinPtTStart, const SkOpPtT* oppPtTStart) { + return Ordered(coinPtTStart->segment(), oppPtTStart->segment()); + } + + static bool Ordered(const SkOpSegment* coin, const SkOpSegment* opp); + void release(const SkOpSegment* ); + bool removeCollapsed(); + bool reorder(); private: - bool addIfMissing(const SkCoincidentSpans* outer, SkOpPtT* over1s, SkOpPtT* over1e, - SkChunkAlloc* ); + void add(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, + const SkOpPtT* oppPtTEnd) { + this->add(const_cast<SkOpPtT*>(coinPtTStart), const_cast<SkOpPtT*>(coinPtTEnd), + const_cast<SkOpPtT*>(oppPtTStart), const_cast<SkOpPtT*>(oppPtTEnd)); + } + + void addEndMovedSpans(const SkOpSpan* base, const SkOpSpanBase* testSpan); + bool addEndMovedSpans(const SkOpPtT* ptT); + + bool addIfMissing(const SkCoincidentSpans* outer, SkOpPtT* over1s, SkOpPtT* over1e); + + bool addIfMissing(const SkCoincidentSpans* outer, const SkOpPtT* over1s, + const SkOpPtT* over1e) { + return addIfMissing(outer, const_cast<SkOpPtT*>(over1s), const_cast<SkOpPtT*>(over1e)); + } + 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* ); - bool addOverlap(SkOpSegment* seg1, SkOpSegment* seg1o, SkOpSegment* seg2, SkOpSegment* seg2o, - SkOpPtT* overS, SkOpPtT* overE, SkChunkAlloc* ); - bool debugAddIfMissing(const SkCoincidentSpans* outer, const SkOpPtT* over1s, - const SkOpPtT* over1e) const; - bool debugAddIfMissing(const SkOpPtT* over1s, const SkOpPtT* over1e, + SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); + + bool addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over1e, + const SkOpPtT* over2s, const SkOpPtT* over2e, + double tStart, double tEnd, + const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + return addIfMissing(over1s, over1e, over2s, over2e, tStart, tEnd, + const_cast<SkOpPtT*>(coinPtTStart), coinPtTEnd, + const_cast<SkOpPtT*>(oppPtTStart), oppPtTEnd); + } + + bool addOrOverlap(SkOpSegment* coinSeg, SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe); + bool addOverlap(const SkOpSegment* seg1, const SkOpSegment* seg1o, + const SkOpSegment* seg2, const SkOpSegment* seg2o, + const SkOpPtT* overS, const SkOpPtT* overE); + bool alreadyAdded(const SkCoincidentSpans* check, const SkCoincidentSpans* outer, + const SkOpPtT* over1s, const SkOpPtT* over1e) const; + bool checkOverlap(SkCoincidentSpans* check, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + SkTDArray<SkCoincidentSpans*>* overlaps) const; + bool contains(const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const; + bool contains(const SkCoincidentSpans* coin, const SkOpSegment* seg, + const SkOpSegment* opp, double oppT) const; +#if DEBUG_COINCIDENCE_VERBOSE + void debugAddIfMissing(const SkCoincidentSpans* outer, const SkOpPtT* over1s, + const SkOpPtT* over1e, const char* id, SkPathOpsDebug::GlitchLog*) const; + void debugAddIfMissing(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) const; + const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd, + const char* id, SkPathOpsDebug::GlitchLog*) const; +#endif + void fixUp(SkCoincidentSpans* coin, SkOpPtT* deleted, const SkOpPtT* kept); + void markCollapsed(SkCoincidentSpans* head, SkOpPtT* test); bool overlap(const SkOpPtT* coinStart1, const SkOpPtT* coinEnd1, const SkOpPtT* coinStart2, const SkOpPtT* coinEnd2, double* overS, double* overE) const; - + bool release(SkCoincidentSpans* coin, SkCoincidentSpans* ); + void restoreHead(); bool testForCoincidence(const SkCoincidentSpans* outer, const SkOpPtT* testS, const SkOpPtT* testE) const; + static void TRange(const SkOpPtT* overS, const SkOpPtT* overE, double tStart, + double tEnd, const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + double* coinTs, double* coinTe); + SkCoincidentSpans* fHead; SkCoincidentSpans* fTop; - SkDEBUGCODE_(SkOpGlobalState* fDebugState); + SkOpGlobalState* fGlobalState; + bool fContinue; + bool fSpanDeleted; + bool fPtAllocated; + bool fCoinExtended; + bool fSpanMerged; }; #endif diff --git a/src/pathops/SkOpContour.cpp b/src/pathops/SkOpContour.cpp index df65437c97..ed533887e5 100644 --- a/src/pathops/SkOpContour.cpp +++ b/src/pathops/SkOpContour.cpp @@ -10,18 +10,18 @@ #include "SkReduceOrder.h" #include "SkTSort.h" -SkOpSegment* SkOpContour::addCurve(SkPath::Verb verb, const SkPoint pts[4], - SkChunkAlloc* allocator) { +SkOpSegment* SkOpContour::addCurve(SkPath::Verb verb, const SkPoint pts[4]) { + SkChunkAlloc* allocator = this->globalState()->allocator(); switch (verb) { case SkPath::kLine_Verb: { SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 2); memcpy(ptStorage, pts, sizeof(SkPoint) * 2); - return appendSegment(allocator).addLine(ptStorage, this); + return appendSegment().addLine(ptStorage, this); } break; case SkPath::kQuad_Verb: { SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 3); memcpy(ptStorage, pts, sizeof(SkPoint) * 3); - return appendSegment(allocator).addQuad(ptStorage, this); + return appendSegment().addQuad(ptStorage, this); } break; case SkPath::kConic_Verb: { SkASSERT(0); // the original curve is a cubic, which will never reduce to a conic @@ -29,7 +29,7 @@ SkOpSegment* SkOpContour::addCurve(SkPath::Verb verb, const SkPoint pts[4], case SkPath::kCubic_Verb: { SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 4); memcpy(ptStorage, pts, sizeof(SkPoint) * 4); - return appendSegment(allocator).addCubic(ptStorage, this); + return appendSegment().addCubic(ptStorage, this); } break; default: SkASSERT(0); diff --git a/src/pathops/SkOpContour.h b/src/pathops/SkOpContour.h index b9f0b85af5..0db6eabdfe 100644 --- a/src/pathops/SkOpContour.h +++ b/src/pathops/SkOpContour.h @@ -11,7 +11,6 @@ #include "SkTDArray.h" #include "SkTSort.h" -class SkChunkAlloc; enum class SkOpRayDir; struct SkOpRayHit; class SkPathWriter; @@ -30,47 +29,31 @@ public: bool operator<(const SkOpContour& rh) const { return fBounds.fTop == rh.fBounds.fTop - ? fBounds.fLeft < rh.fBounds.fLeft - : fBounds.fTop < rh.fBounds.fTop; + ? fBounds.fLeft < rh.fBounds.fLeft + : fBounds.fTop < rh.fBounds.fTop; } - void addAlignIntersections(SkOpContourHead* contourList, SkChunkAlloc* allocator) { - SkASSERT(fCount > 0); - SkOpSegment* segment = &fHead; - do { - segment->addAlignIntersections(contourList, allocator); - } while ((segment = segment->next())); - } - - void addConic(SkPoint pts[3], SkScalar weight, SkChunkAlloc* allocator) { - appendSegment(allocator).addConic(pts, weight, this); + void addConic(SkPoint pts[3], SkScalar weight) { + appendSegment().addConic(pts, weight, this); } - void addCubic(SkPoint pts[4], SkChunkAlloc* allocator) { - appendSegment(allocator).addCubic(pts, this); + void addCubic(SkPoint pts[4]) { + appendSegment().addCubic(pts, this); } - SkOpSegment* addCurve(SkPath::Verb verb, const SkPoint pts[4], SkChunkAlloc* allocator); + SkOpSegment* addCurve(SkPath::Verb verb, const SkPoint pts[4]); - void addLine(SkPoint pts[2], SkChunkAlloc* allocator) { - appendSegment(allocator).addLine(pts, this); + SkOpSegment* addLine(SkPoint pts[2]) { + return appendSegment().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())); + void addQuad(SkPoint pts[3]) { + appendSegment().addQuad(pts, this); } - SkOpSegment& appendSegment(SkChunkAlloc* allocator) { + SkOpSegment& appendSegment() { SkOpSegment* result = fCount++ - ? SkOpTAllocator<SkOpSegment>::Allocate(allocator) : &fHead; + ? SkOpTAllocator<SkOpSegment>::Allocate(this->globalState()->allocator()) : &fHead; result->setPrev(fTail); if (fTail) { fTail->setNext(result); @@ -79,8 +62,8 @@ public: return *result; } - SkOpContour* appendContour(SkChunkAlloc* allocator) { - SkOpContour* contour = SkOpTAllocator<SkOpContour>::New(allocator); + SkOpContour* appendContour() { + SkOpContour* contour = SkOpTAllocator<SkOpContour>::New(this->globalState()->allocator()); contour->setNext(nullptr); SkOpContour* prev = this; SkOpContour* next; @@ -90,16 +73,16 @@ public: prev->setNext(contour); return contour; } - + const SkPathOpsBounds& bounds() const { return fBounds; } - void calcAngles(SkChunkAlloc* allocator) { + void calcAngles() { SkASSERT(fCount > 0); SkOpSegment* segment = &fHead; do { - segment->calcAngles(allocator); + segment->calcAngles(); } while ((segment = segment->next())); } @@ -132,14 +115,21 @@ public: return SkDEBUGRELEASE(this->globalState()->debugAngle(id), nullptr); } + const SkOpCoincidence* debugCoincidence() const { + return this->globalState()->coincidence(); + } + +#if DEBUG_COINCIDENCE_VERBOSE void debugCheckHealth(const char* id, SkPathOpsDebug::GlitchLog* ) const; +#endif SkOpContour* debugContour(int id) { return SkDEBUGRELEASE(this->globalState()->debugContour(id), nullptr); } - void debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log, - const SkOpCoincidence* coincidence) const; +#if DEBUG_COINCIDENCE_VERBOSE + void debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log) const; +#endif const SkOpPtT* debugPtT(int id) const { return SkDEBUGRELEASE(this->globalState()->debugPtT(id), nullptr); @@ -154,7 +144,7 @@ public: } SkOpGlobalState* globalState() const { - return fState; + return fState; } void debugValidate() const { @@ -197,15 +187,6 @@ public: return fTail->pts()[SkPathOpsVerbToPoints(fTail->verb())]; } - bool findCollapsed() { - SkASSERT(fCount > 0); - SkOpSegment* segment = &fHead; - do { - segment->findCollapsed(); - } while ((segment = segment->next())); - return true; - } - SkOpSpan* findSortableTop(SkOpContour* ); SkOpSegment* first() { @@ -237,14 +218,15 @@ public: return fXor; } - void markDone() { + void markAllDone() { SkOpSegment* segment = &fHead; do { segment->markAllDone(); } while ((segment = segment->next())); } - bool missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) { + // Please keep this aligned with debugMissingCoincidence() + bool missingCoincidence() { SkASSERT(fCount > 0); SkOpSegment* segment = &fHead; bool result = false; @@ -253,7 +235,7 @@ public: #if DEBUG_ANGLE segment->debugCheckAngleCoin(); #endif - } else if (segment->missingCoincidence(coincidences, allocator)) { + } else if (segment->missingCoincidence()) { result = true; // FIXME: trying again loops forever in issue3651_6 // The continue below is speculative -- once there's an actual case that requires it, diff --git a/src/pathops/SkOpEdgeBuilder.cpp b/src/pathops/SkOpEdgeBuilder.cpp index 617ca76c82..95152a7ffa 100644 --- a/src/pathops/SkOpEdgeBuilder.cpp +++ b/src/pathops/SkOpEdgeBuilder.cpp @@ -36,9 +36,9 @@ int SkOpEdgeBuilder::count() const { return count; } -bool SkOpEdgeBuilder::finish(SkChunkAlloc* allocator) { +bool SkOpEdgeBuilder::finish() { fOperand = false; - if (fUnparseable || !walk(allocator)) { + if (fUnparseable || !walk()) { return false; } complete(); @@ -162,7 +162,7 @@ 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; @@ -183,20 +183,20 @@ bool SkOpEdgeBuilder::walk(SkChunkAlloc* allocator) { } } if (!fCurrentContour) { - fCurrentContour = fContoursHead->appendContour(allocator); + fCurrentContour = fContoursHead->appendContour(); } 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::kConic_Verb: - fCurrentContour->addConic(pointsPtr, *weightPtr++, fAllocator); + fCurrentContour->addConic(pointsPtr, *weightPtr++); break; case SkPath::kCubic_Verb: { // Split complex cubics (such as self-intersecting curves or @@ -221,13 +221,13 @@ bool SkOpEdgeBuilder::walk(SkChunkAlloc* allocator) { for (int index = 0; index < SkPathOpsVerbToPoints(v2); ++index) { force_small_to_zero(&curve2[index]); } - fCurrentContour->addCurve(v1, curve1, fAllocator); - fCurrentContour->addCurve(v2, curve2, fAllocator); + fCurrentContour->addCurve(v1, curve1); + fCurrentContour->addCurve(v2, curve2); } else { - fCurrentContour->addCubic(pointsPtr, fAllocator); + fCurrentContour->addCubic(pointsPtr); } } else { - fCurrentContour->addCubic(pointsPtr, fAllocator); + fCurrentContour->addCubic(pointsPtr); } } break; case SkPath::kClose_Verb: diff --git a/src/pathops/SkOpEdgeBuilder.h b/src/pathops/SkOpEdgeBuilder.h index f11e0e4c89..1a78a2137e 100644 --- a/src/pathops/SkOpEdgeBuilder.h +++ b/src/pathops/SkOpEdgeBuilder.h @@ -12,20 +12,16 @@ class SkOpEdgeBuilder { public: - SkOpEdgeBuilder(const SkPathWriter& path, SkOpContour* contours2, SkChunkAlloc* allocator, - SkOpGlobalState* globalState) - : fAllocator(allocator) // FIXME: replace with const, tune this - , fGlobalState(globalState) + SkOpEdgeBuilder(const SkPathWriter& path, SkOpContour* contours2, SkOpGlobalState* globalState) + : fGlobalState(globalState) , fPath(path.nativePath()) , fContoursHead(contours2) , fAllowOpenContours(true) { init(); } - SkOpEdgeBuilder(const SkPath& path, SkOpContour* contours2, SkChunkAlloc* allocator, - SkOpGlobalState* globalState) - : fAllocator(allocator) - , fGlobalState(globalState) + SkOpEdgeBuilder(const SkPath& path, SkOpContour* contours2, SkOpGlobalState* globalState) + : fGlobalState(globalState) , fPath(&path) , fContoursHead(contours2) , fAllowOpenContours(false) { @@ -42,7 +38,7 @@ public: } int count() const; - bool finish(SkChunkAlloc* ); + bool finish(); const SkOpContour* head() const { return fContoursHead; @@ -56,9 +52,8 @@ 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; diff --git a/src/pathops/SkOpSegment.cpp b/src/pathops/SkOpSegment.cpp index 41d62369b6..e4e00bbfab 100644 --- a/src/pathops/SkOpSegment.cpp +++ b/src/pathops/SkOpSegment.cpp @@ -9,6 +9,8 @@ #include "SkOpSegment.h" #include "SkPathWriter.h" +#define FAIL_IF(cond) do { if (cond) return false; } while (false) + /* After computing raw intersections, post process all segments to: - find small collections of points that can be collapsed to a single point @@ -159,90 +161,10 @@ bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sum return result; } -void SkOpSegment::addAlignIntersection(SkOpPtT& endPtT, SkPoint& oldPt, - SkOpContourHead* contourList, SkChunkAlloc* allocator) { - const SkPoint& newPt = endPtT.fPt; - if (newPt == oldPt) { - return; - } - SkPoint line[2] = { newPt, oldPt }; - SkPathOpsBounds lineBounds; - lineBounds.setBounds(line, 2); - SkDLine aLine; - aLine.set(line); - SkOpContour* current = contourList; - do { - if (!SkPathOpsBounds::Intersects(current->bounds(), lineBounds)) { - continue; - } - SkOpSegment* segment = current->first(); - do { - if (!SkPathOpsBounds::Intersects(segment->bounds(), lineBounds)) { - continue; - } - if (newPt == segment->fPts[0]) { - continue; - } - if (newPt == segment->fPts[SkPathOpsVerbToPoints(segment->fVerb)]) { - continue; - } - if (oldPt == segment->fPts[0]) { - continue; - } - if (oldPt == segment->fPts[SkPathOpsVerbToPoints(segment->fVerb)]) { - continue; - } - if (endPtT.contains(segment)) { - continue; - } - SkIntersections i; - switch (segment->fVerb) { - case SkPath::kLine_Verb: { - SkDLine bLine; - bLine.set(segment->fPts); - i.intersect(bLine, aLine); - } break; - case SkPath::kQuad_Verb: { - SkDQuad bQuad; - bQuad.set(segment->fPts); - i.intersect(bQuad, aLine); - } break; - case SkPath::kConic_Verb: { - SkDConic bConic; - bConic.set(segment->fPts, segment->fWeight); - i.intersect(bConic, aLine); - } break; - case SkPath::kCubic_Verb: { - SkDCubic bCubic; - bCubic.set(segment->fPts); - i.intersect(bCubic, aLine); - } break; - default: - SkASSERT(0); - } - if (i.used()) { - SkASSERT(i.used() == 1); - SkASSERT(!zero_or_one(i[0][0])); - SkOpSpanBase* checkSpan = fHead.next(); - while (!checkSpan->final()) { - if (checkSpan->contains(segment)) { - goto nextSegment; - } - checkSpan = checkSpan->upCast()->next(); - } - SkOpPtT* ptT = segment->addT(i[0][0], SkOpSegment::kAllowAlias, allocator); - ptT->fPt = newPt; - endPtT.addOpp(ptT); - } - nextSegment: - ; - } while ((segment = segment->next())); - } while ((current = current->next())); -} - bool SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, SkPathWriter* path) const { if (start->starter(end)->alreadyAdded()) { + SkDEBUGF(("same curve added twice aborted pathops\n")); return false; } SkOpCurve edge; @@ -298,29 +220,57 @@ bool SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, return true; } -SkOpPtT* SkOpSegment::addMissing(double t, SkOpSegment* opp, SkChunkAlloc* allocator) { - SkOpSpanBase* existing = nullptr; - SkOpSpanBase* test = &fHead; - double testT; +const SkOpPtT* SkOpSegment::existing(double t, const SkOpSegment* opp) const { + const SkOpSpanBase* test = &fHead; + const SkOpPtT* testPtT; + SkPoint pt = this->ptAtT(t); do { - if ((testT = test->ptT()->fT) >= t) { - if (testT == t) { - existing = test; - } + testPtT = test->ptT(); + if (testPtT->fT == t) { break; } + if (!this->match(testPtT, this, t, pt, opp ? kAllowAliasMatch : kNoAliasMatch)) { + if (t < testPtT->fT) { + return nullptr; + } + continue; + } + if (!opp) { + return testPtT; + } + const SkOpPtT* loop = testPtT->next(); + while (loop != testPtT) { + if (loop->segment() == this && loop->fT == t && loop->fPt == pt) { + goto foundMatch; + } + loop = loop->next(); + } + return nullptr; } while ((test = test->upCast()->next())); - SkOpPtT* result; - if (existing && existing->contains(opp)) { - result = existing->ptT(); - } else { - result = this->addT(t, SkOpSegment::kNoAlias, allocator); +foundMatch: + return opp && !test->contains(opp) ? nullptr : testPtT; +} + +// break the span so that the coincident part does not change the angle of the remainder +bool SkOpSegment::addExpanded(double newT, const SkOpSpanBase* test, bool* startOver) { + if (this->contains(newT)) { + return true; } - SkASSERT(result); - return result; + SkOpPtT* newPtT = this->addT(newT, kAllowAliasMatch, startOver); + if (!newPtT) { + return false; + } + newPtT->fPt = this->ptAtT(newT); + // const cast away to change linked list; pt/t values stays unchanged + SkOpSpanBase* writableTest = const_cast<SkOpSpanBase*>(test); + if (writableTest->ptT()->addOpp(newPtT)) { + writableTest->checkForCollapsedCoincidence(); + } + return true; } -SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* allocator) { +// Please keep this in sync with debugAddT() +SkOpPtT* SkOpSegment::addT(double t, AliasMatch allowAlias, bool* allocated) { debugValidate(); SkPoint pt = this->ptAtT(t); SkOpSpanBase* span = &fHead; @@ -331,9 +281,9 @@ SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* alloca if (t == result->fT) { goto bumpSpan; } - if (this->match(result, this, t, pt)) { + if (this->match(result, this, t, pt, allowAlias)) { // see if any existing alias matches segment, pt, and t - loop = result->next(); + loop = result->next(); duplicatePt = false; while (loop != result) { bool ptMatch = loop->fPt == pt; @@ -343,12 +293,12 @@ SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* alloca duplicatePt |= ptMatch; loop = loop->next(); } - if (kNoAlias == allowAlias) { + if (kNoAliasMatch == allowAlias) { bumpSpan: span->bumpSpanAdds(); return result; } - SkOpPtT* alias = SkOpTAllocator<SkOpPtT>::Allocate(allocator); + SkOpPtT* alias = SkOpTAllocator<SkOpPtT>::Allocate(this->globalState()->allocator()); alias->init(result->span(), t, pt, duplicatePt); result->insert(alias); result->span()->unaligned(); @@ -358,6 +308,9 @@ SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* alloca alias->segment()->debugID(), alias->span()->debugID()); #endif span->bumpSpanAdds(); + if (allocated) { + *allocated = true; + } return alias; } if (t < result->fT) { @@ -365,7 +318,7 @@ SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* alloca if (!prev) { return nullptr; } - SkOpSpan* span = insert(prev, allocator); + SkOpSpan* span = insert(prev); span->init(this, prev, t, pt); this->debugValidate(); #if DEBUG_ADD_T @@ -373,6 +326,9 @@ SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* alloca span->segment()->debugID(), span->debugID()); #endif span->bumpSpanAdds(); + if (allocated) { + *allocated = true; + } return span->ptT(); } SkASSERT(span != &fTail); @@ -381,43 +337,17 @@ SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* alloca return nullptr; } -// 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]); - } - while ((span = span->upCast()->next())) { - if (span == &fTail) { - break; - } - span->align(); - } - if (!span->aligned()) { - span->alignEnd(1, fPts[SkPathOpsVerbToPoints(fVerb)]); - } - if (this->collapsed()) { - SkOpSpan* span = &fHead; - do { - span->setWindValue(0); - span->setOppValue(0); - this->markDone(span); - } while ((span = span->next()->upCastable())); - } - debugValidate(); -} - -void SkOpSegment::calcAngles(SkChunkAlloc* allocator) { +void SkOpSegment::calcAngles() { bool activePrior = !fHead.isCanceled(); if (activePrior && !fHead.simple()) { - addStartSpan(allocator); + addStartSpan(); } SkOpSpan* prior = &fHead; SkOpSpanBase* spanBase = fHead.next(); while (spanBase != &fTail) { if (activePrior) { - SkOpAngle* priorAngle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); + SkOpAngle* priorAngle = SkOpTAllocator<SkOpAngle>::Allocate( + this->globalState()->allocator()); priorAngle->set(spanBase, prior); spanBase->setFromAngle(priorAngle); } @@ -425,7 +355,8 @@ void SkOpSegment::calcAngles(SkChunkAlloc* allocator) { bool active = !span->isCanceled(); SkOpSpanBase* next = span->next(); if (active) { - SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); + SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate( + this->globalState()->allocator()); angle->set(span, next); span->setToAngle(angle); } @@ -434,12 +365,32 @@ void SkOpSegment::calcAngles(SkChunkAlloc* allocator) { spanBase = next; } if (activePrior && !fTail.simple()) { - addEndSpan(allocator); + addEndSpan(); } } +// Please keep this in sync with debugClearAll() +void SkOpSegment::clearAll() { + SkOpSpan* span = &fHead; + do { + this->clearOne(span); + } while ((span = span->next()->upCastable())); + this->globalState()->coincidence()->release(this); +} + +// Please keep this in sync with debugClearOne() +void SkOpSegment::clearOne(SkOpSpan* span) { + span->setWindValue(0); + span->setOppValue(0); + this->markDone(span); +} + +// Quads and conics collapse if the end points are the same, because +// the curve doesn't enclose an area. bool SkOpSegment::collapsed() const { - return fVerb == SkPath::kLine_Verb && fHead.pt() == fTail.pt(); + // FIXME: cubics can have also collapsed -- need to check if the + // control points are on a line with the end points + return fVerb < SkPath::kCubic_Verb && fHead.pt() == fTail.pt(); } void SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, @@ -571,12 +522,26 @@ int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end, return start->starter(end)->windSum(); } +bool SkOpSegment::contains(double newT) const { + const SkOpSpanBase* spanBase = &fHead; + do { + if (spanBase->ptT()->contains(this, newT)) { + return true; + } + if (spanBase == &fTail) { + break; + } + spanBase = spanBase->upCast()->next(); + } while (true); + return false; +} + void SkOpSegment::release(const SkOpSpan* span) { if (span->done()) { --fDoneCount; } --fCount; - SkASSERT(fCount >= fDoneCount); + SkASSERT(this->globalState()->debugSkipAssert() || fCount >= fDoneCount); } double SkOpSegment::distSq(double t, const SkOpAngle* oppAngle) const { @@ -601,15 +566,6 @@ double SkOpSegment::distSq(double t, const SkOpAngle* oppAngle) const { return closestDistSq; } -void SkOpSegment::findCollapsed() { - if (fHead.contains(&fTail)) { - markAllDone(); - // move start and end to the same point - fHead.alignEnd(0, fHead.pt()); - fTail.setAligned(); - } -} - /* The M and S variable name parts stand for the operators. Mi stands for Minuend (see wiki subtraction, analogous to difference) @@ -887,14 +843,12 @@ SkOpSegment* SkOpSegment::findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** n } SkOpGlobalState* SkOpSegment::globalState() const { - return contour()->globalState(); + return contour()->globalState(); } void SkOpSegment::init(SkPoint pts[], SkScalar weight, SkOpContour* contour, SkPath::Verb verb) { fContour = contour; fNext = nullptr; - fOriginal[0] = pts[0]; - fOriginal[1] = pts[SkPathOpsVerbToPoints(verb)]; fPts = pts; fWeight = weight; fVerb = verb; @@ -1095,15 +1049,17 @@ bool SkOpSegment::markWinding(SkOpSpan* span, int winding, int oppWinding) { } 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; + const SkPoint& testPt, AliasMatch aliasMatch) const { + SkASSERT(this == base->segment()); + if (this == testParent) { + if (precisely_equal(base->fT, testT)) { + return true; + } } if (!SkDPoint::ApproximatelyEqual(testPt, base->fPt)) { return false; } - return !this->ptsDisjoint(base->fT, base->fPt, testT, testPt); + return this != testParent || !this->ptsDisjoint(base->fT, base->fPt, testT, testPt); } static SkOpSegment* set_last(SkOpSpanBase** last, SkOpSpanBase* endSpan) { @@ -1178,7 +1134,8 @@ SkOpSegment* SkOpSegment::nextChase(SkOpSpanBase** startPtr, int* stepPtr, SkOpS return other; } -static void clear_visited(SkOpSpanBase* span) { +// Please keep this in sync with DebugClearVisited() +void SkOpSegment::ClearVisited(SkOpSpanBase* span) { // reset visited flag back to false do { SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; @@ -1189,20 +1146,23 @@ static void clear_visited(SkOpSpanBase* span) { } while (!span->final() && (span = span->upCast()->next())); } +// Please keep this in sync with debugMissingCoincidence() // 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 -bool SkOpSegment::missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) { - if (this->verb() != SkPath::kLine_Verb) { - return false; - } +// Even though pairs of curves correct detect coincident runs, a run may be missed +// if the coincidence is a product of multiple intersections. For instance, given +// curves A, B, and C: +// A-B intersect at a point 1; A-C and B-C intersect at point 2, so near +// the end of C that the intersection is replaced with the end of C. +// Even though A-B correctly do not detect an intersection at point 2, +// the resulting run from point 1 to point 2 is coincident on A and B. +bool SkOpSegment::missingCoincidence() { if (this->done()) { return false; } SkOpSpan* prior = nullptr; SkOpSpanBase* spanBase = &fHead; + bool result = false; do { SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; SkASSERT(ptT->span() == spanBase); @@ -1211,9 +1171,6 @@ bool SkOpSegment::missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc continue; } SkOpSegment* opp = ptT->span()->segment(); -// if (opp->verb() == SkPath::kLine_Verb) { -// continue; -// } if (opp->done()) { continue; } @@ -1224,18 +1181,18 @@ bool SkOpSegment::missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc if (spanBase == &fHead) { continue; } + if (ptT->segment() == this) { + continue; + } SkOpSpan* span = spanBase->upCastable(); // FIXME?: this assumes that if the opposite segment is coincident then no more // coincidence needs to be detected. This may not be true. - if (span && span->containsCoincidence(opp)) { - continue; - } - if (spanBase->segment() == opp) { + if (span && span->containsCoincidence(opp)) { continue; } if (spanBase->containsCoinEnd(opp)) { continue; - } + } SkOpPtT* priorPtT = nullptr, * priorStopPtT; // find prior span containing opp segment SkOpSegment* priorOpp = nullptr; @@ -1268,28 +1225,28 @@ bool SkOpSegment::missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc SkTSwap(priorPtT, ptT); SkTSwap(oppStart, oppEnd); } - bool flipped = oppStart->fT > oppEnd->fT; - bool coincident = false; - if (coincidences->contains(priorPtT, ptT, oppStart, oppEnd, flipped)) { + SkOpCoincidence* coincidences = this->globalState()->coincidence(); + SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); + SkOpPtT* rootPtT = ptT->span()->ptT(); + SkOpPtT* rootOppStart = oppStart->span()->ptT(); + SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); + if (coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { goto swapBack; } - if (opp->verb() == SkPath::kLine_Verb) { - coincident = (SkDPoint::ApproximatelyEqual(priorPtT->fPt, oppStart->fPt) || - SkDPoint::ApproximatelyEqual(priorPtT->fPt, oppEnd->fPt)) && - (SkDPoint::ApproximatelyEqual(ptT->fPt, oppStart->fPt) || - SkDPoint::ApproximatelyEqual(ptT->fPt, oppEnd->fPt)); - } - if (!coincident) { - coincident = testForCoincidence(priorPtT, ptT, prior, spanBase, opp, 5000); - } - if (coincident) { + if (this->testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { // mark coincidence - if (!coincidences->extend(priorPtT, ptT, oppStart, oppEnd) - && !coincidences->extend(oppStart, oppEnd, priorPtT, ptT)) { - coincidences->add(priorPtT, ptT, oppStart, oppEnd, allocator); +#if DEBUG_COINCIDENCE_VERBOSE + SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, + rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), + rootOppEnd->debugID()); +#endif + if (!coincidences->extend(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { + coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); } - clear_visited(&fHead); - return true; +#if DEBUG_COINCIDENCE + SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)); +#endif + result = true; } swapBack: if (swapped) { @@ -1297,19 +1254,18 @@ bool SkOpSegment::missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc } } } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); - clear_visited(&fHead); - return false; + ClearVisited(&fHead); + return result; } +// please keep this in sync with debugMoveMultiples() // if a span has more than one intersection, merge the other segments' span as needed bool SkOpSegment::moveMultiples() { debugValidate(); SkOpSpanBase* test = &fHead; do { int addCount = test->spanAddsCount(); - if (addCount < 1) { - return false; - } + FAIL_IF(addCount < 1); if (addCount == 1) { continue; } @@ -1392,66 +1348,126 @@ bool SkOpSegment::moveMultiples() { oppSegment->debugValidate(); goto checkNextSpan; } - tryNextSpan: + tryNextSpan: ; } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); } while ((testPtT = testPtT->next()) != startPtT); -checkNextSpan: +checkNextSpan: ; } while ((test = test->final() ? nullptr : test->upCast()->next())); debugValidate(); return true; } +// adjacent spans may have points close by +bool SkOpSegment::spansNearby(const SkOpSpanBase* refSpan, const SkOpSpanBase* checkSpan) const { + const SkOpPtT* refHead = refSpan->ptT(); + const SkOpPtT* checkHead = checkSpan->ptT(); +// if the first pt pair from adjacent spans are far apart, assume that all are far enough apart + if (!SkDPoint::RoughlyEqual(refHead->fPt, checkHead->fPt)) { +#if DEBUG_COINCIDENCE + // verify that no combination of points are close + const SkOpPtT* dBugRef = refHead; + do { + const SkOpPtT* dBugCheck = checkHead; + do { + SkASSERT(!SkDPoint::ApproximatelyEqual(dBugRef->fPt, dBugCheck->fPt)); + dBugCheck = dBugCheck->next(); + } while (dBugCheck != checkHead); + dBugRef = dBugRef->next(); + } while (dBugRef != refHead); +#endif + return false; + } + // check only unique points + SkScalar distSqBest = SK_ScalarMax; + const SkOpPtT* refBest = nullptr; + const SkOpPtT* checkBest = nullptr; + const SkOpPtT* ref = refHead; + do { + if (ref->deleted()) { + continue; + } + while (ref->ptAlreadySeen(refHead)) { + ref = ref->next(); + if (ref == refHead) { + goto doneCheckingDistance; + } + } + const SkOpPtT* check = checkHead; + const SkOpSegment* refSeg = ref->segment(); + do { + if (check->deleted()) { + continue; + } + while (check->ptAlreadySeen(checkHead)) { + check = check->next(); + if (check == checkHead) { + goto nextRef; + } + } + SkScalar distSq = ref->fPt.distanceToSqd(check->fPt); + if (distSqBest > distSq && (refSeg != check->segment() + || !refSeg->ptsDisjoint(*ref, *check))) { + distSqBest = distSq; + refBest = ref; + checkBest = check; + } + } while ((check = check->next()) != checkHead); +nextRef: + ; + } while ((ref = ref->next()) != refHead); +doneCheckingDistance: + return checkBest && refBest->segment()->match(refBest, checkBest->segment(), checkBest->fT, + checkBest->fPt, kAllowAliasMatch); +} + +// Please keep this function in sync with debugMoveNearby() // Move nearby t values and pts so they all hang off the same span. Alignment happens later. void SkOpSegment::moveNearby() { debugValidate(); - SkOpSpanBase* spanS = &fHead; + // release undeleted spans pointing to this seg that are linked to the primary span + SkOpSpanBase* spanBase = &fHead; do { - SkOpSpanBase* test = spanS->upCast()->next(); - SkOpSpanBase* next; - if (spanS->contains(test)) { - if (!test->final()) { - test->upCast()->release(spanS->ptT()); - continue; - } else if (spanS != &fHead) { - spanS->upCast()->release(test->ptT()); - spanS = test; - continue; + SkOpPtT* ptT = spanBase->ptT(); + const SkOpPtT* headPtT = ptT; + while ((ptT = ptT->next()) != headPtT) { + SkOpSpanBase* test = ptT->span(); + if (ptT->segment() == this && !ptT->deleted() && test != spanBase + && test->ptT() == ptT) { + if (test->final()) { + if (spanBase == &fHead) { + this->clearAll(); + return; + } + spanBase->upCast()->release(ptT); + } else if (test->prev()) { + test->upCast()->release(headPtT); + } + break; } } - do { // iterate through all spans associated with start - SkOpPtT* startBase = spanS->ptT(); - next = test->final() ? nullptr : 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; // if this span has collapsed, remove it from parent - } - this->fTail.merge(spanS->upCast()); - debugValidate(); - return; - } - spanS->merge(test->upCast()); - 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()); + spanBase = spanBase->upCast()->next(); + } while (!spanBase->final()); + + // This loop looks for adjacent spans which are near by + spanBase = &fHead; + do { // iterate through all spans associated with start + SkOpSpanBase* test = spanBase->upCast()->next(); + if (this->spansNearby(spanBase, test)) { + if (test->final()) { + if (spanBase->prev()) { + test->merge(spanBase->upCast()); + } else { + this->clearAll(); + return; + } + } else { + spanBase->merge(test->upCast()); + } + } + spanBase = test; + } while (!spanBase->final()); debugValidate(); } @@ -1679,8 +1695,7 @@ bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, } bool SkOpSegment::testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT, - const SkOpSpanBase* prior, const SkOpSpanBase* spanBase, const SkOpSegment* opp, - SkScalar flatnessLimit) const { + const SkOpSpanBase* prior, const SkOpSpanBase* spanBase, const SkOpSegment* opp) const { // average t, find mid pt double midT = (prior->t() + spanBase->t()) / 2; SkPoint midPt = this->ptAtT(midT); @@ -1688,22 +1703,28 @@ bool SkOpSegment::testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT // 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)) { + if (priorPtT->span() == ptT->span()) { + return false; + } coincident = false; SkIntersections i; - SkVector dxdy = (*CurveSlopeAtT[fVerb])(this->pts(), this->weight(), midT); - SkDLine ray = {{{midPt.fX, midPt.fY}, - {(double) midPt.fX + dxdy.fY, (double) midPt.fY - dxdy.fX}}}; - (*CurveIntersectRay[opp->verb()])(opp->pts(), opp->weight(), ray, &i); + SkDCurve curvePart; + this->subDivide(prior, spanBase, &curvePart); + SkDVector dxdy = (*CurveDDSlopeAtT[fVerb])(curvePart, 0.5f); + SkDPoint partMidPt = (*CurveDDPointAtT[fVerb])(curvePart, 0.5f); + SkDLine ray = {{{midPt.fX, midPt.fY}, {partMidPt.fX + dxdy.fY, partMidPt.fY - dxdy.fX}}}; + SkDCurve oppPart; + opp->subDivide(priorPtT->span(), ptT->span(), &oppPart); + (*CurveDIntersectRay[opp->verb()])(oppPart, ray, &i); // measure distance and see if it's small enough to denote coincidence for (int index = 0; index < i.used(); ++index) { + if (!between(0, i[0][index], 1)) { + continue; + } SkDPoint oppPt = i.pt(index); if (oppPt.approximatelyEqual(midPt)) { - SkVector oppDxdy = (*CurveSlopeAtT[opp->verb()])(opp->pts(), - opp->weight(), i[index][0]); - oppDxdy.normalize(); - dxdy.normalize(); - SkScalar flatness = SkScalarAbs(dxdy.cross(oppDxdy) / FLT_EPSILON); - coincident |= flatness < flatnessLimit; + // the coincidence can occur at almost any angle + coincident = true; } } } diff --git a/src/pathops/SkOpSegment.h b/src/pathops/SkOpSegment.h index 1d67b1c131..55e67a5d80 100644 --- a/src/pathops/SkOpSegment.h +++ b/src/pathops/SkOpSegment.h @@ -23,9 +23,9 @@ class SkPathWriter; class SkOpSegment { public: - enum AllowAlias { - kAllowAlias, - kNoAlias + enum AliasMatch { + kNoAliasMatch, + kAllowAliasMatch, }; bool operator<(const SkOpSegment& rh) const { @@ -45,13 +45,6 @@ public: bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end); bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding); - void addAlignIntersection(SkOpPtT& endPtT, SkPoint& oldPt, - SkOpContourHead* contourList, SkChunkAlloc* allocator); - - void addAlignIntersections(SkOpContourHead* contourList, SkChunkAlloc* allocator) { - this->addAlignIntersection(*fHead.ptT(), fOriginal[0], contourList, allocator); - this->addAlignIntersection(*fTail.ptT(), fOriginal[1], contourList, allocator); - } SkOpSegment* addConic(SkPoint pts[3], SkScalar weight, SkOpContour* parent) { init(pts, weight, parent, SkPath::kConic_Verb); @@ -71,23 +64,25 @@ public: bool addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, SkPathWriter* path) const; - SkOpAngle* addEndSpan(SkChunkAlloc* allocator) { - SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); + SkOpAngle* addEndSpan() { + SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(this->globalState()->allocator()); angle->set(&fTail, fTail.prev()); fTail.setFromAngle(angle); return angle; } + bool addExpanded(double newT, const SkOpSpanBase* test, bool* startOver); + SkOpSegment* addLine(SkPoint pts[2], SkOpContour* parent) { init(pts, 1, parent, SkPath::kLine_Verb); fBounds.set(pts, 2); return this; } - SkOpPtT* addMissing(double t, SkOpSegment* opp, SkChunkAlloc* ); + SkOpPtT* addMissing(double t, SkOpSegment* opp, bool* allExist); - SkOpAngle* addStartSpan(SkChunkAlloc* allocator) { - SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); + SkOpAngle* addStartSpan() { + SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(this->globalState()->allocator()); angle->set(&fHead, fHead.next()); fHead.setToAngle(angle); return angle; @@ -101,9 +96,11 @@ public: return this; } - SkOpPtT* addT(double t, AllowAlias , SkChunkAlloc* ); + SkOpPtT* addT(double t, AliasMatch, bool* allocated); - void align(); + template<typename T> T* allocateArray(int count) { + return SkOpTAllocator<T>::AllocateArray(this->globalState()->allocator(), count); + } const SkPathOpsBounds& bounds() const { return fBounds; @@ -113,7 +110,7 @@ public: ++fCount; } - void calcAngles(SkChunkAlloc*); + void calcAngles(); bool collapsed() const; static void ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, SkOpAngle::IncludeType ); @@ -121,6 +118,11 @@ public: SkOpAngle::IncludeType ); int computeSum(SkOpSpanBase* start, SkOpSpanBase* end, SkOpAngle::IncludeType includeType); + void clearAll(); + void clearOne(SkOpSpan* span); + static void ClearVisited(SkOpSpanBase* span); + bool contains(double t) const; + SkOpContour* contour() const { return fContour; } @@ -129,36 +131,30 @@ public: return fCount; } - void debugAddAngle(double startT, double endT, SkChunkAlloc*); - void debugAddAlignIntersection(const char* id, SkPathOpsDebug::GlitchLog* glitches, - const SkOpPtT& endPtT, const SkPoint& oldPt, - const SkOpContourHead* ) const; - - void debugAddAlignIntersections(const char* id, SkPathOpsDebug::GlitchLog* glitches, - SkOpContourHead* contourList) const { - this->debugAddAlignIntersection(id, glitches, *fHead.ptT(), fOriginal[0], contourList); - this->debugAddAlignIntersection(id, glitches, *fTail.ptT(), fOriginal[1], contourList); - } - - bool debugAddMissing(double t, const SkOpSegment* opp) const; - void debugAlign(const char* id, SkPathOpsDebug::GlitchLog* glitches) const; + void debugAddAngle(double startT, double endT); + const SkOpPtT* debugAddT(double t, AliasMatch , bool* allocated) const; const SkOpAngle* debugAngle(int id) const; #if DEBUG_ANGLE void debugCheckAngleCoin() const; #endif +#if DEBUG_COINCIDENCE_VERBOSE void debugCheckHealth(const char* id, SkPathOpsDebug::GlitchLog* ) const; + void debugClearAll(const char* id, SkPathOpsDebug::GlitchLog* glitches) const; + void debugClearOne(const SkOpSpan* span, const char* id, SkPathOpsDebug::GlitchLog* glitches) const; +#endif + const SkOpCoincidence* debugCoincidence() const; SkOpContour* debugContour(int id); - void debugFindCollapsed(const char* id, SkPathOpsDebug::GlitchLog* glitches) const; int debugID() const { return SkDEBUGRELEASE(fID, -1); } SkOpAngle* debugLastAngle(); - void debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* glitches, - const SkOpCoincidence* coincidences) const; +#if DEBUG_COINCIDENCE_VERBOSE + void debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* glitches) const; void debugMoveMultiples(const char* id, SkPathOpsDebug::GlitchLog* glitches) const; void debugMoveNearby(const char* id, SkPathOpsDebug::GlitchLog* glitches) const; +#endif const SkOpPtT* debugPtT(int id) const; void debugReset(); const SkOpSegment* debugSegment(int id) const; @@ -173,11 +169,24 @@ public: const SkOpSpanBase* debugSpan(int id) const; void debugValidate() const; + +#if DEBUG_COINCIDENCE + static void SkOpSegment::DebugClearVisited(const SkOpSpanBase* span); + + bool debugVisited() const { + if (!fDebugVisited) { + fDebugVisited = true; + return false; + } + return true; + } +#endif + void release(const SkOpSpan* ); double distSq(double t, const SkOpAngle* opp) const; bool done() const { - SkASSERT(fDoneCount <= fCount); + SkASSERT(this->globalState()->debugSkipAssert() || fDoneCount <= fCount); return fDoneCount == fCount; } @@ -200,7 +209,7 @@ public: void dumpPts(const char* prefix = "seg") const; void dumpPtsInner(const char* prefix = "seg") const; - void findCollapsed(); + const SkOpPtT* existing(double t, const SkOpSegment* opp) const; SkOpSegment* findNextOp(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable, SkPathOp op, int xorMiMask, int xorSuMask); @@ -220,8 +229,8 @@ public: void init(SkPoint pts[], SkScalar weight, SkOpContour* parent, SkPath::Verb verb); - SkOpSpan* insert(SkOpSpan* prev, SkChunkAlloc* allocator) { - SkOpSpan* result = SkOpTAllocator<SkOpSpan>::Allocate(allocator); + SkOpSpan* insert(SkOpSpan* prev) { + SkOpSpan* result = SkOpTAllocator<SkOpSpan>::Allocate(this->globalState()->allocator()); SkOpSpanBase* next = prev->next(); result->setPrev(prev); prev->setNext(result); @@ -269,8 +278,9 @@ public: 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; - bool missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator); + bool match(const SkOpPtT* span, const SkOpSegment* parent, double t, const SkPoint& pt, + AliasMatch ) const; + bool missingCoincidence(); bool moveMultiples(); void moveNearby(); @@ -302,17 +312,31 @@ public: } bool ptsDisjoint(const SkOpPtT& span, const SkOpPtT& test) const { + SkASSERT(this == span.segment()); + SkASSERT(this == test.segment()); return ptsDisjoint(span.fT, span.fPt, test.fT, test.fPt); } bool ptsDisjoint(const SkOpPtT& span, double t, const SkPoint& pt) const { + SkASSERT(this == span.segment()); return ptsDisjoint(span.fT, span.fPt, t, pt); } + bool ptsDisjoint(const SkOpSpanBase* span, const SkOpSpanBase* test) const { + SkASSERT(this == span->segment()); + SkASSERT(this == test->segment()); + return ptsDisjoint(span->t(), span->pt(), test->t(), test->pt()); + } + bool ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const; - void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, - SkChunkAlloc* allocator); + void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkChunkAlloc*); + +#if DEBUG_COINCIDENCE + void resetDebugVisited() const { + fDebugVisited = false; + } +#endif void resetVisited() { fVisited = false; @@ -330,10 +354,6 @@ public: fPrev = prev; } - void setVisited() { - fVisited = true; - } - void setUpWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* maxWinding, int* sumWinding) { int deltaSum = SpanSign(start, end); *maxWinding = *sumWinding; @@ -348,6 +368,7 @@ public: void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, int* sumSuWinding, int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); void sortAngles(); + bool spansNearby(const SkOpSpanBase* ref, const SkOpSpanBase* check) const; static int SpanSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { int result = start->t() < end->t() ? -start->upCast()->windValue() @@ -372,9 +393,10 @@ public: } bool testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT, const SkOpSpanBase* prior, - const SkOpSpanBase* spanBase, const SkOpSegment* opp, SkScalar flatnessLimit) const; + const SkOpSpanBase* spanBase, const SkOpSegment* opp) const; void undoneSpan(SkOpSpanBase** start, SkOpSpanBase** end); + bool uniqueT(double t, AliasMatch allowAlias) const; int updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const; int updateOppWinding(const SkOpAngle* angle) const; int updateOppWindingReverse(const SkOpAngle* angle) const; @@ -404,17 +426,12 @@ public: SkOpSpan* windingSpanAtT(double tHit); int windSum(const SkOpAngle* angle) const; - SkPoint* writablePt(bool end) { - return &fPts[end ? SkPathOpsVerbToPoints(fVerb) : 0]; - } - 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 fOriginal[2]; // if aligned, the original unaligned points are here SkPoint* fPts; // pointer into array of points owned by edge builder that may be tweaked SkPathOpsBounds fBounds; // tight bounds SkScalar fWeight; @@ -422,6 +439,9 @@ private: int fDoneCount; // number of processed spans (zero initially) SkPath::Verb fVerb; bool fVisited; // used by missing coincidence check +#if DEBUG_COINCIDENCE + mutable bool fDebugVisited; // used by debug missing coincidence check +#endif SkDEBUGCODE(int fID); }; diff --git a/src/pathops/SkOpSpan.cpp b/src/pathops/SkOpSpan.cpp index f3362235d8..577a9db326 100755 --- a/src/pathops/SkOpSpan.cpp +++ b/src/pathops/SkOpSpan.cpp @@ -35,54 +35,60 @@ bool SkOpPtT::contains(const SkOpPtT* check) const { return false; } -SkOpPtT* SkOpPtT::contains(const SkOpSegment* check) { - SkASSERT(this->segment() != check); - SkOpPtT* ptT = this; +bool SkOpPtT::contains(const SkOpSegment* segment, const SkPoint& pt) const { + SkASSERT(this->segment() != segment); + const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { - if (ptT->segment() == check) { - return ptT; + if (ptT->fPt == pt && ptT->segment() == segment) { + return true; } } - return nullptr; + return false; } -SkOpContour* SkOpPtT::contour() const { - return segment()->contour(); +bool SkOpPtT::contains(const SkOpSegment* segment, double t) const { + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->fT == t && ptT->segment() == segment) { + return true; + } + } + return false; } -SkOpPtT* SkOpPtT::doppelganger() { - SkASSERT(fDeleted); - SkOpPtT* ptT = fNext; - while (ptT->fDeleted) { - ptT = ptT->fNext; - } +const SkOpPtT* SkOpPtT::contains(const SkOpSegment* check) const { + SkASSERT(this->segment() != check); + const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; - do { - if (ptT->fSpan == fSpan) { + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->segment() == check && !ptT->deleted()) { return ptT; } - ptT = ptT->fNext; - } while (stopPtT != ptT); - SkASSERT(0); + } return nullptr; } -SkOpPtT* SkOpPtT::find(SkOpSegment* segment) { - SkOpPtT* ptT = this; +SkOpContour* SkOpPtT::contour() const { + return segment()->contour(); +} + +const SkOpPtT* SkOpPtT::find(const SkOpSegment* segment) const { + const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; do { - if (ptT->segment() == segment) { + if (ptT->segment() == segment && !ptT->deleted()) { return ptT; } ptT = ptT->fNext; } while (stopPtT != ptT); - SkASSERT(0); +// SkASSERT(0); return nullptr; } SkOpGlobalState* SkOpPtT::globalState() const { - return contour()->globalState(); + return contour()->globalState(); } void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplicate) { @@ -92,6 +98,7 @@ void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplica fNext = this; fDuplicatePt = duplicate; fDeleted = false; + fCoincident = false; SkDEBUGCODE(fID = span->globalState()->nextPtTID()); } @@ -104,6 +111,16 @@ bool SkOpPtT::onEnd() const { return span == segment->head() || span == segment->tail(); } +bool SkOpPtT::ptAlreadySeen(const SkOpPtT* check) const { + while (this != check) { + if (this->fPt == check->fPt) { + return true; + } + check = check->fNext; + } + return false; +} + SkOpPtT* SkOpPtT::prev() { SkOpPtT* result = this; SkOpPtT* next = this; @@ -114,12 +131,12 @@ SkOpPtT* SkOpPtT::prev() { return result; } -SkOpPtT* SkOpPtT::remove() { +SkOpPtT* SkOpPtT::remove(const SkOpPtT* kept) { SkOpPtT* prev = this; do { SkOpPtT* next = prev->fNext; if (next == this) { - prev->removeNext(this); + prev->removeNext(kept); SkASSERT(prev->fNext != prev); fDeleted = true; return prev; @@ -130,12 +147,16 @@ SkOpPtT* SkOpPtT::remove() { return nullptr; } -void SkOpPtT::removeNext(SkOpPtT* kept) { +void SkOpPtT::removeNext(const SkOpPtT* kept) { SkASSERT(this->fNext); SkOpPtT* next = this->fNext; SkASSERT(this != next->fNext); this->fNext = next->fNext; SkOpSpanBase* span = next->span(); + SkOpCoincidence* coincidence = span->globalState()->coincidence(); + if (coincidence) { + coincidence->fixUp(next, kept); + } next->setDeleted(); if (span->ptT() == next) { span->upCast()->release(kept); @@ -150,85 +171,68 @@ SkOpSegment* SkOpPtT::segment() { return span()->segment(); } -void SkOpSpanBase::align() { - if (this->fAligned) { +void SkOpPtT::setDeleted() { + SkASSERT(this->span()->debugDeleted() || this->span()->ptT() != this); + SkASSERT(this->globalState()->debugSkipAssert() || !fDeleted); + fDeleted = true; +} + +// please keep this in sync with debugAddOppAndMerge +// If the added points envelop adjacent spans, merge them in. +void SkOpSpanBase::addOppAndMerge(SkOpSpanBase* opp) { + if (this->ptT()->addOpp(opp->ptT())) { + this->checkForCollapsedCoincidence(); + } + // compute bounds of points in span + SkPathOpsBounds bounds; + bounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin); + const SkOpPtT* head = this->ptT(); + const SkOpPtT* nextPt = head; + do { + bounds.add(nextPt->fPt); + } while ((nextPt = nextPt->next()) != head); + if (!bounds.width() && !bounds.height()) { 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]); - } + this->mergeContained(bounds); + opp->mergeContained(bounds); +} + +// Please keep this in sync with debugMergeContained() +void SkOpSpanBase::mergeContained(const SkPathOpsBounds& bounds) { + // while adjacent spans' points are contained by the bounds, merge them + SkOpSpanBase* prev = this; + SkOpSegment* seg = this->segment(); + while ((prev = prev->prev()) && bounds.contains(prev->pt()) && !seg->ptsDisjoint(prev, this)) { + if (prev->prev()) { + this->merge(prev->upCast()); + prev = this; + } else if (this->final()) { + seg->clearAll(); return; + } else { + prev->merge(this->upCast()); } } - 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; + SkOpSpanBase* current = this; + SkOpSpanBase* next = this; + while (next->upCastable() && (next = next->upCast()->next()) + && bounds.contains(next->pt()) && !seg->ptsDisjoint(this, next)) { + if (!current->prev() && next->final()) { + seg->clearAll(); + return; } - if (!zero_or_one(test->fT)) { - continue; + if (current->prev()) { + next->merge(current->upCast()); + current = next; + } else { + current->merge(next->upCast()); + // extra line in debug version } - *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); +#if DEBUG_COINCIDENCE + this->globalState()->coincidence()->debugValidate(); +#endif } bool SkOpSpanBase::contains(const SkOpSpanBase* span) const { @@ -244,11 +248,14 @@ bool SkOpSpanBase::contains(const SkOpSpanBase* span) const { return false; } -SkOpPtT* SkOpSpanBase::contains(const SkOpSegment* segment) { - SkOpPtT* start = &fPtT; - SkOpPtT* walk = start; +const SkOpPtT* SkOpSpanBase::contains(const SkOpSegment* segment) const { + const SkOpPtT* start = &fPtT; + const SkOpPtT* walk = start; while ((walk = walk->next()) != start) { - if (walk->segment() == segment) { + if (walk->deleted()) { + continue; + } + if (walk->segment() == segment && walk->span()->ptT() == walk) { return walk; } } @@ -271,7 +278,7 @@ SkOpContour* SkOpSpanBase::contour() const { } SkOpGlobalState* SkOpSpanBase::globalState() const { - return contour()->globalState(); + return contour()->globalState(); } void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { @@ -285,6 +292,7 @@ void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, cons fChased = false; SkDEBUGCODE(fCount = 1); SkDEBUGCODE(fID = globalState()->nextSpanID()); + SkDEBUGCODE(fDeleted = false); } // this pair of spans share a common t value or point; merge them and eliminate duplicates @@ -294,8 +302,11 @@ void SkOpSpanBase::merge(SkOpSpan* span) { SkASSERT(this->t() != spanPtT->fT); SkASSERT(!zero_or_one(spanPtT->fT)); span->release(this->ptT()); + if (this->contains(span)) { + return; // merge is already in the ptT loop + } SkOpPtT* remainder = spanPtT->next(); - ptT()->insert(spanPtT); + this->ptT()->insert(spanPtT); while (remainder != spanPtT) { SkOpPtT* next = remainder->next(); SkOpPtT* compare = spanPtT->next(); @@ -311,6 +322,26 @@ tryNextRemainder: remainder = next; } fSpanAdds += span->fSpanAdds; + this->checkForCollapsedCoincidence(); +} + +// please keep in sync with debugCheckForCollapsedCoincidence() +void SkOpSpanBase::checkForCollapsedCoincidence() { + SkOpCoincidence* coins = this->globalState()->coincidence(); + if (coins->isEmpty()) { + return; + } +// the insert above may have put both ends of a coincident run in the same span +// for each coincident ptT in loop; see if its opposite in is also in the loop +// this implementation is the motivation for marking that a ptT is referenced by a coincident span + SkOpPtT* head = this->ptT(); + SkOpPtT* test = head; + do { + if (!test->coincident()) { + continue; + } + coins->markCollapsed(test); + } while ((test = test->next()) != head); } int SkOpSpan::computeWindSum() { @@ -334,7 +365,45 @@ bool SkOpSpan::containsCoincidence(const SkOpSegment* segment) const { return false; } -void SkOpSpan::release(SkOpPtT* kept) { +void SkOpSpan::init(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { + SkASSERT(t != 1); + initBase(segment, prev, t, pt); + fCoincident = this; + fToAngle = nullptr; + fWindSum = fOppSum = SK_MinS32; + fWindValue = 1; + fOppValue = 0; + fTopTTry = 0; + fChased = fDone = false; + segment->bumpCount(); + fAlreadyAdded = false; +} + +// Please keep this in sync with debugInsertCoincidence() +bool SkOpSpan::insertCoincidence(const SkOpSegment* segment, bool flipped) { + if (this->containsCoincidence(segment)) { + return true; + } + SkOpPtT* next = &fPtT; + while ((next = next->next()) != &fPtT) { + if (next->segment() == segment) { + SkOpSpan* span = flipped ? next->span()->prev() : next->span()->upCast(); + if (!span) { + return false; + } + this->insertCoincidence(span); + return true; + } + } +#if DEBUG_COINCIDENCE + SkASSERT(0); // FIXME? if we get here, the span is missing its opposite segment... +#endif + return true; +} + +void SkOpSpan::release(const SkOpPtT* kept) { + SkDEBUGCODE(fDeleted = true); + SkASSERT(kept->span() != this); SkASSERT(!final()); SkOpSpan* prev = this->prev(); SkASSERT(prev); @@ -348,20 +417,14 @@ void SkOpSpan::release(SkOpPtT* kept) { 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 = nullptr; - fWindSum = fOppSum = SK_MinS32; - fWindValue = 1; - fOppValue = 0; - fTopTTry = 0; - fChased = fDone = false; - segment->bumpCount(); - fAlreadyAdded = false; + SkOpPtT* stopPtT = this->ptT(); + SkOpPtT* testPtT = stopPtT; + const SkOpSpanBase* keptSpan = kept->span(); + do { + if (this == testPtT->span()) { + testPtT->setSpan(keptSpan); + } + } while ((testPtT = testPtT->next()) != stopPtT); } void SkOpSpan::setOppSum(int oppSum) { diff --git a/src/pathops/SkOpSpan.h b/src/pathops/SkOpSpan.h index c6fc4b138f..63fc1e9a1e 100644 --- a/src/pathops/SkOpSpan.h +++ b/src/pathops/SkOpSpan.h @@ -12,12 +12,13 @@ #include "SkPoint.h" class SkChunkAlloc; -struct SkOpAngle; +class SkOpAngle; class SkOpContour; class SkOpGlobalState; class SkOpSegment; class SkOpSpanBase; class SkOpSpan; +struct SkPathOpsBounds; // subset of op span used by terminal span (when t is equal to one) class SkOpPtT { @@ -27,37 +28,43 @@ public: kIsDuplicate = 1 }; - void addOpp(SkOpPtT* opp) { + // please keep in sync with debugAddOpp() + bool addOpp(SkOpPtT* opp) { // find the fOpp ptr to opp SkOpPtT* oppPrev = opp->fNext; if (oppPrev == this) { - return; + return false; } while (oppPrev->fNext != opp) { oppPrev = oppPrev->fNext; - if (oppPrev == this) { - return; - } + if (oppPrev == this) { + return false; + } } - SkOpPtT* oldNext = this->fNext; SkASSERT(this != opp); this->fNext = opp; SkASSERT(oppPrev != oldNext); oppPrev->fNext = oldNext; + return true; } bool alias() const; + bool coincident() const { return fCoincident; } bool collapsed(const SkOpPtT* ) const; bool contains(const SkOpPtT* ) const; - SkOpPtT* contains(const SkOpSegment* ); + bool contains(const SkOpSegment*, const SkPoint& ) const; + bool contains(const SkOpSegment*, double t) const; + const SkOpPtT* contains(const SkOpSegment* ) const; SkOpContour* contour() const; int debugID() const { return SkDEBUGRELEASE(fID, -1); } + bool debugAddOpp(const SkOpPtT* opp) const; const SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; bool debugContains(const SkOpPtT* ) const; const SkOpPtT* debugContains(const SkOpSegment* check) const; SkOpContour* debugContour(int id); @@ -72,8 +79,6 @@ public: return fDeleted; } - SkOpPtT* doppelganger(); - bool duplicate() const { return fDuplicatePt; } @@ -82,7 +87,7 @@ public: void dumpAll() const; void dumpBase() const; - SkOpPtT* find(SkOpSegment* ); + const SkOpPtT* find(const SkOpSegment* ) const; SkOpGlobalState* globalState() const; void init(SkOpSpanBase* , double t, const SkPoint& , bool dup); @@ -102,14 +107,14 @@ public: bool onEnd() const; - static bool Overlaps(SkOpPtT* s1, SkOpPtT* e1, SkOpPtT* s2, SkOpPtT* e2, - SkOpPtT** sOut, SkOpPtT** eOut) { - SkOpPtT* start1 = s1->fT < e1->fT ? s1 : e1; - SkOpPtT* start2 = s2->fT < e2->fT ? s2 : e2; + static bool Overlaps(const SkOpPtT* s1, const SkOpPtT* e1, const SkOpPtT* s2, + const SkOpPtT* e2, const SkOpPtT** sOut, const SkOpPtT** eOut) { + const SkOpPtT* start1 = s1->fT < e1->fT ? s1 : e1; + const SkOpPtT* start2 = s2->fT < e2->fT ? s2 : e2; *sOut = between(s1->fT, start2->fT, e1->fT) ? start2 : between(s2->fT, start1->fT, e2->fT) ? start1 : nullptr; - SkOpPtT* end1 = s1->fT < e1->fT ? e1 : s1; - SkOpPtT* end2 = s2->fT < e2->fT ? e2 : s2; + const SkOpPtT* end1 = s1->fT < e1->fT ? e1 : s1; + const SkOpPtT* end2 = s2->fT < e2->fT ? e2 : s2; *eOut = between(s1->fT, end2->fT, e1->fT) ? end2 : between(s2->fT, end1->fT, e2->fT) ? end1 : nullptr; if (*sOut == *eOut) { @@ -120,16 +125,23 @@ public: return *sOut && *eOut; } + bool ptAlreadySeen(const SkOpPtT* head) const; SkOpPtT* prev(); - SkOpPtT* remove(); - void removeNext(SkOpPtT* kept); + SkOpPtT* remove(const SkOpPtT* kept); + void removeNext(const SkOpPtT* kept); const SkOpSegment* segment() const; SkOpSegment* segment(); - void setDeleted() { + void setCoincident() const { SkASSERT(!fDeleted); - fDeleted = true; + fCoincident = true; + } + + void setDeleted(); + + void setSpan(const SkOpSpanBase* span) { + fSpan = const_cast<SkOpSpanBase*>(span); } const SkOpSpanBase* span() const { @@ -144,25 +156,21 @@ public: return fT < end->fT ? this : end; } - double fT; + 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 fDeleted; // set if removed from span list bool fDuplicatePt; // set if identical pt is somewhere in the next loop + // below mutable since referrer is otherwise always const + mutable bool fCoincident; // set if at some point a coincident span pointed here SkDEBUGCODE(int fID); }; class SkOpSpanBase { public: - void align(); - - bool aligned() const { - return fAligned; - } - - void alignEnd(double t, const SkPoint& pt); + void addOppAndMerge(SkOpSpanBase* ); void bumpSpanAdds() { ++fSpanAdds; @@ -172,17 +180,14 @@ public: return fChased; } - void clearCoinEnd() { - SkASSERT(fCoinEnd != this); - fCoinEnd = this; - } + void checkForCollapsedCoincidence(); const SkOpSpanBase* coinEnd() const { return fCoinEnd; } bool contains(const SkOpSpanBase* ) const; - SkOpPtT* contains(const SkOpSegment* ); + const SkOpPtT* contains(const SkOpSegment* ) const; bool containsCoinEnd(const SkOpSpanBase* coin) const { SkASSERT(this != coin); @@ -198,6 +203,11 @@ public: bool containsCoinEnd(const SkOpSegment* ) const; SkOpContour* contour() const; +#if DEBUG_COINCIDENCE_VERBOSE + void debugAddOppAndMerge(const char* id, SkPathOpsDebug::GlitchLog* , const SkOpSpanBase* , + bool* del1, bool* del2) const; +#endif + int debugBumpCount() { return SkDEBUGRELEASE(++fCount, -1); } @@ -209,9 +219,21 @@ public: bool debugAlignedEnd(double t, const SkPoint& pt) const; bool debugAlignedInner() const; const SkOpAngle* debugAngle(int id) const; +#if DEBUG_COINCIDENCE_VERBOSE + void debugCheckForCollapsedCoincidence(const char* id, SkPathOpsDebug::GlitchLog* ) const; +#endif + const SkOpCoincidence* debugCoincidence() const; bool debugCoinEndLoopCheck() const; - bool debugContains(const SkOpSegment* ) const; SkOpContour* debugContour(int id); +#ifdef SK_DEBUG + bool debugDeleted() const { return fDeleted; } +#endif +#if DEBUG_COINCIDENCE_VERBOSE + void debugInsertCoinEnd(const char* id, SkPathOpsDebug::GlitchLog* , + const SkOpSpanBase* ) const; + void debugMergeContained(const char* id, SkPathOpsDebug::GlitchLog* , + const SkPathOpsBounds& bounds, bool* deleted) const; +#endif const SkOpPtT* debugPtT(int id) const; const SkOpSegment* debugSegment(int id) const; const SkOpSpanBase* debugSpan(int id) const; @@ -227,6 +249,7 @@ public: void dumpCoin() const; void dumpAll() const; void dumpBase() const; + void dumpHead() const; bool final() const { return fPtT.fT == 1; @@ -238,6 +261,7 @@ public: void initBase(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); + // Please keep this in sync with debugInsertCoinEnd() void insertCoinEnd(SkOpSpanBase* coin) { if (containsCoinEnd(coin)) { SkASSERT(coin->containsCoinEnd(this)); @@ -252,8 +276,13 @@ public: } void merge(SkOpSpan* span); + void mergeContained(const SkPathOpsBounds& bounds); - SkOpSpan* prev() const { + const SkOpSpan* prev() const { + return fPrev; + } + + SkOpSpan* prev() { return fPrev; } @@ -281,8 +310,6 @@ public: fChased = chased; } - SkOpPtT* setCoinEnd(SkOpSpanBase* oldCoinEnd, SkOpSegment* oppSegment); - void setFromAngle(SkOpAngle* angle) { fFromAngle = angle; } @@ -293,7 +320,7 @@ public: bool simple() const { fPtT.debugValidate(); - return fPtT.next()->next() == &fPtT; + return fPtT.next()->next() == &fPtT; } int spanAddsCount() const { @@ -368,6 +395,7 @@ protected: // no direct access to internals to avoid treating a span base as a bool fChased; // set after span has been added to chase array SkDEBUGCODE(int fCount); // number of pt/t pairs added SkDEBUGCODE(int fID); + SkDEBUGCODE(bool fDeleted); // set when span was merged with another span }; class SkOpSpan : public SkOpSpanBase { @@ -404,7 +432,12 @@ public: } bool debugCoinLoopCheck() const; - void release(SkOpPtT* ); +#if DEBUG_COINCIDENCE_VERBOSE + void debugInsertCoincidence(const char* , SkPathOpsDebug::GlitchLog* , const SkOpSpan* ) const; + void debugInsertCoincidence(const char* , SkPathOpsDebug::GlitchLog* , + const SkOpSegment* , bool flipped) const; +#endif + void release(const SkOpPtT* ); bool done() const { SkASSERT(!final()); @@ -414,7 +447,9 @@ public: void dumpCoin() const; bool dumpSpan() const; void init(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); + bool insertCoincidence(const SkOpSegment* , bool flipped); + // Please keep this in sync with debugInsertCoincidence() void insertCoincidence(SkOpSpan* coin) { if (containsCoincidence(coin)) { SkASSERT(coin->containsCoincidence(this)); @@ -470,6 +505,7 @@ public: void setOppValue(int oppValue) { SkASSERT(!final()); SkASSERT(fOppSum == SK_MinS32); + SkASSERT(!oppValue || !fDone); fOppValue = oppValue; } @@ -484,6 +520,7 @@ public: SkASSERT(!final()); SkASSERT(windValue >= 0); SkASSERT(fWindSum == SK_MinS32); + SkASSERT(!windValue || !fDone); fWindValue = windValue; } diff --git a/src/pathops/SkPathOpsBounds.h b/src/pathops/SkPathOpsBounds.h index b99f36f5d9..610d7233a3 100644 --- a/src/pathops/SkPathOpsBounds.h +++ b/src/pathops/SkPathOpsBounds.h @@ -33,6 +33,13 @@ struct SkPathOpsBounds : public SkRect { add(toAdd.fLeft, toAdd.fTop, toAdd.fRight, toAdd.fBottom); } + void add(const SkPoint& pt) { + if (pt.fX < fLeft) fLeft = pt.fX; + if (pt.fY < fTop) fTop = pt.fY; + if (pt.fX > fRight) fRight = pt.fX; + if (pt.fY > fBottom) fBottom = pt.fY; + } + void add(const SkDPoint& pt) { if (pt.fX < fLeft) fLeft = SkDoubleToScalar(pt.fX); if (pt.fY < fTop) fTop = SkDoubleToScalar(pt.fY); @@ -40,13 +47,18 @@ struct SkPathOpsBounds : public SkRect { if (pt.fY > fBottom) fBottom = SkDoubleToScalar(pt.fY); } - bool almostContains(const SkPoint& pt) { + bool almostContains(const SkPoint& pt) const { return AlmostLessOrEqualUlps(fLeft, pt.fX) && AlmostLessOrEqualUlps(pt.fX, fRight) && AlmostLessOrEqualUlps(fTop, pt.fY) && AlmostLessOrEqualUlps(pt.fY, fBottom); } + bool contains(const SkPoint& pt) const { + return fLeft <= pt.fX && fTop <= pt.fY && + fRight >= pt.fX && fBottom >= pt.fY; + } + typedef SkRect INHERITED; }; diff --git a/src/pathops/SkPathOpsCommon.cpp b/src/pathops/SkPathOpsCommon.cpp index 24ef6f1129..fd4c027ddb 100644 --- a/src/pathops/SkPathOpsCommon.cpp +++ b/src/pathops/SkPathOpsCommon.cpp @@ -11,6 +11,28 @@ #include "SkPathWriter.h" #include "SkTSort.h" +SkScalar ScaleFactor(const SkPath& path) { + static const SkScalar twoTo10 = 1024.f; + SkScalar largest = 0; + const SkScalar* oneBounds = &path.getBounds().fLeft; + for (int index = 0; index < 4; ++index) { + largest = SkTMax(largest, SkScalarAbs(oneBounds[index])); + } + SkScalar scale = twoTo10; + SkScalar next; + while ((next = scale * twoTo10) < largest) { + scale = next; + } + return scale == twoTo10 ? SK_Scalar1 : scale; +} + +void ScalePath(const SkPath& path, SkScalar scale, SkPath* scaled) { + SkMatrix matrix; + matrix.setScale(scale, scale); + *scaled = path; + scaled->transform(matrix); +} + const SkOpAngle* AngleWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* windingPtr, bool* sortablePtr) { // find first angle, initialize winding to computed fWindSum @@ -144,15 +166,6 @@ SkOpSegment* FindChase(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** startPtr, return nullptr; } -#if DEBUG_ACTIVE_SPANS -void DebugShowActiveSpans(SkOpContourHead* contourList) { - SkOpContour* contour = contourList; - do { - contour->debugShowActiveSpans(); - } while ((contour = contour->next())); -} -#endif - bool SortContourList(SkOpContourHead** contourList, bool evenOdd, bool oppEvenOdd) { SkTDArray<SkOpContour* > list; SkOpContour* contour = *contourList; @@ -201,7 +214,7 @@ public: void Assemble(const SkPathWriter& path, SkPathWriter* simple) { SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune SkOpContourHead contour; - SkOpGlobalState globalState(nullptr, &contour SkDEBUGPARAMS(false) + SkOpGlobalState globalState(&contour, &allocator SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); #if DEBUG_SHOW_TEST_NAME SkDebugf("</div>\n"); @@ -209,8 +222,8 @@ void Assemble(const SkPathWriter& path, SkPathWriter* simple) { #if DEBUG_PATH_CONSTRUCTION SkDebugf("%s\n", __FUNCTION__); #endif - SkOpEdgeBuilder builder(path, &contour, &allocator, &globalState); - builder.finish(&allocator); + SkOpEdgeBuilder builder(path, &contour, &globalState); + builder.finish(); SkTDArray<const SkOpContour* > runs; // indices of partial contours const SkOpContour* eContour = builder.head(); do { @@ -391,40 +404,18 @@ void Assemble(const SkPathWriter& path, SkPathWriter* simple) { #endif } -static void align(SkOpContourHead* contourList) { - SkOpContour* contour = contourList; - do { - contour->align(); - } while ((contour = contour->next())); -} - -static void addAlignIntersections(SkOpContourHead* contourList, SkChunkAlloc* allocator) { - SkOpContour* contour = contourList; - do { - contour->addAlignIntersections(contourList, allocator); - } while ((contour = contour->next())); -} - -static void calcAngles(SkOpContourHead* contourList, SkChunkAlloc* allocator) { - SkOpContour* contour = contourList; - do { - contour->calcAngles(allocator); - } while ((contour = contour->next())); -} - -static void findCollapsed(SkOpContourHead* contourList) { +static void calcAngles(SkOpContourHead* contourList) { SkOpContour* contour = contourList; do { - contour->findCollapsed(); + contour->calcAngles(); } while ((contour = contour->next())); } -static bool missingCoincidence(SkOpContourHead* contourList, - SkOpCoincidence* coincidence, SkChunkAlloc* allocator) { +static bool missingCoincidence(SkOpContourHead* contourList) { SkOpContour* contour = contourList; bool result = false; do { - result |= contour->missingCoincidence(coincidence, allocator); + result |= contour->missingCoincidence(); } while ((contour = contour->next())); return result; } @@ -453,72 +444,116 @@ static void sortAngles(SkOpContourHead* contourList) { } while ((contour = contour->next())); } -bool HandleCoincidence(SkOpContourHead* contourList, SkOpCoincidence* coincidence, - SkChunkAlloc* allocator) { +bool HandleCoincidence(SkOpContourHead* contourList, SkOpCoincidence* coincidence) { SkOpGlobalState* globalState = contourList->globalState(); - // combine t values when multiple intersections occur on some segments but not others DEBUG_COINCIDENCE_HEALTH(contourList, "start"); +#if DEBUG_VALIDATE + globalState->setPhase(SkOpGlobalState::kIntersecting); +#endif + + // match up points within the coincident runs + if (!coincidence->addExpanded()) { + return false; + } + DEBUG_COINCIDENCE_HEALTH(contourList, "addExpanded"); +#if DEBUG_VALIDATE + globalState->setPhase(SkOpGlobalState::kWalking); +#endif + // combine t values when multiple intersections occur on some segments but not others if (!moveMultiples(contourList)) { return false; } DEBUG_COINCIDENCE_HEALTH(contourList, "moveMultiples"); - findCollapsed(contourList); - DEBUG_COINCIDENCE_HEALTH(contourList, "findCollapsed"); // move t values and points together to eliminate small/tiny gaps - moveNearby(contourList); + (void) moveNearby(contourList); DEBUG_COINCIDENCE_HEALTH(contourList, "moveNearby"); - align(contourList); // give all span members common values - DEBUG_COINCIDENCE_HEALTH(contourList, "align"); - if (!coincidence->fixAligned()) { // aligning may have marked a coincidence pt-t deleted - return false; - } - DEBUG_COINCIDENCE_HEALTH(contourList, "fixAligned"); #if DEBUG_VALIDATE globalState->setPhase(SkOpGlobalState::kIntersecting); #endif - // look for intersections on line segments formed by moving end points - addAlignIntersections(contourList, allocator); - DEBUG_COINCIDENCE_HEALTH(contourList, "addAlignIntersections"); - if (coincidence->addMissing(allocator)) { - DEBUG_COINCIDENCE_HEALTH(contourList, "addMissing"); - moveNearby(contourList); - DEBUG_COINCIDENCE_HEALTH(contourList, "moveNearby2"); - align(contourList); // give all span members common values - DEBUG_COINCIDENCE_HEALTH(contourList, "align2"); - if (!coincidence->fixAligned()) { // aligning may have marked a coincidence pt-t deleted + // add coincidence formed by pairing on curve points and endpoints + coincidence->correctEnds(); + if (!coincidence->addEndMovedSpans()) { + return false; + } + DEBUG_COINCIDENCE_HEALTH(contourList, "addEndMovedSpans"); + + const int SAFETY_COUNT = 100; // FIXME: tune + int safetyHatch = SAFETY_COUNT; + // look for coincidence present in A-B and A-C but missing in B-C + while (coincidence->addMissing()) { + if (!--safetyHatch) { + SkASSERT(0); // FIXME: take this out after verifying std tests don't trigger return false; } - DEBUG_COINCIDENCE_HEALTH(contourList, "fixAligned2"); + DEBUG_COINCIDENCE_HEALTH(contourList, "addMissing"); + moveNearby(contourList); + DEBUG_COINCIDENCE_HEALTH(contourList, "moveNearby"); } -#if DEBUG_VALIDATE - globalState->setPhase(SkOpGlobalState::kWalking); -#endif + DEBUG_COINCIDENCE_HEALTH(contourList, "addMissing2"); + // FIXME: only call this if addMissing modified something when returning false + moveNearby(contourList); + DEBUG_COINCIDENCE_HEALTH(contourList, "moveNearby2"); // check to see if, loosely, coincident ranges may be expanded if (coincidence->expand()) { DEBUG_COINCIDENCE_HEALTH(contourList, "expand1"); - if (!coincidence->addExpanded(allocator PATH_OPS_DEBUG_VALIDATE_PARAMS(globalState))) { + coincidence->addMissing(); + DEBUG_COINCIDENCE_HEALTH(contourList, "addMissing2"); + if (!coincidence->addExpanded()) { + return false; + } + DEBUG_COINCIDENCE_HEALTH(contourList, "addExpanded2"); + if (!moveMultiples(contourList)) { return false; } + DEBUG_COINCIDENCE_HEALTH(contourList, "moveMultiples2"); + moveNearby(contourList); } +#if DEBUG_VALIDATE + globalState->setPhase(SkOpGlobalState::kWalking); +#endif DEBUG_COINCIDENCE_HEALTH(contourList, "expand2"); // the expanded ranges may not align -- add the missing spans + SkAssertResult(coincidence->addExpanded()); + DEBUG_COINCIDENCE_HEALTH(contourList, "addExpanded3"); + coincidence->correctEnds(); if (!coincidence->mark()) { // mark spans of coincident segments as coincident return false; } DEBUG_COINCIDENCE_HEALTH(contourList, "mark1"); - // look for coincidence missed earlier - if (missingCoincidence(contourList, coincidence, allocator)) { + // look for coincidence lines and curves undetected by intersection + if (missingCoincidence(contourList)) { +#if DEBUG_VALIDATE + globalState->setPhase(SkOpGlobalState::kIntersecting); +#endif DEBUG_COINCIDENCE_HEALTH(contourList, "missingCoincidence1"); (void) coincidence->expand(); DEBUG_COINCIDENCE_HEALTH(contourList, "expand3"); - if (!coincidence->addExpanded(allocator PATH_OPS_DEBUG_VALIDATE_PARAMS(globalState))) { + if (!coincidence->addExpanded()) { return false; } - DEBUG_COINCIDENCE_HEALTH(contourList, "addExpanded2"); - coincidence->mark(); +#if DEBUG_VALIDATE + globalState->setPhase(SkOpGlobalState::kWalking); +#endif + DEBUG_COINCIDENCE_HEALTH(contourList, "addExpanded3"); + if (!coincidence->mark()) { + return false; + } + } else { + DEBUG_COINCIDENCE_HEALTH(contourList, "missingCoincidence2"); + (void) coincidence->expand(); } - DEBUG_COINCIDENCE_HEALTH(contourList, "missingCoincidence2"); - SkOpCoincidence overlaps; + DEBUG_COINCIDENCE_HEALTH(contourList, "missingCoincidence3"); + + (void) coincidence->expand(); + +#if 0 // under development + // coincident runs may cross two or more spans, but the opposite spans may be out of order + if (!coincidence->reorder()) { + return false; + } +#endif + DEBUG_COINCIDENCE_HEALTH(contourList, "coincidence.reorder"); + SkOpCoincidence overlaps(globalState); do { SkOpCoincidence* pairs = overlaps.isEmpty() ? coincidence : &overlaps; if (!pairs->apply()) { // adjust the winding value to account for coincident edges @@ -527,22 +562,25 @@ bool HandleCoincidence(SkOpContourHead* contourList, SkOpCoincidence* coincidenc DEBUG_COINCIDENCE_HEALTH(contourList, "pairs->apply"); // For each coincident pair that overlaps another, when the receivers (the 1st of the pair) // are different, construct a new pair to resolve their mutual span - if (!pairs->findOverlaps(&overlaps, allocator)) { + if (!pairs->findOverlaps(&overlaps)) { return false; } DEBUG_COINCIDENCE_HEALTH(contourList, "pairs->findOverlaps"); } while (!overlaps.isEmpty()); - calcAngles(contourList, allocator); + calcAngles(contourList); sortAngles(contourList); if (globalState->angleCoincidence()) { - (void) missingCoincidence(contourList, coincidence, allocator); + (void) missingCoincidence(contourList); if (!coincidence->apply()) { return false; } } -#if DEBUG_ACTIVE_SPANS +#if DEBUG_COINCIDENCE_VERBOSE coincidence->debugShowCoincidence(); - DebugShowActiveSpans(contourList); #endif +#if DEBUG_COINCIDENCE + coincidence->debugValidate(); +#endif + SkPathOpsDebug::ShowActiveSpans(contourList); return true; } diff --git a/src/pathops/SkPathOpsCommon.h b/src/pathops/SkPathOpsCommon.h index 5b4902f53b..ed80318160 100644 --- a/src/pathops/SkPathOpsCommon.h +++ b/src/pathops/SkPathOpsCommon.h @@ -24,12 +24,11 @@ SkOpSegment* FindUndone(SkOpContourHead* , SkOpSpanBase** startPtr, SkOpSpanBase** endPtr); bool FixWinding(SkPath* path); bool SortContourList(SkOpContourHead** , bool evenOdd, bool oppEvenOdd); -bool HandleCoincidence(SkOpContourHead* , SkOpCoincidence* , SkChunkAlloc* ); +bool HandleCoincidence(SkOpContourHead* , SkOpCoincidence* ); bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)); -#if DEBUG_ACTIVE_SPANS -void DebugShowActiveSpans(SkOpContourHead* ); -#endif +SkScalar ScaleFactor(const SkPath& path); +void ScalePath(const SkPath& path, SkScalar scale, SkPath* scaled); #endif diff --git a/src/pathops/SkPathOpsCubic.cpp b/src/pathops/SkPathOpsCubic.cpp index a161e368e7..7fd3dd235d 100644 --- a/src/pathops/SkPathOpsCubic.cpp +++ b/src/pathops/SkPathOpsCubic.cpp @@ -277,7 +277,7 @@ bool SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) { for (int index = 0; index < roots; ++index) { if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) { *t = maxCurvature[index]; - return true; + return *t > 0 && *t < 1; } } } else if (infTCount == 1) { diff --git a/src/pathops/SkPathOpsCurve.cpp b/src/pathops/SkPathOpsCurve.cpp index bf44d25e17..df67efaca5 100644 --- a/src/pathops/SkPathOpsCurve.cpp +++ b/src/pathops/SkPathOpsCurve.cpp @@ -8,6 +8,57 @@ #include "SkPathOpsRect.h" #include "SkPathOpsCurve.h" + // this cheats and assumes that the perpendicular to the point is the closest ray to the curve + // this case (where the line and the curve are nearly coincident) may be the only case that counts +double SkDCurve::nearPoint(SkPath::Verb verb, const SkDPoint& xy, const SkDPoint& opp) const { + int count = SkPathOpsVerbToPoints(verb); + double minX = fCubic.fPts[0].fX; + double maxX = minX; + for (int index = 0; index < count; ++index) { + minX = SkTMin(minX, fCubic.fPts[index].fX); + maxX = SkTMax(maxX, fCubic.fPts[index].fX); + } + if (!AlmostBetweenUlps(minX, xy.fX, maxX)) { + return -1; + } + double minY = fCubic.fPts[0].fY; + double maxY = minY; + for (int index = 0; index < count; ++index) { + minY = SkTMin(minY, fCubic.fPts[index].fY); + maxY = SkTMax(maxY, fCubic.fPts[index].fY); + } + if (!AlmostBetweenUlps(minY, xy.fY, maxY)) { + return -1; + } + SkIntersections i; + SkDLine perp = {{ xy, { xy.fX + opp.fY - xy.fY, xy.fY + xy.fX - opp.fX }}}; + (*CurveDIntersectRay[verb])(*this, perp, &i); + int minIndex = -1; + double minDist = FLT_MAX; + for (int index = 0; index < i.used(); ++index) { + double dist = xy.distance(i.pt(index)); + if (minDist > dist) { + minDist = dist; + minIndex = index; + } + } + if (minIndex < 0) { + return -1; + } + double largest = SkTMax(SkTMax(maxX, maxY), -SkTMin(minX, minY)); + if (!AlmostEqualUlps_Pin(largest, largest + minDist)) { // is distance within ULPS tolerance? + return -1; + } + return SkPinT(i[0][minIndex]); +} + +void SkDCurve::offset(SkPath::Verb verb, const SkDVector& off) { + int count = SkPathOpsVerbToPoints(verb); + for (int index = 0; index < count; ++index) { + fCubic.fPts[index] += off; + } +} + void SkDCurve::setConicBounds(const SkPoint curve[3], SkScalar curveWeight, double tStart, double tEnd, SkPathOpsBounds* bounds) { SkDConic dCurve; @@ -18,7 +69,7 @@ void SkDCurve::setConicBounds(const SkPoint curve[3], SkScalar curveWeight, SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); } -void SkDCurve::setCubicBounds(const SkPoint curve[4], SkScalar , +void SkDCurve::setCubicBounds(const SkPoint curve[4], SkScalar , double tStart, double tEnd, SkPathOpsBounds* bounds) { SkDCubic dCurve; dCurve.set(curve); diff --git a/src/pathops/SkPathOpsCurve.h b/src/pathops/SkPathOpsCurve.h index 97e20be2b1..dc9cec97e4 100644 --- a/src/pathops/SkPathOpsCurve.h +++ b/src/pathops/SkPathOpsCurve.h @@ -64,14 +64,17 @@ struct SkDCurve { return fCubic[n]; } - SkDPoint conicTop(const SkPoint curve[3], SkScalar curveWeight, + SkDPoint conicTop(const SkPoint curve[3], SkScalar curveWeight, double s, double e, double* topT); SkDPoint cubicTop(const SkPoint curve[4], SkScalar , double s, double e, double* topT); + void dump() const; void dumpID(int ) const; SkDPoint lineTop(const SkPoint[2], SkScalar , double , double , double* topT); + double nearPoint(SkPath::Verb verb, const SkDPoint& xy, const SkDPoint& opp) const; + void offset(SkPath::Verb verb, const SkDVector& ); SkDPoint quadTop(const SkPoint curve[3], SkScalar , double s, double e, double* topT); - void setConicBounds(const SkPoint curve[3], SkScalar curveWeight, + void setConicBounds(const SkPoint curve[3], SkScalar curveWeight, double s, double e, SkPathOpsBounds* ); void setCubicBounds(const SkPoint curve[4], SkScalar , double s, double e, SkPathOpsBounds* ); @@ -115,6 +118,30 @@ static SkDPoint (* const CurveDPointAtT[])(const SkPoint[], SkScalar , double ) dcubic_xy_at_t }; +static SkDPoint ddline_xy_at_t(const SkDCurve& c, double t) { + return c.fLine.ptAtT(t); +} + +static SkDPoint ddquad_xy_at_t(const SkDCurve& c, double t) { + return c.fQuad.ptAtT(t); +} + +static SkDPoint ddconic_xy_at_t(const SkDCurve& c, double t) { + return c.fConic.ptAtT(t); +} + +static SkDPoint ddcubic_xy_at_t(const SkDCurve& c, double t) { + return c.fCubic.ptAtT(t); +} + +static SkDPoint (* const CurveDDPointAtT[])(const SkDCurve& , double ) = { + nullptr, + ddline_xy_at_t, + ddquad_xy_at_t, + ddconic_xy_at_t, + ddcubic_xy_at_t +}; + static SkPoint fline_xy_at_t(const SkPoint a[2], SkScalar weight, double t) { return dline_xy_at_t(a, weight, t).asSkPoint(); } @@ -171,6 +198,30 @@ static SkDVector (* const CurveDSlopeAtT[])(const SkPoint[], SkScalar , double ) dcubic_dxdy_at_t }; +static SkDVector ddline_dxdy_at_t(const SkDCurve& c, double ) { + return c.fLine.fPts[1] - c.fLine.fPts[0]; +} + +static SkDVector ddquad_dxdy_at_t(const SkDCurve& c, double t) { + return c.fQuad.dxdyAtT(t); +} + +static SkDVector ddconic_dxdy_at_t(const SkDCurve& c, double t) { + return c.fConic.dxdyAtT(t); +} + +static SkDVector ddcubic_dxdy_at_t(const SkDCurve& c, double t) { + return c.fCubic.dxdyAtT(t); +} + +static SkDVector (* const CurveDDSlopeAtT[])(const SkDCurve& , double ) = { + nullptr, + ddline_dxdy_at_t, + ddquad_dxdy_at_t, + ddconic_dxdy_at_t, + ddcubic_dxdy_at_t +}; + static SkVector fline_dxdy_at_t(const SkPoint a[2], SkScalar , double ) { return a[1] - a[0]; } @@ -269,6 +320,30 @@ static void (* const CurveIntersectRay[])(const SkPoint[] , SkScalar , const SkD cubic_intersect_ray }; +static void dline_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fLine, ray); +} + +static void dquad_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fQuad, ray); +} + +static void dconic_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fConic, ray); +} + +static void dcubic_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fCubic, ray); +} + +static void (* const CurveDIntersectRay[])(const SkDCurve& , const SkDLine& , SkIntersections* ) = { + nullptr, + dline_intersect_ray, + dquad_intersect_ray, + dconic_intersect_ray, + dcubic_intersect_ray +}; + static int line_intercept_h(const SkPoint a[2], SkScalar , SkScalar y, double* roots) { SkDLine line; roots[0] = SkIntersections::HorizontalIntercept(line.set(a), y); diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp index 546771e84c..93743f3d6c 100644 --- a/src/pathops/SkPathOpsDebug.cpp +++ b/src/pathops/SkPathOpsDebug.cpp @@ -12,7 +12,7 @@ #include "SkPathOpsDebug.h" #include "SkString.h" -struct SkCoincidentSpans; +class SkCoincidentSpans; #if DEBUG_VALIDATE extern bool FLAGS_runFail; @@ -46,11 +46,15 @@ bool SkPathOpsDebug::ChaseContains(const SkTDArray<SkOpSpanBase* >& chaseArray, } #endif -#if DEBUG_COINCIDENCE +#if DEBUG_COINCIDENCE_VERBOSE enum GlitchType { kAddCorruptCoin_Glitch, kAddExpandedCoin_Glitch, + kAddExpandedFail_Glitch, + kAddIfMissingCoin_Glitch, kAddMissingCoin_Glitch, + kAddMissingExtend_Glitch, + kAddOrOverlap_Glitch, kCollapsedCoin_Glitch, kCollapsedDone_Glitch, kCollapsedOppValue_Glitch, @@ -60,30 +64,41 @@ enum GlitchType { kExpandCoin_Glitch, kMarkCoinEnd_Glitch, kMarkCoinInsert_Glitch, + kMarkCoinMissing_Glitch, + kMarkCoinStart_Glitch, + kMergeContained_Glitch, kMissingCoin_Glitch, kMissingDone_Glitch, kMissingIntersection_Glitch, kMoveMultiple_Glitch, + kMoveNearbyClearAll_Glitch, + kMoveNearbyClearAll2_Glitch, + kMoveNearbyMerge_Glitch, + kMoveNearbyMergeFinal_Glitch, + kMoveNearbyRelease_Glitch, + kMoveNearbyReleaseFinal_Glitch, + kReleasedSpan_Glitch, kUnaligned_Glitch, kUnalignedHead_Glitch, kUnalignedTail_Glitch, - kUndetachedSpan_Glitch, - kUnmergedSpan_Glitch, }; -static const int kGlitchType_Count = kUnmergedSpan_Glitch + 1; +static const int kGlitchType_Count = kUnalignedTail_Glitch + 1; struct SpanGlitch { const char* fStage; const SkOpSpanBase* fBase; const SkOpSpanBase* fSuspect; - const SkCoincidentSpans* fCoin; const SkOpSegment* fSegment; + const SkOpSegment* fOppSegment; const SkOpPtT* fCoinSpan; const SkOpPtT* fEndSpan; const SkOpPtT* fOppSpan; const SkOpPtT* fOppEndSpan; - double fT; + double fStartT; + double fEndT; + double fOppStartT; + double fOppEndT; SkPoint fPt; GlitchType fType; }; @@ -94,13 +109,16 @@ struct SkPathOpsDebug::GlitchLog { glitch->fStage = stage; glitch->fBase = nullptr; glitch->fSuspect = nullptr; - glitch->fCoin = nullptr; glitch->fSegment = nullptr; + glitch->fOppSegment = nullptr; glitch->fCoinSpan = nullptr; glitch->fEndSpan = nullptr; glitch->fOppSpan = nullptr; glitch->fOppEndSpan = nullptr; - glitch->fT = SK_ScalarNaN; + glitch->fStartT = SK_ScalarNaN; + glitch->fEndT = SK_ScalarNaN; + glitch->fOppStartT = SK_ScalarNaN; + glitch->fOppEndT = SK_ScalarNaN; glitch->fPt = { SK_ScalarNaN, SK_ScalarNaN }; glitch->fType = type; return glitch; @@ -113,11 +131,22 @@ struct SkPathOpsDebug::GlitchLog { glitch->fSuspect = suspect; } + void record(GlitchType type, const char* stage, const SkOpSpanBase* base, + const SkOpPtT* ptT) { + SpanGlitch* glitch = recordCommon(type, stage); + glitch->fBase = base; + glitch->fCoinSpan = ptT; + } + void record(GlitchType type, const char* stage, const SkCoincidentSpans* coin, - const SkOpPtT* coinSpan) { + const SkCoincidentSpans* opp = NULL) { SpanGlitch* glitch = recordCommon(type, stage); - glitch->fCoin = coin; - glitch->fCoinSpan = coinSpan; + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); + if (opp) { + glitch->fOppSpan = opp->coinPtTStart(); + glitch->fOppEndSpan = opp->coinPtTEnd(); + } } void record(GlitchType type, const char* stage, const SkOpSpanBase* base, @@ -125,7 +154,7 @@ struct SkPathOpsDebug::GlitchLog { SpanGlitch* glitch = recordCommon(type, stage); glitch->fBase = base; glitch->fSegment = seg; - glitch->fT = t; + glitch->fStartT = t; glitch->fPt = pt; } @@ -133,23 +162,26 @@ struct SkPathOpsDebug::GlitchLog { SkPoint pt) { SpanGlitch* glitch = recordCommon(type, stage); glitch->fBase = base; - glitch->fT = t; + glitch->fStartT = t; glitch->fPt = pt; } void record(GlitchType type, const char* stage, const SkCoincidentSpans* coin, const SkOpPtT* coinSpan, const SkOpPtT* endSpan) { SpanGlitch* glitch = recordCommon(type, stage); - glitch->fCoin = coin; - glitch->fCoinSpan = coinSpan; + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); glitch->fEndSpan = endSpan; + glitch->fOppSpan = coinSpan; + glitch->fOppEndSpan = endSpan; } void record(GlitchType type, const char* stage, const SkCoincidentSpans* coin, - const SkOpSpanBase* suspect) { + const SkOpSpanBase* base) { SpanGlitch* glitch = recordCommon(type, stage); - glitch->fSuspect = suspect; - glitch->fCoin = coin; + glitch->fBase = base; + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); } void record(GlitchType type, const char* stage, const SkOpPtT* ptTS, const SkOpPtT* ptTE, @@ -161,22 +193,73 @@ struct SkPathOpsDebug::GlitchLog { glitch->fOppEndSpan = oPtTE; } + void record(GlitchType type, const char* stage, const SkOpSegment* seg, double startT, + double endT, const SkOpSegment* oppSeg, double oppStartT, double oppEndT) { + SpanGlitch* glitch = recordCommon(type, stage); + glitch->fSegment = seg; + glitch->fStartT = startT; + glitch->fEndT = endT; + glitch->fOppSegment = oppSeg; + glitch->fOppStartT = oppStartT; + glitch->fOppEndT = oppEndT; + } + + void record(GlitchType type, const char* stage, const SkOpSegment* seg, + const SkOpSpan* span) { + SpanGlitch* glitch = recordCommon(type, stage); + glitch->fSegment = seg; + glitch->fBase = span; + } + + void record(GlitchType type, const char* stage, double t, const SkOpSpanBase* span) { + SpanGlitch* glitch = recordCommon(type, stage); + glitch->fStartT = t; + glitch->fBase = span; + } + + void record(GlitchType type, const char* stage, const SkOpSegment* seg) { + SpanGlitch* glitch = recordCommon(type, stage); + glitch->fSegment = seg; + } + + void record(GlitchType type, const char* stage, const SkCoincidentSpans* coin, + const SkOpPtT* ptT) { + SpanGlitch* glitch = recordCommon(type, stage); + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = ptT; + } + SkTDArray<SpanGlitch> fGlitches; }; +#endif +void SkPathOpsDebug::ShowActiveSpans(SkOpContourHead* contourList) { +#if DEBUG_ACTIVE_SPANS + SkOpContour* contour = contourList; + do { + contour->debugShowActiveSpans(); + } while ((contour = contour->next())); +#endif +} + +#if DEBUG_COINCIDENCE void SkPathOpsDebug::CheckHealth(SkOpContourHead* contourList, const char* id) { + contourList->globalState()->debugSetCheckHealth(true); +#if DEBUG_COINCIDENCE_VERBOSE GlitchLog glitches; const SkOpContour* contour = contourList; const SkOpCoincidence* coincidence = contour->globalState()->coincidence(); + coincidence->debugCheckValid(id, &glitches); // don't call validate; spans may be inconsistent do { contour->debugCheckHealth(id, &glitches); - contour->debugMissingCoincidence(id, &glitches, coincidence); + contour->debugMissingCoincidence(id, &glitches); } while ((contour = contour->next())); - coincidence->debugFixAligned(id, &glitches); + coincidence->debugRemoveCollapsed(id, &glitches); coincidence->debugAddMissing(id, &glitches); coincidence->debugExpand(id, &glitches); coincidence->debugAddExpanded(id, &glitches); coincidence->debugMark(id, &glitches); + coincidence->debugReorder(id, &glitches); unsigned mask = 0; for (int index = 0; index < glitches.fGlitches.count(); ++index) { const SpanGlitch& glitch = glitches.fGlitches[index]; @@ -186,6 +269,92 @@ void SkPathOpsDebug::CheckHealth(SkOpContourHead* contourList, const char* id) { SkDebugf(mask & (1 << index) ? "x" : "-"); } SkDebugf(" %s\n", id); + for (int index = 0; index < glitches.fGlitches.count(); ++index) { + const SpanGlitch& glitch = glitches.fGlitches[index]; + SkDebugf("%02d: ", index); + if (glitch.fBase) { + SkDebugf(" base=%d", glitch.fBase->debugID()); + } + if (glitch.fSuspect) { + SkDebugf(" base=%d", glitch.fSuspect->debugID()); + } + if (glitch.fSegment) { + SkDebugf(" segment=%d", glitch.fSegment->debugID()); + } + if (glitch.fCoinSpan) { + SkDebugf(" coinSpan=%d", glitch.fCoinSpan->debugID()); + } + if (glitch.fEndSpan) { + SkDebugf(" endSpan=%d", glitch.fEndSpan->debugID()); + } + if (glitch.fOppSpan) { + SkDebugf(" oppSpan=%d", glitch.fOppSpan->debugID()); + } + if (glitch.fOppEndSpan) { + SkDebugf(" oppEndSpan=%d", glitch.fOppEndSpan->debugID()); + } + if (!SkScalarIsNaN(glitch.fStartT)) { + SkDebugf(" startT=%g", glitch.fStartT); + } + if (!SkScalarIsNaN(glitch.fEndT)) { + SkDebugf(" endT=%g", glitch.fEndT); + } + if (glitch.fOppSegment) { + SkDebugf(" segment=%d", glitch.fOppSegment->debugID()); + } + if (!SkScalarIsNaN(glitch.fOppStartT)) { + SkDebugf(" oppStartT=%g", glitch.fOppStartT); + } + if (!SkScalarIsNaN(glitch.fOppEndT)) { + SkDebugf(" oppEndT=%g", glitch.fOppEndT); + } + if (!SkScalarIsNaN(glitch.fPt.fX) || !SkScalarIsNaN(glitch.fPt.fY)) { + SkDebugf(" pt=%g,%g", glitch.fPt.fX, glitch.fPt.fY); + } + switch (glitch.fType) { + case kAddCorruptCoin_Glitch: SkDebugf(" AddCorruptCoin"); break; + case kAddExpandedCoin_Glitch: SkDebugf(" AddExpandedCoin"); break; + case kAddExpandedFail_Glitch: SkDebugf(" AddExpandedFail"); break; + case kAddIfMissingCoin_Glitch: SkDebugf(" AddIfMissingCoin"); break; + case kAddMissingCoin_Glitch: SkDebugf(" AddMissingCoin"); break; + case kAddMissingExtend_Glitch: SkDebugf(" AddMissingExtend"); break; + case kAddOrOverlap_Glitch: SkDebugf(" AAddOrOverlap"); break; + case kCollapsedCoin_Glitch: SkDebugf(" CollapsedCoin"); break; + case kCollapsedDone_Glitch: SkDebugf(" CollapsedDone"); break; + case kCollapsedOppValue_Glitch: SkDebugf(" CollapsedOppValue"); break; + case kCollapsedSpan_Glitch: SkDebugf(" CollapsedSpan"); break; + case kCollapsedWindValue_Glitch: SkDebugf(" CollapsedWindValue"); break; + case kDeletedCoin_Glitch: SkDebugf(" DeletedCoin"); break; + case kExpandCoin_Glitch: SkDebugf(" ExpandCoin"); break; + case kMarkCoinEnd_Glitch: SkDebugf(" MarkCoinEnd"); break; + case kMarkCoinInsert_Glitch: SkDebugf(" MarkCoinInsert"); break; + case kMarkCoinMissing_Glitch: SkDebugf(" MarkCoinMissing"); break; + case kMarkCoinStart_Glitch: SkDebugf(" MarkCoinStart"); break; + case kMergeContained_Glitch: SkDebugf(" MergeContained"); break; + case kMissingCoin_Glitch: SkDebugf(" MissingCoin"); break; + case kMissingDone_Glitch: SkDebugf(" MissingDone"); break; + case kMissingIntersection_Glitch: SkDebugf(" MissingIntersection"); break; + case kMoveMultiple_Glitch: SkDebugf(" MoveMultiple"); break; + case kMoveNearbyClearAll_Glitch: SkDebugf(" MoveNearbyClearAll"); break; + case kMoveNearbyClearAll2_Glitch: SkDebugf(" MoveNearbyClearAll2"); break; + case kMoveNearbyMerge_Glitch: SkDebugf(" MoveNearbyMerge"); break; + case kMoveNearbyMergeFinal_Glitch: SkDebugf(" MoveNearbyMergeFinal"); break; + case kMoveNearbyRelease_Glitch: SkDebugf(" MoveNearbyRelease"); break; + case kMoveNearbyReleaseFinal_Glitch: SkDebugf(" MoveNearbyReleaseFinal"); break; + case kReleasedSpan_Glitch: SkDebugf(" ReleasedSpan"); break; + case kUnaligned_Glitch: SkDebugf(" Unaligned"); break; + case kUnalignedHead_Glitch: SkDebugf(" UnalignedHead"); break; + case kUnalignedTail_Glitch: SkDebugf(" UnalignedTail"); break; + default: SkASSERT(0); + } + SkDebugf("\n"); + } + contourList->globalState()->debugSetCheckHealth(false); +#if DEBUG_ACTIVE_SPANS + SkDebugf("active after %s:\n", id); + ShowActiveSpans(contourList); +#endif +#endif } #endif @@ -254,7 +423,7 @@ static const char* gOpStrs[] = { "kDifference_SkPathOp", "kIntersect_SkPathOp", "kUnion_SkPathOp", - "kXor_PathOp", + "kXOR_PathOp", "kReverseDifference_SkPathOp", }; @@ -412,132 +581,72 @@ void SkDRect::debugInit() { #include "SkOpSegment.h" #if DEBUG_COINCIDENCE -void SkOpSegment::debugAddAlignIntersection(const char* id, SkPathOpsDebug::GlitchLog* log, - const SkOpPtT& endPtT, const SkPoint& oldPt, const SkOpContourHead* contourList) const { - const SkPoint& newPt = endPtT.fPt; - if (newPt == oldPt) { - return; - } - SkPoint line[2] = { newPt, oldPt }; - SkPathOpsBounds lineBounds; - lineBounds.setBounds(line, 2); - SkDLine aLine; - aLine.set(line); - const SkOpContour* current = contourList; +// commented-out lines keep this in sync with addT() + const SkOpPtT* SkOpSegment::debugAddT(double t, AliasMatch allowAlias, bool* allocated) const { + debugValidate(); + SkPoint pt = this->ptAtT(t); + const SkOpSpanBase* span = &fHead; do { - if (!SkPathOpsBounds::Intersects(current->bounds(), lineBounds)) { - continue; - } - const SkOpSegment* segment = current->first(); - do { - if (!SkPathOpsBounds::Intersects(segment->bounds(), lineBounds)) { - continue; - } - if (newPt == segment->fPts[0]) { - continue; - } - if (newPt == segment->fPts[SkPathOpsVerbToPoints(segment->fVerb)]) { - continue; - } - if (oldPt == segment->fPts[0]) { - continue; - } - if (oldPt == segment->fPts[SkPathOpsVerbToPoints(segment->fVerb)]) { - continue; - } - if (endPtT.debugContains(segment)) { - continue; - } - SkIntersections i; - switch (segment->fVerb) { - case SkPath::kLine_Verb: { - SkDLine bLine; - bLine.set(segment->fPts); - i.intersect(bLine, aLine); - } break; - case SkPath::kQuad_Verb: { - SkDQuad bQuad; - bQuad.set(segment->fPts); - i.intersect(bQuad, aLine); - } break; - case SkPath::kConic_Verb: { - SkDConic bConic; - bConic.set(segment->fPts, segment->fWeight); - i.intersect(bConic, aLine); - } break; - case SkPath::kCubic_Verb: { - SkDCubic bCubic; - bCubic.set(segment->fPts); - i.intersect(bCubic, aLine); - } break; - default: - SkASSERT(0); - } - if (i.used()) { - SkASSERT(i.used() == 1); - SkASSERT(!zero_or_one(i[0][0])); - SkOpSpanBase* checkSpan = fHead.next(); - while (!checkSpan->final()) { - if (checkSpan->contains(segment)) { - goto nextSegment; - } - checkSpan = checkSpan->upCast()->next(); + const SkOpPtT* result = span->ptT(); + const SkOpPtT* loop; + bool duplicatePt; + if (t == result->fT) { + goto bumpSpan; + } + if (this->match(result, this, t, pt, allowAlias)) { + // see if any existing alias matches segment, pt, and t + loop = result->next(); + duplicatePt = false; + while (loop != result) { + bool ptMatch = loop->fPt == pt; + if (loop->segment() == this && loop->fT == t && ptMatch) { + goto bumpSpan; } - log->record(kMissingIntersection_Glitch, id, checkSpan, segment, i[0][0], newPt); + duplicatePt |= ptMatch; + loop = loop->next(); } - nextSegment: - ; - } while ((segment = segment->next())); - } while ((current = current->next())); -} - -bool SkOpSegment::debugAddMissing(double t, const SkOpSegment* opp) const { - const SkOpSpanBase* existing = nullptr; - const SkOpSpanBase* test = &fHead; - double testT; - do { - if ((testT = test->ptT()->fT) >= t) { - if (testT == t) { - existing = test; + if (kNoAliasMatch == allowAlias) { + bumpSpan: +// span->bumpSpanAdds(); + return result; } - break; - } - } while ((test = test->upCast()->next())); - return !existing || !existing->debugContains(opp); -} - -void SkOpSegment::debugAlign(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { - const SkOpSpanBase* span = &fHead; - if (!span->aligned()) { - if (!span->debugAlignedEnd(0, fPts[0])) { - glitches->record(kUnalignedHead_Glitch, id, span); - } - } - while ((span = span->upCast()->next())) { - if (span == &fTail) { - break; - } - if (!span->aligned()) { - glitches->record(kUnaligned_Glitch, id, span); - } - } - if (!span->aligned()) { - span->debugAlignedEnd(1, fPts[SkPathOpsVerbToPoints(fVerb)]); - } - if (this->collapsed()) { - const SkOpSpan* span = &fHead; - do { - if (span->windValue()) { - glitches->record(kCollapsedWindValue_Glitch, id, span); +// 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 +// span->bumpSpanAdds(); + if (allocated) { + *allocated = true; } - if (span->oppValue()) { - glitches->record(kCollapsedOppValue_Glitch, id, span); + return nullptr; + } + if (t < result->fT) { + const SkOpSpan* prev = result->span()->prev(); + if (!prev) { + return nullptr; // FIXME: this is a fail case; nullptr return elsewhere means result was allocated in non-const version } - if (!span->done()) { - glitches->record(kCollapsedDone_Glitch, id, span); +// 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 +// span->bumpSpanAdds(); + if (allocated) { + *allocated = true; } - } while ((span = span->next()->upCastable())); - } + return nullptr; + } + SkASSERT(span != &fTail); + } while ((span = span->upCast()->next())); + SkASSERT(0); + return nullptr; } #endif @@ -547,7 +656,7 @@ void SkOpSegment::debugCheckAngleCoin() const { const SkOpSpan* span; do { const SkOpAngle* angle = base->fromAngle(); - if (angle && angle->fCheckCoincidence) { + if (angle && angle->debugCheckCoincidence()) { angle->debugCheckNearCoincidence(); } if (base->final()) { @@ -555,41 +664,35 @@ void SkOpSegment::debugCheckAngleCoin() const { } span = base->upCast(); angle = span->toAngle(); - if (angle && angle->fCheckCoincidence) { + if (angle && angle->debugCheckCoincidence()) { angle->debugCheckNearCoincidence(); } } while ((base = span->next())); } #endif -#if DEBUG_COINCIDENCE +#if DEBUG_COINCIDENCE_VERBOSE // this mimics the order of the checks in handle coincidence void SkOpSegment::debugCheckHealth(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { debugMoveMultiples(id, glitches); - debugFindCollapsed(id, glitches); debugMoveNearby(id, glitches); - debugAlign(id, glitches); - debugAddAlignIntersections(id, glitches, this->globalState()->contourHead()); + debugMissingCoincidence(id, glitches); +} +// commented-out lines keep this in sync with clearAll() +void SkOpSegment::debugClearAll(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { + const SkOpSpan* span = &fHead; + do { + this->debugClearOne(span, id, glitches); + } while ((span = span->next()->upCastable())); + this->globalState()->coincidence()->debugRelease(id, glitches, this); } -void SkOpSegment::debugFindCollapsed(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { - if (fHead.contains(&fTail)) { - const SkOpSpan* span = this->head(); - bool missingDone = false; - do { - missingDone |= !span->done(); - } while ((span = span->next()->upCastable())); - if (missingDone) { - glitches->record(kMissingDone_Glitch, id, &fHead); - } - if (!fHead.debugAlignedEnd(0, fHead.pt())) { - glitches->record(kUnalignedHead_Glitch, id, &fHead); - } - if (!fTail.aligned()) { - glitches->record(kUnalignedTail_Glitch, id, &fTail); - } - } +// commented-out lines keep this in sync with clearOne() +void SkOpSegment::debugClearOne(const SkOpSpan* span, const char* id, SkPathOpsDebug::GlitchLog* glitches) const { + if (span->windValue()) glitches->record(kCollapsedWindValue_Glitch, id, span); + if (span->oppValue()) glitches->record(kCollapsedOppValue_Glitch, id, span); + if (!span->done()) glitches->record(kCollapsedDone_Glitch, id, span); } #endif @@ -607,16 +710,37 @@ SkOpAngle* SkOpSegment::debugLastAngle() { } #if DEBUG_COINCIDENCE -void SkOpSegment::debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log, - const SkOpCoincidence* coincidences) const { - if (this->verb() != SkPath::kLine_Verb) { - return; - } +// commented-out lines keep this in sync with ClearVisited +void SkOpSegment::DebugClearVisited(const SkOpSpanBase* span) { + // reset visited flag back to false + do { + const SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + const SkOpSegment* opp = ptT->segment(); + opp->resetDebugVisited(); + } + } while (!span->final() && (span = span->upCast()->next())); +} +#endif + +#if DEBUG_COINCIDENCE_VERBOSE +// commented-out lines keep this in sync with missingCoincidence() +// look for pairs of undetected coincident curves +// assumes that segments going in have visited flag clear +// Even though pairs of curves correct detect coincident runs, a run may be missed +// if the coincidence is a product of multiple intersections. For instance, given +// curves A, B, and C: +// A-B intersect at a point 1; A-C and B-C intersect at point 2, so near +// the end of C that the intersection is replaced with the end of C. +// Even though A-B correctly do not detect an intersection at point 2, +// the resulting run from point 1 to point 2 is coincident on A and B. +void SkOpSegment::debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log) const { if (this->done()) { return; } const SkOpSpan* prior = nullptr; const SkOpSpanBase* spanBase = &fHead; +// bool result = false; do { const SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; SkASSERT(ptT->span() == spanBase); @@ -624,29 +748,29 @@ void SkOpSegment::debugMissingCoincidence(const char* id, SkPathOpsDebug::Glitch if (ptT->deleted()) { continue; } - SkOpSegment* opp = ptT->span()->segment(); -// if (opp->verb() == SkPath::kLine_Verb) { -// continue; -// } + const SkOpSegment* opp = ptT->span()->segment(); if (opp->done()) { continue; } // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence - if (!opp->visited()) { + if (!opp->debugVisited()) { continue; } if (spanBase == &fHead) { continue; } + if (ptT->segment() == this) { + continue; + } const SkOpSpan* span = spanBase->upCastable(); // FIXME?: this assumes that if the opposite segment is coincident then no more // coincidence needs to be detected. This may not be true. - if (span && span->segment() != opp && span->containsCoincidence(opp)) { + if (span && span->segment() != opp && span->containsCoincidence(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted continue; } - if (spanBase->segment() != opp && spanBase->containsCoinEnd(opp)) { + if (spanBase->segment() != opp && spanBase->containsCoinEnd(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted continue; - } + } const SkOpPtT* priorPtT = nullptr, * priorStopPtT; // find prior span containing opp segment const SkOpSegment* priorOpp = nullptr; @@ -669,6 +793,9 @@ void SkOpSegment::debugMissingCoincidence(const char* id, SkPathOpsDebug::Glitch if (!priorOpp) { continue; } + if (priorPtT == ptT) { + continue; + } const SkOpPtT* oppStart = prior->ptT(); const SkOpPtT* oppEnd = spanBase->ptT(); bool swapped = priorPtT->fT > ptT->fT; @@ -676,22 +803,28 @@ void SkOpSegment::debugMissingCoincidence(const char* id, SkPathOpsDebug::Glitch SkTSwap(priorPtT, ptT); SkTSwap(oppStart, oppEnd); } - bool flipped = oppStart->fT > oppEnd->fT; - bool coincident = false; - if (coincidences->contains(priorPtT, ptT, oppStart, oppEnd, flipped)) { + const SkOpCoincidence* coincidence = this->globalState()->coincidence(); + const SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); + const SkOpPtT* rootPtT = ptT->span()->ptT(); + const SkOpPtT* rootOppStart = oppStart->span()->ptT(); + const SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); + if (coincidence->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { goto swapBack; } - if (opp->verb() == SkPath::kLine_Verb) { - coincident = (SkDPoint::ApproximatelyEqual(priorPtT->fPt, oppStart->fPt) || - SkDPoint::ApproximatelyEqual(priorPtT->fPt, oppEnd->fPt)) && - (SkDPoint::ApproximatelyEqual(ptT->fPt, oppStart->fPt) || - SkDPoint::ApproximatelyEqual(ptT->fPt, oppEnd->fPt)); - } - if (!coincident) { - coincident = testForCoincidence(priorPtT, ptT, prior, spanBase, opp, 5000); - } - if (coincident) { + if (testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { + // mark coincidence +#if DEBUG_COINCIDENCE +// SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, +// rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), +// rootOppEnd->debugID()); +#endif log->record(kMissingCoin_Glitch, id, priorPtT, ptT, oppStart, oppEnd); + // coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); + // } +#if DEBUG_COINCIDENCE +// SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd) +#endif + // result = true; } swapBack: if (swapped) { @@ -699,9 +832,14 @@ void SkOpSegment::debugMissingCoincidence(const char* id, SkPathOpsDebug::Glitch } } } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); + DebugClearVisited(&fHead); + return; } +// commented-out lines keep this in sync with moveMultiples() +// if a span has more than one intersection, merge the other segments' span as needed void SkOpSegment::debugMoveMultiples(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { + debugValidate(); const SkOpSpanBase* test = &fHead; do { int addCount = test->spanAddsCount(); @@ -777,6 +915,7 @@ void SkOpSegment::debugMoveMultiples(const char* id, SkPathOpsDebug::GlitchLog* } while ((matchPtT = matchPtT->next()) != startPtT); goto tryNextSpan; foundMatch: // merge oppTest and oppSpan + oppSegment->debugValidate(); if (oppTest == &oppSegment->fTail || oppTest == &oppSegment->fHead) { SkASSERT(oppSpan != &oppSegment->fHead); // don't expect collapse SkASSERT(oppSpan != &oppSegment->fTail); @@ -784,60 +923,67 @@ void SkOpSegment::debugMoveMultiples(const char* id, SkPathOpsDebug::GlitchLog* } else { glitches->record(kMoveMultiple_Glitch, id, oppSpan, oppTest); } + oppSegment->debugValidate(); goto checkNextSpan; } - tryNextSpan: + tryNextSpan: ; } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); } while ((testPtT = testPtT->next()) != startPtT); -checkNextSpan: +checkNextSpan: ; } while ((test = test->final() ? nullptr : test->upCast()->next())); + debugValidate(); + return; } +// commented-out lines keep this in sync with moveNearby() +// Move nearby t values and pts so they all hang off the same span. Alignment happens later. void SkOpSegment::debugMoveNearby(const char* id, SkPathOpsDebug::GlitchLog* glitches) const { - const SkOpSpanBase* spanS = &fHead; + debugValidate(); + // release undeleted spans pointing to this seg that are linked to the primary span + const SkOpSpanBase* spanBase = &fHead; do { - const SkOpSpanBase* test = spanS->upCast()->next(); - const SkOpSpanBase* next; - if (spanS->contains(test)) { - if (!test->final()) { - glitches->record(kUndetachedSpan_Glitch, id, test, spanS); - } else if (spanS != &fHead) { - glitches->record(kUndetachedSpan_Glitch, id, spanS, test); + const SkOpPtT* ptT = spanBase->ptT(); + const SkOpPtT* headPtT = ptT; + while ((ptT = ptT->next()) != headPtT) { + const SkOpSpanBase* test = ptT->span(); + if (ptT->segment() == this && !ptT->deleted() && test != spanBase + && test->ptT() == ptT) { + if (test->final()) { + if (spanBase == &fHead) { + glitches->record(kMoveNearbyClearAll_Glitch, id, this); +// return; + } + glitches->record(kMoveNearbyReleaseFinal_Glitch, id, spanBase, ptT); + } else if (test->prev()) { + glitches->record(kMoveNearbyRelease_Glitch, id, test, headPtT); + } +// break; } } - do { // iterate through all spans associated with start - const SkOpPtT* startBase = spanS->ptT(); - next = test->final() ? nullptr : test->upCast()->next(); - do { - const 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) { - glitches->record(kCollapsedSpan_Glitch, id, spanS); - } else { - glitches->record(kUnmergedSpan_Glitch, id, &this->fTail, spanS); - } - } else { - glitches->record(kUnmergedSpan_Glitch, id, spanS, test); - 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()); + spanBase = spanBase->upCast()->next(); + } while (!spanBase->final()); + + // This loop looks for adjacent spans which are near by + spanBase = &fHead; + do { // iterate through all spans associated with start + const SkOpSpanBase* test = spanBase->upCast()->next(); + if (this->spansNearby(spanBase, test)) { + if (test->final()) { + if (spanBase->prev()) { + glitches->record(kMoveNearbyMergeFinal_Glitch, id, test); + } else { + glitches->record(kMoveNearbyClearAll2_Glitch, id, this); + // return + } + } else { + glitches->record(kMoveNearbyMerge_Glitch, id, spanBase); + } + } + spanBase = test; + } while (!spanBase->final()); + debugValidate(); } #endif @@ -864,16 +1010,18 @@ void SkOpSegment::debugShowActiveSpans() const { lastId = this->debugID(); lastT = span->t(); SkDebugf("%s id=%d", __FUNCTION__, this->debugID()); - SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); + // since endpoints may have be adjusted, show actual computed curves + SkDCurve curvePart; + this->subDivide(span, span->next(), &curvePart); + const SkDPoint* pts = curvePart.fCubic.fPts; + SkDebugf(" (%1.9g,%1.9g", pts[0].fX, pts[0].fY); for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { - SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); + SkDebugf(" %1.9g,%1.9g", pts[vIndex].fX, pts[vIndex].fY); } if (SkPath::kConic_Verb == fVerb) { - SkDebugf(" %1.9gf", fWeight); + SkDebugf(" %1.9gf", curvePart.fConic.fWeight); } - 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(") t=%1.9g tEnd=%1.9g", span->t(), span->next()->t()); if (span->windSum() == SK_MinS32) { SkDebugf(" windSum=?"); } else { @@ -958,7 +1106,7 @@ void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int // 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 + This checks the distance between start points; the distance between */ #if DEBUG_ANGLE void SkOpAngle::debugCheckNearCoincidence() const { @@ -996,7 +1144,7 @@ void SkOpAngle::debugCheckNearCoincidence() const { SkDebugf("\n"); } test = test->fNext; - } while (test->fNext != this); + } while (test->fNext != this); } #endif @@ -1046,6 +1194,11 @@ void SkOpAngle::debugLoop() const { #endif void SkOpAngle::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif #if DEBUG_VALIDATE const SkOpAngle* first = this; const SkOpAngle* next = this; @@ -1108,243 +1261,409 @@ void SkOpAngle::debugValidateNext() const { #endif } +#ifdef SK_DEBUG +void SkCoincidentSpans::debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, + const SkOpGlobalState* debugState) const { + SkASSERT(coinPtTEnd()->span() == over || !debugState->debugRunFail()); + SkASSERT(oppPtTEnd()->span() == outer || !debugState->debugRunFail()); +} +#endif -#if DEBUG_COINCIDENCE +#if DEBUG_COINCIDENCE_VERBOSE +/* Commented-out lines keep this in sync with expand */ +bool SkCoincidentSpans::debugExpand(const char* id, SkPathOpsDebug::GlitchLog* log) const { + bool expanded = false; + const SkOpSegment* segment = coinPtTStart()->segment(); + const SkOpSegment* oppSegment = oppPtTStart()->segment(); + do { + const SkOpSpan* start = coinPtTStart()->span()->upCast(); + const SkOpSpan* prev = start->prev(); + const SkOpPtT* oppPtT; + if (!prev || !(oppPtT = prev->contains(oppSegment))) { + break; + } + double midT = (prev->t() + start->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + if (log) log->record(kExpandCoin_Glitch, id, this, prev->ptT(), oppPtT); + expanded = true; + } while (false); // actual continues while expansion is possible + do { + const SkOpSpanBase* end = coinPtTEnd()->span(); + SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); + const SkOpPtT* oppPtT; + if (!next || !(oppPtT = next->contains(oppSegment))) { + break; + } + double midT = (end->t() + next->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + if (log) log->record(kExpandCoin_Glitch, id, this, next->ptT(), oppPtT); + expanded = true; + } while (false); // actual continues while expansion is possible + return expanded; +} + +#define FAIL_IF(cond) do { if (cond) log->record(kAddExpandedFail_Glitch, id, coin); } while (false) + +/* Commented-out lines keep this in sync with addExpanded */ +// for each coincident pair, match the spans +// if the spans don't match, add the mssing pt to the segment and loop it in the opposite span void SkOpCoincidence::debugAddExpanded(const char* id, SkPathOpsDebug::GlitchLog* log) const { - // for each coincident pair, match the spans - // if the spans don't match, add the mssing pt to the segment and loop it in the opposite span const SkCoincidentSpans* coin = this->fHead; if (!coin) { - coin = this->fTop; - } - if (!coin) { return; } do { - const SkOpPtT* startPtT = coin->fCoinPtTStart; - const SkOpPtT* oStartPtT = coin->fOppPtTStart; + const SkOpPtT* startPtT = coin->coinPtTStart(); + const SkOpPtT* oStartPtT = coin->oppPtTStart(); SkASSERT(startPtT->contains(oStartPtT)); - SkASSERT(coin->fCoinPtTEnd->contains(coin->fOppPtTEnd)); + SkASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); const SkOpSpanBase* start = startPtT->span(); const SkOpSpanBase* oStart = oStartPtT->span(); - const SkOpSpanBase* end = coin->fCoinPtTEnd->span(); - const SkOpSpanBase* oEnd = coin->fOppPtTEnd->span(); + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); + const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span(); + FAIL_IF(oEnd->deleted()); const SkOpSpanBase* test = start->upCast()->next(); - const SkOpSpanBase* oTest = coin->fFlipped ? oStart->prev() : oStart->upCast()->next(); + const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next(); + if (!oTest) { + return; + } while (test != end || oTest != oEnd) { - bool bumpTest = true; - bool bumpOTest = true; - if (!test->ptT()->contains(oTest->ptT())) { + if (!test->ptT()->contains(oTest->segment()) + || !oTest->ptT()->contains(start->segment())) { // use t ranges to guess which one is missing - double startRange = coin->fCoinPtTEnd->fT - startPtT->fT; + double startRange = coin->coinPtTEnd()->fT - startPtT->fT; + FAIL_IF(!startRange); double startPart = (test->t() - startPtT->fT) / startRange; - double oStartRange = coin->fOppPtTEnd->fT - oStartPtT->fT; + double oStartRange = coin->oppPtTEnd()->fT - oStartPtT->fT; + FAIL_IF(!oStartRange); double oStartPart = (oTest->t() - oStartPtT->fT) / oStartRange; - if (startPart == oStartPart) { - // data is corrupt - log->record(kAddCorruptCoin_Glitch, id, start, oStart); - break; + FAIL_IF(startPart == oStartPart); + bool startOver = false; + if (startPart < oStartPart) + log->record(kAddExpandedCoin_Glitch, id, // strange debug formatting lines up with original + oStartPtT->fT + oStartRange * startPart, test); + else log->record(kAddExpandedCoin_Glitch, id, + startPtT->fT + startRange * oStartPart, oTest); + if (false) { + SkASSERT(0); + return; } - if (startPart < oStartPart) { - double newT = oStartPtT->fT + oStartRange * startPart; - log->record(kAddExpandedCoin_Glitch, id, oStart, newT, test->pt()); - bumpOTest = false; - } else { - double newT = startPtT->fT + startRange * oStartPart; - log->record(kAddExpandedCoin_Glitch, id, start, newT, oTest->pt()); - bumpTest = false; + if (startOver) { + test = start; + oTest = oStart; } } - if (bumpTest && test != end) { + if (test != end) { test = test->upCast()->next(); } - if (bumpOTest && oTest != oEnd) { - oTest = coin->fFlipped ? oTest->prev() : oTest->upCast()->next(); + if (oTest != oEnd) { + oTest = coin->flipped() ? oTest->prev() : oTest->upCast()->next(); + if (!oTest) { + return; + } } } - } while ((coin = coin->fNext)); -} - -static void t_range(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::debugAddIfMissing(const SkCoincidentSpans* outer, const SkOpPtT* over1s, - const SkOpPtT* over1e) const { - const SkCoincidentSpans* check = this->fTop; - while (check) { - if (check->fCoinPtTStart->span() == over1s->span() - && check->fOppPtTStart->span() == outer->fOppPtTStart->span()) { - SkASSERT(check->fCoinPtTEnd->span() == over1e->span() - || !fDebugState->debugRunFail()); - SkASSERT(check->fOppPtTEnd->span() == outer->fOppPtTEnd->span() - || !fDebugState->debugRunFail()); - return false; - } - if (check->fCoinPtTStart->span() == outer->fCoinPtTStart->span() - && check->fOppPtTStart->span() == over1s->span()) { - SkASSERT(check->fCoinPtTEnd->span() == outer->fCoinPtTEnd->span() - || !fDebugState->debugRunFail()); - SkASSERT(check->fOppPtTEnd->span() == over1e->span() - || !fDebugState->debugRunFail()); - return false; - } - check = check->fNext; + } while ((coin = coin->next())); + return; +} + +/* Commented-out lines keep this in sync with addIfMissing() */ +void SkOpCoincidence::debugAddIfMissing(const SkCoincidentSpans* outer, const SkOpPtT* over1s, + const SkOpPtT* over1e, const char* id, SkPathOpsDebug::GlitchLog* log) const { +// SkASSERT(fTop); + if (fTop && alreadyAdded(fTop, outer, over1s, over1e)) { // in debug, fTop may be null + return; } - return true; + if (fHead && alreadyAdded(fHead, outer, over1s, over1e)) { + return; + } + log->record(kAddIfMissingCoin_Glitch, id, outer->coinPtTStart(), outer->coinPtTEnd(), over1s, over1e); + this->debugValidate(); + return; } -bool SkOpCoincidence::debugAddIfMissing(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) const { +/* Commented-out lines keep this in sync addIfMissing() */ +void SkOpCoincidence::debugAddIfMissing(const SkOpPtT* over1s, const SkOpPtT* over1e, + const SkOpPtT* over2s, const SkOpPtT* over2e, double tStart, double tEnd, + const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd, const char* id, SkPathOpsDebug::GlitchLog* log) const { double coinTs, coinTe, oppTs, oppTe; - t_range(over1s, over1e, tStart, tEnd, coinPtTStart, coinPtTEnd, &coinTs, &coinTe); - t_range(over2s, over2e, tStart, tEnd, oppPtTStart, oppPtTEnd, &oppTs, &oppTe); - const SkOpSegment* coinSeg = coinPtTStart->segment(); - const SkOpSegment* oppSeg = oppPtTStart->segment(); - SkASSERT(coinSeg != oppSeg); - const SkCoincidentSpans* check = this->fTop; - ; - while (check) { - const SkOpSegment* checkCoinSeg = check->fCoinPtTStart->segment(); - const SkOpSegment* checkOppSeg; - if (checkCoinSeg != coinSeg && checkCoinSeg != oppSeg) { - goto next; - } - checkOppSeg = check->fOppPtTStart->segment(); - if (checkOppSeg != coinSeg && checkOppSeg != oppSeg) { - goto next; - } - { - 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; - } - } -next: - check = check->fNext; + TRange(over1s, over1e, tStart, tEnd, coinPtTStart, coinPtTEnd, &coinTs, &coinTe); + TRange(over2s, over2e, tStart, tEnd, oppPtTStart, oppPtTEnd, &oppTs, &oppTe); + bool swap = coinTs > coinTe; + if (swap) { + SkTSwap(coinTs, coinTe); } if ((over1s->fT < over1e->fT) != (over2s->fT < over2e->fT)) { SkTSwap(oppTs, oppTe); } - if (coinTs > coinTe) { - SkTSwap(coinTs, coinTe); + if (swap) { SkTSwap(oppTs, oppTe); } - bool cs = coinSeg->debugAddMissing(coinTs, oppSeg); - bool ce = coinSeg->debugAddMissing(coinTe, oppSeg); - if (cs == ce) { - return false; + const SkOpSegment* coinSeg = coinPtTStart->segment(); + const SkOpSegment* oppSeg = oppPtTStart->segment(); + if (coinSeg == oppSeg) { + return; } - return true; + return this->debugAddOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, id, log); +} + +/* Commented-out lines keep this in sync addOrOverlap() */ +void SkOpCoincidence::debugAddOrOverlap(const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, const char* id, SkPathOpsDebug::GlitchLog* log) const { + SkTDArray<SkCoincidentSpans*> overlaps; + SkASSERT(!fTop); // this is (correctly) reversed in addifMissing() + if (fTop && !this->checkOverlap(fTop, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &overlaps)) { + return; + } + if (fHead && !this->checkOverlap(fHead, coinSeg, oppSeg, coinTs, + coinTe, oppTs, oppTe, &overlaps)) { + return; + } + const SkCoincidentSpans* overlap = overlaps.count() ? overlaps[0] : nullptr; + for (int index = 1; index < overlaps.count(); ++index) { // combine overlaps before continuing + const SkCoincidentSpans* test = overlaps[index]; + if (overlap->coinPtTStart()->fT > test->coinPtTStart()->fT) { + log->record(kAddOrOverlap_Glitch, id, overlap, test->coinPtTStart()); + } + if (overlap->coinPtTEnd()->fT < test->coinPtTEnd()->fT) { + log->record(kAddOrOverlap_Glitch, id, overlap, test->coinPtTEnd()); + } + if (overlap->flipped() + ? overlap->oppPtTStart()->fT < test->oppPtTStart()->fT + : overlap->oppPtTStart()->fT > test->oppPtTStart()->fT) { + log->record(kAddOrOverlap_Glitch, id, overlap, test->oppPtTStart()); + } + if (overlap->flipped() + ? overlap->oppPtTEnd()->fT > test->oppPtTEnd()->fT + : overlap->oppPtTEnd()->fT < test->oppPtTEnd()->fT) { + log->record(kAddOrOverlap_Glitch, id, overlap, test->oppPtTEnd()); + } + if (!fHead) { + SkAssertResult(true); + } + } + const SkOpPtT* cs = coinSeg->existing(coinTs, oppSeg); + const SkOpPtT* ce = coinSeg->existing(coinTe, oppSeg); + if (overlap && cs && ce && overlap->contains(cs, ce)) { + return; + } + SkASSERT(cs != ce || !cs); + const SkOpPtT* os = oppSeg->existing(oppTs, coinSeg); + const SkOpPtT* oe = oppSeg->existing(oppTe, coinSeg); + if (overlap && os && oe && overlap->contains(os, oe)) { + return; + } + SkASSERT(true || !cs || !cs->deleted()); + SkASSERT(true || !os || !os->deleted()); + SkASSERT(true || !ce || !ce->deleted()); + SkASSERT(true || !oe || !oe->deleted()); + const SkOpPtT* csExisting = !cs ? coinSeg->existing(coinTs, nullptr) : nullptr; + const SkOpPtT* ceExisting = !ce ? coinSeg->existing(coinTe, nullptr) : nullptr; + if (csExisting && csExisting == ceExisting) { + return; + } + if (csExisting && (csExisting == ce || csExisting->contains(ceExisting ? ceExisting : ce))) { + return; + } + if (ceExisting && (ceExisting == cs || ceExisting->contains(csExisting ? csExisting : cs))) { + return; + } + const SkOpPtT* osExisting = !os ? oppSeg->existing(oppTs, nullptr) : nullptr; + const SkOpPtT* oeExisting = !oe ? oppSeg->existing(oppTe, nullptr) : nullptr; + if (osExisting && osExisting == oeExisting) { + return; + } + if (osExisting && (osExisting == oe || osExisting->contains(oeExisting ? oeExisting : oe))) { + return; + } + if (oeExisting && (oeExisting == os || oeExisting->contains(osExisting ? osExisting : os))) { + return; + } + bool csDeleted = false, osDeleted = false, ceDeleted = false, oeDeleted = false; + this->debugValidate(); + if (!cs || !os) { + if (!cs) + cs = coinSeg->debugAddT(coinTs, SkOpSegment::kNoAliasMatch, nullptr); + if (!os) + os = oppSeg->debugAddT(oppTs, SkOpSegment::kNoAliasMatch, nullptr); + if (cs && os) cs->span()->debugAddOppAndMerge(id, log, os->span(), &csDeleted, &osDeleted); +// cs = csWritable; +// os = osWritable; + if ((ce && ce->deleted()) || (oe && oe->deleted())) { + return; + } + } + if (!ce || !oe) { + if (!ce) + ce = coinSeg->debugAddT(coinTe, SkOpSegment::kNoAliasMatch, nullptr); + if (!oe) + oe = oppSeg->debugAddT(oppTe, SkOpSegment::kNoAliasMatch, nullptr); + if (ce && oe) ce->span()->debugAddOppAndMerge(id, log, oe->span(), &ceDeleted, &oeDeleted); +// ce = ceWritable; +// oe = oeWritable; + } + this->debugValidate(); + if (csDeleted || osDeleted || ceDeleted || oeDeleted) { + return; + } + if (!cs || !ce || cs->contains(ce) || !os || !oe || os->contains(oe)) { + return; + } +// bool result = true; + if (overlap) { + if (overlap->coinPtTStart()->segment() == coinSeg) { + log->record(kAddMissingExtend_Glitch, id, coinSeg, coinTs, coinTe, oppSeg, oppTs, oppTe); + } else { + if (oppTs > oppTe) { + SkTSwap(coinTs, coinTe); + SkTSwap(oppTs, oppTe); + } + log->record(kAddMissingExtend_Glitch, id, oppSeg, oppTs, oppTe, coinSeg, coinTs, coinTe); + } +#if DEBUG_COINCIDENCE_VERBOSE +// if (result) { +// overlap->debugShow(); +// } +#endif + } else { + log->record(kAddMissingCoin_Glitch, id, coinSeg, coinTs, coinTe, oppSeg, oppTs, oppTe); +#if DEBUG_COINCIDENCE_VERBOSE +// fHead->debugShow(); +#endif + } + this->debugValidate(); + return; } +// Extra commented-out lines keep this in sync with addMissing() +/* detects overlaps of different coincident runs on same segment */ +/* does not detect overlaps for pairs without any segments in common */ +// returns true if caller should loop again void SkOpCoincidence::debugAddMissing(const char* id, SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* outer = fHead; if (!outer) { return; } + // bool added = false; + // fTop = outer; + // fHead = nullptr; do { // addifmissing can modify the list that this is walking // save head so that walker can iterate over old data unperturbed // addifmissing adds to head freely then add saved head in the end - const SkOpSegment* outerCoin = outer->fCoinPtTStart->segment(); - SkASSERT(outerCoin == outer->fCoinPtTEnd->segment()); - const SkOpSegment* outerOpp = outer->fOppPtTStart->segment(); - SkASSERT(outerOpp == outer->fOppPtTEnd->segment()); + const SkOpSegment* outerCoin = outer->coinPtTStart()->segment(); + const SkOpSegment* outerOpp = outer->oppPtTStart()->segment(); + if (outerCoin->done() || outerOpp->done()) { + continue; + } const SkCoincidentSpans* inner = outer; - while ((inner = inner->fNext)) { + while ((inner = inner->next())) { + this->debugValidate(); double overS, overE; - const SkOpSegment* innerCoin = inner->fCoinPtTStart->segment(); - SkASSERT(innerCoin == inner->fCoinPtTEnd->segment()); - const SkOpSegment* innerOpp = inner->fOppPtTStart->segment(); - SkASSERT(innerOpp == inner->fOppPtTEnd->segment()); - if (outerCoin == innerCoin - && this->overlap(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, &overS, &overE)) { - if (this->debugAddIfMissing(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, overS, overE, - outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd)) { - log->record(kAddMissingCoin_Glitch, id, outer, inner->fCoinPtTStart); + const SkOpSegment* innerCoin = inner->coinPtTStart()->segment(); + const SkOpSegment* innerOpp = inner->oppPtTStart()->segment(); + if (innerCoin->done() || innerOpp->done()) { + continue; + } + if (outerCoin == innerCoin) { + if (outerOpp != innerOpp + && this->overlap(outer->coinPtTStart(), outer->coinPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), &overS, &overE)) { + this->debugAddIfMissing(outer->coinPtTStart(), outer->coinPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), overS, overE, + outer->oppPtTStart(), outer->oppPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), id, log); } - } else if (outerCoin == innerOpp - && this->overlap(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, &overS, &overE)) { - if (this->debugAddIfMissing(outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, overS, overE, - outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd)) { - log->record(kAddMissingCoin_Glitch, id, outer, inner->fOppPtTStart); + } else if (outerCoin == innerOpp) { + if (outerOpp != innerCoin + && this->overlap(outer->coinPtTStart(), outer->coinPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), &overS, &overE)) { + this->debugAddIfMissing(outer->coinPtTStart(), outer->coinPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), overS, overE, + outer->oppPtTStart(), outer->oppPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), id, log); } - } else if (outerOpp == innerCoin - && this->overlap(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, &overS, &overE)) { - if (this->debugAddIfMissing(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd, overS, overE, - outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd)) { - log->record(kAddMissingCoin_Glitch, id, outer, inner->fCoinPtTStart); + } else if (outerOpp == innerCoin) { + SkASSERT(outerCoin != innerOpp); + if (this->overlap(outer->oppPtTStart(), outer->oppPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), &overS, &overE)) { + this->debugAddIfMissing(outer->oppPtTStart(), outer->oppPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), overS, overE, + outer->coinPtTStart(), outer->coinPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), id, log); } - } else if (outerOpp == innerOpp - && this->overlap(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, &overS, &overE)) { - if (this->debugAddIfMissing(outer->fOppPtTStart, outer->fOppPtTEnd, - inner->fOppPtTStart, inner->fOppPtTEnd, overS, overE, - outer->fCoinPtTStart, outer->fCoinPtTEnd, - inner->fCoinPtTStart, inner->fCoinPtTEnd)) { - log->record(kAddMissingCoin_Glitch, id, outer, inner->fOppPtTStart); - } - } else if (outerCoin != innerCoin) { - // check to see if outer span overlaps the inner span - // look for inner segment in pt-t list - // if present, and if t values are in coincident range - // add two pairs of new coincidence - const SkOpPtT* testS = outer->fCoinPtTStart->debugContains(innerCoin); - const SkOpPtT* testE = outer->fCoinPtTEnd->debugContains(innerCoin); - if (testS && testS->fT >= inner->fCoinPtTStart->fT - && testE && testE->fT <= inner->fCoinPtTEnd->fT - && this->testForCoincidence(outer, testS, testE)) { - if (this->debugAddIfMissing(outer, testS, testE)) { - log->record(kAddMissingCoin_Glitch, id, outer, testS, testE); - } - } else { - testS = inner->fCoinPtTStart->debugContains(outerCoin); - testE = inner->fCoinPtTEnd->debugContains(outerCoin); - if (testS && testS->fT >= outer->fCoinPtTStart->fT - && testE && testE->fT <= outer->fCoinPtTEnd->fT - && this->testForCoincidence(inner, testS, testE)) { - if (this->debugAddIfMissing(inner, testS, testE)) { - log->record(kAddMissingCoin_Glitch, id, inner, testS, testE); - } - } + } else if (outerOpp == innerOpp) { + SkASSERT(outerCoin != innerCoin); + if (this->overlap(outer->oppPtTStart(), outer->oppPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), &overS, &overE)) { + this->debugAddIfMissing(outer->oppPtTStart(), outer->oppPtTEnd(), + inner->oppPtTStart(), inner->oppPtTEnd(), overS, overE, + outer->coinPtTStart(), outer->coinPtTEnd(), + inner->coinPtTStart(), inner->coinPtTEnd(), id, log); } } + this->debugValidate(); + } + } while ((outer = outer->next())); + // this->restoreHead(); + return; +} + +// Commented-out lines keep this in sync with release() +void SkOpCoincidence::debugRelease(const char* id, SkPathOpsDebug::GlitchLog* log, const SkOpSegment* deleted) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + if (coin->coinPtTStart()->segment() == deleted + || coin->coinPtTEnd()->segment() == deleted + || coin->oppPtTStart()->segment() == deleted + || coin->oppPtTEnd()->segment() == deleted) { + log->record(kReleasedSpan_Glitch, id, coin); + } + } while ((coin = coin->next())); +} + +// Commented-out lines keep this in sync with reorder() +// iterate through all coincident pairs, looking for ranges greater than 1 +// if found, see if the opposite pair can match it -- which may require +// reordering the ptT pairs +void SkOpCoincidence::debugReorder(const char* id, SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + // most commonly, concidence are one span long; check for that first + int intervals = coin->spanCount(); + if (intervals = 1) { +#if DEBUG_COINCIDENCE_VERBOSE + // SkASSERT(!coin->debugExpand(nullptr, nullptr)); +#endif + continue; + } + coin->debugExpand(id, log); + if (coin->spanCount() <= 0) { + return; } - } while ((outer = outer->fNext)); + // check to see if every span in coin has a mate in opp + const SkOpSpan* start = coin->coinPtTStart()->span()->upCast(); + bool flipped = coin->flipped(); + const SkOpSpanBase* oppStartBase = coin->oppPtTStart()->span(); + const SkOpSpan* oppStart = flipped ? oppStartBase->prev() : oppStartBase->upCast(); + SkDebugf("", start, oppStart); + } while ((coin = coin->next())); + return; } +// Commented-out lines keep this in sync with expand() +// expand the range by checking adjacent spans for coincidence bool SkOpCoincidence::debugExpand(const char* id, SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* coin = fHead; if (!coin) { @@ -1352,109 +1671,296 @@ bool SkOpCoincidence::debugExpand(const char* id, SkPathOpsDebug::GlitchLog* log } bool expanded = false; do { - const SkOpSpan* start = coin->fCoinPtTStart->span()->upCast(); - const SkOpSpanBase* end = coin->fCoinPtTEnd->span(); - const SkOpSegment* segment = coin->fCoinPtTStart->segment(); - const SkOpSegment* oppSegment = coin->fOppPtTStart->segment(); - const SkOpSpan* prev = start->prev(); - if (prev && prev->debugContains(oppSegment)) { - double midT = (prev->t() + start->t()) / 2; - if (segment->isClose(midT, oppSegment)) { - log->record(kExpandCoin_Glitch, id, coin, prev); - } - } - SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); - if (next && next->debugContains(oppSegment)) { - double midT = (end->t() + next->t()) / 2; - if (segment->isClose(midT, oppSegment)) { - log->record(kExpandCoin_Glitch, id, coin, next); - } + if (coin->debugExpand(id, log)) { + // check to see if multiple spans expanded so they are now identical + const SkCoincidentSpans* test = fHead; + do { + if (coin == test) { + continue; + } + if (coin->coinPtTStart() == test->coinPtTStart() + && coin->oppPtTStart() == test->oppPtTStart()) { + if (log) log->record(kExpandCoin_Glitch, id, fHead, test->coinPtTStart()); + break; + } + } while ((test = test->next())); + expanded = true; } - } while ((coin = coin->fNext)); + } while ((coin = coin->next())); return expanded; } -void SkOpCoincidence::debugFixAligned(const char* id, SkPathOpsDebug::GlitchLog* log) const { +// Commented-out lines keep this in sync with removeCollapsed() +void SkOpCoincidence::debugRemoveCollapsed(const char* id, SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* coin = fHead; if (!coin) { return; } + // SkCoincidentSpans** priorPtr = &fHead; do { - if (coin->fCoinPtTStart->deleted()) { - log->record(kDeletedCoin_Glitch, id, coin, coin->fCoinPtTStart); - } - if (coin->fCoinPtTEnd->deleted()) { - log->record(kDeletedCoin_Glitch, id, coin, coin->fCoinPtTEnd); - } - if (coin->fOppPtTStart->deleted()) { - log->record(kDeletedCoin_Glitch, id, coin, coin->fOppPtTStart); + if (coin->coinPtTStart() == coin->coinPtTEnd()) { + return; } - if (coin->fOppPtTEnd->deleted()) { - log->record(kDeletedCoin_Glitch, id, coin, coin->fOppPtTEnd); + if (coin->oppPtTStart() == coin->oppPtTEnd()) { + return; } - } while ((coin = coin->fNext)); - coin = fHead; - do { - if (coin->fCoinPtTStart->collapsed(coin->fCoinPtTEnd)) { - log->record(kCollapsedCoin_Glitch, id, coin, coin->fCoinPtTStart); + if (coin->coinPtTStart()->collapsed(coin->coinPtTEnd())) { + log->record(kCollapsedCoin_Glitch, id, coin); +// continue; } - if (coin->fOppPtTStart->collapsed(coin->fOppPtTEnd)) { - log->record(kCollapsedCoin_Glitch, id, coin, coin->fOppPtTStart); + if (coin->oppPtTStart()->collapsed(coin->oppPtTEnd())) { + log->record(kCollapsedCoin_Glitch, id, coin, coin); +// continue; } - } while ((coin = coin->fNext)); + // priorPtr = &coin->nextPtr(); + } while ((coin = coin->next())); + return; } +// Commented-out lines keep this in sync with mark() +/* this sets up the coincidence links in the segments when the coincidence crosses multiple spans */ void SkOpCoincidence::debugMark(const char* id, SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* coin = fHead; if (!coin) { return; } do { - const SkOpSpanBase* end = coin->fCoinPtTEnd->span(); - const SkOpSpanBase* oldEnd = end; - const SkOpSpan* start = coin->fCoinPtTStart->span()->debugStarter(&end); - const SkOpSpanBase* oEnd = coin->fOppPtTEnd->span(); - const SkOpSpanBase* oOldEnd = oEnd; - const SkOpSpanBase* oStart = coin->fOppPtTStart->span()->debugStarter(&oEnd); - bool flipped = (end == oldEnd) != (oEnd == oOldEnd); + const SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast(); +// SkASSERT(start->deleted()); + const SkOpSpanBase* end = coin->coinPtTEndWritable()->span(); +// SkASSERT(end->deleted()); + const SkOpSpanBase* oStart = coin->oppPtTStartWritable()->span(); +// SkASSERT(oStart->deleted()); + const SkOpSpanBase* oEnd = coin->oppPtTEndWritable()->span(); +// SkASSERT(oEnd->deleted()); + bool flipped = coin->flipped(); if (flipped) { SkTSwap(oStart, oEnd); } + /* coin and opp spans may not match up. Mark the ends, and then let the interior + get marked as many times as the spans allow */ + start->debugInsertCoincidence(id, log, oStart->upCast()); + end->debugInsertCoinEnd(id, log, oEnd); + const SkOpSegment* segment = start->segment(); + const SkOpSegment* oSegment = oStart->segment(); const SkOpSpanBase* next = start; const SkOpSpanBase* oNext = oStart; - do { - next = next->upCast()->next(); - oNext = flipped ? oNext->prev() : oNext->upCast()->next(); - if (next == end || oNext == oEnd) { - break; + while ((next = next->upCast()->next()) != end) { + if (next->upCast()->debugInsertCoincidence(id, log, oSegment, flipped), false) { + return; } - if (!next->containsCoinEnd(oNext)) { - log->record(kMarkCoinEnd_Glitch, id, next, oNext); + } + while ((oNext = oNext->upCast()->next()) != oEnd) { + if (oNext->upCast()->debugInsertCoincidence(id, log, segment, flipped), false) { + return; } - const SkOpSpan* nextSpan = next->upCast(); - const SkOpSpan* oNextSpan = oNext->upCast(); - if (!nextSpan->containsCoincidence(oNextSpan)) { - log->record(kMarkCoinInsert_Glitch, id, nextSpan, oNextSpan); + } + } while ((coin = coin->next())); + return; +} +#endif + +#if DEBUG_COINCIDENCE_VERBOSE +// Commented-out lines keep this in sync with markCollapsed() +void SkOpCoincidence::debugMarkCollapsed(const char* id, SkPathOpsDebug::GlitchLog* log, const SkCoincidentSpans* coin, const SkOpPtT* test) const { + while (coin) { + if (coin->collapsed(test)) { + if (zero_or_one(coin->coinPtTStart()->fT) && zero_or_one(coin->coinPtTEnd()->fT)) { + log->record(kCollapsedCoin_Glitch, id, coin); } - } while (true); - } while ((coin = coin->fNext)); + if (zero_or_one(coin->oppPtTStart()->fT) && zero_or_one(coin->oppPtTEnd()->fT)) { + log->record(kCollapsedCoin_Glitch, id, coin); + } + } + coin = coin->next(); + } +} + +// Commented-out lines keep this in sync with markCollapsed() +void SkOpCoincidence::debugMarkCollapsed(const char* id, SkPathOpsDebug::GlitchLog* log, const SkOpPtT* test) const { + this->debugMarkCollapsed(id, log, fHead, test); + this->debugMarkCollapsed(id, log, fTop, test); } #endif +void SkCoincidentSpans::debugShow() const { + SkDebugf("%s - id=%d t=%1.9g tEnd=%1.9g\n", __FUNCTION__, + coinPtTStart()->segment()->debugID(), + coinPtTStart()->fT, coinPtTEnd()->fT); + SkDebugf("%s + id=%d t=%1.9g tEnd=%1.9g\n", __FUNCTION__, + oppPtTStart()->segment()->debugID(), + oppPtTStart()->fT, oppPtTEnd()->fT); +} + void SkOpCoincidence::debugShowCoincidence() const { - SkCoincidentSpans* span = fHead; +#if DEBUG_COINCIDENCE + const SkCoincidentSpans* span = fHead; while (span) { - SkDebugf("%s - id=%d t=%1.9g tEnd=%1.9g\n", __FUNCTION__, - span->fCoinPtTStart->segment()->debugID(), - span->fCoinPtTStart->fT, span->fCoinPtTEnd->fT); - SkDebugf("%s + id=%d t=%1.9g tEnd=%1.9g\n", __FUNCTION__, - span->fOppPtTStart->segment()->debugID(), - span->fOppPtTStart->fT, span->fOppPtTEnd->fT); - span = span->fNext; + span->debugShow(); + span = span->next(); } +#endif } #if DEBUG_COINCIDENCE +static void DebugValidate(const SkOpSpanBase* next, const SkOpSpanBase* end, + double oStart, double oEnd, const SkOpSegment* oSegment, + const char* id, SkPathOpsDebug::GlitchLog* log) { + SkASSERT(next != end); + SkASSERT(!next->contains(end) || log); + if (next->t() > end->t()) { + SkTSwap(next, end); + } + do { + const SkOpPtT* ptT = next->ptT(); + int index = 0; + bool somethingBetween; + do { + ++index; + ptT = ptT->next(); + const SkOpPtT* checkPtT = next->ptT(); + if (ptT == checkPtT) { + break; + } + bool looped = false; + for (int check = 0; check < index; ++check) { + if ((looped = checkPtT == ptT)) { + break; + } + checkPtT = checkPtT->next(); + } + if (looped) { + SkASSERT(0); + break; + } + if (ptT->deleted()) { + continue; + } + if (ptT->segment() != oSegment) { + continue; + } + somethingBetween |= between(oStart, ptT->fT, oEnd); + } while (true); + SkASSERT(somethingBetween); + } while (next != end && (next = next->upCast()->next())); +} + +static void DebugCheckOverlap(const SkCoincidentSpans* test, const SkCoincidentSpans* list, + const char* id, SkPathOpsDebug::GlitchLog* log) { + if (!list) { + return; + } + const SkOpSegment* coinSeg = test->coinPtTStart()->segment(); + SkASSERT(coinSeg == test->coinPtTEnd()->segment()); + const SkOpSegment* oppSeg = test->oppPtTStart()->segment(); + SkASSERT(oppSeg == test->oppPtTEnd()->segment()); + SkASSERT(coinSeg != test->oppPtTStart()->segment()); + SkDEBUGCODE(double tcs = test->coinPtTStart()->fT); + SkASSERT(between(0, tcs, 1)); + SkDEBUGCODE(double tce = test->coinPtTEnd()->fT); + SkASSERT(between(0, tce, 1)); + SkASSERT(tcs < tce); + double tos = test->oppPtTStart()->fT; + SkASSERT(between(0, tos, 1)); + double toe = test->oppPtTEnd()->fT; + SkASSERT(between(0, toe, 1)); + SkASSERT(tos != toe); + if (tos > toe) { + SkTSwap(tos, toe); + } + do { + double lcs, lce, los, loe; + if (coinSeg == list->coinPtTStart()->segment()) { + if (oppSeg != list->oppPtTStart()->segment()) { + continue; + } + lcs = list->coinPtTStart()->fT; + lce = list->coinPtTEnd()->fT; + los = list->oppPtTStart()->fT; + loe = list->oppPtTEnd()->fT; + if (los > loe) { + SkTSwap(los, loe); + } + } else if (coinSeg == list->oppPtTStart()->segment()) { + if (oppSeg != list->coinPtTStart()->segment()) { + continue; + } + lcs = list->oppPtTStart()->fT; + lce = list->oppPtTEnd()->fT; + if (lcs > lce) { + SkTSwap(lcs, lce); + } + los = list->coinPtTStart()->fT; + loe = list->coinPtTEnd()->fT; + } else { + continue; + } + SkASSERT(tce < lcs || lce < tcs); + SkASSERT(toe < los || loe < tos); + } while ((list = list->next())); +} + + +static void DebugCheckOverlapTop(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, + const char* id, SkPathOpsDebug::GlitchLog* log) { + // check for overlapping coincident spans + const SkCoincidentSpans* test = head; + while (test) { + const SkCoincidentSpans* next = test->next(); + DebugCheckOverlap(test, next, id, log); + DebugCheckOverlap(test, opt, id, log); + test = next; + } +} + +#if DEBUG_COINCIDENCE_VERBOSE +void SkOpCoincidence::debugCheckOverlap(const char* id, SkPathOpsDebug::GlitchLog* log) const { + DebugCheckOverlapTop(fHead, fTop, id, log); + DebugCheckOverlapTop(fTop, nullptr, id, log); +} +#endif + +static void DebugValidate(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, + const char* id, SkPathOpsDebug::GlitchLog* log) { + // look for pts inside coincident spans that are not inside the opposite spans + const SkCoincidentSpans* coin = head; + while (coin) { + SkASSERT(SkOpCoincidence::Ordered(coin->coinPtTStart()->segment(), + coin->oppPtTStart()->segment())); + SkASSERT(coin->coinPtTStart()->span()->ptT() == coin->coinPtTStart()); + SkASSERT(coin->coinPtTEnd()->span()->ptT() == coin->coinPtTEnd()); + SkASSERT(coin->oppPtTStart()->span()->ptT() == coin->oppPtTStart()); + SkASSERT(coin->oppPtTEnd()->span()->ptT() == coin->oppPtTEnd()); + DebugValidate(coin->coinPtTStart()->span(), coin->coinPtTEnd()->span(), + coin->oppPtTStart()->fT, coin->oppPtTEnd()->fT, coin->oppPtTStart()->segment(), + id, log); + DebugValidate(coin->oppPtTStart()->span(), coin->oppPtTEnd()->span(), + coin->coinPtTStart()->fT, coin->coinPtTEnd()->fT, coin->coinPtTStart()->segment(), + id, log); + coin = coin->next(); + } + DebugCheckOverlapTop(head, opt, id, log); +} +#endif + +void SkOpCoincidence::debugValidate() const { +#if DEBUG_COINCIDENCE + // if (fGlobalState->debugCheckHealth()) { +// return; +// } + DebugValidate(fHead, fTop, nullptr, nullptr); + DebugValidate(fTop, nullptr, nullptr, nullptr); +#endif +} + +#if DEBUG_COINCIDENCE_VERBOSE +void SkOpCoincidence::debugCheckValid(const char* id, SkPathOpsDebug::GlitchLog* log) const { + DebugValidate(fHead, fTop, id, log); + DebugValidate(fTop, nullptr, id, log); +} +#endif + +#if DEBUG_COINCIDENCE_VERBOSE void SkOpContour::debugCheckHealth(const char* id, SkPathOpsDebug::GlitchLog* log) const { const SkOpSegment* segment = &fHead; do { @@ -1462,16 +1968,36 @@ void SkOpContour::debugCheckHealth(const char* id, SkPathOpsDebug::GlitchLog* lo } while ((segment = segment->next())); } -void SkOpContour::debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log, - const SkOpCoincidence* coincidence) const { +// commmented-out lines keep this aligned with missingCoincidence() +void SkOpContour::debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log) const { +// SkASSERT(fCount > 0); const SkOpSegment* segment = &fHead; +// bool result = false; do { - segment->debugMissingCoincidence(id, log, coincidence); - } while ((segment = segment->next())); + if (fState->angleCoincidence()) { +// #if DEBUG_ANGLE +// segment->debugCheckAngleCoin(); +// #endif + } else if (segment->debugMissingCoincidence(id, log), false) { +// result = true; +// see FIXME in missingCoincidence() +// +// +// + // continue; + } + segment = segment->next(); + } while (segment); + return; } #endif void SkOpSegment::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif #if DEBUG_VALIDATE const SkOpSpanBase* span = &fHead; double lastT = -1; @@ -1500,56 +2026,47 @@ void SkOpSegment::debugValidate() const { #endif } -bool SkOpSpanBase::debugAlignedEnd(double t, const SkPoint& pt) const { - SkASSERT(zero_or_one(t)); - const SkOpSegment* segment = this->segment(); - SkASSERT(t ? segment->lastPt() == pt : segment->pts()[0] == pt); - if (!debugAlignedInner()) { - return false; - } - if ((t ? segment->lastPt() : segment->pts()[0]) != pt) { - return false; +#if DEBUG_COINCIDENCE_VERBOSE +// Commented-out lines keep this in sync with addOppAndMerge() +// If the added points envelop adjacent spans, merge them in. +void SkOpSpanBase::debugAddOppAndMerge(const char* id, SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* opp, bool* spanDeleted, bool* oppDeleted) const { + if (this->ptT()->debugAddOpp(opp->ptT())) { + this->debugCheckForCollapsedCoincidence(id, log); } - const SkOpPtT* ptT = &this->fPtT; - SkASSERT(t == ptT->fT); - SkASSERT(pt == ptT->fPt); - const SkOpPtT* test = ptT, * stopPtT = ptT; - while ((test = test->next()) != stopPtT) { - const SkOpSegment* other = test->segment(); - if (other == this->segment()) { - continue; - } - if (!zero_or_one(test->fT)) { - continue; - } - if ((test->fT ? other->lastPt() : other->pts()[0]) != pt) { - return false; - } + // compute bounds of points in span + SkPathOpsBounds bounds; + bounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin); + const SkOpPtT* head = this->ptT(); + const SkOpPtT* nextPt = head; + do { + bounds.add(nextPt->fPt); + } while ((nextPt = nextPt->next()) != head); + if (!bounds.width() && !bounds.height()) { + return; } - return this->fAligned; + this->debugMergeContained(id, log, bounds, spanDeleted); + opp->debugMergeContained(id, log, bounds, oppDeleted); } -bool SkOpSpanBase::debugAlignedInner() const { - // force the spans to share points and t values - const SkOpPtT* ptT = &this->fPtT, * stopPtT = ptT; - const SkPoint& pt = ptT->fPt; +// Commented-out lines keep this in sync with checkForCollapsedCoincidence() +void SkOpSpanBase::debugCheckForCollapsedCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log) const { + const SkOpCoincidence* coins = this->globalState()->coincidence(); + if (coins->isEmpty()) { + return; + } +// the insert above may have put both ends of a coincident run in the same span +// for each coincident ptT in loop; see if its opposite in is also in the loop +// this implementation is the motivation for marking that a ptT is referenced by a coincident span + const SkOpPtT* head = this->ptT(); + const SkOpPtT* test = head; do { - if (ptT->fPt != pt) { - return false; + if (!test->coincident()) { + continue; } - const SkOpSpanBase* span = ptT->span(); - const SkOpPtT* test = ptT; - do { - if ((test = test->next()) == stopPtT) { - break; - } - if (span == test->span() && !span->segment()->ptsDisjoint(*ptT, *test)) { - return false; - } - } while (true); - } while ((ptT = ptT->next()) != stopPtT); - return true; + coins->debugMarkCollapsed(id, log, test); + } while ((test = test->next()) != head); } +#endif bool SkOpSpanBase::debugCoinEndLoopCheck() const { int loop = 0; @@ -1574,16 +2091,57 @@ bool SkOpSpanBase::debugCoinEndLoopCheck() const { return true; } -bool SkOpSpanBase::debugContains(const SkOpSegment* segment) const { - const SkOpPtT* start = &fPtT; - const SkOpPtT* walk = start; - while ((walk = walk->next()) != start) { - if (walk->segment() == segment) { - return true; +#if DEBUG_COINCIDENCE_VERBOSE +// Commented-out lines keep this in sync with insertCoinEnd() +void SkOpSpanBase::debugInsertCoinEnd(const char* id, SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* coin) const { + if (containsCoinEnd(coin)) { +// SkASSERT(coin->containsCoinEnd(this)); + return; + } + debugValidate(); +// SkASSERT(this != coin); + log->record(kMarkCoinEnd_Glitch, id, this, coin); +// coin->fCoinEnd = this->fCoinEnd; +// this->fCoinEnd = coinNext; + debugValidate(); +} + +// Commented-out lines keep this in sync with mergeContained() +void SkOpSpanBase::debugMergeContained(const char* id, SkPathOpsDebug::GlitchLog* log, const SkPathOpsBounds& bounds, bool* deleted) const { + // while adjacent spans' points are contained by the bounds, merge them + const SkOpSpanBase* prev = this; + const SkOpSegment* seg = this->segment(); + while ((prev = prev->prev()) && bounds.contains(prev->pt()) && !seg->ptsDisjoint(prev, this)) { + if (prev->prev()) { + log->record(kMergeContained_Glitch, id, this, prev); + } else if (this->final()) { + log->record(kMergeContained_Glitch, id, this, prev); + // return; + } else { + log->record(kMergeContained_Glitch, id, prev, this); } } - return false; + const SkOpSpanBase* current = this; + const SkOpSpanBase* next = this; + while (next->upCastable() && (next = next->upCast()->next()) + && bounds.contains(next->pt()) && !seg->ptsDisjoint(this, next)) { + if (!current->prev() && next->final()) { + log->record(kMergeContained_Glitch, id, next, current); + current = next; + } + if (current->prev()) { + log->record(kMergeContained_Glitch, id, next, current); + current = next; + } else { + log->record(kMergeContained_Glitch, id, next, current); + current = next; + } + } +#if DEBUG_COINCIDENCE + // this->globalState()->coincidence()->debugValidate(); +#endif } +#endif const SkOpSpan* SkOpSpanBase::debugStarter(SkOpSpanBase const** endPtr) const { const SkOpSpanBase* end = *endPtr; @@ -1599,6 +2157,11 @@ const SkOpSpan* SkOpSpanBase::debugStarter(SkOpSpanBase const** endPtr) const { } void SkOpSpanBase::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif #if DEBUG_VALIDATE const SkOpPtT* ptT = &fPtT; SkASSERT(ptT->span() == this); @@ -1643,6 +2206,39 @@ bool SkOpSpan::debugCoinLoopCheck() const { return true; } +#if DEBUG_COINCIDENCE_VERBOSE +// Commented-out lines keep this in sync with insertCoincidence() in header +void SkOpSpan::debugInsertCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log, const SkOpSpan* coin) const { + if (containsCoincidence(coin)) { +// SkASSERT(coin->containsCoincidence(this)); + return; + } + debugValidate(); +// SkASSERT(this != coin); + log->record(kMarkCoinStart_Glitch, id, this, coin); +// coin->fCoincident = this->fCoincident; +// this->fCoincident = coinNext; + debugValidate(); +} + +// Commented-out lines keep this in sync with insertCoincidence() +void SkOpSpan::debugInsertCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log, const SkOpSegment* segment, bool flipped) const { + if (this->containsCoincidence(segment)) { + return; + } + const SkOpPtT* next = &fPtT; + while ((next = next->next()) != &fPtT) { + if (next->segment() == segment) { + log->record(kMarkCoinInsert_Glitch, id, flipped ? next->span()->prev() : next->span()); + return; + } + } +#if DEBUG_COINCIDENCE + log->record(kMarkCoinMissing_Glitch, id, segment, this); +#endif +} +#endif + // called only by test code int SkIntersections::debugCoincidentUsed() const { if (!fIsCoincident[0]) { @@ -1667,6 +2263,27 @@ int SkIntersections::debugCoincidentUsed() const { #include "SkOpContour.h" +// Commented-out lines keep this in sync with addOpp() +bool SkOpPtT::debugAddOpp(const SkOpPtT* opp) const { + // find the fOpp ptr to opp + const SkOpPtT* oppPrev = opp->fNext; + if (oppPrev == this) { + return false; + } + while (oppPrev->fNext != opp) { + oppPrev = oppPrev->fNext; + if (oppPrev == this) { + return false; + } + } +// const SkOpPtT* oldNext = this->fNext; + SkASSERT(this != opp); +// this->fNext = opp; +// SkASSERT(oppPrev != oldNext); +// oppPrev->fNext = oldNext; + return true; +} + bool SkOpPtT::debugContains(const SkOpPtT* check) const { SkASSERT(this != check); const SkOpPtT* ptT = this; @@ -1736,6 +2353,11 @@ int SkOpPtT::debugLoopLimit(bool report) const { } void SkOpPtT::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif #if DEBUG_VALIDATE SkOpGlobalState::Phase phase = contour()->globalState()->phase(); if (phase == SkOpGlobalState::kIntersecting diff --git a/src/pathops/SkPathOpsDebug.h b/src/pathops/SkPathOpsDebug.h index bd27e7df2d..9e910686b7 100644 --- a/src/pathops/SkPathOpsDebug.h +++ b/src/pathops/SkPathOpsDebug.h @@ -37,6 +37,8 @@ if (!SkPathOpsDebug::ValidWind(x)) strcpy(x##Str, "?"); \ else SK_SNPRINTF(x##Str, sizeof(x##Str), "%d", x) +#define DEBUG_UNDER_DEVELOPMENT 01 + #if FORCE_RELEASE #define DEBUG_ACTIVE_OP 0 @@ -47,6 +49,7 @@ #define DEBUG_ANGLE 0 #define DEBUG_ASSEMBLE 0 #define DEBUG_COINCIDENCE 0 +#define DEBUG_COINCIDENCE_VERBOSE 0 #define DEBUG_CUBIC_BINARY_SEARCH 0 #define DEBUG_CUBIC_SPLIT 0 #define DEBUG_DUMP_SEGMENTS 0 @@ -74,12 +77,13 @@ #define DEBUG_ALIGNMENT 0 #define DEBUG_ANGLE 1 #define DEBUG_ASSEMBLE 1 -#define DEBUG_COINCIDENCE 0 +#define DEBUG_COINCIDENCE 01 +#define DEBUG_COINCIDENCE_VERBOSE 01 #define DEBUG_CUBIC_BINARY_SEARCH 0 #define DEBUG_CUBIC_SPLIT 1 #define DEBUG_DUMP_SEGMENTS 1 #define DEBUG_FLOW 1 -#define DEBUG_LIMIT_WIND_SUM 5 +#define DEBUG_LIMIT_WIND_SUM 15 #define DEBUG_MARK_DONE 1 #define DEBUG_PATH_CONSTRUCTION 1 #define DEBUG_PERP 1 @@ -186,6 +190,7 @@ public: static void BumpTestName(char* ); #endif static const char* OpStr(SkPathOp ); + static void ShowActiveSpans(SkOpContourHead* contourList); 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); @@ -193,37 +198,37 @@ public: static void CheckHealth(class SkOpContourHead* contourList, const char* id); - 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 class SkOpAngle* DebugAngleAngle(const class SkOpAngle*, int id); + static class SkOpContour* DebugAngleContour(class SkOpAngle*, int id); + static const class SkOpPtT* DebugAnglePtT(const class SkOpAngle*, int id); + static const class SkOpSegment* DebugAngleSegment(const class SkOpAngle*, int id); + static const class SkOpSpanBase* DebugAngleSpan(const class SkOpAngle*, int id); - static const struct SkOpAngle* DebugContourAngle(class SkOpContour*, int id); + static const class 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* DebugCoincidenceAngle(class SkOpCoincidence*, int id); + static const class SkOpAngle* DebugCoincidenceAngle(class SkOpCoincidence*, int id); static class SkOpContour* DebugCoincidenceContour(class SkOpCoincidence*, int id); static const class SkOpPtT* DebugCoincidencePtT(class SkOpCoincidence*, int id); static const class SkOpSegment* DebugCoincidenceSegment(class SkOpCoincidence*, int id); static const class SkOpSpanBase* DebugCoincidenceSpan(class SkOpCoincidence*, int id); - static const struct SkOpAngle* DebugPtTAngle(const class SkOpPtT*, int id); + static const class 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 const class 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 const class 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); diff --git a/src/pathops/SkPathOpsOp.cpp b/src/pathops/SkPathOpsOp.cpp index 1970eea124..188af5722b 100644 --- a/src/pathops/SkPathOpsOp.cpp +++ b/src/pathops/SkPathOpsOp.cpp @@ -88,7 +88,7 @@ static SkOpSegment* findChaseOp(SkTDArray<SkOpSpanBase*>& chase, SkOpSpanBase** } static bool bridgeOp(SkOpContourHead* contourList, const SkPathOp op, - const int xorMask, const int xorOpMask, SkPathWriter* simple, SkChunkAlloc* allocator) { + const int xorMask, const int xorOpMask, SkPathWriter* simple) { bool unsortable = false; do { SkOpSpan* span = FindSortableTop(contourList); @@ -117,11 +117,9 @@ static bool bridgeOp(SkOpContourHead* contourList, const SkPathOp op, if (!current->addCurveTo(start, end, simple)) { return false; } - #if DEBUG_ACTIVE_SPANS if (!simple->isClosed()) { - DebugShowActiveSpans(contourList); + SkPathOpsDebug::ShowActiveSpans(contourList); } - #endif } break; } @@ -163,9 +161,7 @@ static bool bridgeOp(SkOpContourHead* contourList, const SkPathOp op, } } current = findChaseOp(chase, &start, &end); - #if DEBUG_ACTIVE_SPANS - DebugShowActiveSpans(contourList); - #endif + SkPathOpsDebug::ShowActiveSpans(contourList); if (!current) { break; } @@ -226,7 +222,7 @@ 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 @@ -253,40 +249,42 @@ bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result SkChunkAlloc allocator(4096); // FIXME: add a constant expression here, tune SkOpContour contour; SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour); - SkOpCoincidence coincidence; - SkOpGlobalState globalState(&coincidence, contourList - SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); + SkOpGlobalState globalState(contourList, &allocator + SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); + SkOpCoincidence coincidence(&globalState); #if DEBUGGING_PATHOPS_FROM_HOST dump_op(one, two, op); -#endif -#if 0 && DEBUG_SHOW_TEST_NAME - char* debugName = DEBUG_FILENAME_STRING; - if (debugName && debugName[0]) { - SkPathOpsDebug::BumpTestName(debugName); - SkPathOpsDebug::ShowPath(one, two, op, debugName); - } #endif op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()]; SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()] ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType; - const SkPath* minuend = &one; - const SkPath* subtrahend = &two; + SkScalar scaleFactor = SkTMax(ScaleFactor(one), ScaleFactor(two)); + SkPath scaledOne, scaledTwo; + const SkPath* minuend, * subtrahend; + if (scaleFactor > SK_Scalar1) { + ScalePath(one, 1.f / scaleFactor, &scaledOne); + minuend = &scaledOne; + ScalePath(two, 1.f / scaleFactor, &scaledTwo); + subtrahend = &scaledTwo; + } else { + minuend = &one; + subtrahend = &two; + } if (op == kReverseDifference_SkPathOp) { - minuend = &two; - subtrahend = &one; + SkTSwap(minuend, subtrahend); op = kDifference_SkPathOp; } #if DEBUG_SORT SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; #endif // turn path into list of segments - SkOpEdgeBuilder builder(*minuend, &contour, &allocator, &globalState); + SkOpEdgeBuilder builder(*minuend, &contour, &globalState); if (builder.unparseable()) { return false; } const int xorMask = builder.xorMask(); builder.addOperand(*subtrahend); - if (!builder.finish(&allocator)) { + if (!builder.finish()) { return false; } #if DEBUG_DUMP_SEGMENTS @@ -304,14 +302,14 @@ bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result SkOpContour* current = contourList; do { SkOpContour* next = current; - while (AddIntersectTs(current, next, &coincidence, &allocator) + while (AddIntersectTs(current, next, &coincidence) && (next = next->next())) ; } while ((current = current->next())); #if DEBUG_VALIDATE globalState.setPhase(SkOpGlobalState::kWalking); #endif - if (!HandleCoincidence(contourList, &coincidence, &allocator)) { + if (!HandleCoincidence(contourList, &coincidence)) { return false; } #if DEBUG_ALIGNMENT @@ -321,7 +319,7 @@ bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result result->reset(); result->setFillType(fillType); 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); @@ -339,6 +337,9 @@ bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result debugWorstState.debugDoYourWorst(&globalState); } #endif + if (scaleFactor > 1) { + ScalePath(*result, scaleFactor, result); + } return true; } @@ -458,6 +459,6 @@ bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) { } return true; #else - return OpDebug(one, two, op, result SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); + return OpDebug(one, two, op, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); #endif } diff --git a/src/pathops/SkPathOpsPoint.h b/src/pathops/SkPathOpsPoint.h index e3f722bede..f30f155e8f 100644 --- a/src/pathops/SkPathOpsPoint.h +++ b/src/pathops/SkPathOpsPoint.h @@ -58,12 +58,20 @@ struct SkDVector { } // similar to cross, this bastardization considers nearly coincident to be zero + // uses ulps epsilon == 16 double crossCheck(const SkDVector& a) const { double xy = fX * a.fY; double yx = fY * a.fX; return AlmostEqualUlps(xy, yx) ? 0 : xy - yx; } + // allow tinier numbers + double crossNoNormalCheck(const SkDVector& a) const { + double xy = fX * a.fY; + double yx = fY * a.fX; + return AlmostEqualUlpsNoNormalCheck(xy, yx) ? 0 : xy - yx; + } + double dot(const SkDVector& a) const { return fX * a.fX + fY * a.fY; } @@ -75,6 +83,12 @@ struct SkDVector { double lengthSquared() const { return fX * fX + fY * fY; } + + void normalize() { + double inverseLength = 1 / this->length(); + fX *= inverseLength; + fY *= inverseLength; + } }; struct SkDPoint { @@ -164,7 +178,7 @@ 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 AlmostDequalUlps((double) largest, largest + dist); // is dist within ULPS tolerance? } // only used by testing diff --git a/src/pathops/SkPathOpsQuad.cpp b/src/pathops/SkPathOpsQuad.cpp index 3deab21133..dafa3f5b75 100644 --- a/src/pathops/SkPathOpsQuad.cpp +++ b/src/pathops/SkPathOpsQuad.cpp @@ -191,6 +191,12 @@ SkDPoint SkDQuad::ptAtT(double t) const { } static double interp_quad_coords(const double* src, double t) { + if (0 == t) { + return src[0]; + } + if (1 == t) { + return src[4]; + } double ab = SkDInterp(src[0], src[2], t); double bc = SkDInterp(src[2], src[4], t); double abc = SkDInterp(ab, bc, t); @@ -228,8 +234,11 @@ Group the known values on one side: B = D*2 - A/2 - C/2 */ -// OPTIMIZE : special case either or both of t1 = 0, t2 = 1 +// OPTIMIZE? : special case t1 = 1 && t2 = 0 SkDQuad SkDQuad::subDivide(double t1, double t2) const { + if (0 == t1 && 1 == t2) { + return *this; + } SkDQuad dst; double ax = dst[0].fX = interp_quad_coords(&fPts[0].fX, t1); double ay = dst[0].fY = interp_quad_coords(&fPts[0].fY, t1); @@ -263,7 +272,7 @@ SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, dou b = i.pt(0); } else { SkASSERT(i.used() <= 2); - b = SkDPoint::Mid(b0[1], b1[1]); + return SkDPoint::Mid(b0[1], b1[1]); } if (t1 == 0 || t2 == 0) { align(0, &b); diff --git a/src/pathops/SkPathOpsSimplify.cpp b/src/pathops/SkPathOpsSimplify.cpp index fa1003054e..dcd75f1666 100644 --- a/src/pathops/SkPathOpsSimplify.cpp +++ b/src/pathops/SkPathOpsSimplify.cpp @@ -10,8 +10,7 @@ #include "SkPathOpsCommon.h" #include "SkPathWriter.h" -static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* simple, - SkChunkAlloc* allocator, bool* closable) { +static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* simple, bool* closable) { bool unsortable = false; do { SkOpSpan* span = FindSortableTop(contourList); @@ -40,11 +39,9 @@ static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* simple, if (!current->addCurveTo(start, end, simple)) { return false; } - #if DEBUG_ACTIVE_SPANS if (!simple->isClosed()) { - DebugShowActiveSpans(contourList); + SkPathOpsDebug::ShowActiveSpans(contourList); } - #endif } break; } @@ -86,9 +83,7 @@ static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* simple, } } current = FindChase(&chase, &start, &end); - #if DEBUG_ACTIVE_SPANS - DebugShowActiveSpans(contourList); - #endif + SkPathOpsDebug::ShowActiveSpans(contourList); if (!current) { break; } @@ -99,8 +94,7 @@ static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* simple, } // returns true if all edges were processed -static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* simple, - SkChunkAlloc* allocator, bool* closable) { +static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* simple, bool* closable) { SkOpSegment* current; SkOpSpanBase* start; SkOpSpanBase* end; @@ -108,11 +102,9 @@ static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* simple, *closable = true; while ((current = FindUndone(contourList, &start, &end))) { do { - #if DEBUG_ACTIVE_SPANS if (!unsortable && current->done()) { - DebugShowActiveSpans(contourList); + SkPathOpsDebug::ShowActiveSpans(contourList); } - #endif SkASSERT(unsortable || !current->done()); SkOpSpanBase* nextStart = start; SkOpSpanBase* nextEnd = end; @@ -124,11 +116,9 @@ static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* simple, if (!current->addCurveTo(start, end, simple)) { return false; } - #if DEBUG_ACTIVE_SPANS if (!simple->isClosed()) { - DebugShowActiveSpans(contourList); + SkPathOpsDebug::ShowActiveSpans(contourList); } - #endif } break; } @@ -156,16 +146,14 @@ static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* simple, *closable = false; } simple->close(); - #if DEBUG_ACTIVE_SPANS - DebugShowActiveSpans(contourList); - #endif + SkPathOpsDebug::ShowActiveSpans(contourList); } return true; } // FIXME : add this as a member of SkPath -bool Simplify(const SkPath& path, SkPath* result) { - SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune +bool SimplifyDebug(const SkPath& path, SkPath* result + SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) { // returns 1 for evenodd, -1 for winding, regardless of inverse-ness SkPath::FillType fillType = path.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType; @@ -177,16 +165,26 @@ bool Simplify(const SkPath& path, SkPath* result) { return true; } // turn path into list of segments - SkOpCoincidence coincidence; + SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune SkOpContour contour; SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour); - SkOpGlobalState globalState(&coincidence, contourList SkDEBUGPARAMS(false) - SkDEBUGPARAMS(nullptr)); + SkOpGlobalState globalState(contourList, &allocator + SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); + SkOpCoincidence coincidence(&globalState); + SkScalar scaleFactor = ScaleFactor(path); + SkPath scaledPath; + const SkPath* workingPath; + if (scaleFactor > SK_Scalar1) { + ScalePath(path, 1.f / scaleFactor, &scaledPath); + workingPath = &scaledPath; + } else { + workingPath = &path; + } #if DEBUG_SORT SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; #endif - SkOpEdgeBuilder builder(path, &contour, &allocator, &globalState); - if (!builder.finish(&allocator)) { + SkOpEdgeBuilder builder(*workingPath, &contour, &globalState); + if (!builder.finish()) { return false; } #if DEBUG_DUMP_SEGMENTS @@ -201,13 +199,13 @@ bool Simplify(const SkPath& path, SkPath* result) { SkOpContour* current = contourList; do { SkOpContour* next = current; - while (AddIntersectTs(current, next, &coincidence, &allocator) + while (AddIntersectTs(current, next, &coincidence) && (next = next->next())); } while ((current = current->next())); #if DEBUG_VALIDATE globalState.setPhase(SkOpGlobalState::kWalking); #endif - if (!HandleCoincidence(contourList, &coincidence, &allocator)) { + if (!HandleCoincidence(contourList, &coincidence)) { return false; } #if DEBUG_DUMP_ALIGNMENT @@ -219,8 +217,8 @@ bool Simplify(const SkPath& path, SkPath* result) { SkPathWriter wrapper(*result); bool closable SK_INIT_TO_AVOID_WARNING; if (builder.xorMask() == kWinding_PathOpsMask - ? !bridgeWinding(contourList, &wrapper, &allocator, &closable) - : !bridgeXor(contourList, &wrapper, &allocator, &closable)) { + ? !bridgeWinding(contourList, &wrapper, &closable) + : !bridgeXor(contourList, &wrapper, &closable)) { return false; } if (!closable) @@ -232,6 +230,12 @@ bool Simplify(const SkPath& path, SkPath* result) { *result = *assembled.nativePath(); result->setFillType(fillType); } + if (scaleFactor > 1) { + ScalePath(*result, scaleFactor, result); + } return true; } +bool Simplify(const SkPath& path, SkPath* result) { + return SimplifyDebug(path, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); +} diff --git a/src/pathops/SkPathOpsTSect.h b/src/pathops/SkPathOpsTSect.h index 48aa540945..9032af83eb 100644 --- a/src/pathops/SkPathOpsTSect.h +++ b/src/pathops/SkPathOpsTSect.h @@ -250,7 +250,7 @@ private: SkTSpan<TCurve, OppCurve>* addFollowing(SkTSpan<TCurve, OppCurve>* prior); void addForPerp(SkTSpan<OppCurve, TCurve>* span, double t); SkTSpan<TCurve, OppCurve>* addOne(); - + SkTSpan<TCurve, OppCurve>* addSplitAt(SkTSpan<TCurve, OppCurve>* span, double t) { SkTSpan<TCurve, OppCurve>* result = this->addOne(); result->splitAt(span, t, &fHeap); @@ -343,7 +343,7 @@ void SkTCoincident<TCurve, OppCurve>::setPerp(const TCurve& c1, double t, if (used == 0 || used == 3) { this->init(); return; - } + } fPerpT = i[0][0]; fPerpPt = i.pt(0); SkASSERT(used <= 2); @@ -858,7 +858,7 @@ SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::addOne() { result->reset(); result->fHasPerp = false; result->fDeleted = false; - ++fActiveCount; + ++fActiveCount; PATH_OPS_DEBUG_T_SECT_CODE(result->fID = fDebugCount++ * 2 + fID); SkDEBUGCODE(result->fDebugSect = this); #ifdef SK_DEBUG @@ -969,6 +969,9 @@ void SkTSect<TCurve, OppCurve>::coincidentCheck(SkTSect<OppCurve, TCurve>* sect2 do { coinStart = this->extractCoincident(sect2, coinStart, last); } while (coinStart && !last->fDeleted); + if (!fHead || !sect2->fHead) { + break; + } } while ((first = next)); } @@ -1208,7 +1211,7 @@ SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::extractCoincident( } this->validate(); sect2->validate(); - return last && !last->fDeleted ? last : nullptr; + return last && !last->fDeleted && fHead && sect2->fHead ? last : nullptr; } template<typename TCurve, typename OppCurve> @@ -1511,7 +1514,7 @@ template<typename TCurve, typename OppCurve> void SkTSect<TCurve, OppCurve>::matchedDirCheck(double t, const SkTSect<OppCurve, TCurve>* sect2, double t2, bool* calcMatched, bool* oppMatched) const { if (*calcMatched) { - SkASSERT(*oppMatched == this->matchedDirection(t, sect2, t2)); + SkASSERT(*oppMatched == this->matchedDirection(t, sect2, t2)); } else { *oppMatched = this->matchedDirection(t, sect2, t2); *calcMatched = true; @@ -1584,7 +1587,7 @@ SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::prev( test = test->fNext; SkASSERT(test); } - return result; + return result; } template<typename TCurve, typename OppCurve> @@ -1939,7 +1942,7 @@ struct SkClosestRecord { fC1Index = mate.fC1Index; fC2Index = mate.fC2Index; } - + void reset() { fClosest = FLT_MAX; SkDEBUGCODE(fC1Span = nullptr); @@ -2099,7 +2102,7 @@ void SkTSect<TCurve, OppCurve>::BinarySearch(SkTSect<TCurve, OppCurve>* sect1, gets stuck in a loop. It adds an extension to allow a coincident end perpendicular to track its intersection in the opposite curve. However, the bounding box of the extension does not intersect the original curve, - so the extension is discarded, only to be added again the next time around. */ + so the extension is discarded, only to be added again the next time around. */ sect1->coincidentForce(sect2, start1s, start1e); sect1->validate(); sect2->validate(); diff --git a/src/pathops/SkPathOpsTightBounds.cpp b/src/pathops/SkPathOpsTightBounds.cpp index 19593c228b..e3a3108bd7 100644 --- a/src/pathops/SkPathOpsTightBounds.cpp +++ b/src/pathops/SkPathOpsTightBounds.cpp @@ -11,11 +11,20 @@ bool TightBounds(const SkPath& path, SkRect* result) { SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune SkOpContour contour; SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour); - SkOpGlobalState globalState(nullptr, contourList SkDEBUGPARAMS(false) + SkOpGlobalState globalState(contourList, &allocator SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); // turn path into list of segments - SkOpEdgeBuilder builder(path, &contour, &allocator, &globalState); - if (!builder.finish(&allocator)) { + SkScalar scaleFactor = ScaleFactor(path); + SkPath scaledPath; + const SkPath* workingPath; + if (scaleFactor > SK_Scalar1) { + ScalePath(path, 1.f / scaleFactor, &scaledPath); + workingPath = &scaledPath; + } else { + workingPath = &path; + } + SkOpEdgeBuilder builder(*workingPath, &contour, &globalState); + if (!builder.finish()) { return false; } if (!SortContourList(&contourList, false, false)) { diff --git a/src/pathops/SkPathOpsTypes.cpp b/src/pathops/SkPathOpsTypes.cpp index bad1bc7a8b..d25df12873 100644 --- a/src/pathops/SkPathOpsTypes.cpp +++ b/src/pathops/SkPathOpsTypes.cpp @@ -25,6 +25,13 @@ static bool equal_ulps(float a, float b, int epsilon, int depsilon) { return aBits < bBits + epsilon && bBits < aBits + epsilon; } +static bool equal_ulps_no_normal_check(float a, float b, int epsilon, int depsilon) { + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + static bool equal_ulps_pin(float a, float b, int epsilon, int depsilon) { if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) { return false; @@ -120,6 +127,11 @@ bool AlmostEqualUlps(float a, float b) { return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); } +bool AlmostEqualUlpsNoNormalCheck(float a, float b) { + const int UlpsEpsilon = 16; + return equal_ulps_no_normal_check(a, b, UlpsEpsilon, UlpsEpsilon); +} + bool AlmostEqualUlps_Pin(float a, float b) { const int UlpsEpsilon = 16; return equal_ulps_pin(a, b, UlpsEpsilon, UlpsEpsilon); @@ -212,10 +224,12 @@ double SkDCubeRoot(double x) { return result; } -SkOpGlobalState::SkOpGlobalState(SkOpCoincidence* coincidence, SkOpContourHead* head +SkOpGlobalState::SkOpGlobalState(SkOpContourHead* head, + SkChunkAlloc* allocator SkDEBUGPARAMS(bool debugSkipAssert) SkDEBUGPARAMS(const char* testName)) - : fCoincidence(coincidence) + : fAllocator(allocator) + , fCoincidence(nullptr) , fContourHead(head) , fNested(0) , fWindingFailed(false) @@ -227,13 +241,9 @@ SkOpGlobalState::SkOpGlobalState(SkOpCoincidence* coincidence, SkOpContourHead* SkDEBUGPARAMS(fContourID(0)) SkDEBUGPARAMS(fPtTID(0)) SkDEBUGPARAMS(fSegmentID(0)) - SkDEBUGPARAMS(fSpanID(0)) - SkDEBUGPARAMS(fDebugSkipAssert(debugSkipAssert)) { - if (coincidence) { - coincidence->debugSetGlobalState(this); - } +SkDEBUGPARAMS(fSpanID(0)) +SkDEBUGPARAMS(fDebugSkipAssert(debugSkipAssert)) { #if DEBUG_T_SECT_LOOP_COUNT debugResetLoopCounts(); #endif } - diff --git a/src/pathops/SkPathOpsTypes.h b/src/pathops/SkPathOpsTypes.h index 00c3e5fe93..ad2ad463e6 100644 --- a/src/pathops/SkPathOpsTypes.h +++ b/src/pathops/SkPathOpsTypes.h @@ -22,6 +22,7 @@ enum SkPathOpsMask { kEvenOdd_PathOpsMask = 1 }; +class SkChunkAlloc; class SkOpCoincidence; class SkOpContour; class SkOpContourHead; @@ -30,8 +31,8 @@ class SkIntersectionHelper; class SkOpGlobalState { public: - SkOpGlobalState(SkOpCoincidence* coincidence, SkOpContourHead* head - SkDEBUGPARAMS(bool debugSkipAssert) + SkOpGlobalState(SkOpContourHead* head, + SkChunkAlloc* allocator SkDEBUGPARAMS(bool debugSkipAssert) SkDEBUGPARAMS(const char* testName)); enum Phase { @@ -44,6 +45,10 @@ public: kMaxWindingTries = 10 }; + SkChunkAlloc* allocator() { + return fAllocator; + } + bool angleCoincidence() const { return fAngleCoincidence; } @@ -65,7 +70,8 @@ public: } #ifdef SK_DEBUG - const struct SkOpAngle* debugAngle(int id) const; + const class SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; SkOpContour* debugContour(int id); const class SkOpPtT* debugPtT(int id) const; bool debugRunFail() const; @@ -83,6 +89,11 @@ public: void debugResetLoopCounts(); #endif +#if DEBUG_COINCIDENCE + void debugSetCheckHealth(bool check) { fDebugCheckHealth = check; } + bool debugCheckHealth() const { return fDebugCheckHealth; } +#endif + int nested() const { return fNested; } @@ -120,6 +131,10 @@ public: void setAngleCoincidence() { fAngleCoincidence = true; } + + void setCoincidence(SkOpCoincidence* coincidence) { + fCoincidence = coincidence; + } void setContourHead(SkOpContourHead* contourHead) { fContourHead = contourHead; @@ -140,6 +155,7 @@ public: } private: + SkChunkAlloc* fAllocator; SkOpCoincidence* fCoincidence; SkOpContourHead* fContourHead; int fNested; @@ -162,6 +178,9 @@ private: SkPoint fDebugWorstPts[24]; float fDebugWorstWeight[6]; #endif +#if DEBUG_COINCIDENCE + bool fDebugCheckHealth; +#endif }; // Use Almost Equal when comparing coordinates. Use epsilon to compare T values. @@ -170,6 +189,11 @@ inline bool AlmostEqualUlps(double a, double b) { return AlmostEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } +bool AlmostEqualUlpsNoNormalCheck(float a, float b); +inline bool AlmostEqualUlpsNoNormalCheck(double a, double b) { + return AlmostEqualUlpsNoNormalCheck(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + bool AlmostEqualUlps_Pin(float a, float b); inline bool AlmostEqualUlps_Pin(double a, double b) { return AlmostEqualUlps_Pin(SkDoubleToScalar(a), SkDoubleToScalar(b)); @@ -246,6 +270,8 @@ const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256; const double WAY_ROUGH_EPSILON = FLT_EPSILON * 2048; const double BUMP_EPSILON = FLT_EPSILON * 4096; +const SkScalar INVERSE_NUMBER_RANGE = FLT_EPSILON_ORDERABLE_ERR; + inline bool zero_or_one(double x) { return x == 0 || x == 1; } @@ -298,7 +324,6 @@ inline bool approximately_zero_inverse(double x) { return fabs(x) > FLT_EPSILON_INVERSE; } -// OPTIMIZATION: if called multiple times with the same denom, we want to pass 1/y instead inline bool approximately_zero_when_compared_to(double x, double y) { return x == 0 || fabs(x) < fabs(y * FLT_EPSILON); } @@ -307,6 +332,10 @@ inline bool precisely_zero_when_compared_to(double x, double y) { return x == 0 || fabs(x) < fabs(y * DBL_EPSILON); } +inline bool roughly_zero_when_compared_to(double x, double y) { + return x == 0 || fabs(x) < fabs(y * ROUGH_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) { diff --git a/src/pathops/SkPathOpsWinding.cpp b/src/pathops/SkPathOpsWinding.cpp index 584ebd457a..4c6c636e2b 100644 --- a/src/pathops/SkPathOpsWinding.cpp +++ b/src/pathops/SkPathOpsWinding.cpp @@ -192,7 +192,7 @@ SkOpSpan* SkOpSegment::windingSpanAtT(double tHit) { next = span->next(); if (approximately_equal(tHit, next->t())) { return nullptr; - } + } if (tHit < next->t()) { return span; } @@ -243,6 +243,10 @@ bool SkOpSpan::sortableTop(SkOpContour* contourHead) { } SkOpRayHit* hitHead = &hitBase; dir = static_cast<SkOpRayDir>(static_cast<int>(dir) + dirOffset); + if (hitBase.fSpan && hitBase.fSpan->segment()->verb() > SkPath::kLine_Verb + && !pt_yx(hitBase.fSlope.asSkVector(), dir)) { + return false; + } SkOpContour* contour = contourHead; do { contour->rayCheck(hitBase, dir, &hitHead, &allocator); @@ -256,11 +260,11 @@ bool SkOpSpan::sortableTop(SkOpContour* contourHead) { } int count = sorted.count(); SkTQSort(sorted.begin(), sorted.end() - 1, xy_index(dir) - ? less_than(dir) ? hit_compare_y : reverse_hit_compare_y + ? less_than(dir) ? hit_compare_y : reverse_hit_compare_y : less_than(dir) ? hit_compare_x : reverse_hit_compare_x); // verify windings #if DEBUG_WINDING - SkDebugf("%s dir=%s seg=%d t=%1.9g pt=(%1.9g,%1.9g)\n", __FUNCTION__, + SkDebugf("%s dir=%s seg=%d t=%1.9g pt=(%1.9g,%1.9g)\n", __FUNCTION__, gDebugRayDirName[static_cast<int>(dir)], hitBase.fSpan->segment()->debugID(), hitBase.fT, hitBase.fPt.fX, hitBase.fPt.fY); for (int index = 0; index < count; ++index) { @@ -378,15 +382,20 @@ SkOpSpan* SkOpSegment::findSortableTop(SkOpContour* contourHead) { SkOpSpan* SkOpContour::findSortableTop(SkOpContour* contourHead) { SkOpSegment* testSegment = &fHead; + bool allDone = true; do { if (testSegment->done()) { continue; } + allDone = false; SkOpSpan* result = testSegment->findSortableTop(contourHead); if (result) { return result; } } while ((testSegment = testSegment->next())); + if (allDone) { + fDone = true; + } return nullptr; } diff --git a/src/pathops/SkReduceOrder.cpp b/src/pathops/SkReduceOrder.cpp index 52a19d6138..48624baee9 100644 --- a/src/pathops/SkReduceOrder.cpp +++ b/src/pathops/SkReduceOrder.cpp @@ -78,10 +78,12 @@ int SkReduceOrder::reduce(const SkDQuad& quad) { minYSet |= 1 << index; } } + if ((minXSet & 0x05) == 0x5 && (minYSet & 0x05) == 0x5) { // test for degenerate + // this quad starts and ends at the same place, so never contributes + // to the fill + return coincident_line(quad, fQuad); + } if (minXSet == 0x7) { // test for vertical line - if (minYSet == 0x7) { // return 1 if all three are coincident - return coincident_line(quad, fQuad); - } return vertical_line(quad, fQuad); } if (minYSet == 0x7) { // test for horizontal line |